diff --git a/.gitattributes b/.gitattributes index e1bccc4bf..433a2de9a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,7 @@ .gitignore export-ignore ncs.* export-ignore phpstan*.neon export-ignore +src/**/*.latte export-ignore tests/ export-ignore *.php* diff=php diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml index af3408d91..27ca2fbfa 100644 --- a/.github/workflows/coding-style.yml +++ b/.github/workflows/coding-style.yml @@ -14,7 +14,7 @@ jobs: coverage: none - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress - - run: php temp/code-checker/code-checker --strict-types --no-progress -i tests/Tracy/fixtures -i examples/assets + - run: php temp/code-checker/code-checker --strict-types --no-progress -i tests/Tracy/fixtures -i examples/assets -i *.latte nette_cs: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c5f98677b..2b287d79b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,6 +37,7 @@ jobs: code_coverage: name: Code Coverage runs-on: ubuntu-latest + continue-on-error: true steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index 97d54a97f..32b3d654c 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,9 @@ "nette/tester": "^2.6", "latte/latte": "^2.5 || ^3.0", "psr/log": "^1.0 || ^2.0 || ^3.0", - "phpstan/phpstan": "^2.0@stable" + "phpstan/phpstan": "^2.1@stable", + "phpstan/extension-installer": "^1.4@stable", + "nette/phpstan-rules": "^1.0" }, "conflict": { "nette/di": "<3.0" @@ -46,7 +48,12 @@ }, "extra": { "branch-alias": { - "dev-master": "2.11-dev" + "dev-master": "3.0-dev" + } + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true } } } diff --git a/examples/lazy-panels.php b/examples/lazy-panels.php new file mode 100644 index 000000000..8d0f8398e --- /dev/null +++ b/examples/lazy-panels.php @@ -0,0 +1,143 @@ +⚡ Normal'; + } + + public function getPanel(): string + { + return '

Normal Panel

' + . '
' + . '

This panel was rendered during the request (eager).

' + . '

Time: ' . date('H:i:s') . '

' + . '
'; + } +} + + +/** + * Example: A "heavy" panel that simulates expensive computation. + * When registered with lazy: true, getPanel() is NOT called during the request. + * Instead, it is rendered in the shutdown function and served via AJAX on click. + */ +class HeavyPanel implements IBarPanel +{ + public function getTab(): string + { + return '🐢 Heavy'; + } + + public function getPanel(): string + { + // Simulate expensive operation (e.g., database profiling, API calls) + usleep(500_000); // 500ms delay + + return '

Heavy Panel (lazy loaded)

' + . '
' + . '

This panel was rendered after the response (lazy).

' + . '

It simulates a 500ms expensive computation.

' + . '

Time: ' . date('H:i:s') . '

' + . '' + . '' + . '' + . '' + . '
KeyValue
PHP Version' . PHP_VERSION . '
Memory Peak' . number_format(memory_get_peak_usage() / 1024 / 1024, 2) . ' MB
Extensions' . count(get_loaded_extensions()) . ' loaded
' + . '
'; + } +} + + +/** + * Example: Another lazy panel showing database-like profiling info. + */ +class DatabasePanel implements IBarPanel +{ + public function getTab(): string + { + return '🗄️ DB'; + } + + public function getPanel(): string + { + usleep(300_000); // 300ms delay + + $queries = [ + ['SELECT * FROM users WHERE id = 1', '0.5ms'], + ['SELECT * FROM posts WHERE user_id = 1 ORDER BY created_at DESC LIMIT 10', '2.1ms'], + ['UPDATE users SET last_login = NOW() WHERE id = 1', '0.3ms'], + ]; + + $html = '

Database Panel (lazy loaded)

' + . '
' + . '

Simulated database queries — rendered lazily after the response was sent.

' + . ''; + + foreach ($queries as $i => [$query, $time]) { + $html .= ''; + } + + $html .= '
#QueryTime
' . ($i + 1) . '' . htmlspecialchars($query) . '' . $time . '
'; + return $html; + } +} + + +// Register panels: +// Normal panel (eager) — rendered during the request +Debugger::getBar()->addPanel(new NormalPanel, 'example-normal'); + +// Heavy panel — lazy: true means getPanel() is deferred to shutdown function +Debugger::getBar()->addPanel(new HeavyPanel, 'example-heavy', lazy: true); + +// Database panel — also lazy +Debugger::getBar()->addPanel(new DatabasePanel, 'example-database', lazy: true); + +?> + + +

Tracy: Lazy Panel Loading Demo

+ +

How it works

+

This demo shows the lazy: true parameter for Debugger::getBar()->addPanel().

+ + + +

Usage

+
// Register a lazy panel — getPanel() is NOT called during the request
+Debugger::getBar()->addPanel(new MyExpensivePanel, 'my-panel', lazy: true);
+
+ +

Lazy panels have their getTab() called normally (so the tab is always visible), +but getPanel() is deferred to a shutdown function. The content is stored in the session +and fetched via AJAX when you click or hover over the panel tab.

+ +

This is useful for panels that perform expensive operations like database profiling, +API call logging, or heavy data analysis — they won't slow down your page response time.

+ +For security reasons, Tracy is visible only on localhost. Look into the source code to see how to enable Tracy.

'; +} diff --git a/phpstan.neon b/phpstan.neon index 94b109077..cdecbdc15 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,33 +1,51 @@ parameters: - level: 6 + level: 8 paths: - src - checkMissingCallableSignature: true + fileExtensions: + - php + - phtml ignoreErrors: # Template variables used in required .phtml files via variable scope - identifier: closure.unusedUse path: src/Tracy/Bar/Bar.php + - + identifier: closure.unusedUse + path: src/Tracy/BlueScreen/dist/markdown.phtml # Tracy doesn't need generic type parameters for Fiber, ArrayObject, DOMNodeList, etc. - identifier: missingType.generics - # Private methods called from .phtml template files - - - identifier: method.unused - path: src/Tracy/BlueScreen/BlueScreen.php - # Runtime validation of callable-string and Closure types - identifier: function.alreadyNarrowedType + paths: + - src/Tracy/Bar/dist/loader.phtml + - src/Tracy/BlueScreen/BlueScreen.php + - src/Tracy/Helpers.php # Tracy uses dynamic properties on exceptions and panels - identifier: property.notFound + paths: + - src/Tracy/Bar/dist/info.panel.phtml + - src/Tracy/Bar/dist/info.tab.phtml + - src/Tracy/Bar/panels/info.panel.php + - src/Tracy/Debugger/DevelopmentStrategy.php + - src/Tracy/Helpers.php + + # Private methods called from .phtml template files + - + identifier: method.unused + path: src/Tracy/BlueScreen/BlueScreen.php + - + identifier: method.private + path: src/Tracy/BlueScreen/dist # PHPStan doesn't track reference assignments to snapshot array - @@ -74,3 +92,29 @@ parameters: - identifier: missingType.return path: src/Tracy/Logger/ILogger.php + + # Arrow function callback receives class names from get_declared_classes() etc. + - + identifier: argument.type + message: '#class\-string#' + path: src/Tracy/Bar/panels/info.panel.php + + # getPanel() returns ?IBarPanel but panel is always registered; dynamic props correct by design + - + identifier: property.nonObject + path: src/Tracy/Debugger/DevelopmentStrategy.php + + # Value::$id and $value are always non-null when used as array keys (snapshot/above maps) + - + identifier: offsetAccess.invalidOffset + paths: + - src/Tracy/Dumper/Describer.php + - src/Tracy/Dumper/Renderer.php + + # Generated phtml templates use is_bool() as a runtime type guard; PHPStan sees it as always-false for string-typed vars + - + identifier: function.impossibleType + paths: + - src/Tracy/Bar/dist + - src/Tracy/BlueScreen/dist + - src/Tracy/Debugger/dist diff --git a/src/Bridges/Nette/Bridge.php b/src/Bridges/Nette/Bridge.php index f5272479c..2ad596922 100644 --- a/src/Bridges/Nette/Bridge.php +++ b/src/Bridges/Nette/Bridge.php @@ -46,16 +46,12 @@ public static function renderMemberAccessException(?\Throwable $e): ?array $loc = Tracy\Debugger::mapSource($loc['file'], $loc['line']) ?? $loc; if (preg_match('#Cannot (?:read|write to) an undeclared property .+::\$(\w+), did you mean \$(\w+)\?#A', $e->getMessage(), $m)) { - return [ - 'link' => Helpers::editorUri($loc['file'], $loc['line'], 'fix', '->' . $m[1], '->' . $m[2]), - 'label' => 'fix it', - ]; + $link = Helpers::editorUri($loc['file'], $loc['line'], 'fix', '->' . $m[1], '->' . $m[2]); + return $link !== null ? ['link' => $link, 'label' => 'fix it'] : null; } elseif (preg_match('#Call to undefined (static )?method .+::(\w+)\(\), did you mean (\w+)\(\)?#A', $e->getMessage(), $m)) { $operator = $m[1] ? '::' : '->'; - return [ - 'link' => Helpers::editorUri($loc['file'], $loc['line'], 'fix', $operator . $m[2] . '(', $operator . $m[3] . '('), - 'label' => 'fix it', - ]; + $link = Helpers::editorUri($loc['file'], $loc['line'], 'fix', $operator . $m[2] . '(', $operator . $m[3] . '('); + return $link !== null ? ['link' => $link, 'label' => 'fix it'] : null; } return null; diff --git a/src/Bridges/Nette/TracyExtension.php b/src/Bridges/Nette/TracyExtension.php index c966812f2..1342bbd50 100644 --- a/src/Bridges/Nette/TracyExtension.php +++ b/src/Bridges/Nette/TracyExtension.php @@ -131,14 +131,14 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void } $initialize->addBody($builder->formatPhp('if ($logger instanceof Tracy\Logger) $logger->mailer = ?;', [ - [new Statement(Tracy\Bridges\Nette\MailSender::class, $params), 'send'], + [new Statement(Tracy\Bridges\Nette\MailSender::class, $params), 'send'], // TODO: nette/di must be able to create closures ])); } if ($this->debugMode) { foreach ($config->bar as $item) { if (is_string($item) && str_starts_with($item, '@')) { - $item = new Statement(['@' . $builder::THIS_CONTAINER, 'getService'], [substr($item, 1)]); + $item = new Statement(['@' . $builder::ThisContainer, 'getService'], [substr($item, 1)]); } elseif (is_string($item)) { $item = new Statement($item); } @@ -182,7 +182,8 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void private function parseErrorSeverity(string|array $value): int { $value = implode('|', (array) $value); - $res = (int) @parse_ini_string('e = ' . $value)['e']; // @ may fail + $ini = @parse_ini_string('e = ' . $value); // @ may fail + $res = (int) ($ini['e'] ?? 0); if (!$res) { throw new Nette\InvalidStateException("Syntax error in expression '$value'"); } diff --git a/src/Tracy/Bar/Bar.php b/src/Tracy/Bar/Bar.php index cd69a351e..c47a957a4 100644 --- a/src/Tracy/Bar/Bar.php +++ b/src/Tracy/Bar/Bar.php @@ -19,13 +19,19 @@ class Bar { /** @var IBarPanel[] */ private array $panels = []; + + /** @var array panel ID => lazy flag */ + private array $lazyPanels = []; private bool $loaderRendered = false; /** * Add custom panel. + * @param bool $lazy If true, panel content is rendered after the response is sent + * and loaded via AJAX when the user clicks on the tab. + * Use for panels whose getPanel() is expensive and not needed on every request. */ - public function addPanel(IBarPanel $panel, ?string $id = null): static + public function addPanel(IBarPanel $panel, ?string $id = null, bool $lazy = false): static { if ($id === null) { $c = 0; @@ -35,6 +41,10 @@ public function addPanel(IBarPanel $panel, ?string $id = null): static } $this->panels[$id] = $panel; + if ($lazy) { + $this->lazyPanels[$id] = true; + } + return $this; } @@ -60,9 +70,8 @@ public function renderLoader(DeferredContent $defer): void $this->loaderRendered = true; $requestId = $defer->getRequestId(); - $nonceAttr = Helpers::getNonceAttr(); $async = true; - require __DIR__ . '/assets/loader.phtml'; + require __DIR__ . '/dist/loader.phtml'; } @@ -74,7 +83,7 @@ public function render(DeferredContent $defer): void $redirectQueue = &$defer->getItems('redirect'); $requestId = $defer->getRequestId(); - if (Helpers::isAjax()) { + if ($defer->isDeferred()) { if ($defer->isAvailable()) { $defer->addSetup('Tracy.Debug.loadAjax', $this->renderPartial('ajax', '-ajax:' . $requestId)); } @@ -102,10 +111,9 @@ public function render(DeferredContent $defer): void $defer->addSetup('Tracy.Debug.init', $content); } else { - $nonceAttr = Helpers::getNonceAttr(); $async = false; Debugger::removeOutputBuffers(errorOccurred: false); - require __DIR__ . '/assets/loader.phtml'; + require __DIR__ . '/dist/loader.phtml'; } } } @@ -118,16 +126,16 @@ private function renderPartial(string $type, string $suffix = ''): array return [ 'bar' => Helpers::capture(function () use ($type, $panels) { - require __DIR__ . '/assets/bar.phtml'; + require __DIR__ . '/dist/bar.phtml'; }), 'panels' => Helpers::capture(function () use ($type, $panels) { - require __DIR__ . '/assets/panels.phtml'; + require __DIR__ . '/dist/panels.phtml'; }), ]; } - /** @return \stdClass[] */ + /** @return list<\stdClass> */ private function renderPanels(string $suffix = ''): array { set_error_handler(function (int $severity, string $message, string $file, int $line): bool { @@ -143,9 +151,14 @@ private function renderPanels(string $suffix = ''): array foreach ($this->panels as $id => $panel) { $idHtml = preg_replace('#[^a-z0-9]+#i', '-', $id) . $suffix; + $lazy = isset($this->lazyPanels[$id]); try { $tab = (string) $panel->getTab(); - $panelHtml = $tab ? $panel->getPanel() : null; + if ($lazy && $tab) { + $panelHtml = null; // will be rendered later via shutdown function + } else { + $panelHtml = $tab ? $panel->getPanel() : null; + } } catch (\Throwable $e) { while (ob_get_level() > $obLevel) { // restore ob-level if broken @@ -155,13 +168,68 @@ private function renderPanels(string $suffix = ''): array $idHtml = "error-$idHtml"; $tab = "Error in $id"; $panelHtml = "

Error: $id

" . nl2br(Helpers::escapeHtml($e)) . '
'; + $lazy = false; unset($e); } - $panels[] = (object) ['id' => $idHtml, 'tab' => $tab, 'panel' => $panelHtml]; + $panels[] = (object) ['id' => $idHtml, 'tab' => $tab, 'panel' => $panelHtml, 'lazy' => $lazy]; } restore_error_handler(); return $panels; } + + + /** + * Renders lazy panels in shutdown function and stores them in session. + * @internal + */ + public function renderLazyPanels(DeferredContent $defer): void + { + if (!$defer->isAvailable()) { + return; + } + + set_error_handler(function (int $severity, string $message, string $file, int $line): bool { + if (error_reporting() & $severity) { + throw new \ErrorException($message, 0, $severity, $file, $line); + } + + return true; + }); + + $obLevel = ob_get_level(); + + foreach ($this->panels as $id => $panel) { + if (!isset($this->lazyPanels[$id])) { + continue; + } + + try { + $tab = (string) $panel->getTab(); + $panelHtml = $tab ? $panel->getPanel() : null; + } catch (\Throwable $e) { + while (ob_get_level() > $obLevel) { + ob_end_clean(); + } + + $panelHtml = "

Error: $id

