diff --git a/composer.json b/composer.json index ff94363c2..bdfd6859b 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=8.0 <8.3", + "php": ">=7.2 <8.3", "ext-session": "*", "ext-json": "*" }, @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-master": "3.0-dev" } } } diff --git a/examples/assets/style.css b/examples/assets/style.css index 6a75aeacc..ebc3b9e89 100644 --- a/examples/assets/style.css +++ b/examples/assets/style.css @@ -22,10 +22,8 @@ h2 { font-size: 140%; } -pre.tracy-dump { +tracy-dump { border: 1px solid silver; - padding: 1em; - margin: 1em 0; } a { diff --git a/examples/firelogger.php b/examples/firelogger.php deleted file mode 100644 index a6d885f98..000000000 --- a/examples/firelogger.php +++ /dev/null @@ -1,45 +0,0 @@ - 'val1', 'key2' => true]]; - -// will show in FireLogger -Debugger::fireLog('Hello World'); -Debugger::fireLog($arr); - - -function first($arg1, $arg2) -{ - second(true, false); -} - - -function second($arg1, $arg2) -{ - third([1, 2, 3]); -} - - -function third($arg1) -{ - throw new Exception('The my exception', 123); -} - - -try { - first(10, 'any string'); -} catch (Throwable $e) { - Debugger::fireLog($e); -} - -?> - - -

Tracy: FireLogger demo

- -

How to enable FireLogger?

