Commit d6ddce83 authored by Tomáš Pospíšil's avatar Tomáš Pospíšil
Browse files

Internal smarty

parent 79284701
......@@ -2,10 +2,34 @@
namespace Praguebest\BarPanel;
use Nette\Utils\Strings;
use Tracy\Helpers;
class Helper
{
private static $genericClasses = [
'Db',
'WTObject',
'WTModel',
'Database',
'EPObject',
'EPModel',
'EPBase',
];
public static function getInternalSmartyBacktrace(array $backtraces = null): array
{
$filtered = [];
foreach ($backtraces as $backtrace) {
$match = Strings::match($backtrace['file'], '#.*file\.(.*\.tpl).*#');
if ($match) {
$filtered[] = $match[1];
}
}
return $filtered;
}
public static function getBacktrace(array $backtraces = null): array
{
if ($backtraces === null) {
......@@ -13,22 +37,28 @@ class Helper
}
$lines = [];
$first = null;
for ($i = 1, $iMax = count($backtraces); $i < $iMax; $i++) {
for ($i = 0, $iMax = count($backtraces); $i < $iMax; $i++) {
$isGeneric = false;
$backtrace = $backtraces[$i];
$nextBacktrace = $backtrace;
if (array_key_exists($i + 1, $backtraces)) {
$nextBacktrace = $backtraces[$i + 1];
}
$loc = '';
if (isset($backtrace['file'])) {
$loc .= Helpers::editorLink($backtrace['file'], $backtrace['line']) . ' ';
}
$name = ' ';
if ($nextBacktrace !== null && isset($nextBacktrace['class'])) {
$name .= $nextBacktrace['class'] . $nextBacktrace['type'];
}
if (isset($backtrace['class'])) {
$isGeneric = in_array($backtrace['class'], ['Db', 'WTObject', 'WTModel', 'Database', 'EPObject', 'EPModel', 'EPBase'], true);
$name .= $backtrace['class'] . $backtrace['type'];
$isGeneric = in_array($backtrace['class'], self::$genericClasses, true);
}
if ($i > 0 && isset($backtrace['function'])) {
$name .= $backtrace['function'] . '() ';
if ($nextBacktrace !== null && isset($nextBacktrace['function'])) {
$name .= $nextBacktrace['function'] . '() ';
}
$line = compact('loc', 'name');
......
<?php
namespace Praguebest\BarPanel;
use Praguebest\Tools\OnOffSwitcher;
use Praguebest\Tools\TraceableSmarty;
use Smarty;
use SmartyException;
use Tracy;
use Tracy\Dumper;
use Tracy\Helpers;
final class InternalSmartyBarPanel implements Tracy\IBarPanel
{
private static $maxDepth = 8;
private static $maxLength = 512;
public $data;
public $id = 'database';
/**
* @var Smarty
*/
private $smarty;
/**
* @var TraceableSmarty
*/
private $currentSmarty;
public function __construct(TraceableSmarty $currentSmarty, Smarty $smarty)
{
$this->smarty = $smarty;
$this->currentSmarty = $currentSmarty;
}
/**
* Renders HTML code for custom panel.
* @return string
* @throws SmartyException
*/
public function getPanel(): string
{
$this->assignSmarty();
return $this->smarty->fetch(dirname(__DIR__) . '/templates/internalSmarty/smartyVars.tpl');
}
/**
* Renders HTML code for custom tab.
* @return string
* @throws SmartyException
*/
public function getTab(): string
{
$this->assignSmarty();
return $this->smarty->fetch(dirname(__DIR__) . '/templates/internalSmarty/tab.tpl');
}
private function assignSmarty(): void
{
$variables = $this->currentSmarty->getAssigns();
foreach ($variables as $name => &$assigns) {
foreach ($assigns as &$assign) {
$assign['backtrace'] = array_map(
static function ($backtrace) {
return Helpers::editorLink($backtrace);
},
$assign['backtrace']
);
$assign['first'] = array_shift($assign['backtrace']);
$assign['value'] = Dumper::toHtml(
$assign['value'],
[
Dumper::DEPTH => self::$maxDepth,
Dumper::TRUNCATE => self::$maxLength,
Dumper::LOCATION => Dumper::LOCATION_CLASS,
Dumper::LAZY => true,
Dumper::DEBUGINFO => true,
]
);
}
}
$this->smarty->assign('internalVariables', $variables);
$this->smarty->assign('countInternalVariables', count($variables));
$this->smarty->assign('profileInternalVariables', OnOffSwitcher::shouldProfile(TraceableSmarty::INTERNAL_VARIABLES));
}
}
......@@ -3,6 +3,7 @@
namespace Praguebest\BarPanel;
use Nette\Utils\FileSystem;
use Nette\Utils\Strings;
use Praguebest\Tools;
use Praguebest\Tools\WebSwitcherUrls;
use Smarty;
......@@ -41,7 +42,7 @@ final class Registrator
Debugger::$strictMode = true;
Debugger::$maxLength = 1024;
Debugger::$maxDepth = 5;
Debugger::enable("42@84.42.168.182,42@185.156.123.46", realpath($errorDir), $emailDeveloper);
Debugger::enable("42@84.42.168.182,42@185.156.123.46", realpath($errorDir), Strings::replace($emailDeveloper, '/,;/', ','));
return $this;
}
......@@ -81,7 +82,7 @@ final class Registrator
return $this;
}
public function registerSmarty(Smarty $smarty): Registrator
public function registerSmarty(Tools\TraceableSmarty $smarty): Registrator
{
$this->addPanel(
__METHOD__,
......@@ -93,6 +94,18 @@ final class Registrator
return $this;
}
public function registerInternalSmarty(Tools\TraceableSmarty $smarty): Registrator
{
$this->addPanel(
__METHOD__,
function () use ($smarty) {
return new InternalSmartyBarPanel($smarty, $this->smarty);
}
);
return $this;
}
private function addPanel(string $method, callable $panelCreator): void
{
if (!array_key_exists($method, $this->registeredPanels) && Debugger::isEnabled()) {
......
......@@ -2,6 +2,8 @@
namespace Praguebest\BarPanel;
use Praguebest\Tools\OnOffSwitcher;
use Praguebest\Tools\TraceableSmarty;
use Smarty;
use SmartyException;
use Tracy;
......@@ -21,11 +23,11 @@ final class SmartyBarPanel implements Tracy\IBarPanel
*/
private $smarty;
/**
* @var PBSmarty
* @var TraceableSmarty
*/
private $currentSmarty;
public function __construct(Smarty $currentSmarty, Smarty $smarty)
public function __construct(TraceableSmarty $currentSmarty, Smarty $smarty)
{
$this->smarty = $smarty;
$this->currentSmarty = $currentSmarty;
......@@ -57,31 +59,35 @@ final class SmartyBarPanel implements Tracy\IBarPanel
private function assignSmarty(): void
{
if (method_exists($this->currentSmarty, 'getBacktraces')) {
$smartyBacktrace = [];
$smartyBacktrace = [];
$smartyVars = [];
if (OnOffSwitcher::shouldProfile(TraceableSmarty::SMARTY_VARIABLES)) {
foreach ($this->currentSmarty->getBacktraces() as $key => $backtrace) {
$smartyBacktrace[$key] = Helper::getBacktrace($backtrace);
}
$this->smarty->assign('smartyBacktrace', $smartyBacktrace);
}
$smartyVars =
array_map(
static function ($value) {
return Dumper::toHtml(
$value,
[
Dumper::DEPTH => self::$maxDepth,
Dumper::TRUNCATE => self::$maxLength,
Dumper::LOCATION => Dumper::LOCATION_CLASS,
Dumper::LAZY => true,
Dumper::DEBUGINFO => true,
]
);
},
array_combine(array_keys($this->currentSmarty->tpl_vars), array_column($this->currentSmarty->tpl_vars, 'value'))
);
ksort($smartyVars, SORT_STRING | SORT_FLAG_CASE);
$this->smarty->assign('smartyVars', $smartyVars);
$smartyVars =
array_map(
static function ($value) {
return Dumper::toHtml(
$value,
[
Dumper::DEPTH => self::$maxDepth,
Dumper::TRUNCATE => self::$maxLength,
Dumper::LOCATION => Dumper::LOCATION_CLASS,
Dumper::LAZY => true,
Dumper::DEBUGINFO => true,
]
);
},
array_combine(array_keys($this->currentSmarty->tpl_vars), array_column($this->currentSmarty->tpl_vars, 'value'))
);
ksort($smartyVars, SORT_STRING | SORT_FLAG_CASE);
}
$this->smarty->assign('smartyVariables', $smartyVars);
$this->smarty->assign('countSmartyVariables', count($smartyVars));
$this->smarty->assign('profileSmartyVariables', OnOffSwitcher::shouldProfile(TraceableSmarty::SMARTY_VARIABLES));
}
}
......@@ -33,30 +33,7 @@ final class DBProfiler
private function initProfiling(): void
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$get = filter_input(INPUT_GET, DBProfiler::PROFILE_DB, FILTER_VALIDATE_BOOLEAN);
if ($get === true || ($get === null && $this->getFromSession() === true)) {
$this->setToSession(true);
$this->profile = true;
}
if ($get === false || ($get === null && $this->getFromSession() === false)) {
$this->setToSession(false);
$this->profile = false;
}
}
private function getFromSession(): bool
{
$profileDB = $_SESSION[DBProfiler::PROFILE_DB] ?? false;
return (bool)$profileDB;
}
private function setToSession(bool $value): void
{
$_SESSION[DBProfiler::PROFILE_DB] = $value;
$this->profile = OnOffSwitcher::shouldProfile(DBProfiler::PROFILE_DB);
}
public static function getInstance(): self
......
<?php
namespace Praguebest\Tools;
class OnOffSwitcher
{
public static function shouldProfile(string $profileName): bool
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$profile = false;
$get = filter_input(INPUT_GET, $profileName, FILTER_VALIDATE_BOOLEAN);
if ($get === true || ($get === null && static::getFromSession($profileName) === true)) {
static::setToSession($profileName, true);
$profile = true;
}
if ($get === false || ($get === null && static::getFromSession($profileName) === false)) {
static::setToSession($profileName, false);
$profile = false;
}
return $profile;
}
private static function getFromSession(string $profileName): bool
{
$profileDB = $_SESSION[$profileName] ?? false;
return (bool)$profileDB;
}
private static function setToSession(string $profileName, bool $value): void
{
$_SESSION[$profileName] = $value;
}
}
<?php
namespace Praguebest\Tools;
class TraceableInternalTemplate extends \Smarty_Internal_Template
{
private $assignedByScope = [];
/**
* @param string $varName
* @param mixed $value
* @param bool $nocache
* @param int $scope
*/
public function _assignInScope($varName, $value, $nocache = false, $scope = 0)
{
parent::_assignInScope($varName, $value, $nocache, $scope);
$this->addAssignedByScope($varName, $value);
}
public function addAssignedByScope($varName, $value, array $backtrace = [])
{
$backtrace[] = reset($this->compiled->file_dependency)[0];
if ($this->parent instanceof TraceableInternalTemplate) {
$this->parent->addAssignedByScope($varName, $value, $backtrace);
} else {
$this->assignedByScope[$varName] = $this->assignedByScope[$varName] ?? [];
$this->assignedByScope[$varName][] = [
'value' => $value,
'backtrace' => $backtrace,
];
}
}
/**
* @param string $template
* @param mixed $cache_id
* @param mixed $compile_id
* @param int $caching
* @param int $cache_lifetime
* @param array $data
* @param int $scope
* @param bool $forceTplCache
* @param string|null $uid
* @param string|null $content_func
*/
public function _subTemplateRender(
$template,
$cache_id,
$compile_id,
$caching,
$cache_lifetime,
$data,
$scope,
$forceTplCache,
$uid = null,
$content_func = null
) {
parent::_subTemplateRender(
$template,
$cache_id,
$compile_id,
$caching,
$cache_lifetime,
$data,
$scope,
$forceTplCache,
$uid,
$content_func
);
foreach ($data as $key => $value) {
$this->addAssignedByScope($key, $value);
}
}
public function getAssigns(): array
{
return $this->assignedByScope;
}
}
......@@ -7,8 +7,30 @@ use Smarty;
class TraceableSmarty extends Smarty
{
public const SMARTY_VARIABLES = 'SMARTY_VARIABLES';
public const INTERNAL_VARIABLES = 'INTERNAL_VARIABLES';
/** @var array */
private $backtraces;
private $backtraces = [];
/** @var TraceableInternalTemplate[] */
private $generatedTemplates = [];
private $collectVariables = false;
private $collectBacktraces = false;
/**
* TraceableSmarty constructor.
*/
public function __construct()
{
parent::__construct();
if (OnOffSwitcher::shouldProfile(TraceableSmarty::INTERNAL_VARIABLES)) {
$this->template_class = TraceableInternalTemplate::class;
$this->collectVariables = true;
}
$this->collectBacktraces = OnOffSwitcher::shouldProfile(TraceableSmarty::SMARTY_VARIABLES);
}
/**
* @param array|string $tpl_var
......@@ -24,7 +46,9 @@ class TraceableSmarty extends Smarty
$this->assign($var, $value, $nocache);
}
} else {
$this->backtraces[$tpl_var] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
if ($this->collectBacktraces) {
$this->backtraces[$tpl_var] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
parent::assign($tpl_var, $value, $nocache);
}
......@@ -32,11 +56,39 @@ class TraceableSmarty extends Smarty
return $this;
}
public function assignByRef($tpl_var, &$value, $nocache = false)
{
if ($this->collectBacktraces) {
$this->backtraces[$tpl_var] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
parent::assignByRef($tpl_var, $value, $nocache);
}
public function display($template = null, $cache_id = null, $compile_id = null, $parent = null)
{
parent::display($template, $cache_id, $compile_id, $parent);
Registrator::getInstance($this->getCompileDir(), $this->getCacheDir())->registerSmarty($this);
$registrator = Registrator::getInstance($this->getCompileDir(), $this->getCacheDir());
$registrator->registerSmarty($this);
$registrator->registerInternalSmarty($this);
}
/**
* @param string $template
* @param mixed|null $cache_id
* @param mixed|null $compile_id
* @param object|null $parent
* @param bool $do_clone
* @return Smarty_Internal_Template
*/
public function createTemplate($template, $cache_id = null, $compile_id = null, $parent = null, $do_clone = true)
{
$template = parent::createTemplate($template, $cache_id, $compile_id, $parent, $do_clone);
if ($this->collectVariables) {
$this->generatedTemplates[] = $template;
}
return $template;
}
public function getBacktraces(): array
......@@ -44,4 +96,19 @@ class TraceableSmarty extends Smarty
return $this->backtraces;
}
public function getAssigns(): array
{
$assigns = [];
foreach ($this->generatedTemplates as $template) {
$assigns[] = $template->getAssigns();
}
$assigns = array_filter($assigns);
if (count($assigns) > 0) {
$assigns = array_merge_recursive(...$assigns);
}
return $assigns;
}
}
{if $count > 0}
<h1>Queries: {$count}, {($time * 1000)|number_format:2} ms</h1>
{/if}
{if $count === 0 && ($profile === false || $profile === null)}
<h1><a href="?profileDB=1">Turn profiling on</a></h1>
{/if}
{if $count > 0}
<h1>Queries: {$count}, {($time * 1000)|number_format:2} ms</h1>
<div class="tracy-inner">
<div class="tracy-inner-container">
<table class="tracy-sortable">
......
{if $countInternalVariables === 0 && ($profileInternalVariables === false || $profileInternalVariables === null)}
<h1><a href="?INTERNAL_VARIABLES=1">Turn profiling on</a></h1>
{/if}
{if $countInternalVariables > 0}
<h1>Variables: {$countInternalVariables}</h1>
<style>
#tracy-debug pre.tracy-dump {
background: transparent !important;
}
</style>
<div class="tracy-inner">
<div class="tracy-inner-container">
<table>
{foreach $internalVariables as $name => $assigns}
{foreach $assigns as $assign}
<tr>
{if $assign@first}
<td rowspan="{count($assigns)}">{$name}</td>
{/if}
<td>{$assign['first'] nofilter}
{if count($assign['backtrace']) > 0}
<a class="tracy-toggle tracy-collapsed" data-tracy-ref="^tr .nette-DbConnectionPanel-trace"></a>
<div class="tracy-collapsed nette-DbConnectionPanel-trace">
{foreach $assign['backtrace'] as $backtrace}
<div>{$backtrace nofilter}</div>
{/foreach}
</div>
{/if}
</td>
<td>{$assign['value'] nofilter}</td>
</tr>
{/foreach}
{/foreach}
</table>
</div>
</div>
<h2><a href="?INTERNAL_VARIABLES=0">Turn profiling off</a></h2>
{/if}
<span>
{if $countInternalVariables === 0}
<span style="color: gray; text-decoration: line-through">{ldelim}${rdelim} Internal variables</span>
<span class="tracy-label"></span>
{else}
<span style="color: cadetblue">{ldelim}</span>
<span style="color: coral">$</span>
<span style="color: cadetblue">{rdelim}</span>
<span class="tracy-label">{$countInternalVariables} internal variables</span>
{/if}
</span>