" . nl2br(Helpers::escapeHtml($e)) . '
'; + unset($e); + } + + if ($panelHtml !== null) { + $icons = '
' + . '¤' + . '×' + . '
'; + $lazyItems = &$defer->getItems('lazy-panels'); + $lazyItems[$defer->getRequestId() . '.' . preg_replace('#[^a-z0-9]+#i', '-', $id)] = [ + 'content' => $panelHtml . "\n" . $icons, + 'time' => time(), + ]; + } + } + + restore_error_handler(); + } } diff --git a/src/Tracy/Bar/DefaultBarPanel.php b/src/Tracy/Bar/DefaultBarPanel.php index 0dd286942..79551249e 100644 --- a/src/Tracy/Bar/DefaultBarPanel.php +++ b/src/Tracy/Bar/DefaultBarPanel.php @@ -33,7 +33,7 @@ public function getTab(): string { return Helpers::capture(function () { $data = $this->data; - require __DIR__ . "/panels/{$this->id}.tab.phtml"; + require __DIR__ . "/dist/{$this->id}.tab.phtml"; }); } @@ -44,9 +44,9 @@ public function getTab(): string public function getPanel(): string { return Helpers::capture(function () { - if (is_file(__DIR__ . "/panels/{$this->id}.panel.phtml")) { + if (is_file(__DIR__ . "/dist/{$this->id}.panel.phtml")) { $data = $this->data; - require __DIR__ . "/panels/{$this->id}.panel.phtml"; + require __DIR__ . "/dist/{$this->id}.panel.phtml"; } }); } diff --git a/src/Tracy/Bar/IBarPanel.php b/src/Tracy/Bar/IBarPanel.php index f162d6644..82cdd97e2 100644 --- a/src/Tracy/Bar/IBarPanel.php +++ b/src/Tracy/Bar/IBarPanel.php @@ -17,13 +17,13 @@ interface IBarPanel { /** * Renders HTML code for custom tab. - * @return string + * @return ?string */ function getTab(); /** * Renders HTML code for custom panel. - * @return string + * @return ?string */ function getPanel(); } diff --git a/src/Tracy/Bar/assets/bar.js b/src/Tracy/Bar/assets/bar.js index 56f237958..41d1b7de8 100644 --- a/src/Tracy/Bar/assets/bar.js +++ b/src/Tracy/Bar/assets/bar.js @@ -31,10 +31,16 @@ class Panel { let elem = this.elem; this.init = function () {}; - elem.innerHTML = elem.tracyContent = elem.dataset.tracyContent; - delete elem.dataset.tracyContent; - Tracy.Dumper.init(Debug.layer); - evalScripts(elem); + + if (elem.dataset.tracyLazy && !elem.dataset.tracyContent) { + elem.innerHTML = elem.tracyContent = '

Loading\u2026

Loading panel content\u2026

'; + this.fetchLazyContent(); + } else { + elem.innerHTML = elem.tracyContent = elem.dataset.tracyContent; + delete elem.dataset.tracyContent; + Tracy.Dumper.init(Debug.layer); + evalScripts(elem); + } draggable(elem, { handles: elem.querySelectorAll('h1'), @@ -87,6 +93,45 @@ class Panel { } + fetchLazyContent() { + let elem = this.elem; + let panelId = elem.id.replace('tracy-debug-panel-', ''); + let url = baseUrl + '_tracy_bar=lazy-panel.' + requestId + '.' + panelId + '&XDEBUG_SESSION_STOP=1&v=' + Math.random(); + + fetch(url) + .then((response) => response.json()) + .then((data) => { + if (data.content) { + elem.innerHTML = elem.tracyContent = data.content; + delete elem.dataset.tracyLazy; + Tracy.Dumper.init(Debug.layer); + evalScripts(elem); + + elem.querySelectorAll('.tracy-icons a').forEach((link) => { + link.addEventListener('click', (e) => { + if (link.dataset.tracyAction === 'close') { + this.toPeek(); + } else if (link.dataset.tracyAction === 'window') { + this.toWindow(); + } + e.preventDefault(); + e.stopImmediatePropagation(); + }); + }); + + if (this.is('tracy-panel-persist')) { + Tracy.Toggle.persist(elem); + } + } else { + elem.innerHTML = elem.tracyContent = '

Error

Lazy panel content not available. The panel may have expired from the session.

'; + } + }) + .catch(() => { + elem.innerHTML = elem.tracyContent = '

Error

Failed to load lazy panel content.

'; + }); + } + + is(mode) { return this.elem.classList.contains(mode); } diff --git a/src/Tracy/Bar/assets/bar.latte b/src/Tracy/Bar/assets/bar.latte new file mode 100644 index 000000000..ec3af6e36 --- /dev/null +++ b/src/Tracy/Bar/assets/bar.latte @@ -0,0 +1,33 @@ +{* + * @var string $type + * @var stdClass[] $panels + *} + + diff --git a/src/Tracy/Bar/assets/bar.phtml b/src/Tracy/Bar/assets/bar.phtml deleted file mode 100644 index 146798944..000000000 --- a/src/Tracy/Bar/assets/bar.phtml +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/src/Tracy/Bar/assets/loader.latte b/src/Tracy/Bar/assets/loader.latte new file mode 100644 index 000000000..a34468b78 --- /dev/null +++ b/src/Tracy/Bar/assets/loader.latte @@ -0,0 +1,22 @@ +{* + * @var bool $async + * @var string $requestId + *} + +{do $nonce = Tracy\Helpers::getNonce()} +{do $baseUrl = $_SERVER['REQUEST_URI'] ?? ''} +{do $baseUrl .= strpos($baseUrl, '?') === false ? '?' : '&'} + +{if empty($content)} + +{else} + + + + + + + +{/if} diff --git a/src/Tracy/Bar/assets/loader.phtml b/src/Tracy/Bar/assets/loader.phtml deleted file mode 100644 index 11247c664..000000000 --- a/src/Tracy/Bar/assets/loader.phtml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - -> -Tracy.Debug.init(); - - diff --git a/src/Tracy/Bar/assets/panels.latte b/src/Tracy/Bar/assets/panels.latte new file mode 100644 index 000000000..f159a1f0c --- /dev/null +++ b/src/Tracy/Bar/assets/panels.latte @@ -0,0 +1,19 @@ +{* + * @var string $type + * @var stdClass[] $panels + *} +{use Tracy\Dumper} + +{do $icons = ' +
+ ¤ + × +
+'} +
+ {foreach $panels as $panel} + {do $content = $panel->panel ? $panel->panel . "\n" . $icons : ''} +
+ {/foreach} + +
diff --git a/src/Tracy/Bar/assets/panels.phtml b/src/Tracy/Bar/assets/panels.phtml deleted file mode 100644 index 9091cc4ea..000000000 --- a/src/Tracy/Bar/assets/panels.phtml +++ /dev/null @@ -1,30 +0,0 @@ - - ¤ - × - -'; - -echo '
'; - -foreach ($panels as $panel) { - $content = $panel->panel ? ($panel->panel . "\n" . $icons) : ''; - $class = 'tracy-panel ' . ($type === 'ajax' ? '' : 'tracy-panel-persist') . ' tracy-panel-' . $type; ?> -
'>
'; -echo '
'; diff --git a/src/Tracy/Bar/dist/bar.phtml b/src/Tracy/Bar/dist/bar.phtml new file mode 100644 index 000000000..db0f23bf1 --- /dev/null +++ b/src/Tracy/Bar/dist/bar.phtml @@ -0,0 +1,31 @@ + + + diff --git a/src/Tracy/Bar/dist/dumps.panel.phtml b/src/Tracy/Bar/dist/dumps.panel.phtml new file mode 100644 index 000000000..3054b46b7 --- /dev/null +++ b/src/Tracy/Bar/dist/dumps.panel.phtml @@ -0,0 +1,24 @@ + + $data + */ ?> + +

Dumps

+ +
+

+

+ + + +
diff --git a/src/Tracy/Bar/dist/dumps.tab.phtml b/src/Tracy/Bar/dist/dumps.tab.phtml new file mode 100644 index 000000000..106e74efb --- /dev/null +++ b/src/Tracy/Bar/dist/dumps.tab.phtml @@ -0,0 +1,8 @@ + + + + +dumps diff --git a/src/Tracy/Bar/dist/info.panel.phtml b/src/Tracy/Bar/dist/info.panel.phtml new file mode 100644 index 000000000..2aa9b6dcb --- /dev/null +++ b/src/Tracy/Bar/dist/info.panel.phtml @@ -0,0 +1,69 @@ + + $info + * @var object[] $packages + * @var object[] $devPackages + */ ?> + + +

System info

+ +
+
+ + $val): ?> + 25): ?> + + +
+ + + +
+ +

+ Composer Packages ( + +) +

+ +
+ + + + + +
name) ?> +version) ?> +version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : '') ?> +
+ +

Dev Packages

+ + + + + + +
name) ?> +version) ?> +version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : '') ?> +
+
+
+
diff --git a/src/Tracy/Bar/dist/info.tab.phtml b/src/Tracy/Bar/dist/info.tab.phtml new file mode 100644 index 000000000..da90a717d --- /dev/null +++ b/src/Tracy/Bar/dist/info.tab.phtml @@ -0,0 +1,15 @@ + +time = microtime(true) - Tracy\Debugger::$time ?> + + + + + + time * 1000, 1, '.', ' ')) ?> + ms + diff --git a/src/Tracy/Bar/dist/loader.phtml b/src/Tracy/Bar/dist/loader.phtml new file mode 100644 index 000000000..51874792a --- /dev/null +++ b/src/Tracy/Bar/dist/loader.phtml @@ -0,0 +1,26 @@ + + + + + + + + + + +> +Tracy.Debug.init( +); + + diff --git a/src/Tracy/Bar/dist/panels.phtml b/src/Tracy/Bar/dist/panels.phtml new file mode 100644 index 000000000..0e06718d2 --- /dev/null +++ b/src/Tracy/Bar/dist/panels.phtml @@ -0,0 +1,19 @@ + + + + ¤ + × + +' ?>
+panel ? $panel->panel . "\n" . $icons : '' ?>
lazy ?? false): ?> data-tracy-lazy="1" data-tracy-content=' +'>
+ +> +
diff --git a/src/Tracy/Bar/dist/warnings.panel.phtml b/src/Tracy/Bar/dist/warnings.panel.phtml new file mode 100644 index 000000000..7e41a7f5d --- /dev/null +++ b/src/Tracy/Bar/dist/warnings.panel.phtml @@ -0,0 +1,18 @@ + + $data + */ ?>

Warnings

+ +
+ + $count): ?> + + + +
+

+ in 
+
+
diff --git a/src/Tracy/Bar/dist/warnings.tab.phtml b/src/Tracy/Bar/dist/warnings.tab.phtml new file mode 100644 index 000000000..fdf880db6 --- /dev/null +++ b/src/Tracy/Bar/dist/warnings.tab.phtml @@ -0,0 +1,23 @@ + + + + + + + + + 1 ? ' warnings' : ' warning') ?> + + diff --git a/src/Tracy/Bar/panels/dumps.panel.latte b/src/Tracy/Bar/panels/dumps.panel.latte new file mode 100644 index 000000000..716ed0d09 --- /dev/null +++ b/src/Tracy/Bar/panels/dumps.panel.latte @@ -0,0 +1,24 @@ +{* + * @var array $data + *} + + +

Dumps

+ +
+ {foreach $data as $item} + {if $item[title]} +

{$item[title]}

+ {/if} + + {$item[dump]|noescape} + {/foreach} +
diff --git a/src/Tracy/Bar/panels/dumps.panel.phtml b/src/Tracy/Bar/panels/dumps.panel.phtml deleted file mode 100644 index 28c146c86..000000000 --- a/src/Tracy/Bar/panels/dumps.panel.phtml +++ /dev/null @@ -1,29 +0,0 @@ - - - -

Dumps

- -
- - -

- - - - -
diff --git a/src/Tracy/Bar/panels/dumps.tab.latte b/src/Tracy/Bar/panels/dumps.tab.latte new file mode 100644 index 000000000..58c6b0705 --- /dev/null +++ b/src/Tracy/Bar/panels/dumps.tab.latte @@ -0,0 +1,6 @@ +{exitIf empty($data)} + + + +dumps diff --git a/src/Tracy/Bar/panels/dumps.tab.phtml b/src/Tracy/Bar/panels/dumps.tab.phtml deleted file mode 100644 index 12e0e0b11..000000000 --- a/src/Tracy/Bar/panels/dumps.tab.phtml +++ /dev/null @@ -1,13 +0,0 @@ - - -dumps diff --git a/src/Tracy/Bar/panels/info.panel.latte b/src/Tracy/Bar/panels/info.panel.latte new file mode 100644 index 000000000..14fee4360 --- /dev/null +++ b/src/Tracy/Bar/panels/info.panel.latte @@ -0,0 +1,71 @@ +{include '../panels/info.panel.php'} +{* + * @var array $info + * @var object[] $packages + * @var object[] $devPackages + *} + + + +

System info

+ +
+
+ + {foreach $info as $key => $val} + + {if strlen($val) > 25} + + {else} + + {/if} + + {/foreach} +
{$key} {$val}{$key}{$val}
+ + {if $packages || $devPackages} +

+ Composer Packages ({count($packages)}{$devPackages ? ' + ' . count($devPackages) . ' dev' : ''}) +

+ +
+ {if $packages} + + {foreach $packages as $package} + + + + + {/foreach} +
{$package->name}{$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : ''}
+ {/if} + + {if $devPackages} +

Dev Packages