diff --git a/readme.md b/readme.md index b883db257..8ffc9a978 100644 --- a/readme.md +++ b/readme.md @@ -49,7 +49,8 @@ Alternatively, you can download the whole package or [tracy.phar](https://github | Tracy | compatible with PHP | compatible with browsers |-----------|---------------|---------- -| Tracy 2.10| PHP 8.0 – 8.2 | Chrome 64+, Firefox 69+, Safari 13.1+ and iOS Safari 13.4+ +| Tracy 3.0 | PHP 8.0 – 8.2 | Chrome 64+, Firefox 69+, Safari 15.4+ and iOS Safari 15.4+ +| Tracy 2.10| PHP 8.0 – 8.2 | Chrome 64+, Firefox 69+, Safari 15.4+ and iOS Safari 15.4+ | Tracy 2.9 | PHP 7.2 – 8.2 | Chrome 64+, Firefox 69+, Safari 13.1+ and iOS Safari 13.4+ | Tracy 2.8 | PHP 7.2 – 8.1 | Chrome 55+, Firefox 53+, Safari 11+ and iOS Safari 11+ | Tracy 2.7 | PHP 7.1 – 8.0 | Chrome 55+, Firefox 53+, MS Edge 16+, Safari 11+ and iOS Safari 11+ @@ -144,7 +145,7 @@ In order to detect misspellings when assigning to an object, we use [trait Nette Content Security Policy ----------------------- -If your site uses Content Security Policy, you'll need to add `'nonce-'` to `script-src` for Tracy to work properly. Some 3rd plugins may require additional directives. +If your site uses Content Security Policy, you'll need to add `'nonce-'` and `'strict-dynamic'` to `script-src` for Tracy to work properly. Some 3rd plugins may require additional directives. Nonce is not supported in the `style-src` directive, if you use this directive you need to add `'unsafe-inline'`, but this should be avoided in production mode. Configuration example for [Nette Framework](https://nette.org): @@ -152,14 +153,14 @@ Configuration example for [Nette Framework](https://nette.org): ```neon http: csp: - script-src: nonce + script-src: [nonce, strict-dynamic] ``` Example in pure PHP: ```php $nonce = base64_encode(random_bytes(20)); -header("Content-Security-Policy: script-src 'nonce-$nonce';"); +header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';"); ``` @@ -264,6 +265,23 @@ If you use the Nette Framework, you can set this and others in the configuration To protect your e-mail box from flood, Tracy sends **only one message** and creates a file `email-sent`. When a developer receives the e-mail notification, he checks the log, corrects his application and deletes the `email-sent` monitoring file. This activates the e-mail sending again. +Monolog integration +------------------- + +This package provides a PSR-3 adapter, allowing for integration of [monolog/monolog](https://github.com/Seldaek/monolog). + +```php +$monolog = new Monolog\Logger('main-channel'); +$monolog->pushHandler(new Monolog\Handler\StreamHandler($logFilePath, Monolog\Logger::DEBUG)); + +$tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog); +Debugger::setLogger($tracyLogger); +Debugger::enable(); + +Debugger::log('info'); // writes: [] main-channel.INFO: info [] [] +Debugger::log('warning', Debugger::WARNING); // writes: [] main-channel.WARNING: warning [] [] +``` + Variable dumping ---------------- @@ -343,35 +361,6 @@ echo Debugger::timer(); // elapsed time in seconds ``` -FireLogger ----------- - -You cannot always send debugging information to the browser window. This applies to AJAX requests or generating XML files to output. In such cases, you can send the messages by a separate channel into FireLogger. Error, Notice and Warning levels are sent to FireLogger window automatically. It is also possible to log suppressed exceptions in running application when attention to them is important. - -How to do it? - -- install extension [FireLogger for Chrome](https://chrome.google.com/webstore/detail/firelogger-for-chrome/hmagilfopmdjkeomnjpchokglfdfjfeh) -- turn on Chrome DevTools (using Ctrl-Shift-I key) and open Console - -Navigate to the [demo page](https://examples.nette.org/tracy/) and you will see messages sent from PHP. - -Because Tracy\Debugger communicates with FireLogger via HTTP headers, you must call the logging function before the PHP script sends anything to output. It is also possible to enable output buffering and delay the output. - -```php -use Tracy\Debugger; - -Debugger::fireLog('Hello World'); // send string into FireLogger console - -Debugger::fireLog($_SERVER); // or even arrays and objects - -Debugger::fireLog(new Exception('Test Exception')); // or exceptions -``` - -The result looks like this: - -![FireLogger](https://nette.github.io/tracy/images/tracy-firelogger.png) - - Custom Logger ------------- diff --git a/src/Bridges/Nette/Bridge.php b/src/Bridges/Nette/Bridge.php index 15c49f500..41d68033a 100644 --- a/src/Bridges/Nette/Bridge.php +++ b/src/Bridges/Nette/Bridge.php @@ -9,7 +9,6 @@ namespace Tracy\Bridges\Nette; -use Latte; use Nette; use Tracy; use Tracy\BlueScreen; @@ -17,89 +16,18 @@ /** - * Bridge for NEON & Latte. + * Bridge for NEON. */ class Bridge { public static function initialize(): void { $blueScreen = Tracy\Debugger::getBlueScreen(); - if (!class_exists(Latte\Bridges\Tracy\BlueScreenPanel::class)) { - $blueScreen->addPanel([self::class, 'renderLatteError']); - $blueScreen->addAction([self::class, 'renderLatteUnknownMacro']); - $blueScreen->addFileGenerator(function (string $file) { - return substr($file, -6) === '.latte' - ? "{block content}\n\$END\$" - : null; - }); - Tracy\Debugger::addSourceMapper([self::class, 'mapLatteSourceCode']); - } - $blueScreen->addAction([self::class, 'renderMemberAccessException']); $blueScreen->addPanel([self::class, 'renderNeonError']); } - public static function renderLatteError(?\Throwable $e): ?array - { - if ($e instanceof Latte\CompileException && $e->sourceName) { - return [ - 'tab' => 'Template', - 'panel' => (preg_match('#\n|\?#', $e->sourceName) - ? '' - : '

' - . (@is_file($e->sourceName) // @ - may trigger error - ? 'File: ' . Helpers::editorLink($e->sourceName, $e->sourceLine) - : '' . htmlspecialchars($e->sourceName . ($e->sourceLine ? ':' . $e->sourceLine : '')) . '') - . '

') - . BlueScreen::highlightFile($e->sourceCode, $e->sourceLine, 15, false), - ]; - } - - return null; - } - - - public static function renderLatteUnknownMacro(?\Throwable $e): ?array - { - if ( - $e instanceof Latte\CompileException - && $e->sourceName - && @is_file($e->sourceName) // @ - may trigger error - && (preg_match('#Unknown macro (\{\w+)\}, did you mean (\{\w+)\}\?#A', $e->getMessage(), $m) - || preg_match('#Unknown attribute (n:\w+), did you mean (n:\w+)\?#A', $e->getMessage(), $m)) - ) { - return [ - 'link' => Helpers::editorUri($e->sourceName, $e->sourceLine, 'fix', $m[1], $m[2]), - 'label' => 'fix it', - ]; - } - - return null; - } - - - /** @return array{file: string, line: int, label: string, active: bool} */ - public static function mapLatteSourceCode(string $file, int $line): ?array - { - if (!strpos($file, '.latte--')) { - return null; - } - - $lines = file($file); - if ( - !preg_match('#^/(?:\*\*|/) source: (\S+\.latte)#m', implode('', array_slice($lines, 0, 10)), $m) - || !@is_file($m[1]) // @ - may trigger error - ) { - return null; - } - - $file = $m[1]; - $line = $line && preg_match('#/\* line (\d+) \*/#', $lines[$line - 1], $m) ? (int) $m[1] : 0; - return ['file' => $file, 'line' => $line, 'label' => 'Latte', 'active' => true]; - } - - public static function renderMemberAccessException(?\Throwable $e): ?array { if (!$e instanceof Nette\MemberAccessException && !$e instanceof \LogicException) { diff --git a/src/Bridges/Nette/MailSender.php b/src/Bridges/Nette/MailSender.php index dd4c43c59..bac96ae50 100644 --- a/src/Bridges/Nette/MailSender.php +++ b/src/Bridges/Nette/MailSender.php @@ -20,11 +20,10 @@ class MailSender { use Nette\SmartObject; - /** @var Nette\Mail\IMailer */ - private $mailer; + private Nette\Mail\IMailer $mailer; /** @var string|null sender of email notifications */ - private $fromEmail; + private ?string $fromEmail = null; public function __construct(Nette\Mail\IMailer $mailer, ?string $fromEmail = null) @@ -34,10 +33,7 @@ public function __construct(Nette\Mail\IMailer $mailer, ?string $fromEmail = nul } - /** - * @param mixed $message - */ - public function send($message, string $email): void + public function send(mixed $message, string $email): void { $host = preg_replace('#[^\w.-]+#', '', $_SERVER['SERVER_NAME'] ?? php_uname('n')); diff --git a/src/Bridges/Nette/TracyExtension.php b/src/Bridges/Nette/TracyExtension.php index 1ac434444..9d4df697c 100644 --- a/src/Bridges/Nette/TracyExtension.php +++ b/src/Bridges/Nette/TracyExtension.php @@ -22,17 +22,11 @@ class TracyExtension extends Nette\DI\CompilerExtension { private const ErrorSeverityPattern = 'E_(?:ALL|PARSE|STRICT|RECOVERABLE_ERROR|(?:CORE|COMPILE)_(?:ERROR|WARNING)|(?:USER_)?(?:ERROR|WARNING|NOTICE|DEPRECATED))'; - /** @var bool */ - private $debugMode; - /** @var bool */ - private $cliMode; - - - public function __construct(bool $debugMode = false, bool $cliMode = false) - { - $this->debugMode = $debugMode; - $this->cliMode = $cliMode; + public function __construct( + private bool $debugMode = false, + private bool $cliMode = false, + ) { } @@ -116,7 +110,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class) ]; $initialize->addBody($builder->formatPhp( ($tbl[$key] ?? 'Tracy\Debugger::$' . $key . ' = ?') . ';', - Nette\DI\Helpers::filterArguments([$value]) + Nette\DI\Helpers::filterArguments([$value]), )); } } @@ -130,14 +124,14 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class) if ($this->debugMode) { foreach ($this->config->bar as $item) { if (is_string($item) && substr($item, 0, 1) === '@') { - $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); } $initialize->addBody($builder->formatPhp( '$this->getService(?)->addPanel(?);', - Nette\DI\Helpers::filterArguments([$this->prefix('bar'), $item]) + Nette\DI\Helpers::filterArguments([$this->prefix('bar'), $item]), )); } @@ -154,7 +148,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class) foreach ($this->config->blueScreen as $item) { $initialize->addBody($builder->formatPhp( '$this->getService(?)->addPanel(?);', - Nette\DI\Helpers::filterArguments([$this->prefix('blueScreen'), $item]) + Nette\DI\Helpers::filterArguments([$this->prefix('blueScreen'), $item]), )); } @@ -171,7 +165,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class) /** * @param string|string[] $value */ - private function parseErrorSeverity($value): int + private function parseErrorSeverity(string|array $value): int { $value = implode('|', (array) $value); $res = (int) @parse_ini_string('e = ' . $value)['e']; // @ may fail diff --git a/src/Bridges/Psr/PsrToTracyLoggerAdapter.php b/src/Bridges/Psr/PsrToTracyLoggerAdapter.php index b28526bee..5d8b0430b 100644 --- a/src/Bridges/Psr/PsrToTracyLoggerAdapter.php +++ b/src/Bridges/Psr/PsrToTracyLoggerAdapter.php @@ -28,20 +28,17 @@ class PsrToTracyLoggerAdapter implements Tracy\ILogger Tracy\ILogger::CRITICAL => Psr\Log\LogLevel::CRITICAL, ]; - /** @var Psr\Log\LoggerInterface */ - private $psrLogger; - - public function __construct(Psr\Log\LoggerInterface $psrLogger) - { - $this->psrLogger = $psrLogger; + public function __construct( + private Psr\Log\LoggerInterface $psrLogger, + ) { } - public function log($value, $level = self::INFO) + public function log(mixed $value, string $level = self::INFO) { if ($value instanceof \Throwable) { - $message = Tracy\Helpers::getClass($value) . ': ' . $value->getMessage() . ($value->getCode() ? ' #' . $value->getCode() : '') . ' in ' . $value->getFile() . ':' . $value->getLine(); + $message = get_debug_type($value) . ': ' . $value->getMessage() . ($value->getCode() ? ' #' . $value->getCode() : '') . ' in ' . $value->getFile() . ':' . $value->getLine(); $context = ['exception' => $value]; } elseif (!is_string($value)) { @@ -56,7 +53,7 @@ public function log($value, $level = self::INFO) $this->psrLogger->log( self::LevelMap[$level] ?? Psr\Log\LogLevel::ERROR, $message, - $context + $context, ); } } diff --git a/src/Bridges/Psr/TracyToPsrLoggerAdapter.php b/src/Bridges/Psr/TracyToPsrLoggerAdapter.php index 663846159..7b1f578e9 100644 --- a/src/Bridges/Psr/TracyToPsrLoggerAdapter.php +++ b/src/Bridges/Psr/TracyToPsrLoggerAdapter.php @@ -30,13 +30,10 @@ class TracyToPsrLoggerAdapter extends Psr\Log\AbstractLogger Psr\Log\LogLevel::DEBUG => Tracy\ILogger::DEBUG, ]; - /** @var Tracy\ILogger */ - private $tracyLogger; - - public function __construct(Tracy\ILogger $tracyLogger) - { - $this->tracyLogger = $tracyLogger; + public function __construct( + private Tracy\ILogger $tracyLogger, + ) { } diff --git a/src/Tracy/Bar/Bar.php b/src/Tracy/Bar/Bar.php index fd68c4049..037bc3cee 100644 --- a/src/Tracy/Bar/Bar.php +++ b/src/Tracy/Bar/Bar.php @@ -16,10 +16,8 @@ class Bar { /** @var IBarPanel[] */ - private $panels = []; - - /** @var bool */ - private $loaderRendered = false; + private array $panels = []; + private bool $loaderRendered = false; /** @@ -31,7 +29,7 @@ public function addPanel(IBarPanel $panel, ?string $id = null): self if ($id === null) { $c = 0; do { - $id = get_class($panel) . ($c++ ? "-$c" : ''); + $id = $panel::class . ($c++ ? "-$c" : ''); } while (isset($this->panels[$id])); } diff --git a/src/Tracy/Bar/assets/bar.css b/src/Tracy/Bar/assets/bar.css index 80cf43af0..70c2f70e5 100644 --- a/src/Tracy/Bar/assets/bar.css +++ b/src/Tracy/Bar/assets/bar.css @@ -12,63 +12,64 @@ body#tracy-debug { /* in popup window */ display: block; } -#tracy-debug:not(body) { +:host { position: absolute; left: 0; top: 0; + margin: 0; } -#tracy-debug a { +a { color: #125EAE; text-decoration: none; } -#tracy-debug a:hover, -#tracy-debug a:focus { +a:hover, +a:focus { background-color: #125EAE; color: white; } -#tracy-debug h2, -#tracy-debug h3, -#tracy-debug p { +h2, +h3, +p { margin: .4em 0; } -#tracy-debug table { +table { background: #FDF5CE; width: 100%; } -#tracy-debug tr:nth-child(2n) td { +tr:nth-child(2n) td { background: rgba(0, 0, 0, 0.02); } -#tracy-debug td, -#tracy-debug th { +td, +th { border: 1px solid #E6DFBF; padding: 2px 5px; vertical-align: top; text-align: left; } -#tracy-debug th { +th { background: #F4F3F1; color: #655E5E; font-size: 90%; font-weight: bold; } -#tracy-debug pre, -#tracy-debug code { +pre, +code { font: 9pt/1.5 Consolas, monospace; } -#tracy-debug table .tracy-right { +table .tracy-right { text-align: right; } -#tracy-debug svg { +svg { display: inline; } @@ -168,7 +169,7 @@ body#tracy-debug { /* in popup window */ /* panels */ -#tracy-debug .tracy-panel { +.tracy-panel { display: none; font: normal normal 12px/1.5 sans-serif; background: white; @@ -176,11 +177,11 @@ body#tracy-debug { /* in popup window */ text-align: left; } -body#tracy-debug .tracy-panel { /* in popup window */ +body.tracy-panel { /* in popup window */ display: block; } -#tracy-debug h1 { +h1 { font: normal normal 23px/1.4 Tahoma, sans-serif; color: #575753; margin: -5px -5px 5px; @@ -188,29 +189,29 @@ body#tracy-debug .tracy-panel { /* in popup window */ word-wrap: break-word; } -#tracy-debug .tracy-inner { +.tracy-inner { overflow: auto; flex: 1; } -#tracy-debug .tracy-panel .tracy-icons { +.tracy-panel .tracy-icons { display: none; } -#tracy-debug .tracy-panel-ajax h1::after, -#tracy-debug .tracy-panel-redirect h1::after { +.tracy-panel-ajax h1::after, +.tracy-panel-redirect h1::after { content: 'ajax'; float: right; font-size: 65%; margin: 0 .3em; } -#tracy-debug .tracy-panel-redirect h1::after { +.tracy-panel-redirect h1::after { content: 'redirect'; } -#tracy-debug .tracy-mode-peek, -#tracy-debug .tracy-mode-float { +.tracy-mode-peek, +.tracy-mode-float { position: fixed; flex-direction: column; padding: 10px; @@ -221,24 +222,24 @@ body#tracy-debug .tracy-panel { /* in popup window */ border: 1px solid rgba(0, 0, 0, 0.1); } -#tracy-debug .tracy-mode-peek, -#tracy-debug .tracy-mode-float:not(.tracy-panel-resized) { +.tracy-mode-peek, +.tracy-mode-float:not(.tracy-panel-resized) { max-width: 700px; max-height: 500px; } @media (max-height: 555px) { - #tracy-debug .tracy-mode-peek, - #tracy-debug .tracy-mode-float:not(.tracy-panel-resized) { + .tracy-mode-peek, + .tracy-mode-float:not(.tracy-panel-resized) { max-height: 100vh; } } -#tracy-debug .tracy-mode-peek h1 { +.tracy-mode-peek h1 { cursor: move; } -#tracy-debug .tracy-mode-float { +.tracy-mode-float { display: flex; opacity: .95; transition: opacity 0.2s; @@ -247,18 +248,18 @@ body#tracy-debug .tracy-panel { /* in popup window */ resize: both; } -#tracy-debug .tracy-focused { +.tracy-focused { display: flex; opacity: 1; transition: opacity 0.1s; } -#tracy-debug .tracy-mode-float h1 { +.tracy-mode-float h1 { cursor: move; padding-right: 25px; } -#tracy-debug .tracy-mode-float .tracy-icons { +.tracy-mode-float .tracy-icons { display: block; position: absolute; top: 0; @@ -266,26 +267,26 @@ body#tracy-debug .tracy-panel { /* in popup window */ font-size: 18px; } -#tracy-debug .tracy-mode-window { +.tracy-mode-window { padding: 10px; } -#tracy-debug .tracy-icons a { +.tracy-icons a { color: #575753; } -#tracy-debug .tracy-icons a:hover { +.tracy-icons a:hover { color: white; } -#tracy-debug .tracy-inner-container { +.tracy-inner-container { min-width: 100%; float: left; } @media print { - #tracy-debug * { + * { display: none; } } diff --git a/src/Tracy/Bar/assets/bar.js b/src/Tracy/Bar/assets/bar.js index 50f36902c..b11c2fa14 100644 --- a/src/Tracy/Bar/assets/bar.js +++ b/src/Tracy/Bar/assets/bar.js @@ -2,10 +2,10 @@ * This file is part of the Tracy (https://tracy.nette.org) */ -let nonce = document.currentScript.getAttribute('nonce') || document.currentScript.nonce, - requestId = document.currentScript.dataset.id, +let requestId = document.currentScript.dataset.id, ajaxCounter = 1, - baseUrl = location.href.split('#')[0]; + baseUrl = location.href.split('#')[0], + root; baseUrl += (baseUrl.indexOf('?') < 0 ? '?' : '&'); @@ -25,7 +25,7 @@ class Panel { constructor(id) { this.id = id; - this.elem = document.getElementById(this.id); + this.elem = root.getElementById(this.id); this.elem.Tracy = this.elem.Tracy || {}; } @@ -35,7 +35,7 @@ class Panel this.init = function() {}; elem.innerHTML = elem.dataset.tracyContent; - Tracy.Dumper.init(Debug.layer); + Tracy.Dumper.init(root); evalScripts(elem); draggable(elem, { @@ -249,7 +249,7 @@ class Bar { init() { this.id = 'tracy-debug-bar'; - this.elem = document.getElementById(this.id); + this.elem = root.getElementById(this.id); draggable(this.elem, { handles: this.elem.querySelectorAll('li:first-child'), @@ -353,7 +353,7 @@ class Bar close() { - document.getElementById('tracy-debug').style.display = 'none'; + root.getElementById('tracy-debug').style.display = 'none'; } @@ -393,15 +393,16 @@ class Debug static init(content) { Debug.bar = new Bar; Debug.panels = {}; - Debug.layer = document.createElement('tracy-div'); - Debug.layer.setAttribute('id', 'tracy-debug'); - Debug.layer.innerHTML = content; + Debug.layer = document.createElement('tracy-debug'); + root = Debug.layer.shadowRoot; + root.innerHTML = content; + root.appendChild(document.querySelector('style.tracy-debug')); (document.body || document.documentElement).appendChild(Debug.layer); - evalScripts(Debug.layer); + evalScripts(root); Debug.layer.style.display = 'block'; Debug.bar.init(); - Debug.layer.querySelectorAll('.tracy-panel').forEach((panel) => { + root.querySelectorAll('.tracy-panel').forEach((panel) => { Debug.panels[panel.id] = new Panel(panel.id); Debug.panels[panel.id].restorePosition(); }); @@ -438,12 +439,12 @@ class Debug }); } - Debug.layer.insertAdjacentHTML('beforeend', content.panels); - evalScripts(Debug.layer); + root.insertAdjacentHTML('beforeend', content.panels); + evalScripts(root); Debug.bar.elem.insertAdjacentHTML('beforeend', content.bar); let ajaxBar = Debug.bar.elem.querySelector('.tracy-row:last-child'); - Debug.layer.querySelectorAll('.tracy-panel').forEach((panel) => { + root.querySelectorAll('.tracy-panel').forEach((panel) => { if (!Debug.panels[panel.id]) { Debug.panels[panel.id] = new Panel(panel.id); Debug.panels[panel.id].restorePosition(); @@ -525,7 +526,6 @@ class Debug } Debug.scriptElem = document.createElement('script'); Debug.scriptElem.src = url; - Debug.scriptElem.setAttribute('nonce', nonce); (document.body || document.documentElement).appendChild(Debug.scriptElem); } } @@ -537,7 +537,6 @@ function evalScripts(elem) { let document = script.ownerDocument; let dolly = document.createElement('script'); dolly.textContent = script.textContent; - dolly.setAttribute('nonce', nonce); (document.body || document.documentElement).appendChild(dolly); script.tracyEvaluated = true; } @@ -678,6 +677,16 @@ function getPosition(elem) { } +class TracyDebugElement extends HTMLElement { + constructor() { + super(); + this.attachShadow({mode: 'open'}); + } +} + +window.customElements.define('tracy-debug', TracyDebugElement); + + let Tracy = window.Tracy = window.Tracy || {}; Tracy.DebugPanel = Panel; Tracy.DebugBar = Bar; diff --git a/src/Tracy/BlueScreen/BlueScreen.php b/src/Tracy/BlueScreen/BlueScreen.php index f2ff25a4c..e9bdce188 100644 --- a/src/Tracy/BlueScreen/BlueScreen.php +++ b/src/Tracy/BlueScreen/BlueScreen.php @@ -18,46 +18,36 @@ class BlueScreen private const MaxMessageLength = 2000; /** @var string[] */ - public $info = []; + public array $info = []; /** @var string[] paths to be collapsed in stack trace (e.g. core libraries) */ - public $collapsePaths = []; + public array $collapsePaths = []; - /** @var int */ - public $maxDepth = 5; - - /** @var int */ - public $maxLength = 150; - - /** @var int */ - public $maxItems = 100; + public int $maxDepth = 5; + public int $maxLength = 150; + public int $maxItems = 100; /** @var callable|null a callable returning true for sensitive data; fn(string $key, mixed $val): bool */ public $scrubber; /** @var string[] */ - public $keysToHide = [ + public array $keysToHide = [ 'password', 'passwd', 'pass', 'pwd', 'creditcard', 'credit card', 'cc', 'pin', 'authorization', self::class . '::$snapshot', ]; - /** @var bool */ - public $showEnvironment = true; + public bool $showEnvironment = true; /** @var callable[] */ - private $panels = []; + private array $panels = []; /** @var callable[] functions that returns action for exceptions */ - private $actions = []; - - /** @var callable[] */ - private $fileGenerators = []; - - /** @var array */ - private $snapshot; + private array $actions = []; + private array $fileGenerators = []; + private ?array $snapshot = null; /** @var \WeakMap<\Fiber|\Generator> */ - private $fibers; + private \WeakMap $fibers; public function __construct() @@ -66,7 +56,7 @@ public function __construct() ? [$m[1] . '/tracy', $m[1] . '/nette', $m[1] . '/latte'] : [dirname(__DIR__)]; $this->fileGenerators[] = [self::class, 'generateNewPhpFileContents']; - $this->fibers = PHP_VERSION_ID < 80000 ? new \SplObjectStorage : new \WeakMap; + $this->fibers = new \WeakMap; } @@ -107,11 +97,7 @@ public function addFileGenerator(callable $generator): self } - /** - * @param \Fiber|\Generator $fiber - * @return static - */ - public function addFiber($fiber): self + public function addFiber(\Fiber|\Generator $fiber): static { $this->fibers[$fiber] = true; return $this; @@ -134,9 +120,7 @@ public function render(\Throwable $exception): void /** @internal */ public function renderToAjax(\Throwable $exception, DeferredContent $defer): void { - $defer->addSetup('Tracy.BlueScreen.loadAjax', Helpers::capture(function () use ($exception) { - $this->renderTemplate($exception, __DIR__ . '/assets/content.phtml'); - })); + $defer->addSetup('Tracy.BlueScreen.loadAjax', Helpers::capture(fn() => $this->renderTemplate($exception, __DIR__ . '/assets/content.phtml'))); } @@ -159,17 +143,17 @@ public function renderToFile(\Throwable $exception, string $file): bool } - private function renderTemplate(\Throwable $exception, string $template, $toScreen = true): void + private function renderTemplate(\Throwable $exception, string $template, bool $toScreen = true): void { [$generators, $fibers] = $this->findGeneratorsAndFibers($exception); $headersSent = headers_sent($headersFile, $headersLine); $obStatus = Debugger::$obStatus; - $showEnvironment = $this->showEnvironment && (strpos($exception->getMessage(), 'Allowed memory size') === false); + $showEnvironment = $this->showEnvironment && (!str_contains($exception->getMessage(), 'Allowed memory size')); $info = array_filter($this->info); $source = Helpers::getSource(); $title = $exception instanceof \ErrorException ? Helpers::errorTypeToString($exception->getSeverity()) - : Helpers::getClass($exception); + : get_debug_type($exception); $lastError = $exception instanceof \ErrorException || $exception instanceof \Error ? null : error_get_last(); @@ -177,8 +161,8 @@ private function renderTemplate(\Throwable $exception, string $template, $toScre if (function_exists('apache_request_headers')) { $httpHeaders = apache_request_headers(); } else { - $httpHeaders = array_filter($_SERVER, function ($k) { return strncmp($k, 'HTTP_', 5) === 0; }, ARRAY_FILTER_USE_KEY); - $httpHeaders = array_combine(array_map(function ($k) { return strtolower(strtr(substr($k, 5), '_', '-')); }, array_keys($httpHeaders)), $httpHeaders); + $httpHeaders = array_filter($_SERVER, fn($k) => strncmp($k, 'HTTP_', 5) === 0, ARRAY_FILTER_USE_KEY); + $httpHeaders = array_combine(array_map(fn($k) => strtolower(strtr(substr($k, 5), '_', '-')), array_keys($httpHeaders)), $httpHeaders); } $snapshot = &$this->snapshot; @@ -288,7 +272,7 @@ private function renderActions(\Throwable $ex): array ]; } - $query = ($ex instanceof \ErrorException ? '' : Helpers::getClass($ex) . ' ') + $query = ($ex instanceof \ErrorException ? '' : get_debug_type($ex) . ' ') . preg_replace('#\'.*\'|".*"#Us', '', $ex->getMessage()); $actions[] = [ 'link' => 'https://www.google.com/search?sourceid=tracy&q=' . urlencode($query), @@ -319,7 +303,7 @@ public static function highlightFile( int $line, int $lines = 15, bool $php = true, - int $column = 0 + int $column = 0, ): ?string { $source = @file_get_contents($file); // @ file may not exist @@ -354,6 +338,7 @@ public static function highlightPhp(string $source, int $line, int $lines = 15, $source = preg_replace('#(__halt_compiler\s*\(\)\s*;).*#is', '$1', $source); $source = str_replace(["\r\n", "\r"], "\n", $source); + $source = preg_replace('#/\*sensitive\{\*/.*?/\*\}\*/#s', Dumper\Describer::HiddenValue, $source); $source = explode("\n", highlight_string($source, true)); $out = $source[0]; // $source = str_replace('
', "\n", $source[1]); @@ -398,14 +383,14 @@ public static function highlightLine(string $html, int $line, int $lines = 15, i '#((?:&.*?;|[^&]){' . ($column - 1) . '})(&.*?;|.)#u', '\1\2', $s . ' ', - 1 + 1, ); } $out .= sprintf( "%{$numWidth}s: %s\n%s", $n, $s, - implode('', $tags[0]) + implode('', $tags[0]), ); } else { $out .= sprintf("%{$numWidth}s: %s\n", $n, $s); @@ -451,7 +436,7 @@ function ($m) use ($colors, &$stack): string { return "\e[0m\e[" . end($stack) . 'm'; }, - $s + $s, ); $s = htmlspecialchars_decode(strip_tags($s), ENT_QUOTES | ENT_HTML5); return $s; @@ -479,17 +464,15 @@ public function isCollapsed(string $file): bool /** @internal */ public function getDumper(): \Closure { - return function ($v, $k = null): string { - return Dumper::toHtml($v, [ - Dumper::DEPTH => $this->maxDepth, - Dumper::TRUNCATE => $this->maxLength, - Dumper::ITEMS => $this->maxItems, - Dumper::SNAPSHOT => &$this->snapshot, - Dumper::LOCATION => Dumper::LOCATION_CLASS, - Dumper::SCRUBBER => $this->scrubber, - Dumper::KEYS_TO_HIDE => $this->keysToHide, - ], $k); - }; + return fn($v, $k = null): string => Dumper::toHtml($v, [ + Dumper::DEPTH => $this->maxDepth, + Dumper::TRUNCATE => $this->maxLength, + Dumper::ITEMS => $this->maxItems, + Dumper::SNAPSHOT => &$this->snapshot, + Dumper::LOCATION => Dumper::LOCATION_CLASS, + Dumper::SCRUBBER => $this->scrubber, + Dumper::KEYS_TO_HIDE => $this->keysToHide, + ], $k); } @@ -501,7 +484,7 @@ public function formatMessage(\Throwable $exception): string $msg = preg_replace( '#\'\S(?:[^\']|\\\\\')*\S\'|"\S(?:[^"]|\\\\")*\S"#', '$0', - $msg + $msg, ); // clickable class & methods @@ -520,18 +503,16 @@ function ($m) { return '' . $m[0] . ''; }, - $msg + $msg, ); // clickable file name $msg = preg_replace_callback( '#([\w\\\\/.:-]+\.(?:php|phpt|phtml|latte|neon))(?|:(\d+)| on line (\d+))?#', - function ($m) { - return @is_file($m[1]) + fn($m) => @is_file($m[1]) ? '' . $m[0] . '' - : $m[0]; - }, - $msg + : $m[0], + $msg, ); return $msg; @@ -547,7 +528,7 @@ private function renderPhpInfo(): void @phpinfo(INFO_CONFIGURATION | INFO_MODULES); // @ phpinfo may be disabled $info = ob_get_clean(); - if (strpos($license, '', Helpers::escapeHtml($info), ''; } else { $info = str_replace(' :first-child { +tr > :first-child { width: 20%; } -#tracy-bs tr:nth-child(2n), -#tracy-bs tr:nth-child(2n) pre { +tr:nth-child(2n), +tr:nth-child(2n) pre { background-color: #F7F0CB; } -#tracy-bs .tracy-footer--sticky { +.tracy-footer--sticky { position: fixed; width: 100%; bottom: 0; } -#tracy-bs footer ul { +footer ul { font-size: 7pt; padding: var(--tracy-space); margin: var(--tracy-space) 0 0; @@ -164,11 +164,11 @@ html.tracy-bs-visible body { list-style: none; } -#tracy-bs .tracy-footer-logo { +.tracy-footer-logo { position: relative; } -#tracy-bs .tracy-footer-logo a { +.tracy-footer-logo a { position: absolute; bottom: 0; right: 0; @@ -180,19 +180,19 @@ html.tracy-bs-visible body { margin: 0; } -#tracy-bs .tracy-footer-logo a:hover, -#tracy-bs .tracy-footer-logo a:focus { +.tracy-footer-logo a:hover, +.tracy-footer-logo a:focus { opacity: 1; transition: opacity 0.1s; } -#tracy-bs .tracy-section { +.tracy-section { padding-left: calc(1.5 * var(--tracy-space)); padding-right: calc(1.5 * var(--tracy-space)); } -#tracy-bs .tracy-section-panel { +.tracy-section-panel { background: #F4F3F1; padding: var(--tracy-space) var(--tracy-space) 0; margin: 0 0 var(--tracy-space); @@ -201,8 +201,8 @@ html.tracy-bs-visible body { overflow: hidden; } -#tracy-bs .outer, /* deprecated */ -#tracy-bs .tracy-pane { +.outer, /* deprecated */ +.tracy-pane { overflow: auto; } @@ -212,62 +212,62 @@ html.tracy-bs-visible body { /* header */ -#tracy-bs .tracy-section--error { +.tracy-section--error { background: #CD1818; color: white; font-size: 13pt; padding-top: var(--tracy-space); } -#tracy-bs .tracy-section--error h1 { +.tracy-section--error h1 { color: white; } -#tracy-bs .tracy-section--error::selection, -#tracy-bs .tracy-section--error ::selection { +.tracy-section--error::selection, +.tracy-section--error ::selection { color: black !important; background: #FDF5CE !important; } -#tracy-bs .tracy-section--error a { +.tracy-section--error a { color: #ffefa1 !important; } -#tracy-bs .tracy-section--error span span { +.tracy-section--error span span { font-size: 80%; color: rgba(255, 255, 255, 0.5); text-shadow: none; } -#tracy-bs .tracy-section--error a.tracy-action { +.tracy-section--error a.tracy-action { color: white !important; opacity: 0; font-size: .7em; border-bottom: none !important; } -#tracy-bs .tracy-section--error:hover a.tracy-action { +.tracy-section--error:hover a.tracy-action { opacity: .6; } -#tracy-bs .tracy-section--error a.tracy-action:hover { +.tracy-section--error a.tracy-action:hover { opacity: 1; } -#tracy-bs .tracy-section--error i { +.tracy-section--error i { color: #ffefa1; font-style: normal; } /* source code */ -#tracy-bs pre.tracy-code > div { +pre.tracy-code > div { min-width: 100%; float: left; white-space: pre; } -#tracy-bs .tracy-line-highlight { +.tracy-line-highlight { background: #CD1818; color: white; font-weight: bold; @@ -277,38 +277,38 @@ html.tracy-bs-visible body { margin: 0 -1ch; } -#tracy-bs .tracy-column-highlight { +.tracy-column-highlight { display: inline-block; backdrop-filter: grayscale(1); margin: 0 -1px; padding: 0 1px; } -#tracy-bs .tracy-line { +.tracy-line { color: #9F9C7F; font-weight: normal; font-style: normal; } -#tracy-bs a.tracy-editor { +a.tracy-editor { color: inherit; border-bottom: 1px dotted rgba(0, 0, 0, .3); border-radius: 3px; } -#tracy-bs a.tracy-editor:hover { +a.tracy-editor:hover { background: #0001; } -#tracy-bs span[data-tracy-href] { +span[data-tracy-href] { border-bottom: 1px dotted rgba(0, 0, 0, .3); } -#tracy-bs .tracy-dump-whitespace { +.tracy-dump-whitespace { color: #0003; } -#tracy-bs .tracy-caused { +.tracy-caused { float: right; padding: .3em calc(1.5 * var(--tracy-space)); background: #df8075; @@ -316,45 +316,45 @@ html.tracy-bs-visible body { white-space: nowrap; } -#tracy-bs .tracy-caused a { +.tracy-caused a { color: white; } -#tracy-bs .tracy-callstack { +.tracy-callstack { display: grid; grid-template-columns: max-content 1fr; margin-bottom: calc(.5 * var(--tracy-space)); } -#tracy-bs .tracy-callstack-file { +.tracy-callstack-file { text-align: right; padding-right: var(--tracy-space); white-space: nowrap; height: calc(1.5 * var(--tracy-space)); } -#tracy-bs .tracy-callstack-callee { +.tracy-callstack-callee { white-space: nowrap; height: calc(1.5 * var(--tracy-space)); } -#tracy-bs .tracy-callstack-additional { +.tracy-callstack-additional { grid-column-start: 1; grid-column-end: 3; } -#tracy-bs .tracy-callstack-args tr:first-child > * { +.tracy-callstack-args tr:first-child > * { position: relative; } -#tracy-bs .tracy-callstack-args tr:first-child td:before { +.tracy-callstack-args tr:first-child td:before { position: absolute; right: .3em; content: 'may not be true'; opacity: .4; } -#tracy-bs .tracy-panel-fadein { +.tracy-panel-fadein { animation: tracy-panel-fadein .12s ease; } @@ -364,26 +364,26 @@ html.tracy-bs-visible body { } } -#tracy-bs .tracy-section--causedby { +.tracy-section--causedby { flex-direction: column; padding: 0; } -#tracy-bs .tracy-section--causedby:not(.tracy-collapsed) { +.tracy-section--causedby:not(.tracy-collapsed) { display: flex; } -#tracy-bs .tracy-section--causedby .tracy-section--error { +.tracy-section--causedby .tracy-section--error { background: #cd1818a6; } -#tracy-bs .tracy-section--error + .tracy-section--stack { +.tracy-section--error + .tracy-section--stack { margin-top: calc(1.5 * var(--tracy-space)); } /* tabs */ -#tracy-bs .tracy-tab-bar { +.tracy-tab-bar { display: flex; list-style: none; padding-left: 0; @@ -392,11 +392,11 @@ html.tracy-bs-visible body { font-size: 110%; } -#tracy-bs .tracy-tab-bar > *:not(:first-child) { +.tracy-tab-bar > *:not(:first-child) { margin-left: var(--tracy-space); } -#tracy-bs .tracy-tab-bar a { +.tracy-tab-bar a { display: block; padding: calc(.5 * var(--tracy-space)) var(--tracy-space); margin: 0; @@ -407,11 +407,11 @@ html.tracy-bs-visible body { transition: all 0.1s; } -#tracy-bs .tracy-tab-bar > .tracy-active a { +.tracy-tab-bar > .tracy-active a { background: white; } -#tracy-bs .tracy-tab-panel { +.tracy-tab-panel { border-top: 2px solid white; padding-top: var(--tracy-space); overflow: auto; diff --git a/src/Tracy/BlueScreen/assets/bluescreen.js b/src/Tracy/BlueScreen/assets/bluescreen.js index 7a1b5b68f..cb4291bec 100644 --- a/src/Tracy/BlueScreen/assets/bluescreen.js +++ b/src/Tracy/BlueScreen/assets/bluescreen.js @@ -15,10 +15,11 @@ class BlueScreen } blueScreen.addEventListener('tracy-toggle', (e) => { - if (e.target.matches('#tracy-bs-toggle')) { // blue screen toggle + let target = e.composedPath()[0]; + if (target.matches('#tracy-bs-toggle')) { // blue screen toggle document.documentElement.classList.toggle('tracy-bs-visible', !e.detail.collapsed); - } else if (!e.target.matches('.tracy-dump *') && e.detail.originalEvent) { // panel toggle + } else if (!target.matches('.tracy-dump *') && e.detail.originalEvent) { // panel toggle e.detail.relatedTarget.classList.toggle('tracy-panel-fadein', !e.detail.collapsed); } }); @@ -30,7 +31,7 @@ class BlueScreen sessionStorage.setItem('tracy-toggles-bskey', id); } - (new ResizeObserver(stickyFooter)).observe(blueScreen); + //(new ResizeObserver(stickyFooter)).observe(blueScreen); if (document.documentElement.classList.contains('tracy-bs-visible')) { window.scrollTo(0, 0); @@ -75,3 +76,4 @@ function stickyFooter() { let Tracy = window.Tracy = window.Tracy || {}; Tracy.BlueScreen = Tracy.BlueScreen || BlueScreen; + diff --git a/src/Tracy/BlueScreen/assets/content.phtml b/src/Tracy/BlueScreen/assets/content.phtml index 107b6a0c4..f99948c5e 100644 --- a/src/Tracy/BlueScreen/assets/content.phtml +++ b/src/Tracy/BlueScreen/assets/content.phtml @@ -23,7 +23,11 @@ namespace Tracy; * @var \Fiber[] $fibers */ ?> - + + + 
@@ -70,4 +74,4 @@ namespace Tracy;
> -
+ diff --git a/src/Tracy/BlueScreen/assets/page.phtml b/src/Tracy/BlueScreen/assets/page.phtml index 100bd670d..9fd7720b9 100644 --- a/src/Tracy/BlueScreen/assets/page.phtml +++ b/src/Tracy/BlueScreen/assets/page.phtml @@ -9,6 +9,7 @@ namespace Tracy; * @var string $title * @var ?string $nonce * @var string $css + * @var string $source */ $code = $exception->getCode() ? ' #' . $exception->getCode() : ''; @@ -22,20 +23,37 @@ $chain = Helpers::getExceptionChain($exception); <?= Helpers::escapeHtml($title . ': ' . $exception->getMessage() . $code) ?> - + 1): ?> + - + + > diff --git a/src/Tracy/BlueScreen/assets/section-exception-exception.phtml b/src/Tracy/BlueScreen/assets/section-exception-exception.phtml index 82af273bc..104edd4e3 100644 --- a/src/Tracy/BlueScreen/assets/section-exception-exception.phtml +++ b/src/Tracy/BlueScreen/assets/section-exception-exception.phtml @@ -9,7 +9,7 @@ namespace Tracy; * @var callable $dump */ -if (count((array) $ex) <= count((array) new \Exception)) { +if (count(get_mangled_object_vars($ex)) <= count(get_mangled_object_vars(new \Exception))) { return; } ?> diff --git a/src/Tracy/BlueScreen/assets/section-exception-variables.phtml b/src/Tracy/BlueScreen/assets/section-exception-variables.phtml deleted file mode 100644 index ef821c26d..000000000 --- a/src/Tracy/BlueScreen/assets/section-exception-variables.phtml +++ /dev/null @@ -1,28 +0,0 @@ -context) || !is_array($ex->context)) { - return; -} -?> -
- - -
-
- -context as $k => $v): ?> - - -
-
-
-
diff --git a/src/Tracy/BlueScreen/assets/section-exception.phtml b/src/Tracy/BlueScreen/assets/section-exception.phtml index 74517d814..fba06273b 100644 --- a/src/Tracy/BlueScreen/assets/section-exception.phtml +++ b/src/Tracy/BlueScreen/assets/section-exception.phtml @@ -67,8 +67,6 @@ namespace Tracy; - - diff --git a/src/Tracy/BlueScreen/assets/section-header.phtml b/src/Tracy/BlueScreen/assets/section-header.phtml index 8be19a997..a6f049473 100644 --- a/src/Tracy/BlueScreen/assets/section-header.phtml +++ b/src/Tracy/BlueScreen/assets/section-header.phtml @@ -13,7 +13,7 @@ namespace Tracy; $title = $ex instanceof \ErrorException ? Helpers::errorTypeToString($ex->getSeverity()) - : Helpers::getClass($ex); + : get_debug_type($ex); $code = $ex->getCode() ? ' #' . $ex->getCode() : ''; ?> @@ -30,6 +30,6 @@ $code = $ex->getCode() ? ' #' . $ex->getCode() : ''; getPrevious()): ?> diff --git a/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml b/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml index fa94d4e51..9b9cbe639 100644 --- a/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml +++ b/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml @@ -72,7 +72,7 @@ if (!$stack) { try { $r = isset($row['class']) ? new \ReflectionMethod($row['class'], $row['function']) : new \ReflectionFunction($row['function']); $params = $r->getParameters(); - } catch (\Exception $e) { + } catch (\Exception) { $params = []; } foreach ($row['args'] as $k => $v) { diff --git a/src/Tracy/Debugger/Debugger.php b/src/Tracy/Debugger/Debugger.php index 7088fb5be..1c2fec8cf 100644 --- a/src/Tracy/Debugger/Debugger.php +++ b/src/Tracy/Debugger/Debugger.php @@ -17,7 +17,7 @@ */ class Debugger { - public const VERSION = '2.10-dev'; + public const VERSION = '3.0-dev'; /** server modes for Debugger::enable() */ public const @@ -33,76 +33,72 @@ class Debugger public const CookieSecret = 'tracy-debug'; public const COOKIE_SECRET = self::CookieSecret; - /** @var bool in production mode is suppressed any debugging output */ - public static $productionMode = self::Detect; + /** in production mode is suppressed any debugging output */ + public static ?bool $productionMode = self::DETECT; - /** @var bool whether to display debug bar in development mode */ - public static $showBar = true; + /** whether to display debug bar in development mode */ + public static bool $showBar = true; - /** @var bool whether to send data to FireLogger in development mode */ - public static $showFireLogger = true; + /** size of reserved memory */ + public static int $reservedMemorySize = 500_000; - /** @var int size of reserved memory */ - public static $reservedMemorySize = 500000; + private static bool $enabled = false; - /** @var bool */ - private static $enabled = false; + /** reserved memory; also prevents double rendering */ + private static ?string $reserved = null; - /** @var string|null reserved memory; also prevents double rendering */ - private static $reserved; + /** initial output buffer level */ + private static int $obLevel; - /** @var int initial output buffer level */ - private static $obLevel; - - /** @var ?array output buffer status @internal */ - public static $obStatus; + /** output buffer status @internal */ + public static ?array $obStatus = null; /********************* errors and exceptions reporting ****************d*g**/ - /** @var bool|int determines whether any error will cause immediate death in development mode; if integer that it's matched against error severity */ - public static $strictMode = false; + /** determines whether any error will cause immediate death in development mode; if integer that it's matched against error severity */ + public static bool|int $strictMode = false; - /** @var bool|int disables the @ (shut-up) operator so that notices and warnings are no longer hidden; if integer than it's matched against error severity */ - public static $scream = false; + /** disables the @ (shut-up) operator so that notices and warnings are no longer hidden; if integer than it's matched against error severity */ + public static bool|int $scream = false; /** @var callable[] functions that are automatically called after fatal error */ - public static $onFatalError = []; + public static array $onFatalError = []; /********************* Debugger::dump() ****************d*g**/ - /** @var int how many nested levels of array/object properties display by dump() */ - public static $maxDepth = 15; + /** how many nested levels of array/object properties display by dump() */ + public static int $maxDepth = 15; - /** @var int how long strings display by dump() */ - public static $maxLength = 150; + /** how long strings display by dump() */ + public static int $maxLength = 150; - /** @var int how many items in array/object display by dump() */ - public static $maxItems = 100; + /** how many items in array/object display by dump() */ + public static int $maxItems = 100; - /** @var bool display location by dump()? */ - public static $showLocation; + /** display location by dump()? */ + public static ?bool $showLocation = null; /** @var string[] sensitive keys not displayed by dump() */ - public static $keysToHide = []; + public static array $keysToHide = []; - /** @var string theme for dump() */ - public static $dumpTheme = 'light'; + /** theme for dump() */ + public static string $dumpTheme = 'light'; /** @deprecated */ public static $maxLen; /********************* logging ****************d*g**/ - /** @var string|null name of the directory where errors should be logged */ - public static $logDirectory; + /** name of the directory where errors should be logged */ + public static ?string $logDirectory = null; - /** @var int log bluescreen in production mode for this error severity */ - public static $logSeverity = 0; + /** log bluescreen in production mode for this error severity */ + public static int $logSeverity = 0; - /** @var string|array email(s) to which send error notifications */ - public static $email; + /** email(s) to which send error notifications */ + public static string|array|null $email = null; - /** for Debugger::log() and Debugger::fireLog() */ + /** for Debugger::log() */ public const DEBUG = ILogger::DEBUG, INFO = ILogger::INFO, @@ -113,52 +109,41 @@ class Debugger /********************* misc ****************d*g**/ - /** @var float timestamp with microseconds of the start of the request */ - public static $time; + /** timestamp with microseconds of the start of the request */ + public static float $time; - /** @var string URI pattern mask to open editor */ - public static $editor = 'editor://%action/?file=%file&line=%line&search=%search&replace=%replace'; + /** URI pattern mask to open editor */ + public static ?string $editor = 'editor://%action/?file=%file&line=%line&search=%search&replace=%replace'; - /** @var array replacements in path */ - public static $editorMapping = []; + /** replacements in path */ + public static array $editorMapping = []; - /** @var string command to open browser (use 'start ""' in Windows) */ - public static $browser; + /** command to open browser (use 'start ""' in Windows) */ + public static ?string $browser = null; - /** @var string custom static error template */ - public static $errorTemplate; + /** custom static error template */ + public static ?string $errorTemplate = null; /** @var string[] */ - public static $customCssFiles = []; + public static array $customCssFiles = []; /** @var string[] */ - public static $customJsFiles = []; + public static array $customJsFiles = []; /** @var callable[] */ private static $sourceMappers = []; - /** @var array|null */ - private static $cpuUsage; + private static ?array $cpuUsage = null; /********************* services ****************d*g**/ - /** @var BlueScreen */ - private static $blueScreen; - - /** @var Bar */ - private static $bar; - - /** @var ILogger */ - private static $logger; - - /** @var ILogger */ - private static $fireLogger; + private static BlueScreen $blueScreen; + private static Bar $bar; + private static ILogger $logger; /** @var array{DevelopmentStrategy, ProductionStrategy} */ - private static $strategy; - - /** @var SessionStorage */ - private static $sessionStorage; + private static array $strategy; + private static SessionStorage $sessionStorage; /** @@ -176,7 +161,11 @@ final public function __construct() * @param string $logDirectory error log directory * @param string|array $email administrator email; enables email sending in production mode */ - public static function enable($mode = null, ?string $logDirectory = null, $email = null): void + public static function enable( + bool|string|array|null $mode = null, + ?string $logDirectory = null, + string|array|null $email = null, + ): void { if ($mode !== null || self::$productionMode === null) { self::$productionMode = is_bool($mode) @@ -242,7 +231,6 @@ public static function enable($mode = null, ?string $logDirectory = null, $email 'Dumper/Exposer', 'Dumper/Renderer', 'Dumper/Value', - 'Logger/FireLogger', 'Logger/Logger', 'Session/SessionStorage', 'Session/FileSession', @@ -320,7 +308,7 @@ public static function exceptionHandler(\Throwable $exception): void self::$obStatus = ob_get_status(true); if (!headers_sent()) { - http_response_code(isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE ') !== false ? 503 : 500); + http_response_code(isset($_SERVER['HTTP_USER_AGENT']) && str_contains($_SERVER['HTTP_USER_AGENT'], 'MSIE ') ? 503 : 500); } Helpers::improveException($exception); @@ -352,7 +340,6 @@ public static function errorHandler( string $message, string $file, int $line, - ?array $context = null ): bool { $error = error_get_last(); @@ -361,30 +348,14 @@ public static function errorHandler( self::errorHandler($error['type'], $error['message'], $error['file'], $error['line']); } - if ($context) { - $context = (array) (object) $context; // workaround for PHP bug #80234 - } - if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) { - if (Helpers::findTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), '*::__toString')) { // workaround for PHP < 7.4 - $previous = isset($context['e']) && $context['e'] instanceof \Throwable - ? $context['e'] - : null; - $e = new ErrorException($message, 0, $severity, $file, $line, $previous); - @$e->context = $context; // dynamic properties are deprecated since PHP 8.2 - self::exceptionHandler($e); - exit(255); - } - - $e = new ErrorException($message, 0, $severity, $file, $line); - @$e->context = $context; // dynamic properties are deprecated since PHP 8.2 - throw $e; + throw new ErrorException($message, 0, $severity, $file, $line); } elseif ( ($severity & error_reporting()) || (is_int(self::$scream) ? $severity & self::$scream : self::$scream) ) { - self::getStrategy()->handleError($severity, $message, $file, $line, $context); + self::getStrategy()->handleError($severity, $message, $file, $line); } return false; // calls normal error handler to fill-in error_get_last() @@ -415,7 +386,7 @@ public static function removeOutputBuffers(bool $errorOccurred): void public static function getBlueScreen(): BlueScreen { - if (!self::$blueScreen) { + if (empty(self::$blueScreen)) { self::$blueScreen = new BlueScreen; self::$blueScreen->info = [ 'PHP ' . PHP_VERSION, @@ -430,7 +401,7 @@ public static function getBlueScreen(): BlueScreen public static function getBar(): Bar { - if (!self::$bar) { + if (empty(self::$bar)) { self::$bar = new Bar; self::$bar->addPanel($info = new DefaultBarPanel('info'), 'Tracy:info'); $info->cpuUsage = self::$cpuUsage; @@ -449,7 +420,7 @@ public static function setLogger(ILogger $logger): void public static function getLogger(): ILogger { - if (!self::$logger) { + if (empty(self::$logger)) { self::$logger = new Logger(self::$logDirectory, self::$email, self::getBlueScreen()); self::$logger->directory = &self::$logDirectory; // back compatiblity self::$logger->email = &self::$email; @@ -459,18 +430,8 @@ public static function getLogger(): ILogger } - public static function getFireLogger(): ILogger - { - if (!self::$fireLogger) { - self::$fireLogger = new FireLogger; - } - - return self::$fireLogger; - } - - - /** @return ProductionStrategy|DevelopmentStrategy @internal */ - public static function getStrategy() + /** @internal */ + public static function getStrategy(): ProductionStrategy|DevelopmentStrategy { if (empty(self::$strategy[self::$productionMode])) { self::$strategy[self::$productionMode] = self::$productionMode @@ -484,7 +445,7 @@ public static function getStrategy() public static function setSessionStorage(SessionStorage $storage): void { - if (self::$sessionStorage) { + if (isset(self::$sessionStorage)) { throw new \Exception('Storage is already set.'); } @@ -495,7 +456,7 @@ public static function setSessionStorage(SessionStorage $storage): void /** @internal */ public static function getSessionStorage(): SessionStorage { - if (!self::$sessionStorage) { + if (empty(self::$sessionStorage)) { self::$sessionStorage = @is_dir($dir = session_save_path()) || @is_dir($dir = ini_get('upload_tmp_dir')) || @is_dir($dir = sys_get_temp_dir()) @@ -518,7 +479,7 @@ public static function getSessionStorage(): SessionStorage * @param bool $return return output instead of printing it? (bypasses $productionMode) * @return mixed variable itself or dump */ - public static function dump($var, bool $return = false) + public static function dump(mixed $var, bool $return = false): mixed { if ($return) { $options = [ @@ -528,13 +489,13 @@ public static function dump($var, bool $return = false) ]; return Helpers::isCli() ? Dumper::toText($var) - : Helpers::capture(function () use ($var, $options) { - Dumper::dump($var, $options); - }); + : Helpers::capture(fn() => Dumper::dump($var, $options)); } elseif (!self::$productionMode) { - $html = Helpers::isHtmlMode(); - echo $html ? '' : ''; + if ($html = Helpers::isHtmlMode()) { + Dumper::renderAssets(); + echo ''; + } Dumper::dump($var, [ Dumper::DEPTH => self::$maxDepth, Dumper::TRUNCATE => self::$maxLength, @@ -543,7 +504,7 @@ public static function dump($var, bool $return = false) Dumper::THEME => self::$dumpTheme, Dumper::KEYS_TO_HIDE => self::$keysToHide, ]); - echo $html ? '' : ''; + echo $html ? '' : ''; } return $var; @@ -557,20 +518,19 @@ public static function dump($var, bool $return = false) public static function timer(?string $name = null): float { static $time = []; - $now = microtime(true); + $now = hrtime(true); $delta = isset($time[$name]) ? $now - $time[$name] : 0; $time[$name] = $now; - return $delta; + return $delta / 1e9; } /** * Dumps information about a variable in Tracy Debug Bar. * @tracySkipLocation - * @param mixed $var * @return mixed variable itself */ - public static function barDump($var, ?string $title = null, array $options = []) + public static function barDump(mixed $var, ?string $title = null, array $options = []): mixed { if (!self::$productionMode) { static $panel; @@ -592,27 +552,13 @@ public static function barDump($var, ?string $title = null, array $options = []) /** * Logs message or exception. - * @param mixed $message - * @return mixed */ - public static function log($message, string $level = ILogger::INFO) + public static function log(mixed $message, string $level = ILogger::INFO): mixed { return self::getLogger()->log($message, $level); } - /** - * Sends message to FireLogger console. - * @param mixed $message - */ - public static function fireLog($message): bool - { - return !self::$productionMode && self::$showFireLogger - ? self::getFireLogger()->log($message) - : false; - } - - /** @internal */ public static function addSourceMapper(callable $mapper): void { @@ -637,7 +583,7 @@ public static function mapSource(string $file, int $line): ?array * Detects debug mode by IP address. * @param string|array $list IP addresses or computer names whitelist detection */ - public static function detectDebugMode($list = null): bool + public static function detectDebugMode(string|array|null $list = null): bool { $addr = $_SERVER['REMOTE_ADDR'] ?? php_uname('n'); $secret = isset($_COOKIE[self::CookieSecret]) && is_string($_COOKIE[self::CookieSecret]) diff --git a/src/Tracy/Debugger/DeferredContent.php b/src/Tracy/Debugger/DeferredContent.php index fafc8be82..2ed27ffca 100644 --- a/src/Tracy/Debugger/DeferredContent.php +++ b/src/Tracy/Debugger/DeferredContent.php @@ -15,14 +15,9 @@ */ final class DeferredContent { - /** @var SessionStorage */ - private $sessionStorage; - - /** @var string */ - private $requestId; - - /** @var bool */ - private $useSession = false; + private SessionStorage $sessionStorage; + private string $requestId; + private bool $useSession = false; public function __construct(SessionStorage $sessionStorage) @@ -52,7 +47,7 @@ public function &getItems(string $key): array } - public function addSetup(string $method, $argument): void + public function addSetup(string $method, mixed $argument): void { $argument = json_encode($argument, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE); $item = &$this->getItems('setup')[$this->requestId]; @@ -66,7 +61,7 @@ public function sendAssets(): bool if (headers_sent($file, $line) || ob_get_length()) { throw new \LogicException( __METHOD__ . '() called after some output has been sent. ' - . ($file ? "Output started at $file:$line." : 'Try Tracy\OutputDebugger to find where output started.') + . ($file ? "Output started at $file:$line." : 'Try Tracy\OutputDebugger to find where output started.'), ); } @@ -126,7 +121,7 @@ private function buildJsCss(): string __DIR__ . '/../BlueScreen/assets/bluescreen.css', ], Debugger::$customCssFiles)); - $js1 = array_map(function ($file) { return '(function() {' . file_get_contents($file) . '})();'; }, [ + $js1 = array_map(fn($file) => '(function() {' . file_get_contents($file) . '})();', [ __DIR__ . '/../Bar/assets/bar.js', __DIR__ . '/../assets/toggle.js', __DIR__ . '/../assets/table-sort.js', @@ -153,9 +148,7 @@ public function clean(): void { foreach ($this->sessionStorage->getData() as &$items) { $items = array_slice((array) $items, -10, null, true); - $items = array_filter($items, function ($item) { - return isset($item['time']) && $item['time'] > time() - 60; - }); + $items = array_filter($items, fn($item) => isset($item['time']) && $item['time'] > time() - 60); } } } diff --git a/src/Tracy/Debugger/DevelopmentStrategy.php b/src/Tracy/Debugger/DevelopmentStrategy.php index 27e778639..d778027d3 100644 --- a/src/Tracy/Debugger/DevelopmentStrategy.php +++ b/src/Tracy/Debugger/DevelopmentStrategy.php @@ -17,21 +17,11 @@ */ final class DevelopmentStrategy { - /** @var Bar */ - private $bar; - - /** @var BlueScreen */ - private $blueScreen; - - /** @var DeferredContent */ - private $defer; - - - public function __construct(Bar $bar, BlueScreen $blueScreen, DeferredContent $defer) - { - $this->bar = $bar; - $this->blueScreen = $blueScreen; - $this->defer = $defer; + public function __construct( + private Bar $bar, + private BlueScreen $blueScreen, + private DeferredContent $defer, + ) { } @@ -49,7 +39,6 @@ public function handleException(\Throwable $exception, bool $firstTime): void $this->blueScreen->render($exception); } else { - Debugger::fireLog($exception); $this->renderExceptionCli($exception); } } @@ -84,7 +73,6 @@ public function handleError( string $message, string $file, int $line, - array $context = null ): void { if (function_exists('ini_set')) { @@ -96,20 +84,16 @@ public function handleError( && !isset($_GET['_tracy_skip_error']) ) { $e = new ErrorException($message, 0, $severity, $file, $line); - @$e->context = $context; // dynamic properties are deprecated since PHP 8.2 - @$e->skippable = true; + @$e->skippable = true; // dynamic properties are deprecated since PHP 8.2 Debugger::exceptionHandler($e); exit(255); } - $message = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message, (array) $context); + $message = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message); $count = &$this->bar->getPanel('Tracy:errors')->data["$file|$line|$message"]; - if (!$count++) { // not repeated error - Debugger::fireLog(new ErrorException($message, 0, $severity, $file, $line)); - if (!Helpers::isHtmlMode() && !Helpers::isAjax()) { - echo "\n$message in $file on line $line\n"; - } + if (!$count++ && !Helpers::isHtmlMode() && !Helpers::isAjax()) { + echo "\n$message in $file on line $line\n"; } if (function_exists('ini_set')) { diff --git a/src/Tracy/Debugger/ProductionStrategy.php b/src/Tracy/Debugger/ProductionStrategy.php index 289c1972c..93ddfa2f2 100644 --- a/src/Tracy/Debugger/ProductionStrategy.php +++ b/src/Tracy/Debugger/ProductionStrategy.php @@ -40,9 +40,7 @@ public function handleException(\Throwable $exception, bool $firstTime): void header('Content-Type: text/html; charset=UTF-8'); } - (function ($logged) use ($exception) { - require Debugger::$errorTemplate ?: __DIR__ . '/assets/error.500.phtml'; - })(empty($e)); + (fn($logged) => require Debugger::$errorTemplate ?: __DIR__ . '/assets/error.500.phtml')(empty($e)); } elseif (Helpers::isCli()) { // @ triggers E_NOTICE when strerr is closed since PHP 7.4 @@ -60,15 +58,13 @@ public function handleError( string $message, string $file, int $line, - array $context = null ): void { if ($severity & Debugger::$logSeverity) { $err = new ErrorException($message, 0, $severity, $file, $line); - @$err->context = $context; // dynamic properties are deprecated since PHP 8.2 Helpers::improveException($err); } else { - $err = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message, (array) $context) . " in $file:$line"; + $err = 'PHP ' . Helpers::errorTypeToString($severity) . ': ' . Helpers::improveError($message) . " in $file:$line"; } try { diff --git a/src/Tracy/Dumper/Describer.php b/src/Tracy/Dumper/Describer.php index d974bfec2..be0d6e1c2 100644 --- a/src/Tracy/Dumper/Describer.php +++ b/src/Tracy/Dumper/Describer.php @@ -24,45 +24,36 @@ final class Describer // Number.MAX_SAFE_INTEGER private const JsSafeInteger = 1 << 53 - 1; - /** @var int */ - public $maxDepth = 7; - - /** @var int */ - public $maxLength = 150; - - /** @var int */ - public $maxItems = 100; + public int $maxDepth = 7; + public int $maxLength = 150; + public int $maxItems = 100; /** @var Value[] */ - public $snapshot = []; - - /** @var bool */ - public $debugInfo = false; - - /** @var array */ - public $keysToHide = []; + public array $snapshot = []; + public bool $debugInfo = false; + public array $keysToHide = []; - /** @var callable|null fn(string $key, mixed $val): bool */ + /** @var (callable(string, mixed): bool)|null */ public $scrubber; - /** @var bool */ - public $location = false; + public bool $location = false; /** @var callable[] */ - public $resourceExposers; + public array $resourceExposers = []; /** @var array */ - public $objectExposers; + public array $objectExposers = []; + + /** @var array */ + public array $enumProperties = []; /** @var (int|\stdClass)[] */ - public $references = []; + public array $references = []; - public function describe($var): \stdClass + public function describe(mixed $var): \stdClass { - uksort($this->objectExposers, function ($a, $b): int { - return $b === '' || (class_exists($a, false) && is_subclass_of($a, $b)) ? -1 : 1; - }); + uksort($this->objectExposers, fn($a, $b): int => $b === '' || (class_exists($a, false) && is_subclass_of($a, $b)) ? -1 : 1); try { return (object) [ @@ -79,10 +70,7 @@ public function describe($var): \stdClass } - /** - * @return mixed - */ - private function describeVar($var, int $depth = 0, ?int $refId = null) + private function describeVar(mixed $var, int $depth = 0, ?int $refId = null): mixed { if ($var === null || is_bool($var)) { return $var; @@ -93,10 +81,7 @@ private function describeVar($var, int $depth = 0, ?int $refId = null) } - /** - * @return Value|int - */ - private function describeInteger(int $num) + private function describeInteger(int $num): Value|int { return $num <= self::JsSafeInteger && $num >= -self::JsSafeInteger ? $num @@ -104,10 +89,7 @@ private function describeInteger(int $num) } - /** - * @return Value|float - */ - private function describeDouble(float $num) + private function describeDouble(float $num): Value|float { if (!is_finite($num)) { return new Value(Value::TypeNumber, (string) $num); @@ -120,10 +102,7 @@ private function describeDouble(float $num) } - /** - * @return Value|string - */ - private function describeString(string $s, int $depth = 0) + private function describeString(string $s, int $depth = 0): Value|string { $encoded = Helpers::encodeString($s, $depth ? $this->maxLength : null); if ($encoded === $s) { @@ -136,10 +115,7 @@ private function describeString(string $s, int $depth = 0) } - /** - * @return Value|array - */ - private function describeArray(array $arr, int $depth = 0, ?int $refId = null) + private function describeArray(array $arr, int $depth = 0, ?int $refId = null): Value|array { if ($refId) { $res = new Value(Value::TypeRef, 'p' . $refId); @@ -194,7 +170,7 @@ private function describeObject(object $obj, int $depth = 0): Value return new Value(Value::TypeRef, $id); } - $value = new Value(Value::TypeObject, Helpers::getClass($obj)); + $value = new Value(Value::TypeObject, get_debug_type($obj)); $value->id = $id; $value->depth = $depth; $value->holder = $obj; // to be not released by garbage collector in collecting mode @@ -243,10 +219,7 @@ private function describeResource($resource, int $depth = 0): Value } - /** - * @return Value|string - */ - public function describeKey(string $key) + public function describeKey(string $key): Value|string { if (preg_match('#^[\w!\#$%&*+./;<>?@^{|}~-]{1,50}$#D', $key) && !preg_match('#^(true|false|null)$#iD', $key)) { return $key; @@ -262,22 +235,24 @@ public function describeKey(string $key) public function addPropertyTo( Value $value, string $k, - $v, - $type = Value::PropertyVirtual, + mixed $v, + int $type = Value::PropertyVirtual, ?int $refId = null, - ?string $class = null - ) { + ?string $class = null, + ?Value $described = null, + ): void + { if ($value->depth && $this->maxItems && count($value->items ?? []) >= $this->maxItems) { $value->length = ($value->length ?? count($value->items)) + 1; return; } - $class = $class ?? $value->value; + $class ??= $value->value; $value->items[] = [ $this->describeKey($k), $type !== Value::PropertyVirtual && $this->isSensitive($k, $v, $class) ? new Value(Value::TypeText, self::hideValue($v)) - : $this->describeVar($v, $value->depth + 1, $refId), + : ($described ?? $this->describeVar($v, $value->depth + 1, $refId)), $type === Value::PropertyPrivate ? $class : $type, ] + ($refId ? [3 => $refId] : []); } @@ -300,7 +275,7 @@ private function exposeObject(object $obj, Value $value): ?array } - private function isSensitive(string $key, $val, ?string $class = null): bool + private function isSensitive(string $key, mixed $val, ?string $class = null): bool { return $val instanceof \SensitiveParameterValue || ($this->scrubber !== null && ($this->scrubber)($key, $val, $class)) @@ -309,47 +284,43 @@ private function isSensitive(string $key, $val, ?string $class = null): bool } - private static function hideValue($val): string + private static function hideValue(mixed $val): string { if ($val instanceof \SensitiveParameterValue) { $val = $val->getValue(); } - return self::HiddenValue . ' (' . (is_object($val) ? Helpers::getClass($val) : gettype($val)) . ')'; + return self::HiddenValue . ' (' . get_debug_type($val) . ')'; } - public function getReferenceId($arr, $key): ?int + public function describeEnumProperty(string $class, string $property, mixed $value): ?Value { - if (PHP_VERSION_ID >= 70400) { - if ((!$rr = \ReflectionReference::fromArrayElement($arr, $key))) { - return null; - } + [$set, $constants] = $this->enumProperties["$class::$property"] ?? null; + if (!is_int($value) + || !$constants + || !($constants = Helpers::decomposeFlags($value, $set, $constants)) + ) { + return null; + } - $tmp = &$this->references[$rr->getId()]; - if ($tmp === null) { - return $tmp = count($this->references); - } + $constants = array_map(fn(string $const): string => str_replace("$class::", 'self::', $const), $constants); + return new Value(Value::TypeNumber, implode(' | ', $constants) . " ($value)"); + } - return $tmp; - } - $uniq = new \stdClass; - $copy = $arr; - $orig = $copy[$key]; - $copy[$key] = $uniq; - if ($arr[$key] !== $uniq) { + public function getReferenceId(array $arr, string|int $key): ?int + { + if ((!$rr = \ReflectionReference::fromArrayElement($arr, $key))) { return null; } - $res = array_search($uniq, $this->references, true); - $copy[$key] = $orig; - if ($res === false) { - $this->references[] = &$arr[$key]; - return count($this->references); + $tmp = &$this->references[$rr->getId()]; + if ($tmp === null) { + return $tmp = count($this->references); } - return $res + 1; + return $tmp; } diff --git a/src/Tracy/Dumper/Dumper.php b/src/Tracy/Dumper/Dumper.php index ebc089a7d..8c1cfb0ce 100644 --- a/src/Tracy/Dumper/Dumper.php +++ b/src/Tracy/Dumper/Dumper.php @@ -45,10 +45,9 @@ class Dumper public const HIDDEN_VALUE = Describer::HiddenValue; /** @var Dumper\Value[] */ - public static $liveSnapshot = []; + public static array $liveSnapshot = []; - /** @var array */ - public static $terminalColors = [ + public static ?array $terminalColors = [ 'bool' => '1;33', 'null' => '1;33', 'number' => '1;32', @@ -64,18 +63,17 @@ class Dumper 'indent' => '1;30', ]; - /** @var array */ - public static $resources = [ + public static array $resources = [ 'stream' => 'stream_get_meta_data', 'stream-context' => 'stream_context_get_options', 'curl' => 'curl_getinfo', ]; - /** @var array */ - public static $objectExporters = [ + public static array $objectExporters = [ \Closure::class => [Exposer::class, 'exposeClosure'], \UnitEnum::class => [Exposer::class, 'exposeEnum'], \ArrayObject::class => [Exposer::class, 'exposeArrayObject'], + \ArrayIterator::class => [Exposer::class, 'exposeArrayIterator'], \SplFileInfo::class => [Exposer::class, 'exposeSplFileInfo'], \SplObjectStorage::class => [Exposer::class, 'exposeSplObjectStorage'], \__PHP_Incomplete_Class::class => [Exposer::class, 'exposePhpIncompleteClass'], @@ -88,18 +86,17 @@ class Dumper Ds\Map::class => [Exposer::class, 'exposeDsMap'], ]; - /** @var Describer */ - private $describer; + /** @var array */ + private static array $enumProperties = []; - /** @var Renderer */ - private $renderer; + private Describer $describer; + private Renderer $renderer; /** * Dumps variable to the output. - * @return mixed variable */ - public static function dump($var, array $options = []) + public static function dump(mixed $var, array $options = []): mixed { if (Helpers::isCli()) { $useColors = self::$terminalColors && Helpers::detectColors(); @@ -107,8 +104,7 @@ public static function dump($var, array $options = []) fwrite(STDOUT, $dumper->asTerminal($var, $useColors ? self::$terminalColors : [])); } elseif (Helpers::isHtmlMode()) { - $options[self::LOCATION] = $options[self::LOCATION] ?? true; - self::renderAssets(); + $options[self::LOCATION] ??= true; echo self::toHtml($var, $options); } else { @@ -122,7 +118,7 @@ public static function dump($var, array $options = []) /** * Dumps variable to HTML. */ - public static function toHtml($var, array $options = [], $key = null): string + public static function toHtml(mixed $var, array $options = [], mixed $key = null): string { return (new self($options))->asHtml($var, $key); } @@ -131,7 +127,7 @@ public static function toHtml($var, array $options = [], $key = null): string /** * Dumps variable to plain text. */ - public static function toText($var, array $options = []): string + public static function toText(mixed $var, array $options = []): string { return (new self($options))->asTerminal($var); } @@ -140,7 +136,7 @@ public static function toText($var, array $options = []): string /** * Dumps variable to x-terminal. */ - public static function toTerminal($var, array $options = []): string + public static function toTerminal(mixed $var, array $options = []): string { return (new self($options))->asTerminal($var, self::$terminalColors); } @@ -158,18 +154,18 @@ public static function renderAssets(): void $sent = true; + echo '\n"; - if (!Debugger::isEnabled()) { - $s = '(function(){' . file_get_contents(__DIR__ . '/../assets/toggle.js') . '})();' - . '(function(){' . file_get_contents(__DIR__ . '/../Dumper/assets/dumper.js') . '})();'; - echo "", str_replace([' + %A% @@ -161,7 +162,6 @@ -
diff --git a/tests/Tracy/expected/Debugger.error-in-eval.expect b/tests/Tracy/expected/Debugger.error-in-eval.expect index 7313f73f6..e67703bf7 100644 --- a/tests/Tracy/expected/Debugger.error-in-eval.expect +++ b/tests/Tracy/expected/Debugger.error-in-eval.expect @@ -7,6 +7,7 @@ User Error: The my error + %A% @@ -96,7 +97,7 @@
-%A% +
diff --git a/tests/Tracy/expected/Debugger.exception.fiber.html.expect b/tests/Tracy/expected/Debugger.exception.fiber.html.expect index dcc968709..c144219e9 100644 --- a/tests/Tracy/expected/Debugger.exception.fiber.html.expect +++ b/tests/Tracy/expected/Debugger.exception.fiber.html.expect @@ -7,6 +7,7 @@ Exception: The my exception #123 + %A% diff --git a/tests/Tracy/expected/Debugger.exception.generator.html.expect b/tests/Tracy/expected/Debugger.exception.generator.html.expect index 52b76ed70..a627d292a 100644 --- a/tests/Tracy/expected/Debugger.exception.generator.html.expect +++ b/tests/Tracy/expected/Debugger.exception.generator.html.expect @@ -7,6 +7,7 @@ Exception: The my exception #123 + %A% diff --git a/tests/Tracy/expected/Debugger.exception.html.expect b/tests/Tracy/expected/Debugger.exception.html.expect index 362c57b07..587011e03 100644 --- a/tests/Tracy/expected/Debugger.exception.html.expect +++ b/tests/Tracy/expected/Debugger.exception.html.expect @@ -7,6 +7,7 @@ Exception: The my exception #123 + %A% @@ -102,7 +103,6 @@ -
diff --git a/tests/Tracy/expected/Debugger.strict.html.expect b/tests/Tracy/expected/Debugger.strict.html.expect index a12e10209..5af044ab2 100644 --- a/tests/Tracy/expected/Debugger.strict.html.expect +++ b/tests/Tracy/expected/Debugger.strict.html.expect @@ -7,6 +7,7 @@ Notice: Only variables should be assigned by reference + %A% @@ -99,7 +100,7 @@
-%A% +
diff --git a/tests/Tracy/fixtures/DumpClass.74.php b/tests/Tracy/fixtures/DumpClass.74.php deleted file mode 100644 index 5c7c1e995..000000000 --- a/tests/Tracy/fixtures/DumpClass.74.php +++ /dev/null @@ -1,21 +0,0 @@ -getSubPathname()}\n"; $s = file_get_contents($file->getPathname()); - if (strpos($s, '@tracySkipLocation') === false) { + if (!str_contains($s, '@tracySkipLocation')) { $s = php_strip_whitespace($file->getPathname()); } @@ -61,7 +61,7 @@ function compressCss(string $s): string $s = preg_replace_callback('#(<(script|style).*(?)(.*)(