+ + + {foreach $devPackages as $package} + + + + + {/foreach} +
{$package->name}{$package->version}{strpos($package->version, dev) !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : ''}
+ {/if} +
+ {/if} +
+
diff --git a/src/Tracy/Bar/panels/info.panel.phtml b/src/Tracy/Bar/panels/info.panel.php similarity index 50% rename from src/Tracy/Bar/panels/info.panel.phtml rename to src/Tracy/Bar/panels/info.panel.php index 3f6af5d14..c5ec8697f 100644 --- a/src/Tracy/Bar/panels/info.panel.phtml +++ b/src/Tracy/Bar/panels/info.panel.php @@ -4,21 +4,21 @@ namespace Tracy; +use Composer\Autoload\ClassLoader; +use function count; +use const PHP_VERSION, PHP_ZTS; + /** @var DefaultBarPanel $this */ if (isset($this->cpuUsage) && $this->time) { - foreach (getrusage() as $key => $val) { + foreach (getrusage() ?: [] as $key => $val) { $this->cpuUsage[$key] -= $val; } $userUsage = -round(($this->cpuUsage['ru_utime.tv_sec'] * 1e6 + $this->cpuUsage['ru_utime.tv_usec']) / $this->time / 10000); $systemUsage = -round(($this->cpuUsage['ru_stime.tv_sec'] * 1e6 + $this->cpuUsage['ru_stime.tv_usec']) / $this->time / 10000); } -$countClasses = function (array $list): int { - return count(array_filter($list, function (string $name): bool { - return (new \ReflectionClass($name))->isUserDefined(); - })); -}; +$countClasses = fn(array $list): int => count(array_filter($list, fn(string $name): bool => (new \ReflectionClass($name))->isUserDefined())); $ipFormatter = static function (?string $ip): ?string { if ($ip === '127.0.0.1' || $ip === '::1') { @@ -33,8 +33,8 @@ $info = [ 'Execution time' => number_format($this->time * 1000, 1, '.', "\u{202f}") . "\u{202f}ms", - 'CPU usage user + system' => isset($userUsage) ? (int) $userUsage . "\u{202f}% + " . (int) $systemUsage . "\u{202f}%" : null, - 'Peak of allocated memory' => number_format(memory_get_peak_usage() / 1000000, 2, '.', "\u{202f}") . "\u{202f}MB", + 'CPU usage user + system' => isset($userUsage, $systemUsage) ? (int) $userUsage . "\u{202f}% + " . (int) $systemUsage . "\u{202f}%" : null, + 'Peak of allocated memory' => number_format(memory_get_peak_usage() / 1_000_000, 2, '.', "\u{202f}") . "\u{202f}MB", 'Included files' => count(get_included_files()), 'Classes + interfaces + traits' => $countClasses(get_declared_classes()) . ' + ' . $countClasses(get_declared_interfaces()) . ' + ' . $countClasses(get_declared_traits()), @@ -52,74 +52,20 @@ $info = array_map('strval', array_filter($info + (array) $this->data)); $packages = $devPackages = []; -if (class_exists('Composer\Autoload\ClassLoader', autoload: false)) { +if (class_exists(ClassLoader::class, autoload: false)) { $baseDir = (function () { - @include dirname((new \ReflectionClass('Composer\Autoload\ClassLoader'))->getFileName()) . '/autoload_psr4.php'; // @ may not exist + $baseDir = null; + @include dirname((string) (new \ReflectionClass(ClassLoader::class))->getFileName()) . '/autoload_psr4.php'; // @ may not exist return $baseDir; })(); + $composer = @json_decode((string) file_get_contents($baseDir . '/composer.lock')); // @ may not exist or be valid [$packages, $devPackages] = [(array) @$composer->packages, (array) @$composer->{'packages-dev'}]; // @ keys may not exist + foreach ([&$packages, &$devPackages] as &$items) { array_walk($items, function ($package) { $package->hash = $package->source->reference ?? $package->dist->reference ?? null; }, $items); - usort($items, function ($a, $b): int { return $a->name <=> $b->name; }); + usort($items, fn($a, $b): int => $a->name <=> $b->name); } } - -?> - - -

System info

- -
-
- - $val): ?> - - 25): ?> - - - - - - -
- - -

Composer Packages ()

- -
- - - - - -
name) ?>version . (strpos($package->version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : '')) ?>
- - - -

Dev Packages

- - - - -
name) ?>version . (strpos($package->version, 'dev') !== false && $package->hash ? ' #' . substr($package->hash, 0, 4) : '')) ?>
- -
- -
-
diff --git a/src/Tracy/Bar/panels/info.tab.latte b/src/Tracy/Bar/panels/info.tab.latte new file mode 100644 index 000000000..d238fb9cd --- /dev/null +++ b/src/Tracy/Bar/panels/info.tab.latte @@ -0,0 +1,14 @@ +{use Tracy\DefaultBarPanel} +{* + * @var DefaultBarPanel $this + *} +{do $this->time = microtime(true) - Tracy\Debugger::$time} + + + + + + + {number_format($this->time * 1000, 1, '.', ' ')} ms + diff --git a/src/Tracy/Bar/panels/info.tab.phtml b/src/Tracy/Bar/panels/info.tab.phtml deleted file mode 100644 index 1e8c4da17..000000000 --- a/src/Tracy/Bar/panels/info.tab.phtml +++ /dev/null @@ -1,15 +0,0 @@ -time = microtime(true) - Debugger::$time; - -?> - - -time * 1000, 1, '.', "\u{202f}") ?> ms - diff --git a/src/Tracy/Bar/panels/warnings.panel.latte b/src/Tracy/Bar/panels/warnings.panel.latte new file mode 100644 index 000000000..99eff5aaa --- /dev/null +++ b/src/Tracy/Bar/panels/warnings.panel.latte @@ -0,0 +1,16 @@ +{* + * @var array $data + *} +

Warnings

+ +
+ + {foreach $data as $item => $count} + {do [$file, $line, $message] = explode('|', $item, 3)} + + + + + {/foreach} +
{$count ? $count . '×' : ''}
{$message} in {Tracy\Helpers::editorLink($file, (int) $line)}
+
diff --git a/src/Tracy/Bar/panels/warnings.panel.phtml b/src/Tracy/Bar/panels/warnings.panel.phtml deleted file mode 100644 index 96883ddf7..000000000 --- a/src/Tracy/Bar/panels/warnings.panel.phtml +++ /dev/null @@ -1,20 +0,0 @@ - -

Warnings

- -
- - $count): [$file, $line, $message] = explode('|', $item, 3) ?> - - - - - -
-
diff --git a/src/Tracy/Bar/panels/warnings.tab.latte b/src/Tracy/Bar/panels/warnings.tab.latte new file mode 100644 index 000000000..f7bc2bf5c --- /dev/null +++ b/src/Tracy/Bar/panels/warnings.tab.latte @@ -0,0 +1,19 @@ +{exitIf empty($data)} + + + + + + + {$sum = array_sum($data)}{$sum > 1 ? ' warnings' : ' warning'} + diff --git a/src/Tracy/Bar/panels/warnings.tab.phtml b/src/Tracy/Bar/panels/warnings.tab.phtml deleted file mode 100644 index baef3e1cf..000000000 --- a/src/Tracy/Bar/panels/warnings.tab.phtml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - 1 ? ' warnings' : ' warning' ?> - diff --git a/src/Tracy/BlueScreen/BlueScreen.php b/src/Tracy/BlueScreen/BlueScreen.php index 9cf95f266..fdbb93cb0 100644 --- a/src/Tracy/BlueScreen/BlueScreen.php +++ b/src/Tracy/BlueScreen/BlueScreen.php @@ -9,8 +9,8 @@ namespace Tracy; -use function in_array, strlen; -use const ARRAY_FILTER_USE_KEY, ENT_IGNORE; +use function in_array; +use const ARRAY_FILTER_USE_KEY, ENT_IGNORE, PHP_VERSION_ID; /** @@ -120,14 +120,14 @@ public function render(\Throwable $exception): void header('Content-Type: text/html; charset=UTF-8'); } - $this->renderTemplate($exception, __DIR__ . '/assets/page.phtml'); + $this->renderTemplate($exception, __DIR__ . '/dist/page.phtml'); } /** @internal */ public function renderToAjax(\Throwable $exception, DeferredContent $defer): void { - $defer->addSetup('Tracy.BlueScreen.loadAjax', Helpers::capture(fn() => $this->renderTemplate($exception, __DIR__ . '/assets/content.phtml'))); + $defer->addSetup('Tracy.BlueScreen.loadAjax', Helpers::capture(fn() => $this->renderTemplate($exception, __DIR__ . '/dist/content.phtml'))); } @@ -142,7 +142,7 @@ public function renderToFile(\Throwable $exception, string $file): bool fwrite($handle, $buffer); return ''; }, 4096); - $this->renderTemplate($exception, __DIR__ . '/assets/page.phtml', toScreen: false); + $this->renderTemplate($exception, __DIR__ . '/dist/page.phtml', toScreen: false); ob_end_flush(); ob_end_clean(); fclose($handle); @@ -189,15 +189,26 @@ private function renderTemplate(\Throwable $exception, string $template, bool $t ], Debugger::$customCssFiles)); $css = Helpers::minifyCss(implode('', $css)); - $nonceAttr = $toScreen ? Helpers::getNonceAttr() : null; + $js = array_map(fn($file) => '(function(){' . file_get_contents($file) . '})();', [ + __DIR__ . '/../assets/toggle.js', + __DIR__ . '/../assets/table-sort.js', + __DIR__ . '/../assets/tabs.js', + __DIR__ . '/../assets/helpers.js', + __DIR__ . '/../Dumper/assets/dumper.js', + __DIR__ . '/assets/bluescreen.js', + ]); + $js = Helpers::minifyJs(implode('', $js)); + + $nonce = $toScreen ? Helpers::getNonce() : null; $actions = $toScreen ? $this->renderActions($exception) : []; + $blueScreen = $this; require $template; } /** - * @return \stdClass[] + * @return list<\stdClass> */ private function renderPanels(?\Throwable $ex): array { @@ -325,7 +336,7 @@ public static function highlightFile( ? CodeHighlighter::highlightPhp($source, $line, $column) : '
' . CodeHighlighter::highlightLine(htmlspecialchars($source, ENT_IGNORE, 'UTF-8'), $line, $column) . '
'; - if ($editor = Helpers::editorUri($file, $line)) { + if ($editor = Helpers::editorUri($file, line: $line, column: $column)) { $source = substr_replace($source, ' title="Ctrl-Click to open in editor" data-tracy-href="' . Helpers::escapeHtml($editor) . '"', 4, 0); } diff --git a/src/Tracy/BlueScreen/assets/content.latte b/src/Tracy/BlueScreen/assets/content.latte new file mode 100644 index 000000000..38de0d9bc --- /dev/null +++ b/src/Tracy/BlueScreen/assets/content.latte @@ -0,0 +1,76 @@ +{* + * @var Throwable $exception + * @var array{link: string, label: string, external?: bool}[] $actions + * @var string[] $info + * @var string $source + * @var ?array{type: int, message: string, file: string, line: int} $lastError + * @var array $httpHeaders + * @var callable $dump + * @var array{0?: Tracy\Dumper\Value[], 1?: mixed[]} $snapshot + * @var bool $showEnvironment + * @var Tracy\BlueScreen $blueScreen + * @var bool $headersSent + * @var ?string $headersFile + * @var ?int $headersLine + * @var mixed[][] $obStatus + * @var Generator[] $generators + * @var Fiber[] $fibers + *} +{use Tracy\Dumper} + + +  + +
+ {do $ex = $exception} + {do $exceptions = []} + + {include 'section-exception.phtml'} + {include 'section-lastMutedError.phtml'} + + {do $bottomPanels = []} + {foreach $blueScreen->renderPanels(null) as $panel} + {if !empty($panel->bottom)} + {do $bottomPanels[] = $panel} + {continueIf true} + {/if} + + {do $collapsed = !isset($panel->collapsed) || $panel->collapsed} +
+ + +
+ {$panel->panel|noescape} +
+
+ {/foreach} + + {include 'section-environment.phtml'} + {include 'section-cli.phtml'} + {include 'section-http.phtml'} + + {foreach $bottomPanels as $panel} +
+ + +
+ {$panel->panel|noescape} +
+
+ {/foreach} + + +
+ + +
diff --git a/src/Tracy/BlueScreen/assets/content.phtml b/src/Tracy/BlueScreen/assets/content.phtml deleted file mode 100644 index 107b6a0c4..000000000 --- a/src/Tracy/BlueScreen/assets/content.phtml +++ /dev/null @@ -1,73 +0,0 @@ - - -  -
- - - - - - -renderPanels(null) as $panel): ?> -bottom)) { $bottomPanels[] = $panel; continue; } ?> -collapsed) || $panel->collapsed ? ' tracy-collapsed' : ''; ?> -
- - -
- panel ?> -
-
- - - - - - - - - -
- - -
- panel ?> -
-
- - - -
- > -
diff --git a/src/Tracy/BlueScreen/assets/page.latte b/src/Tracy/BlueScreen/assets/page.latte new file mode 100644 index 000000000..fd9a56fa2 --- /dev/null +++ b/src/Tracy/BlueScreen/assets/page.latte @@ -0,0 +1,43 @@ +{* + * @var Throwable $exception + * @var string $title + * @var ?string $nonce + * @var string $css + * @var string $js + * @var string $source + *} +{use Tracy\Helpers} + +{do $code = $exception->getCode() ? ' #' . $exception->getCode() : ''} +{do $chain = Helpers::getExceptionChain($exception)} +

+ + + + + + + + {$title}: {$exception->getMessage()}{$code} + + {if count($chain) > 1} + + {/if} + + + + + + + +{include 'content.phtml'} + + + + diff --git a/src/Tracy/BlueScreen/assets/page.phtml b/src/Tracy/BlueScreen/assets/page.phtml deleted file mode 100644 index a6846dc6c..000000000 --- a/src/Tracy/BlueScreen/assets/page.phtml +++ /dev/null @@ -1,57 +0,0 @@ -getCode() ? ' #' . $exception->getCode() : ''; -$chain = Helpers::getExceptionChain($exception); -?>

- - - - - - - <?= Helpers::escapeHtml($title . ': ' . $exception->getMessage() . $code) ?> - - 1): ?> - - - - - - - - - - - -> -'use strict'; - -Tracy.BlueScreen.init(); - - - diff --git a/src/Tracy/BlueScreen/assets/section-cli.latte b/src/Tracy/BlueScreen/assets/section-cli.latte new file mode 100644 index 000000000..7d4d27428 --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-cli.latte @@ -0,0 +1,36 @@ +{* + * @var string $source + * @var callable $dump + *} +{use Tracy\Helpers} + +{exitIf !Helpers::isCli()} + +
+ + +
+

Process ID {getmypid()}

+ + {if count($tmp = explode('):', $source, 2)) === 2} +
php{$tmp[1]}
+ {/if} + + {if isset($_SERVER[argv])} +

Arguments

+ +
+ + {foreach $_SERVER[argv] as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ {/if} +
+
diff --git a/src/Tracy/BlueScreen/assets/section-cli.phtml b/src/Tracy/BlueScreen/assets/section-cli.phtml deleted file mode 100644 index 6d06e40d6..000000000 --- a/src/Tracy/BlueScreen/assets/section-cli.phtml +++ /dev/null @@ -1,36 +0,0 @@ - -
- - -
-

Process ID

- -
php
- - - -

Arguments

-
- - $v): ?> - - -
-
- -
-
diff --git a/src/Tracy/BlueScreen/assets/section-environment.latte b/src/Tracy/BlueScreen/assets/section-environment.latte new file mode 100644 index 000000000..e1677cec4 --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-environment.latte @@ -0,0 +1,99 @@ +{* + * @var callable $dump + * @var bool $showEnvironment + * @var mixed[][] $obStatus + * @var Tracy\BlueScreen $blueScreen + *} +{use Tracy\Dumper} + +{exitIf !$showEnvironment} +{do $constants = get_defined_constants(true)[user] ?? []} + +
+ + +
+
+ + +
+
+ + {foreach $_SERVER as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ + {if $_SESSION ?? null} +
+
+ + {foreach $_SESSION as $k => $v} + + + + + {/foreach} +
{$k}{if $k === __NF}Nette Session{else}{$dump($v, $k)}{/if}
+
+ + {if !empty($_SESSION[__NF][DATA])} +

Nette Session

+
+ + {foreach $_SESSION[__NF][DATA] as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ {/if} +
+ {/if} + + {if $constants} +
+ + {foreach $constants as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ {/if} + +
+ {do $blueScreen->renderPhpInfo()} +
+ + {if $obStatus} +
+ {Dumper::toHtml($obStatus, [Dumper::COLLAPSE_COUNT => 10])} +
+ {/if} +
+
+
+
diff --git a/src/Tracy/BlueScreen/assets/section-environment.phtml b/src/Tracy/BlueScreen/assets/section-environment.phtml deleted file mode 100644 index 866ec8976..000000000 --- a/src/Tracy/BlueScreen/assets/section-environment.phtml +++ /dev/null @@ -1,103 +0,0 @@ - -
- - -
- -
- - - -
-
- - $v): ?> - - -
-
- - - -
-
- - $v): ?> - - -
Nette Session' : $dump($v, $k) ?>
-
- - - -

Nette Session

-
- - $v): ?> - - -
-
- -
- - - - - -
- - $v): ?> - - -
-
- - - -
- renderPhpInfo() ?> - -
- - - -
- 10]) ?> -
- -
-
-
-
diff --git a/src/Tracy/BlueScreen/assets/section-exception-causedBy.latte b/src/Tracy/BlueScreen/assets/section-exception-causedBy.latte new file mode 100644 index 000000000..75707eb4e --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-exception-causedBy.latte @@ -0,0 +1,20 @@ +{* + * @var Throwable $ex + * @var Throwable[] $exceptions + * @var array{link: string, label: string, external?: bool}[] $actions + * @var callable $dump + *} +{do $ex = $ex->getPrevious()} +{exitIf !$ex || in_array($ex, $exceptions, true)} +{do $exceptions[] = $ex} +{do $collapsed = count($exceptions) > 1} + +
+ + +
+ {include 'section-exception.phtml'} +
+
diff --git a/src/Tracy/BlueScreen/assets/section-exception-causedBy.phtml b/src/Tracy/BlueScreen/assets/section-exception-causedBy.phtml deleted file mode 100644 index 8807bf071..000000000 --- a/src/Tracy/BlueScreen/assets/section-exception-causedBy.phtml +++ /dev/null @@ -1,29 +0,0 @@ -getPrevious(); -if (!$ex || in_array($ex, $exceptions, true)) { - return; -} -$exceptions[] = $ex; -?> - -
- - -
- - -
-
diff --git a/src/Tracy/BlueScreen/assets/section-exception-exception.latte b/src/Tracy/BlueScreen/assets/section-exception-exception.latte new file mode 100644 index 000000000..0ce45f61e --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-exception-exception.latte @@ -0,0 +1,15 @@ +{* + * @var Throwable $ex + * @var callable $dump + *} +{exitIf count(get_mangled_object_vars($ex)) <= count(get_mangled_object_vars(new Exception))} + +
+ + +
+ {$dump($ex)} +
+
diff --git a/src/Tracy/BlueScreen/assets/section-exception-exception.phtml b/src/Tracy/BlueScreen/assets/section-exception-exception.phtml deleted file mode 100644 index 104edd4e3..000000000 --- a/src/Tracy/BlueScreen/assets/section-exception-exception.phtml +++ /dev/null @@ -1,21 +0,0 @@ - -
- -
- -
-
diff --git a/src/Tracy/BlueScreen/assets/section-exception.latte b/src/Tracy/BlueScreen/assets/section-exception.latte new file mode 100644 index 000000000..a5f8b9ecb --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-exception.latte @@ -0,0 +1,67 @@ +{* + * @var Throwable $ex + * @var Throwable[] $exceptions + * @var array{link: string, label: string, external?: bool}[] $actions + * @var callable $dump + * @var Tracy\BlueScreen $blueScreen + * @var Generator[] $generators + * @var Fiber[] $fibers + *} + +{include 'section-header.phtml'} + +{foreach $blueScreen->renderPanels($ex) as $panel} +
+ + +
+ {$panel->panel|noescape} +
+
+{/foreach} + +{if !$exceptions && ($generators || $fibers)} +
+
+
+ + +
+
+ {include '../assets/section-stack-exception.php'} +
+ + {foreach $generators as $generator} +
+ {include '../assets/section-stack-generator.php'} +
+ {/foreach} + + {foreach $fibers as $fiber} +
+ {include '../assets/section-stack-fiber.php'} +
+ {/foreach} +
+
+
+
+{else} + {include '../assets/section-stack-exception.php'} +{/if} + +{include 'section-exception-exception.phtml'} + +{include 'section-exception-causedBy.phtml'} diff --git a/src/Tracy/BlueScreen/assets/section-exception.phtml b/src/Tracy/BlueScreen/assets/section-exception.phtml deleted file mode 100644 index fba06273b..000000000 --- a/src/Tracy/BlueScreen/assets/section-exception.phtml +++ /dev/null @@ -1,72 +0,0 @@ - - - -renderPanels($ex) as $panel): ?> -
- - -
- panel ?> -
-
- - - -
-
-
- - -
-
- -
- - -
- -
- - - -
- -
- -
-
-
-
- - - - - - - diff --git a/src/Tracy/BlueScreen/assets/section-header.latte b/src/Tracy/BlueScreen/assets/section-header.latte new file mode 100644 index 000000000..94ada907a --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-header.latte @@ -0,0 +1,27 @@ +{* + * @var Throwable $ex + * @var Throwable[] $exceptions + * @var array{link: string, label: string, external?: bool}[] $actions + * @var Tracy\BlueScreen $blueScreen + *} +{use Tracy\Helpers} + +{do $title = $ex instanceof ErrorException ? Helpers::errorTypeToString($ex->getSeverity()) : get_debug_type($ex)} +{do $code = $ex->getCode() ? ' #' . $ex->getCode() : ''} + +
+ {if $ex->getMessage()}

{$title}{$code}

{/if} + +

+ {$blueScreen->formatMessage($ex) ?: htmlspecialchars($title . $code)} + {foreach $actions as $item} + {$item[label]}► + {/foreach} +

+ + {if $ex->getPrevious()} + + {/if} +
diff --git a/src/Tracy/BlueScreen/assets/section-header.phtml b/src/Tracy/BlueScreen/assets/section-header.phtml deleted file mode 100644 index f4ab19f2e..000000000 --- a/src/Tracy/BlueScreen/assets/section-header.phtml +++ /dev/null @@ -1,35 +0,0 @@ -getSeverity()) - : get_debug_type($ex); -$code = $ex->getCode() ? ' #' . $ex->getCode() : ''; - -?> -
- getMessage()): ?>

- - -

formatMessage($ex) ?: Helpers::escapeHtml($title . $code) ?> - - > - -

- - getPrevious()): ?> - - -
diff --git a/src/Tracy/BlueScreen/assets/section-http.latte b/src/Tracy/BlueScreen/assets/section-http.latte new file mode 100644 index 000000000..c76d07d1a --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-http.latte @@ -0,0 +1,133 @@ +{* + * @var string $source + * @var array $httpHeaders + * @var callable $dump + * @var bool $headersSent + * @var ?string $headersFile + * @var ?int $headersLine + *} +{use Tracy\BlueScreen} +{use Tracy\Helpers} + +{exitIf Helpers::isCli()} + +
+ + +
+
+ + +
+
+

{$_SERVER[REQUEST_METHOD] ?? URL} {$source}

+ + {if $httpHeaders} +
+ + {foreach $httpHeaders as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ {/if} + +

$_GET

+ {if empty($_GET)} +

empty

+ {else} +
+ + {foreach $_GET as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ {/if} + + {if ($_SERVER[REQUEST_METHOD] ?? null) === POST} + {if empty($_POST)} + {if ($post = file_get_contents('php://input', length: 2000)) === ''} +

$_POST

+

empty

+ {else} +

POST (preview)

+ {$dump($post)} + {/if} + {else} +

$_POST

+
+ + {foreach $_POST as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ {/if} + {/if} + +

$_COOKIE

+ {if empty($_COOKIE)} +

empty

+ {else} +
+ + {foreach $_COOKIE as $k => $v} + + + + + {/foreach} +
{$k}{$dump($v, $k)}
+
+ {/if} +
+ + +
+

Code: {http_response_code()}

+ + {if headers_list()} +
+ + {foreach headers_list() as $s} + {do $s = explode(':', $s, 2)} + + + + + {/foreach} +
{$s[0]}{$dump(trim($s[1]), $s[0])}
+
+ {else} +

no headers

+ {/if} + + {if $headersSent && $headersFile && $headersLine !== null && @is_file($headersFile)} +

Headers have been sent, output started at {Helpers::editorLink($headersFile, $headersLine)} source

+ +
{BlueScreen::highlightFile($headersFile, $headersLine)}
+ {elseif $headersSent} +

Headers have been sent

+ {else} +

Headers were not sent at the time the exception was thrown

+ {/if} +
+
+
+
+
diff --git a/src/Tracy/BlueScreen/assets/section-http.phtml b/src/Tracy/BlueScreen/assets/section-http.phtml deleted file mode 100644 index 000ef1a8b..000000000 --- a/src/Tracy/BlueScreen/assets/section-http.phtml +++ /dev/null @@ -1,124 +0,0 @@ - -
- - -
- -
- - -
- -
-

- - -
- - $v): ?> - - -
-
- - - -

$_GET

- -

empty

- -
- - $v): ?> - - -
-
- - - - - - -

$_POST

-

empty

- -

POST (preview)

- - - -

$_POST

-
- - $v): ?> - - -
-
- - - -

$_COOKIE

- -

empty

- -
- - $v): ?> - - -
-
- -
- - -
-

Code:

- -
- - - - -
-
- -

no headers

- - - - -

Headers have been sent, output started at source

-
- -

Headers have been sent

- -

Headers were not sent at the time the exception was thrown

- -
-
-
-
-
diff --git a/src/Tracy/BlueScreen/assets/section-lastMutedError.latte b/src/Tracy/BlueScreen/assets/section-lastMutedError.latte new file mode 100644 index 000000000..c24b61aeb --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-lastMutedError.latte @@ -0,0 +1,27 @@ +{* + * @var ?array{type: int, message: string, file: string, line: int} $lastError + *} +{use Tracy\BlueScreen} +{use Tracy\Helpers} + +{exitIf !$lastError} + +
+ + +
+

{Helpers::errorTypeToString($lastError[type])}: {$lastError[message]}

+ +

Note: the last muted error may have nothing to do with the thrown exception.

+ + {if @is_file($lastError[file])} +

{Helpers::editorLink($lastError[file], $lastError[line])}

+ +
{BlueScreen::highlightFile($lastError[file], $lastError[line])}
+ {else} +

inner-code:{$lastError[line]}

+ {/if} +
+
diff --git a/src/Tracy/BlueScreen/assets/section-lastMutedError.phtml b/src/Tracy/BlueScreen/assets/section-lastMutedError.phtml deleted file mode 100644 index a4f79f73c..000000000 --- a/src/Tracy/BlueScreen/assets/section-lastMutedError.phtml +++ /dev/null @@ -1,29 +0,0 @@ - -
- -
- -

:

-

Note: the last muted error may have nothing to do with the thrown exception.

- - -

-
- -

inner-code

- -
-
diff --git a/src/Tracy/BlueScreen/assets/section-stack-callStack.latte b/src/Tracy/BlueScreen/assets/section-stack-callStack.latte new file mode 100644 index 000000000..6a6044457 --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-stack-callStack.latte @@ -0,0 +1,86 @@ +{* + * @var callable $dump + * @var ?int $expanded + * @var array> $stack + *} +{use Tracy\BlueScreen} +{use Tracy\Debugger} +{use Tracy\Helpers} + +{exitIf !$stack} + +
+ + +
+
+ {foreach $stack as $key => $row} + {do $clickable = !empty($row[args]) || isset($row[file]) && @is_file($row[file])} + +
+ {if isset($row[file]) && @is_file($row[file])} + {Helpers::editorLink($row[file], $row[line])} + {else} + inner-code{if isset($row[line])}:{$row[line]}{/if} + {/if} +
+ + + + {if $clickable} +
+ {do $sourceOriginal = isset($row[file]) && @is_file($row[file]) ? [$row[file], $row[line]] : null} + {do $sourceMapped = $sourceOriginal ? Debugger::mapSource(...$sourceOriginal) : null} + {if $sourceOriginal && $sourceMapped} +
+ + +
+
+ {BlueScreen::highlightFile(...$sourceOriginal)} +
+ +
+ {BlueScreen::highlightFile($sourceMapped[file], line: $sourceMapped[line], column: $sourceMapped[column], php: false)} +
+
+
+ {elseif $sourceOriginal} + {BlueScreen::highlightFile(...$sourceOriginal)} + {/if} + + + {if !empty($row[args])} + + {try} + {do $params = (isset($row[class]) ? new \ReflectionMethod($row[class], $row[function]) : new \ReflectionFunction($row[function]))->getParameters()} + {rollback} + {do $params = []} + {/try} + + {foreach $row[args] as $k => $v} + {do $argName = isset($params[$k]) && !$params[$k]->isVariadic() ? $params[$k]->name : $k} + + + + + {/foreach} +
{is_string($argName) ? '$' : '#'}{$argName}{$dump($v, (string) $argName)}
+ {/if} +
+ {/if} + {/foreach} +
+
+
diff --git a/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml b/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml deleted file mode 100644 index f11007713..000000000 --- a/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml +++ /dev/null @@ -1,92 +0,0 @@ - - -
- - -
-
- $row): ?> - - -
- - - - inner-code - - -
- -
- - -  - - -
- - -
- - - -
- - -
-
- -
- -
- -
-
-
- - - - - - - -getParameters(); - } catch (\Exception) { - $params = []; - } - foreach ($row['args'] as $k => $v) { - $argName = isset($params[$k]) && !$params[$k]->isVariadic() ? $params[$k]->name : $k; - echo '\n"; - } -?> -
', Helpers::escapeHtml((is_string($argName) ? '$' : '#') . $argName), ''; - echo $dump($v, (string) $argName); - echo "
- -
- - -
-
-
diff --git a/src/Tracy/BlueScreen/assets/section-stack-exception.phtml b/src/Tracy/BlueScreen/assets/section-stack-exception.php similarity index 56% rename from src/Tracy/BlueScreen/assets/section-stack-exception.phtml rename to src/Tracy/BlueScreen/assets/section-stack-exception.php index 7f578cc73..80f90aac1 100644 --- a/src/Tracy/BlueScreen/assets/section-stack-exception.phtml +++ b/src/Tracy/BlueScreen/assets/section-stack-exception.php @@ -4,27 +4,32 @@ namespace Tracy; +use function in_array; + /** * @var \Throwable $ex * @var callable $dump - * @var BlueScreen $this + * @var BlueScreen $blueScreen */ $stack = $ex->getTrace(); if (in_array($stack[0]['class'] ?? null, [DevelopmentStrategy::class, ProductionStrategy::class], true)) { array_shift($stack); } -if (($stack[0]['class'] ?? null) === Debugger::class && in_array($stack[0]['function'], ['shutdownHandler', 'errorHandler'], true)) { +if ( + ($stack[0]['class'] ?? null) === Debugger::class + && in_array($stack[0]['function'], ['shutdownHandler', 'errorHandler'], true) +) { array_shift($stack); } $expanded = null; if ( (!$ex instanceof \ErrorException || in_array($ex->getSeverity(), [E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED], true)) - && $this->isCollapsed($ex->getFile()) + && $blueScreen->isCollapsed($ex->getFile()) ) { foreach ($stack as $key => $row) { - if (isset($row['file']) && !$this->isCollapsed($row['file'])) { + if (isset($row['file']) && !$blueScreen->isCollapsed($row['file'])) { $expanded = $key; break; } @@ -34,5 +39,5 @@ $file = $ex->getFile(); $line = $ex->getLine(); -require __DIR__ . '/section-stack-sourceFile.phtml'; -require __DIR__ . '/section-stack-callStack.phtml'; +require __DIR__ . '/../dist/section-stack-sourceFile.phtml'; +require __DIR__ . '/../dist/section-stack-callStack.phtml'; diff --git a/src/Tracy/BlueScreen/assets/section-stack-fiber.phtml b/src/Tracy/BlueScreen/assets/section-stack-fiber.php similarity index 75% rename from src/Tracy/BlueScreen/assets/section-stack-fiber.phtml rename to src/Tracy/BlueScreen/assets/section-stack-fiber.php index 4427be843..6ab4f1799 100644 --- a/src/Tracy/BlueScreen/assets/section-stack-fiber.phtml +++ b/src/Tracy/BlueScreen/assets/section-stack-fiber.php @@ -13,4 +13,4 @@ $stack = $ref->getTrace(); $expanded = 0; -require __DIR__ . '/section-stack-callStack.phtml'; +require __DIR__ . '/../dist/section-stack-callStack.phtml'; diff --git a/src/Tracy/BlueScreen/assets/section-stack-generator.phtml b/src/Tracy/BlueScreen/assets/section-stack-generator.php similarity index 75% rename from src/Tracy/BlueScreen/assets/section-stack-generator.phtml rename to src/Tracy/BlueScreen/assets/section-stack-generator.php index 60aa6fc1d..5b14d1e90 100644 --- a/src/Tracy/BlueScreen/assets/section-stack-generator.phtml +++ b/src/Tracy/BlueScreen/assets/section-stack-generator.php @@ -17,5 +17,5 @@ $file = $refExec->getExecutingFile(); $line = $refExec->getExecutingLine(); -require __DIR__ . '/section-stack-sourceFile.phtml'; -require __DIR__ . '/section-stack-callStack.phtml'; +require __DIR__ . '/../dist/section-stack-sourceFile.phtml'; +require __DIR__ . '/../dist/section-stack-callStack.phtml'; diff --git a/src/Tracy/BlueScreen/assets/section-stack-sourceFile.latte b/src/Tracy/BlueScreen/assets/section-stack-sourceFile.latte new file mode 100644 index 000000000..cf2206e95 --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-stack-sourceFile.latte @@ -0,0 +1,44 @@ +{* + * @var string $file + * @var int $line + * @var ?int $expanded + *} +{use Tracy\BlueScreen} +{use Tracy\Debugger} +{use Tracy\Helpers} + +{do $sourceOriginal = $file && @is_file($file) ? [$file, $line] : null} +{do $sourceMapped = $sourceOriginal ? Debugger::mapSource($file, $line) : null} +{do $collapsed = $expanded !== null} + +
+ + +
+ {if $sourceOriginal && $sourceMapped} +
+ + +
+
+

File: {Helpers::editorLink(...$sourceOriginal)}

+ {BlueScreen::highlightFile(...$sourceOriginal)} +
+ +
+

File: {Helpers::editorLink($sourceMapped[file], $sourceMapped[line])}

+ {BlueScreen::highlightFile($sourceMapped[file], line: $sourceMapped[line], column: $sourceMapped[column], php: false)} +
+
+
+ {else} +

File: {Helpers::editorLink($file, $line)}

+ {if $sourceOriginal}{BlueScreen::highlightFile(...$sourceOriginal)}{/if} + {/if} +
+
diff --git a/src/Tracy/BlueScreen/assets/section-stack-sourceFile.phtml b/src/Tracy/BlueScreen/assets/section-stack-sourceFile.phtml deleted file mode 100644 index bde95a747..000000000 --- a/src/Tracy/BlueScreen/assets/section-stack-sourceFile.phtml +++ /dev/null @@ -1,46 +0,0 @@ - - -
- - -
- -
- - -
-
-

File:

- -
- -
-

File:

- -
-
-
- -

File:

- - - -
-
diff --git a/src/Tracy/BlueScreen/dist/content.phtml b/src/Tracy/BlueScreen/dist/content.phtml new file mode 100644 index 000000000..810c1ff4f --- /dev/null +++ b/src/Tracy/BlueScreen/dist/content.phtml @@ -0,0 +1,65 @@ + + $httpHeaders + * @var callable $dump + * @var array{0?: Tracy\Dumper\Value[], 1?: mixed[]} $snapshot + * @var bool $showEnvironment + * @var Tracy\BlueScreen $blueScreen + * @var bool $headersSent + * @var ?string $headersFile + * @var ?int $headersLine + * @var mixed[][] $obStatus + * @var Generator[] $generators + * @var Fiber[] $fibers + */ ?> + +  + +
+ + +renderPanels(null) as $panel): ?>bottom)): ?> +collapsed) || $panel->collapsed ?>
+ + +
+ panel ?> + +
+
+ + +
+ + +
+ panel ?> + +
+
+ + +
+ + +> +
diff --git a/src/Tracy/BlueScreen/dist/page.phtml b/src/Tracy/BlueScreen/dist/page.phtml new file mode 100644 index 000000000..b7c4f6f02 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/page.phtml @@ -0,0 +1,52 @@ + + +getCode() ? ' #' . $exception->getCode() : '' ?>

+ + + + + + + + <?= Tracy\Helpers::escapeHtml($title) ?> +: <?= Tracy\Helpers::escapeHtml($exception->getMessage()) ?> +<?= Tracy\Helpers::escapeHtml($code) ?> + + + 1): ?> + + + + + + + + +> +'use strict'; + + +Tracy.BlueScreen.init(); + + + diff --git a/src/Tracy/BlueScreen/dist/section-cli.phtml b/src/Tracy/BlueScreen/dist/section-cli.phtml new file mode 100644 index 000000000..98013c746 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-cli.phtml @@ -0,0 +1,34 @@ + + + +
+ + +
+

Process ID +

+ +
php
+
+ +

Arguments

+ +
+ + $v): ?> + + + +
+ +
+
+
+
diff --git a/src/Tracy/BlueScreen/dist/section-environment.phtml b/src/Tracy/BlueScreen/dist/section-environment.phtml new file mode 100644 index 000000000..7aaef6381 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-environment.phtml @@ -0,0 +1,87 @@ + + + +
+ + +
+
+ + +
+
+ + $v): ?> + + + +
+ +
+
+ +
+
+ + $v): ?> + + + +
+ +Nette Session + + +
+
+ +

Nette Session

+
+ + $v): ?> + + + +
+ +
+
+
+ +
+ + $v): ?> + + + +
+ +
+
+ +
+renderPhpInfo() ?>
+ +
+ 10]) ?> + +
+
+
+
+
diff --git a/src/Tracy/BlueScreen/dist/section-exception-causedBy.phtml b/src/Tracy/BlueScreen/dist/section-exception-causedBy.phtml new file mode 100644 index 000000000..9df7e4719 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-exception-causedBy.phtml @@ -0,0 +1,18 @@ + +getPrevious() ?> 1 ?> +
+ + +
+
+
diff --git a/src/Tracy/BlueScreen/dist/section-exception-exception.phtml b/src/Tracy/BlueScreen/dist/section-exception-exception.phtml new file mode 100644 index 000000000..090c2d372 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-exception-exception.phtml @@ -0,0 +1,17 @@ + + +
+ + +
+ + +
+
diff --git a/src/Tracy/BlueScreen/dist/section-exception.phtml b/src/Tracy/BlueScreen/dist/section-exception.phtml new file mode 100644 index 000000000..beea48adc --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-exception.phtml @@ -0,0 +1,54 @@ + + + +renderPanels($ex) as $panel): ?>
+ + +
+ panel ?> + +
+
+ +
+
+
+ + +
+
+
+ +
+
+ +
+
+
+
+
+
+ + + diff --git a/src/Tracy/BlueScreen/dist/section-header.phtml b/src/Tracy/BlueScreen/dist/section-header.phtml new file mode 100644 index 000000000..71d3e87f1 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-header.phtml @@ -0,0 +1,31 @@ + + +getSeverity()) : get_debug_type($ex) ?>getCode() ? ' #' . $ex->getCode() : '' ?> +
+getMessage()): ?> +

+ +

+ + +

+ formatMessage($ex) ?: htmlspecialchars($title . $code) ?> + + > +► +

+ +getPrevious()): ?> +
diff --git a/src/Tracy/BlueScreen/dist/section-http.phtml b/src/Tracy/BlueScreen/dist/section-http.phtml new file mode 100644 index 000000000..ef4a89922 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-http.phtml @@ -0,0 +1,115 @@ + + $httpHeaders + * @var callable $dump + * @var bool $headersSent + * @var ?string $headersFile + * @var ?int $headersLine + */ ?> + +
+ + +
+
+ + +
+
+

+ +

+ +
+ + $v): ?> + + + +
+ +
+
+ +

$_GET

+

empty

+
+ + $v): ?> + + + +
+ +
+
+ +

$_POST

+

empty

+

POST (preview)

+ + +

$_POST

+
+ + $v): ?> + + + +
+ +
+
+ +

$_COOKIE

+

empty

+
+ + $v): ?> + + + +
+ +
+
+
+ + +
+

Code: +

+ +
+ + + + + +
+ +
+
+

no headers

+ +

Headers have been sent, output started at + source

+ +
+
+

Headers have been sent

+

Headers were not sent at the time the exception was thrown

+
+
+
+
+
diff --git a/src/Tracy/BlueScreen/dist/section-lastMutedError.phtml b/src/Tracy/BlueScreen/dist/section-lastMutedError.phtml new file mode 100644 index 000000000..d04449afb --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-lastMutedError.phtml @@ -0,0 +1,28 @@ + + + +
+ + +
+

+: +

+ +

Note: the last muted error may have nothing to do with the thrown exception.

+ +

+

+ +
+
+

inner-code: +

+
+
diff --git a/src/Tracy/BlueScreen/dist/section-stack-callStack.phtml b/src/Tracy/BlueScreen/dist/section-stack-callStack.phtml new file mode 100644 index 000000000..cd6785c93 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-stack-callStack.phtml @@ -0,0 +1,77 @@ + +> $stack + */ ?> + +
+ + +
+
+ $row): ?> +
+ + + inner-code +: + + +
+ + + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+ + + + + +getParameters() ?> + $v): ?>isVariadic() ? $params[$k]->name : $k ?> + + + +
+ + +
+
+
+
+
diff --git a/src/Tracy/BlueScreen/dist/section-stack-sourceFile.phtml b/src/Tracy/BlueScreen/dist/section-stack-sourceFile.phtml new file mode 100644 index 000000000..5a972a915 --- /dev/null +++ b/src/Tracy/BlueScreen/dist/section-stack-sourceFile.phtml @@ -0,0 +1,46 @@ + + + +
+ + +
+
+ + +
+
+

File: +

+ + +
+ +
+

File: +

+ + +
+
+
+

File: +

+ + + + +
+
diff --git a/src/Tracy/Debugger/Debugger.php b/src/Tracy/Debugger/Debugger.php index 9264f9d15..e4fa67cef 100644 --- a/src/Tracy/Debugger/Debugger.php +++ b/src/Tracy/Debugger/Debugger.php @@ -19,7 +19,7 @@ */ class Debugger { - public const Version = '2.11.1'; + public const Version = '3.0-dev'; /** server modes for Debugger::enable() */ public const @@ -29,19 +29,19 @@ class Debugger public const CookieSecret = 'tracy-debug'; - /** @deprecated use Debugger::Version */ + #[\Deprecated('use Debugger::Version')] public const VERSION = self::Version; - /** @deprecated use Debugger::Development */ + #[\Deprecated('use Debugger::Development')] public const DEVELOPMENT = self::Development; - /** @deprecated use Debugger::Production */ + #[\Deprecated('use Debugger::Production')] public const PRODUCTION = self::Production; - /** @deprecated use Debugger::Detect */ + #[\Deprecated('use Debugger::Detect')] public const DETECT = self::Detect; - /** @deprecated use Debugger::CookieSecret */ + #[\Deprecated('use Debugger::CookieSecret')] public const COOKIE_SECRET = self::CookieSecret; /** in production mode is suppressed any debugging output */ @@ -61,7 +61,7 @@ class Debugger /** initial output buffer level */ private static int $obLevel; - /** @var ?list> output buffer status @internal */ + /** @var ?array> output buffer status @internal */ public static ?array $obStatus = null; /********************* errors and exceptions reporting ****************d*g**/ @@ -153,7 +153,7 @@ class Debugger private static Bar $bar; private static ILogger $logger; - /** @var array{DevelopmentStrategy, ProductionStrategy} */ + /** @var array */ private static array $strategy; private static SessionStorage $sessionStorage; @@ -485,9 +485,10 @@ public static function getSessionStorage(): SessionStorage /** * Dumps information about a variable in readable format. * @tracySkipLocation - * @param mixed $var variable to dump - * @param bool $return return output instead of printing it? (bypasses $productionMode) - * @return mixed variable itself or dump + * @template T + * @param T $var variable to dump + * @param bool $return return output instead of printing it? (bypasses $productionMode) + * @return ($return is true ? string : T) */ public static function dump(mixed $var, bool $return = false): mixed { @@ -537,8 +538,10 @@ public static function timer(?string $name = null): float /** * Dumps information about a variable in Tracy Debug Bar. * @tracySkipLocation + * @template T + * @param T $var * @param array $options - * @return mixed variable itself + * @return T */ public static function barDump(mixed $var, ?string $title = null, array $options = []): mixed { diff --git a/src/Tracy/Debugger/DeferredContent.php b/src/Tracy/Debugger/DeferredContent.php index 66a443ef3..8a8d0c2df 100644 --- a/src/Tracy/Debugger/DeferredContent.php +++ b/src/Tracy/Debugger/DeferredContent.php @@ -18,6 +18,7 @@ */ final class DeferredContent { + private readonly bool $deferred; private readonly string $requestId; private bool $useSession = false; @@ -25,7 +26,15 @@ final class DeferredContent public function __construct( private readonly SessionStorage $sessionStorage, ) { - $this->requestId = $_SERVER['HTTP_X_TRACY_AJAX'] ?? Helpers::createId(); + $ajax = $_SERVER['HTTP_X_TRACY_AJAX'] ?? ''; + $this->deferred = (bool) preg_match('#^\w{10,15}$#D', $ajax); + $this->requestId = $this->deferred ? $ajax : Helpers::createId(); + } + + + public function isDeferred(): bool + { + return $this->deferred; } @@ -103,7 +112,22 @@ public function sendAssets(): bool return true; } - if (Helpers::isAjax()) { + if (is_string($asset) && preg_match('#^lazy-panel\.([\w.+-]+)$#', $asset, $m)) { + $key = $m[1]; + header('Content-Type: application/json; charset=UTF-8'); + header('Cache-Control: no-cache'); + header_remove('Set-Cookie'); + $lazyItems = &$this->getItems('lazy-panels'); + $content = $lazyItems[$key]['content'] ?? null; + unset($lazyItems[$key]); + $str = json_encode(['content' => $content], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); + header('Content-Length: ' . strlen($str)); + echo $str; + flush(); + return true; + } + + if ($this->deferred) { header('X-Tracy-Ajax: 1'); // session must be already locked } diff --git a/src/Tracy/Debugger/DevelopmentStrategy.php b/src/Tracy/Debugger/DevelopmentStrategy.php index 4fd4e37c9..036a2f827 100644 --- a/src/Tracy/Debugger/DevelopmentStrategy.php +++ b/src/Tracy/Debugger/DevelopmentStrategy.php @@ -18,6 +18,9 @@ */ final class DevelopmentStrategy { + private bool $assetsSent = false; + + public function __construct( private readonly Bar $bar, private readonly BlueScreen $blueScreen, @@ -28,12 +31,15 @@ public function __construct( public function initialize(): void { + $this->bar->getPanel('Tracy:info')->cpuUsage = function_exists('getrusage') + ? (getrusage() ?: null) + : null; } public function handleException(\Throwable $exception, bool $firstTime): void { - if (Helpers::isAjax() && $this->defer->isAvailable()) { + if ($this->defer->isDeferred() && $this->defer->isAvailable()) { $this->blueScreen->renderToAjax($exception, $this->defer); } elseif ($firstTime && Helpers::isHtmlMode()) { @@ -59,7 +65,7 @@ private function renderExceptionCli(\Throwable $exception): void } if (Helpers::detectColors() && @is_file($exception->getFile())) { - echo "\n\n" . CodeHighlighter::highlightPhpCli(file_get_contents($exception->getFile()), $exception->getLine()) . "\n"; + echo "\n\n" . CodeHighlighter::highlightPhpCli((string) file_get_contents($exception->getFile()), $exception->getLine()) . "\n"; } echo "$exception\n" . ($logFile ? "\n(stored in $logFile)\n" : ''); @@ -93,7 +99,7 @@ public function handleError( $message = Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message); $count = &$this->bar->getPanel('Tracy:warnings')->data["$file|$line|$message"]; - if (!$count++ && !Helpers::isHtmlMode() && !Helpers::isAjax()) { + if (!$count++ && !Helpers::isHtmlMode() && !$this->defer->isDeferred()) { echo "\n$message in $file on line $line\n"; } @@ -105,7 +111,12 @@ public function handleError( public function sendAssets(): bool { - return $this->defer->sendAssets(); + if (!Helpers::isCli() && $this->defer->sendAssets()) { + $this->assetsSent = true; + return true; + } + + return false; } @@ -117,10 +128,15 @@ public function renderLoader(): void public function renderBar(): void { + if ($this->assetsSent || Helpers::isCli()) { + return; + } + if (function_exists('ini_set')) { ini_set('display_errors', '1'); } $this->bar->render($this->defer); + $this->bar->renderLazyPanels($this->defer); } } diff --git a/src/Tracy/Debugger/ProductionStrategy.php b/src/Tracy/Debugger/ProductionStrategy.php index b9664f81f..aadf9520d 100644 --- a/src/Tracy/Debugger/ProductionStrategy.php +++ b/src/Tracy/Debugger/ProductionStrategy.php @@ -39,7 +39,7 @@ public function handleException(\Throwable $exception, bool $firstTime): void header('Content-Type: text/html; charset=UTF-8'); } - (fn($logged) => require Debugger::$errorTemplate ?? __DIR__ . '/assets/error.500.phtml')(!$e); + (fn($logged) => require Debugger::$errorTemplate ?? __DIR__ . '/dist/error.500.phtml')(!$e); } elseif (Helpers::isCli() && is_resource(STDERR)) { fwrite(STDERR, "ERROR: {$exception->getMessage()}\n" @@ -65,7 +65,7 @@ public function handleError( $err = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message) . " in $file:$line"; } - Debugger::tryLog($err, Debugger::ERROR); + Debugger::tryLog($err, Debugger::WARNING); } diff --git a/src/Tracy/Debugger/assets/error.500.latte b/src/Tracy/Debugger/assets/error.500.latte new file mode 100644 index 000000000..163e0905a --- /dev/null +++ b/src/Tracy/Debugger/assets/error.500.latte @@ -0,0 +1,36 @@ +{* + * Default error page. + * @var bool $logged + *} +{do $nonce = Tracy\Helpers::getNonce()} + + + + + +Server Error + + + +
+
+

Server Error

+ +

We're sorry! The server encountered an internal error and + was unable to complete your request. Please try again later.

+ +

error 500 | {date('j. n. Y H:i')}{if !$logged}
Tracy is unable to log error.{/if}

+
+
+ + diff --git a/src/Tracy/Debugger/assets/error.500.phtml b/src/Tracy/Debugger/dist/error.500.phtml similarity index 79% rename from src/Tracy/Debugger/assets/error.500.phtml rename to src/Tracy/Debugger/dist/error.500.phtml index 7fe08f068..7805880ab 100644 --- a/src/Tracy/Debugger/assets/error.500.phtml +++ b/src/Tracy/Debugger/dist/error.500.phtml @@ -1,17 +1,10 @@ + @@ -35,10 +28,13 @@ namespace Tracy;

We're sorry! The server encountered an internal error and was unable to complete your request. Please try again later.

-

error 500 |
Tracy is unable to log error.

+

error 500 | + +
Tracy is unable to log error. +

-> +> document.body.insertBefore(document.getElementById('tracy-error'), document.body.firstChild); diff --git a/src/Tracy/Dumper/Describer.php b/src/Tracy/Dumper/Describer.php index 3da24cf04..b18d25d58 100644 --- a/src/Tracy/Dumper/Describer.php +++ b/src/Tracy/Dumper/Describer.php @@ -11,7 +11,7 @@ use Tracy; use Tracy\Helpers; -use function array_map, array_slice, class_exists, count, explode, file, get_debug_type, get_resource_type, gettype, htmlspecialchars, implode, is_bool, is_file, is_finite, is_int, is_resource, is_string, is_subclass_of, json_encode, method_exists, preg_match, spl_object_id, str_replace, strlen, strpos, strtolower, trim, uksort; +use function array_map, array_slice, class_exists, count, explode, file, get_debug_type, get_resource_type, gettype, htmlspecialchars, implode, is_bool, is_file, is_int, is_resource, is_string, is_subclass_of, json_encode, method_exists, preg_match, spl_object_id, str_replace, strlen, strpos, strtolower, trim, uksort; /** @@ -187,7 +187,7 @@ private function describeObject(object $obj, int $depth = 0): Value $rc = $obj instanceof \Closure ? new \ReflectionFunction($obj) : new \ReflectionClass($obj); - if ($rc->getFileName() && ($editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine()))) { + if ($rc->getFileName() && ($editor = Helpers::editorUri($rc->getFileName(), $rc->getStartLine() ?: null))) { $value->editor = (object) ['file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'url' => $editor]; } } @@ -196,7 +196,7 @@ private function describeObject(object $obj, int $depth = 0): Value $value->items = []; $props = $this->exposeObject($obj, $value); foreach ($props ?? [] as $k => $v) { - $this->addPropertyTo($value, (string) $k, $v, Value::PropertyVirtual, $this->getReferenceId($props, $k)); + $this->addPropertyTo($value, (string) $k, $v, Value::PropertyVirtual, $this->getReferenceId($props ?? [], $k)); } } @@ -252,11 +252,11 @@ public function addPropertyTo( ): void { if ($value->depth && $this->maxItems && count($value->items ?? []) >= $this->maxItems) { - $value->length = ($value->length ?? count($value->items)) + 1; + $value->length = ($value->length ?? count($value->items ?? [])) + 1; return; } - $class ??= $value->value; + $class ??= is_string($value->value) ? $value->value : null; $value->items[] = [ $this->describeKey($k), $type !== Value::PropertyVirtual && $this->isSensitive($k, $v, $class) @@ -307,7 +307,7 @@ private static function hideValue(mixed $val): string /** @param class-string $class */ public function describeEnumProperty(string $class, string $property, mixed $value): ?Value { - [$set, $constants] = $this->enumProperties["$class::$property"] ?? null; + [$set, $constants] = $this->enumProperties["$class::$property"] ?? [false, []]; if (!is_int($value) || !$constants || !($constants = Helpers::decomposeFlags($value, $set, $constants)) diff --git a/src/Tracy/Dumper/Dumper.php b/src/Tracy/Dumper/Dumper.php index 349cea581..5a9d0014a 100644 --- a/src/Tracy/Dumper/Dumper.php +++ b/src/Tracy/Dumper/Dumper.php @@ -47,7 +47,7 @@ class Dumper public const HIDDEN_VALUE = Describer::HiddenValue; - /** @var Dumper\Value[] */ + /** @var array{0?: Dumper\Value[], 1?: mixed[]} */ public static array $liveSnapshot = []; /** @var ?array */ @@ -106,14 +106,17 @@ class Dumper /** * Dumps variable to the output. + * @template T + * @param T $var * @param array $options + * @return T */ public static function dump(mixed $var, array $options = []): mixed { if (Helpers::isCli()) { $useColors = self::$terminalColors && Helpers::detectColors(); $dumper = new self($options); - fwrite(STDOUT, $dumper->asTerminal($var, $useColors ? self::$terminalColors : [])); + fwrite(STDOUT, $dumper->asTerminal($var, $useColors ? self::$terminalColors ?? [] : [])); } elseif (Helpers::isHtmlMode()) { $options[self::LOCATION] ??= true; @@ -154,7 +157,7 @@ public static function toText(mixed $var, array $options = []): string */ public static function toTerminal(mixed $var, array $options = []): string { - return (new self($options))->asTerminal($var, self::$terminalColors); + return (new self($options))->asTerminal($var, self::$terminalColors ?? []); } @@ -170,7 +173,7 @@ public static function renderAssets(): void $sent = true; - $nonceAttr = Helpers::getNonceAttr(); + $nonceAttr = ($nonce = Helpers::getNonce()) ? ' nonce="' . Helpers::escapeHtml($nonce) . '"' : ''; $s = (Debugger::$showBar ? '' : file_get_contents(__DIR__ . '/../assets/reset.css')) . file_get_contents(__DIR__ . '/../assets/toggle.css') . file_get_contents(__DIR__ . '/assets/dumper-light.css') diff --git a/src/Tracy/Dumper/Exposer.php b/src/Tracy/Dumper/Exposer.php index a91941155..ab1424766 100644 --- a/src/Tracy/Dumper/Exposer.php +++ b/src/Tracy/Dumper/Exposer.php @@ -11,7 +11,8 @@ use Dom; use Ds; -use function array_diff_key, array_key_exists, array_key_last, count, end, explode, get_mangled_object_vars, implode, iterator_to_array, preg_match_all, sort; +use function array_diff_key, array_key_exists, count, end, explode, get_mangled_object_vars, implode, iterator_to_array, preg_match_all, sort; +use const PHP_VERSION_ID; /** @@ -216,6 +217,7 @@ public static function exposeSplObjectStorage(\SplObjectStorage $obj, Value $val $describer->addPropertyTo($pair, 'key', $v); $describer->addPropertyTo($pair, 'value', $obj[$v]); $describer->addPropertyTo($value, '', null, described: $pair); + assert($value->items !== null); $value->items[count($value->items) - 1][0] = ''; } } @@ -230,6 +232,7 @@ public static function exposeWeakMap(\WeakMap $obj, Value $value, Describer $des $describer->addPropertyTo($pair, 'key', $k); $describer->addPropertyTo($pair, 'value', $v); $describer->addPropertyTo($value, '', null, described: $pair); + assert($value->items !== null); $value->items[count($value->items) - 1][0] = ''; } } diff --git a/src/Tracy/Dumper/Renderer.php b/src/Tracy/Dumper/Renderer.php index 7564518d7..2c34d1463 100644 --- a/src/Tracy/Dumper/Renderer.php +++ b/src/Tracy/Dumper/Renderer.php @@ -145,7 +145,7 @@ private function renderString(string|Value $str, int $depth, string|int|null $ke $indent = ' ' . str_repeat('| ', $depth - 1) . ' '; return '' . "'" - . (is_string($str) ? Helpers::escapeHtml($str) : str_replace("\n", "\n" . $indent, $str->value)) + . (is_string($str) ? Helpers::escapeHtml($str) : str_replace("\n", "\n" . $indent, (string) $str->value)) . "'" . ''; @@ -164,7 +164,7 @@ private function renderString(string|Value $str, int $depth, string|int|null $ke . ($title ? 'tracy-dump-private' : $classes[$keyType]) . '"' . $title . '>' . (is_string($str) ? Helpers::escapeHtml($str) - : "'" . str_replace("\n", "\n" . $indent, $str->value) . "'") + : "'" . str_replace("\n", "\n" . $indent, (string) $str->value) . "'") . ''; } elseif (is_string($str)) { @@ -179,7 +179,7 @@ private function renderString(string|Value $str, int $depth, string|int|null $ke } else { $unit = $str->type === Value::TypeStringHtml ? 'characters' : 'bytes'; - $count = substr_count($str->value, "\n"); + $count = substr_count((string) $str->value, "\n"); if ($count) { $collapsed = $indent1 = $toggle = null; $indent = ' '; @@ -195,7 +195,7 @@ private function renderString(string|Value $str, int $depth, string|int|null $ke . '" title="' . $str->length . ' ' . $unit . '">' . $indent1 . ''" - . str_replace("\n", "\n" . $indent, $str->value) + . str_replace("\n", "\n" . $indent, (string) $str->value) . "'" . ($depth ? "\n" : '') . ''; @@ -295,11 +295,12 @@ private function renderObject(Value $object, int $depth): string ); } - $pos = strrpos($object->value, '\\'); + $name = (string) $object->value; + $pos = strrpos($name, '\\'); $out = '' . ($pos - ? Helpers::escapeHtml(substr($object->value, 0, $pos + 1)) . '' . Helpers::escapeHtml(substr($object->value, $pos + 1)) . '' - : Helpers::escapeHtml($object->value)) + ? Helpers::escapeHtml(substr($name, 0, $pos + 1)) . '' . Helpers::escapeHtml(substr($name, $pos + 1)) . '' + : Helpers::escapeHtml($name)) . '' . ($object->id && $this->hash ? ' #' . $object->id . '' : ''); @@ -360,8 +361,8 @@ private function renderObject(Value $object, int $depth): string private function renderResource(Value $resource, int $depth): string { - $out = '' . Helpers::escapeHtml($resource->value) . ' ' - . ($this->hash ? '@' . substr($resource->id, 1) . '' : ''); + $out = '' . Helpers::escapeHtml((string) $resource->value) . ' ' + . ($this->hash ? '@' . substr((string) $resource->id, 1) . '' : ''); if (!$resource->items) { return $out; diff --git a/src/Tracy/Dumper/Value.php b/src/Tracy/Dumper/Value.php index ecd4a24db..d516bec5b 100644 --- a/src/Tracy/Dumper/Value.php +++ b/src/Tracy/Dumper/Value.php @@ -39,7 +39,7 @@ final class Value implements \JsonSerializable public int|string|null $id = null; public object $holder; - /** @var list|null */ + /** @var array|null */ public ?array $items = null; public ?\stdClass $editor = null; public ?bool $collapsed = null; diff --git a/src/Tracy/Helpers.php b/src/Tracy/Helpers.php index 8cf6bfec9..2d8090ae0 100644 --- a/src/Tracy/Helpers.php +++ b/src/Tracy/Helpers.php @@ -9,7 +9,7 @@ namespace Tracy; -use function array_filter, array_map, array_merge, array_pop, array_slice, array_unique, basename, bin2hex, class_exists, constant, count, dechex, defined, dirname, end, escapeshellarg, explode, extension_loaded, func_get_args, function_exists, get_class, get_class_methods, get_declared_classes, get_defined_functions, getenv, getmypid, headers_list, htmlspecialchars, htmlspecialchars_decode, iconv_strlen, implode, in_array, is_a, is_array, is_callable, is_file, is_object, is_string, levenshtein, ltrim, mb_strlen, mb_substr, method_exists, ob_end_clean, ob_get_clean, ob_start, ord, preg_match, preg_replace, preg_replace_callback, random_bytes, rawurlencode, rtrim, sapi_windows_vt100_support, spl_object_id, str_contains, str_pad, str_replace, strcasecmp, stream_isatty, strip_tags, strlen, strtoupper, strtr, substr, trait_exists, utf8_decode; +use function array_filter, array_map, array_merge, array_pop, array_slice, array_unique, basename, bin2hex, class_exists, constant, count, dechex, defined, dirname, end, escapeshellarg, explode, extension_loaded, func_get_args, function_exists, get_class_methods, get_declared_classes, get_defined_functions, getenv, getmypid, headers_list, htmlspecialchars, htmlspecialchars_decode, iconv_strlen, implode, in_array, is_a, is_array, is_callable, is_file, is_object, is_string, levenshtein, ltrim, mb_strlen, mb_substr, method_exists, ob_end_clean, ob_get_clean, ob_start, ord, preg_match, preg_replace, preg_replace_callback, random_bytes, rawurlencode, rtrim, sapi_windows_vt100_support, spl_object_id, str_contains, str_pad, str_replace, strcasecmp, stream_isatty, strip_tags, strlen, strtoupper, strtr, substr, trait_exists, utf8_decode; use const DIRECTORY_SEPARATOR, ENT_HTML5, ENT_QUOTES, ENT_SUBSTITUTE, PHP_EOL, PHP_SAPI, STDOUT, STR_PAD_LEFT; @@ -57,6 +57,7 @@ public static function editorUri( string $action = 'open', string $search = '', string $replace = '', + ?int $column = null, ): ?string { if (Debugger::$editor && $file && ($action === 'create' || @is_file($file))) { // @ - may trigger error @@ -67,7 +68,7 @@ public static function editorUri( return strtr(Debugger::$editor, [ '%action' => $action, '%file' => rawurlencode($file), - '%line' => $line ?: 1, + '%line' => ($line ?: 1) . ($column ? ':' . $column : ''), '%search' => rawurlencode($search), '%replace' => rawurlencode($replace), ]); @@ -173,7 +174,7 @@ public static function improveException(\Throwable $e): void ) { // do nothing } elseif (preg_match('~Argument #(\d+)(?: \(\$\w+\))? must be of type callable, (.+ given)~', $message, $m)) { - $arg = $e->getTrace()[0]['args'][$m[1] - 1] ?? null; + $arg = $e->getTrace()[0]['args'][(int) $m[1] - 1] ?? null; if (is_string($arg) && str_contains($arg, '::')) { $arg = explode('::', $arg, 2); } @@ -204,7 +205,7 @@ public static function improveException(\Throwable $e): void $replace = ["$m[2](", "$hint("]; } - } elseif (preg_match('#^Undefined property: ([\w\\\]+)::\$(\w+)#', $message, $m)) { + } elseif (preg_match('#^Undefined property: ([\w\\\]+)::\$(\w+)#', $message, $m) && class_exists($m[1])) { $rc = new \ReflectionClass($m[1]); $items = array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($prop) => !$prop->isStatic()); if ($hint = self::getSuggestion($items, $m[2])) { @@ -212,7 +213,10 @@ public static function improveException(\Throwable $e): void $replace = ["->$m[2]", "->$hint"]; } - } elseif (preg_match('#^Access to undeclared static property:? ([\w\\\]+)::\$(\w+)#', $message, $m)) { + } elseif ( + preg_match('#^Access to undeclared static property:? ([\w\\\]+)::\$(\w+)#', $message, $m) + && class_exists($m[1]) + ) { $rc = new \ReflectionClass($m[1]); $items = array_filter($rc->getProperties(\ReflectionProperty::IS_STATIC), fn($prop) => $prop->isPublic()); if ($hint = self::getSuggestion($items, $m[2])) { @@ -239,7 +243,7 @@ public static function improveException(\Throwable $e): void /** @internal */ public static function improveError(string $message): string { - if (preg_match('#^Undefined property: ([\w\\\]+)::\$(\w+)#', $message, $m)) { + if (preg_match('#^Undefined property: ([\w\\\]+)::\$(\w+)#', $message, $m) && class_exists($m[1])) { $rc = new \ReflectionClass($m[1]); $items = array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($prop) => !$prop->isStatic()); $hint = self::getSuggestion($items, $m[2]); @@ -307,13 +311,6 @@ public static function isHtmlMode(): bool } - /** @internal */ - public static function isAjax(): bool - { - return isset($_SERVER['HTTP_X_TRACY_AJAX']) && preg_match('#^\w{10,15}$#D', $_SERVER['HTTP_X_TRACY_AJAX']); - } - - /** @internal */ public static function isRedirect(): bool { @@ -336,11 +333,11 @@ public static function isCli(): bool /** @internal */ - public static function getNonceAttr(): string + public static function getNonce(): ?string { return preg_match('#^Content-Security-Policy(?:-Report-Only)?:.*\sscript-src\s+(?:[^;]+\s)?\'nonce-([\w+/]+=*)\'#mi', implode("\n", headers_list()), $m) - ? ' nonce="' . self::escapeHtml($m[1]) . '"' - : ''; + ? $m[1] + : null; } @@ -443,7 +440,7 @@ public static function utf8Length(string $s): int { return match (true) { extension_loaded('mbstring') => mb_strlen($s, 'UTF-8'), - extension_loaded('iconv') => iconv_strlen($s, 'UTF-8'), + extension_loaded('iconv') => iconv_strlen($s, 'UTF-8') ?: strlen($s), default => strlen(@utf8_decode($s)), // deprecated }; } @@ -595,7 +592,7 @@ public static function detectColors(): bool } - /** @return \Throwable[] */ + /** @return list<\Throwable> */ public static function getExceptionChain(\Throwable $ex): array { $res = [$ex]; @@ -609,7 +606,7 @@ public static function getExceptionChain(\Throwable $ex): array /** * @param callable(object): void $callback - * @param array $skip + * @param true[] $skip */ public static function traverseValue(mixed $val, callable $callback, array &$skip = [], ?string $refId = null): void { @@ -639,7 +636,7 @@ public static function traverseValue(mixed $val, callable $callback, array &$ski /** * @param string[] $constants - * @return string[]|null + * @return list|null * @internal */ public static function decomposeFlags(int $flags, bool $set, array $constants): ?array diff --git a/src/Tracy/Logger/Logger.php b/src/Tracy/Logger/Logger.php index ba342aefa..bdd027c5f 100644 --- a/src/Tracy/Logger/Logger.php +++ b/src/Tracy/Logger/Logger.php @@ -18,23 +18,22 @@ */ class Logger implements ILogger { - /** @var ?string name of the directory where errors should be logged */ - public $directory; + /** name of the directory where errors should be logged */ + public ?string $directory = null; - /** @var string|string[]|null email or emails to which send error notifications */ - public $email; + /** @var string|string[]|null email or emails to which send error notifications */ + public string|array|null $email = null; - /** @var ?string sender of email notifications */ - public $fromEmail; + /** sender of email notifications */ + public ?string $fromEmail = null; - /** @var mixed interval for sending email is 2 days */ - public $emailSnooze = '2 days'; + /** interval for sending email is 2 days */ + public string|int $emailSnooze = '2 days'; - /** @var callable(mixed $message, string $email): void handler for sending emails */ - public $mailer; + /** @var \Closure(mixed $message, string $email): void handler for sending emails */ + public ?\Closure $mailer = null; - /** @var ?BlueScreen */ - private $blueScreen; + private ?BlueScreen $blueScreen = null; /** @@ -54,7 +53,7 @@ public function __construct(?string $directory, string|array|null $email = null, * For levels ERROR, EXCEPTION and CRITICAL it sends email. * @return ?string logged error filename */ - public function log(mixed $message, string $level = self::INFO) + public function log(mixed $message, string $level = self::INFO): ?string { if (!$this->directory) { throw new \LogicException('Logging directory is not specified.'); @@ -84,10 +83,7 @@ public function log(mixed $message, string $level = self::INFO) } - /** - * @param mixed $message - */ - public static function formatMessage($message): string + public static function formatMessage(mixed $message): string { if ($message instanceof \Throwable) { $tmp = []; @@ -108,10 +104,7 @@ public static function formatMessage($message): string } - /** - * @param mixed $message - */ - public static function formatLogLine($message, ?string $exceptionFile = null): string + public static function formatLogLine(mixed $message, ?string $exceptionFile = null): string { return implode(' ', [ date('[Y-m-d H-i-s]'), @@ -136,6 +129,7 @@ public function getExceptionFile(\Throwable $exception, string $level = self::EX } $hash = substr(hash('xxh128', serialize($data)), 0, 10); + assert($this->directory !== null); $dir = strtr($this->directory . '/', '\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR); foreach (new \DirectoryIterator($this->directory) as $file) { if (strpos($file->getBasename(), $hash)) { @@ -160,10 +154,7 @@ protected function logException(\Throwable $exception, ?string $file = null): st } - /** - * @param mixed $message - */ - protected function sendEmail($message): void + protected function sendEmail(mixed $message): void { $snooze = is_numeric($this->emailSnooze) ? $this->emailSnooze @@ -180,12 +171,7 @@ protected function sendEmail($message): void } - /** - * Default mailer. - * @param mixed $message - * @internal - */ - public function defaultMailer($message, string $email): void + private function defaultMailer(mixed $message, string $email): void { $host = preg_replace('#[^\w.-]+#', '', $_SERVER['SERVER_NAME'] ?? php_uname('n')); mail( diff --git a/src/Tracy/Session/FileSession.php b/src/Tracy/Session/FileSession.php index 1291feafe..a92f8370a 100644 --- a/src/Tracy/Session/FileSession.php +++ b/src/Tracy/Session/FileSession.php @@ -59,12 +59,12 @@ private function open(): void $file = @fopen($path = $this->dir . '/' . self::FilePrefix . $id, 'c+'); // intentionally @ if ($file === false) { - throw new \RuntimeException("Unable to create file '$path'. " . error_get_last()['message']); + throw new \RuntimeException("Unable to create file '$path'. " . (error_get_last()['message'] ?? '')); } } if (!@flock($file, LOCK_EX)) { // intentionally @ - throw new \RuntimeException("Unable to acquire exclusive lock on '$path'. " . error_get_last()['message']); + throw new \RuntimeException("Unable to acquire exclusive lock on '$path'. " . (error_get_last()['message'] ?? '')); } $this->file = $file; @@ -85,7 +85,7 @@ public function &getData(): array public function clean(): void { $old = strtotime('-1 week'); - foreach (glob($this->dir . '/' . self::FilePrefix . '*') as $file) { + foreach (glob($this->dir . '/' . self::FilePrefix . '*') ?: [] as $file) { if (filemtime($file) < $old) { unlink($file); } diff --git a/src/Tracy/functions.php b/src/Tracy/functions.php index 0228d8dfa..401806ab8 100644 --- a/src/Tracy/functions.php +++ b/src/Tracy/functions.php @@ -11,6 +11,9 @@ /** * Tracy\Debugger::dump() shortcut. * @tracySkipLocation + * @template T + * @param T $var + * @return T */ function dump(mixed $var): mixed { @@ -37,6 +40,9 @@ function dumpe(mixed $var): void /** * Tracy\Debugger::barDump() shortcut. * @tracySkipLocation + * @template T + * @param T $var + * @return T */ function bdump(mixed $var): mixed { diff --git a/tests/Tracy/BlueScreen.showEnvironment.phpt b/tests/Tracy/BlueScreen.showEnvironment.phpt index 20687319f..b872ddb03 100644 --- a/tests/Tracy/BlueScreen.showEnvironment.phpt +++ b/tests/Tracy/BlueScreen.showEnvironment.phpt @@ -21,7 +21,7 @@ $render = function ($exception) use ($blueScreen) { $exception = new Exception('foo'); -$lookFor = ''; +$lookFor = 'Environment'; // sanity test: The environment section is present in the rendered string $c = $render($exception); diff --git a/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt b/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt index eab95076e..379a6f73c 100644 --- a/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt +++ b/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt @@ -24,12 +24,26 @@ $onFatalErrorCalled = false; register_shutdown_function(function () use (&$onFatalErrorCalled) { Assert::true($onFatalErrorCalled); - Assert::match('ErrorException: Cannot re-assign $this in %a% -Stack trace: -#0 [internal function]: Tracy\Debugger::shutdownHandler() -#1 {main} -Tracy is unable to log error: Logging directory is not specified. -', ob_get_clean()); + Assert::match( + ini_get('fatal_error_backtraces') ? <<<'XX' + ErrorException: Cannot re-assign $this in %a% + Stack trace: + #0 %a%: third(Array) + #1 %a%: second(true, false) + #2 %a% + #3 {main} + Tracy is unable to log error: Logging directory is not specified. + + XX : <<<'XX' + ErrorException: Cannot re-assign $this in %a% + Stack trace: + #0 [internal function]: Tracy\Debugger::shutdownHandler() + #1 {main} + Tracy is unable to log error: Logging directory is not specified. + + XX, + ob_get_clean(), + ); echo 'OK!'; // prevents PHP bug #62725 }); diff --git a/tests/Tracy/Debugger.barDump().showLocation.phpt b/tests/Tracy/Debugger.barDump().showLocation.phpt index 6b601fb2b..a890dc0b4 100644 --- a/tests/Tracy/Debugger.barDump().showLocation.phpt +++ b/tests/Tracy/Debugger.barDump().showLocation.phpt @@ -34,10 +34,10 @@ register_shutdown_function(function () { %A%

Dumps

- -
barDump('value') 📍'value'
+
%A% XX, $panelContent); diff --git a/tests/Tracy/Debugger.customCssFiles.phpt b/tests/Tracy/Debugger.customCssFiles.phpt index 90cab7f00..34e7bede7 100644 --- a/tests/Tracy/Debugger.customCssFiles.phpt +++ b/tests/Tracy/Debugger.customCssFiles.phpt @@ -18,10 +18,10 @@ $output = ob_get_clean(); Assert::contains('custom-asset{}', $output); -$handler = Tracy\Debugger::getStrategy(); +$defer = new Tracy\DeferredContent(Tracy\Debugger::getSessionStorage()); ob_start(); $_GET['_tracy_bar'] = 'js'; -$handler->sendAssets(); +$defer->sendAssets(); $output = ob_get_clean(); Assert::contains('custom-asset{}', $output); diff --git a/tests/Tracy/Debugger.customJsFiles.phpt b/tests/Tracy/Debugger.customJsFiles.phpt index 26453dab5..ab956378b 100644 --- a/tests/Tracy/Debugger.customJsFiles.phpt +++ b/tests/Tracy/Debugger.customJsFiles.phpt @@ -9,10 +9,10 @@ require __DIR__ . '/../bootstrap.php'; Tracy\Debugger::$customJsFiles[] = __DIR__ . '/fixtures/custom.asset'; -$handler = Tracy\Debugger::getStrategy(); +$defer = new Tracy\DeferredContent(Tracy\Debugger::getSessionStorage()); ob_start(); $_GET['_tracy_bar'] = 'js'; -$handler->sendAssets(); +$defer->sendAssets(); $output = ob_get_clean(); Assert::contains('custom-asset {}', $output); diff --git a/tests/Tracy/Debugger.log().error.phpt b/tests/Tracy/Debugger.log().error.phpt index f1488f754..b0bb34c31 100644 --- a/tests/Tracy/Debugger.log().error.phpt +++ b/tests/Tracy/Debugger.log().error.phpt @@ -11,6 +11,7 @@ use Tracy\Debugger; require __DIR__ . '/../bootstrap.php'; +Tester\Helpers::purge(getTempDir()); Assert::exception( fn() => Debugger::log('Hello'), diff --git a/tests/Tracy/Debugger.log().samefile.phpt b/tests/Tracy/Debugger.log().samefile.phpt index c983c0cfc..8092e8287 100644 --- a/tests/Tracy/Debugger.log().samefile.phpt +++ b/tests/Tracy/Debugger.log().samefile.phpt @@ -13,6 +13,7 @@ require __DIR__ . '/../bootstrap.php'; // Setup environment +Tester\Helpers::purge(getTempDir()); Debugger::$logDirectory = getTempDir(); diff --git a/tests/Tracy/Debugger.logSeverity.E_NOTICE.development.phpt b/tests/Tracy/Debugger.logSeverity.E_NOTICE.development.phpt index 7fbf1f430..63cf91bec 100644 --- a/tests/Tracy/Debugger.logSeverity.E_NOTICE.development.phpt +++ b/tests/Tracy/Debugger.logSeverity.E_NOTICE.development.phpt @@ -13,6 +13,7 @@ require __DIR__ . '/../bootstrap.php'; // Setup environment +Tester\Helpers::purge(getTempDir()); Debugger::enable(Debugger::Development, getTempDir()); Debugger::$logSeverity = E_NOTICE; diff --git a/tests/Tracy/Debugger.logSeverity.E_NOTICE.phpt b/tests/Tracy/Debugger.logSeverity.E_NOTICE.phpt index 9622104a7..2bee86922 100644 --- a/tests/Tracy/Debugger.logSeverity.E_NOTICE.phpt +++ b/tests/Tracy/Debugger.logSeverity.E_NOTICE.phpt @@ -13,6 +13,7 @@ require __DIR__ . '/../bootstrap.php'; // Setup environment +Tester\Helpers::purge(getTempDir()); Debugger::enable(Debugger::Production, getTempDir()); Debugger::$logSeverity = E_NOTICE; diff --git a/tests/Tracy/Debugger.logging.error.phpt b/tests/Tracy/Debugger.logging.error.phpt index d1897ab6f..d57a6c63f 100644 --- a/tests/Tracy/Debugger.logging.error.phpt +++ b/tests/Tracy/Debugger.logging.error.phpt @@ -16,6 +16,7 @@ require __DIR__ . '/../bootstrap.php'; // Setup environment +Tester\Helpers::purge(getTempDir()); $_SERVER['HTTP_HOST'] = 'nette.org'; Debugger::$logDirectory = getTempDir(); diff --git a/tests/Tracy/Debugger.logging.warnings.phpt b/tests/Tracy/Debugger.logging.warnings.phpt index 4a2158839..8ae3c079d 100644 --- a/tests/Tracy/Debugger.logging.warnings.phpt +++ b/tests/Tracy/Debugger.logging.warnings.phpt @@ -13,6 +13,7 @@ require __DIR__ . '/../bootstrap.php'; // Setup environment +Tester\Helpers::purge(getTempDir()); $_SERVER['HTTP_HOST'] = 'nette.org'; $logDirectory = getTempDir(); diff --git a/tests/Tracy/Debugger.warnings.html.phpt b/tests/Tracy/Debugger.warnings.html.phpt index e325b402e..ff0429477 100644 --- a/tests/Tracy/Debugger.warnings.html.phpt +++ b/tests/Tracy/Debugger.warnings.html.phpt @@ -31,19 +31,19 @@ register_shutdown_function(function () { $panelContent = (string) DomQuery::fromHtml($rawContent)->find('#tracy-debug-panel-Tracy-warnings')[0]['data-tracy-content']; Assert::match(<<<'XX' %A% - - - - - - - - - - - - -
1%a%
Notice: Only variables should be assigned by reference in %a%:%d%
1%a%
Warning: hex2bin(): Hexadecimal input string must have an even length in %a%:%d%
1%a%
Compile Warning: Unsupported declare 'foo' in %a%:%d%
+ + 1%a% +
Notice: Only variables should be assigned by reference in %a%:%d%
+ + + 1%a% +
Warning: hex2bin(): Hexadecimal input string must have an even length in %a%:%d%
+ + + 1%a% +
Compile Warning: Unsupported declare 'foo' in %a%:%d%
+ + %A% XX, $panelContent); echo 'OK!'; // prevents PHP bug #62725 diff --git a/tests/Tracy/Logger.log().errors.phpt b/tests/Tracy/Logger.log().errors.phpt index 238e325da..161508914 100644 --- a/tests/Tracy/Logger.log().errors.phpt +++ b/tests/Tracy/Logger.log().errors.phpt @@ -12,6 +12,7 @@ use Tracy\Logger; require __DIR__ . '/../bootstrap.php'; +Tester\Helpers::purge(getTempDir()); $logger = new Logger(getTempDir()); $logger->log('Hello'); // no error diff --git a/tests/Tracy/Logger.log().phpt b/tests/Tracy/Logger.log().phpt index 5c6cb1a3e..290b0ba43 100644 --- a/tests/Tracy/Logger.log().phpt +++ b/tests/Tracy/Logger.log().phpt @@ -11,6 +11,7 @@ use Tracy\Logger; require __DIR__ . '/../bootstrap.php'; +Tester\Helpers::purge(getTempDir()); test('', function () { $e = new Exception('First'); diff --git a/tests/Tracy/expected/Debugger.barDump().expect b/tests/Tracy/expected/Debugger.barDump().expect index f93fb52a2..6d514712a 100644 --- a/tests/Tracy/expected/Debugger.barDump().expect +++ b/tests/Tracy/expected/Debugger.barDump().expect @@ -5,15 +5,15 @@

Dumps

diff --git a/tests/Tracy/expected/Debugger.error-in-eval.expect b/tests/Tracy/expected/Debugger.error-in-eval.expect index af76ed7ec..eec4dbf86 100644 --- a/tests/Tracy/expected/Debugger.error-in-eval.expect +++ b/tests/Tracy/expected/Debugger.error-in-eval.expect @@ -1,4 +1,5 @@

+ @@ -16,201 +17,202 @@  +

User Error

-

The my error - search► +

+ The my error + search►

-
- - - +
- +
-

File: %a% : eval()'d code:1

- +

File: %a% : eval()'d code:1

-
- +
-
- -
- inner-code:1 -
- - - -
- - - - + + + + + +
$message
+				
+ inner-code:1 +
+ + + +
+ + + + + - -
$message
'The my error'
-
$error%a%
256
-
+
$error%a%
256
+
+
+ + + + +
+
+
+					
+ + + + +
+
+
+							
+									
+										
+										
+									
+									
+										
+										
+									
+							
$user
'root'
+
$pass

+
+
- - - -
- eval () -
- -
-
-
-		
- - - - - -
-
-
-			
-
-
-			
$user
'root'
-
$pass

-
-
-
-
- + +

+
 	
- - - -
- +
- -
- - - -
-
- - %A% -
-
- - - - - -
- - %A% -
-
- - -
-%A%%A% - - - -
+ %A% +
+
+ +
+ + %A% +
+
+
%A%
+ +
+
%A%
+ +
-
- -
- +
- -
- - -
- -
-

URL %a%

- - - -

$_GET

-

empty

- - - -

$_COOKIE

-

empty

-
- - -
-

Code: %d%

-
- %A%
-
- - -

Headers were not sent at the time the exception was thrown

+
+ + +
+
+

URL %a%

+ +

$_GET

+

empty

+

$_COOKIE

+

empty

+
+ + +
+

Code: %d%

+ +
+ +%A% +
+
+

Headers were not sent at the time the exception was thrown

+
-
- -
    -
  • %a%️
  • +
  • %a%
  • Report generated at %a%
  • -
  • PHP %a%
+
  • PHP %a%
  • +
  • Tracy %a%
  • + +
    - -
    + + -%A%%A% \ No newline at end of file +%A%%A% diff --git a/tests/Tracy/expected/Debugger.exception.fiber.html.expect b/tests/Tracy/expected/Debugger.exception.fiber.html.expect index b3878636b..c2cf945e6 100644 --- a/tests/Tracy/expected/Debugger.exception.fiber.html.expect +++ b/tests/Tracy/expected/Debugger.exception.fiber.html.expect @@ -1,4 +1,5 @@

    + @@ -16,175 +17,200 @@  +

    Exception #123

    -

    The my exception - search► +

    + The my exception + search►

    -
    - - -
    -
    -
    - +
    +
    +
    +
    + +
    +
    - +
    -

    File: %a%

    -
    %A%
    +

    File: %a%Debugger.exception.fiber.html.phpt:%d%

    +
    %A%
    -
    - +
    -
    - - - - - -
    -
    -
    -			
    -
    -			
    $arg1
    
    -
    -
    - - +
    + - + -
    -
    +					
    +
    %A%
    - - - -
    $arg1
    true
    -
    $arg2
    false
    -
    -
    + + + + + +
    $arg1
    
    +
    +
    + - + - +
    +
    %A%
    + + + + + + + + + + +
    $arg1
    true
    +
    $arg2
    false
    +
    +
    + -
    -
    +				
     
    -			
    -
    -
    -			
    $arg1
    
    -
    $arg2
    'any string'
    -
    +
    +
    %A%
    + + + + + + + + + + +
    $arg1
    
    +
    $arg2
    'any string'
    +
    +
    -
    -
    - - -
    +
    +
    - +
    -
    - -
    - %a% -
    - - - -
    -
    %A%
    +
    + - - -
    $value
    123
    -
    -
    + -
    - %a% -
    +
    +
    %A%
    - + + + + + +
    $value
    123
    +
    +
    + -
    -
    %A%
    + - - -
    $a
    123
    -
    -
    +
    +
    %A%
    -
    - %a% -
    + + + + + +
    $a
    123
    +
    +
    + -
    - gen1 () -
    + -
    -
    %A%
    +
    +
    %A%
    -
    +
    +
    + inner-code
    -
    - inner-code -
    +
    +{closure%a?%} () +
    -
    -{closure%a?%} ()
    - -
    -
    -
    +
    +
    +
    - - - + %A% \ No newline at end of file diff --git a/tests/Tracy/expected/Debugger.exception.generator.html.expect b/tests/Tracy/expected/Debugger.exception.generator.html.expect index 453ef246a..62413f8ed 100644 --- a/tests/Tracy/expected/Debugger.exception.generator.html.expect +++ b/tests/Tracy/expected/Debugger.exception.generator.html.expect @@ -1,4 +1,5 @@

    + @@ -16,138 +17,173 @@  +

    Exception #123

    -

    The my exception - search► +

    + The my exception + search►

    -
    - - -
    -
    -
    +
    +
    +
    + +
    +
    - +
    -

    File: %a%

    -
    %A%
    +

    File: %a%

    +
    %d%: +%d%: +%d%: function second($arg1, $arg2) +%d%: { +%d%: third([1, 2, 3]); +%d%: } +%d%: +%d%: +%d%: function third($arg1) +%d%: { +%d%: throw new Exception('The my exception', 123); +%d%: } +%d%: +%d%: +%d%: first(gen1(), 'any string'); +
    -
    - +
    -
    - - - - - -
    -
    -
    -			
    -
    -			
    $arg1
    
    -
    -
    - - +
    + - + -
    -
    +					
    +
     
    -			
    -
    -
    -			
    $arg1
    true
    -
    $arg2
    false
    -
    -
    + + + + + +
    $arg1
    
    +
    +
    + - + - +
    +
    +
    +							
    +									
    +										
    +										
    +									
    +									
    +										
    +										
    +									
    +							
    $arg1
    true
    +
    $arg2
    false
    +
    +
    + -
    -
    +				
     
    -			
    -
    -
    -			
    $arg1
    
    -
    $arg2
    'any string'
    -
    +
    +
    +
    +							
    +									
    +										
    +										
    +									
    +									
    +										
    +										
    +									
    +							
    $arg1
    
    +
    $arg2
    'any string'
    +
    +
    -
    -
    - -
    +
    +
    - +
    -

    File: %a%

    -
    %A%
    +

    File: %a%

    +
    %A%
    -
    - +
    -
    +
    +
    + inner-code
    -
    - inner-code -
    +
    +gen1 () +
    -
    -gen1 ()
    - -
    -
    - -
    +
    +
    +
    - - - + +%A% + + -%A% \ No newline at end of file +%A%%A% \ No newline at end of file diff --git a/tests/Tracy/expected/Debugger.exception.html.expect b/tests/Tracy/expected/Debugger.exception.html.expect index 0608ac45a..f79b1ddcc 100644 --- a/tests/Tracy/expected/Debugger.exception.html.expect +++ b/tests/Tracy/expected/Debugger.exception.html.expect @@ -1,4 +1,5 @@

    + @@ -16,24 +17,25 @@  +

    Exception #123

    -

    The my exception - search► +

    + The my exception + search►

    -
    - - - +
    - +
    -

    File: %a%

    -
    %d%: +

    File: %a%Debugger.exception.html.phpt:%d%

    +
    %d%: %d%: %d%: function second($arg1, $arg2) %d%: { @@ -51,23 +53,25 @@
    -
    - +
    -
    - - - - - -
    -
    %d%: +
    + + + + +
    +
    %d%: %d%: %d%: function first($arg1, $arg2) %d%: { @@ -84,22 +88,26 @@ %d%: function third($arg1)
    - - -
    $arg1
    
    -
    -
    - - - - - -
    -
    19: + + + + + +
    $arg1
    
    +
    +
    + + + + +
    +
    %d%: %d%: %d%: Debugger::$productionMode = false; %d%: setHtmlMode(); @@ -116,24 +124,31 @@ %d%: function second($arg1, $arg2)
    - - - -
    $arg1
    true
    -
    $arg2
    false
    -
    -
    - - - - - -
    -
    %d%: + + + + + + + + + +
    $arg1
    true
    +
    $arg2
    false
    +
    +
    + + + + +
    +
    %d%: %d%: %d%: function third($arg1) %d%: { @@ -146,133 +161,126 @@ %d%: first(10, 'any string');
    - - - -
    $arg1
    10
    -
    $arg2
    'any string'
    -
    + + + + + + + + + +
    $arg1
    10
    +
    $arg2
    'any string'
    +
    +
    -
    - - -
    - + +
    +

    Warning: hex2bin(): Hexadecimal input string must have an even length

    -

    Warning: hex2bin(): Hexadecimal input string must have an even length

    -

    Note: the last muted error may have nothing to do with the thrown exception.

    +

    Note: the last muted error may have nothing to do with the thrown exception.

    -

    %a%Debugger.exception.html.phpt:%d%

    -
    %A%
    +

    %a%Debugger.exception.html.phpt:%d%

    + +
    %A%
    - -
    - +
    - -
    - - - -
    -
    - - %A% -
    -
    - - - - - -
    - - %A% -
    -
    - - -
    -%A%%A% - - - -
    + %A% +
    +
    + +
    + + %A% +
    +
    +
    +%A%
    + +
    +
    +
    +					
    -
    - -
    - +
    - -
    - - -
    - -
    -

    URL %a%

    - - - -

    $_GET

    -

    empty

    - - - -

    $_COOKIE

    -

    empty

    -
    - - -
    -

    Code: %d%

    -
    - %A%
    -
    - - -

    Headers were not sent at the time the exception was thrown

    +
    + + +
    +
    +

    URL %a%

    + +

    $_GET

    +

    empty

    +

    $_COOKIE

    +

    empty

    +
    + + +
    +

    Code: %d%

    + +
    + +%A% +
    +
    +

    Headers were not sent at the time the exception was thrown

    +
    -
    - -
      -
    • %a%️
    • +
    • %a%
    • Report generated at %a%
    • -
    • PHP %a%
    +
  • PHP %a%
  • +
  • Tracy %a%
  • + +
    - -
    + +
    diff --git a/tests/Tracy/expected/Debugger.exception.in-generator.html.expect b/tests/Tracy/expected/Debugger.exception.in-generator.html.expect index 7d4d5238c..8292b7857 100644 --- a/tests/Tracy/expected/Debugger.exception.in-generator.html.expect +++ b/tests/Tracy/expected/Debugger.exception.in-generator.html.expect @@ -1,4 +1,5 @@

    + @@ -16,24 +17,25 @@  +

    Exception #123

    -

    The my exception - search► +

    + The my exception + search►

    -
    - - - +
    - +
    -

    File: %a%Debugger.exception.in-generator.html.phpt:%d%

    -
    %d%: +

    File: %a%Debugger.exception.in-generator.html.phpt:%d%

    +
    %d%: %d%: %d%: Debugger::$productionMode = false; %d%: setHtmlMode(); @@ -51,23 +53,25 @@
    -
    - +
    -
    - - - - - -
    -
    %d%: setHtmlMode(); +
    + + + + +
    +
    %d%: setHtmlMode(); %d%: %d%: Debugger::enable(); %d%: @@ -84,18 +88,19 @@ %d%: $fn($generator);
    -
    - - +
    + - + -
    -
    %d%: +
    +
    %d%: %d%: $generator = (function (): iterable { %d%: yield 5; %d%: throw new Exception('The my exception', 123); @@ -108,12 +113,19 @@ %d%: $fn($generator);
    - - -
    #0
    
    -
    + + + + + +
    #0
    
    +
    +
    -
    %A% + + + +%A% \ No newline at end of file diff --git a/tests/Tracy/expected/Debugger.strict.html.expect b/tests/Tracy/expected/Debugger.strict.html.expect index 6c0d14b74..b5bbcd216 100644 --- a/tests/Tracy/expected/Debugger.strict.html.expect +++ b/tests/Tracy/expected/Debugger.strict.html.expect @@ -1,4 +1,5 @@

    + @@ -16,204 +17,211 @@  +

    Notice

    -

    Only variables should be assigned by reference - search► +

    + Only variables should be assigned by reference + search►

    -
    - - - +
    - +
    -

    File: %a%

    -
    %A%
    +

    File: %a%Debugger.strict.html.phpt:%d%

    +
    %A%
    -
    - +
    -
    - - - - - -
    -
    -
    -			
    -
    -			
    $arg1
    
    -
    -
    - - - - - -
    -
    -
    -			
    -
    -
    -			
    $arg1
    true
    -
    $arg2
    false
    -
    -
    - - - - - -
    -
    -
    -			
    -
    -
    -			
    $arg1
    %d%
    -
    $arg2
    'any string'
    -
    +
    + + + + +
    +
    +
    +							
    +									
    +										
    +										
    +									
    +							
    $arg1
    
    +
    +
    + + + + +
    +
    +
    +							
    +									
    +										
    +										
    +									
    +									
    +										
    +										
    +									
    +							
    $arg1
    true
    +
    $arg2
    false
    +
    +
    + + + + +
    +
    +
    +							
    +									
    +										
    +										
    +									
    +									
    +										
    +										
    +									
    +							
    $arg1
    10
    +
    $arg2
    'any string'
    +
    +
    -
    -
    - + +
    
    +
     	
    - - - -
    - +
    - -
    - - - -
    -
    - - %A% -
    -
    - - - - - -
    - - %A% -
    -
    - - -
    -%A%%A% - - - -
    + %A% +
    +
    + +
    + + %A% +
    +
    +
    +%A%
    + +
    +
    %A%
    + +
    -
    - -
    - +
    - -
    - - -
    - -
    -

    URL %a%

    - - - -

    $_GET

    -

    empty

    - - - -

    $_COOKIE

    -

    empty

    -
    - - -
    -

    Code: %d%

    -
    - %A%
    -
    - - -

    Headers were not sent at the time the exception was thrown

    +
    + + +
    +
    +

    URL %a%

    + +

    $_GET

    +

    empty

    +

    $_COOKIE

    +

    empty

    +
    + + +
    +

    Code: %d%

    + +
    + + %A% +
    +
    +

    Headers were not sent at the time the exception was thrown

    +
    -
    - -
      -
    • %a%️
    • +
    • %a%
    • Report generated at %a%
    • -
    • PHP %a%
    +
  • PHP %a%
  • +
  • Tracy %a%
  • + +
    - -
    + +
    -%A%%A% \ No newline at end of file +%A%%A% diff --git a/tools/open-in-editor/linux/open-editor.sh b/tools/open-in-editor/linux/open-editor.sh index 69e37be83..8cabf2789 100755 --- a/tools/open-in-editor/linux/open-editor.sh +++ b/tools/open-in-editor/linux/open-editor.sh @@ -6,7 +6,7 @@ declare -A mapping # # Visual Studio Code -#editor='code --goto "$FILE":"$LINE"' +#editor='code --goto "$FILE":"$LINE":"$COLUMN"' # Emacs #editor='emacs +$LINE "$FILE"' # gVim @@ -17,11 +17,9 @@ declare -A mapping #editor='pluma +$LINE "$FILE"' # PHPStorm # To enable PHPStorm command-line interface, folow this guide: https://www.jetbrains.com/help/phpstorm/working-with-the-ide-features-from-command-line.html -#editor='phpstorm --line $LINE "$FILE"' +#editor='phpstorm --line $LINE --column $COLUMN "$FILE"' # VS Codium #editor='codium --goto "$FILE":"$LINE"' -# Visual Studio Code -#editor='code --goto "$FILE":"$LINE"' # # Optionally configure custom mapping here: @@ -56,16 +54,21 @@ action=`echo $url | sed -r "s/$regex/\1/i"` uri_params=`echo $url | sed -r "s/$regex/\2/i"` file=`get_param $uri_params "file"` -line=`get_param $uri_params "line"` +line_param=`get_param $uri_params "line"` search=`get_param $uri_params "search"` replace=`get_param $uri_params "replace"` +# Parse line and column from line parameter (format: "12:5" or just "12") +IFS=':' read -r line column <<< "$line_param" +column="${column:-1}" + # Debug? #echo "action '$action'" #echo "file '$file'" #echo "line '$line'" #echo "search '$search'" #echo "replace '$replace'" +#echo "column '$column'" # Convert URI encoded codes to normal characters (e.g. '%2F' => '/'). printf -v file "${file//%/\\x}" @@ -102,6 +105,7 @@ fi # Format the command according to the selected editor. command="${editor//\$FILE/$file}" command="${command//\$LINE/$line}" +command="${command//\$COLUMN/$column}" # Debug? #echo $command diff --git a/tools/open-in-editor/windows/open-editor.js b/tools/open-in-editor/windows/open-editor.js index ac1093e70..86f699483 100644 --- a/tools/open-in-editor/windows/open-editor.js +++ b/tools/open-in-editor/windows/open-editor.js @@ -1,7 +1,7 @@ var settings = { // PhpStorm - // editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% "%file%"', + // editor: '"C:\\Program Files\\JetBrains\\PhpStorm 2018.1.2\\bin\\phpstorm64.exe" --line %line% --column %column% "%file%"', // title: 'PhpStorm', // NetBeans @@ -14,7 +14,7 @@ var settings = { // editor: '"C:\\Program Files\\SciTE\\scite.exe" "-open:%file%" -goto:%line%', // EmEditor - // editor: '"C:\\Program Files\\EmEditor\\EmEditor.exe" "%file%" /l %line%', + // editor: '"C:\\Program Files\\EmEditor\\EmEditor.exe" "%file%" /l %line% /cl %column%', // PSPad Editor // editor: '"C:\\Program Files\\PSPad editor\\PSPad.exe" -%line% "%file%"', @@ -26,7 +26,7 @@ var settings = { // editor: '"C:\\Program Files\\Sublime Text 2\\sublime_text.exe" "%file%:%line%"', // Visual Studio Code / VSCodium - // editor: '"C:\\Program Files\\Microsoft VS Code\\Code.exe" --goto "%file%:%line%"', + // editor: '"C:\\Program Files\\Microsoft VS Code\\Code.exe" --goto "%file%:%line%:%column%"', mappings: { // '/remotepath': '/localpath' @@ -41,7 +41,7 @@ if (!settings.editor) { } var url = WScript.Arguments(0); -var match = /^editor:\/\/(open|create|fix)\/?\?file=([^&]+)&line=(\d+)(?:&search=([^&]*)&replace=([^&]*))?/.exec(url); +var match = /^editor:\/\/(open|create|fix)\/?\?file=([^&]+)&line=(\d+)(?::(\d+))?(?:&search=([^&]*)&replace=([^&]*))?/.exec(url); if (!match) { WScript.Echo('Unexpected URI ' + url); WScript.Quit(); @@ -53,8 +53,9 @@ for (var i in match) { var action = match[1]; var file = match[2]; var line = match[3]; -var search = match[4]; -var replace = match[5]; +var column = match[4] || 1; +var search = match[5]; +var replace = match[6]; var shell = new ActiveXObject('WScript.Shell'); var fileSystem = new ActiveXObject('Scripting.FileSystemObject'); @@ -76,7 +77,7 @@ if (action === 'create' && !fileSystem.FileExists(file)) { fileSystem.OpenTextFile(file, 2).Write(lines.join('\n')); } -var command = settings.editor.replace(/%line%/, line).replace(/%file%/, file); +var command = settings.editor.replace(/%line%/, line).replace(/%column%/, column).replace(/%file%/, file); shell.Exec(command); if (settings.title) {