From 886f0e8933c4b9d037a8423ee4c98b108d44c900 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 3 Jan 2024 22:09:42 +0100 Subject: [PATCH 01/10] Helpers::improveException() refactoring --- src/Tracy/Helpers.php | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Tracy/Helpers.php b/src/Tracy/Helpers.php index 7bcdcbc7f..8315c136c 100644 --- a/src/Tracy/Helpers.php +++ b/src/Tracy/Helpers.php @@ -194,42 +194,48 @@ public static function improveException(\Throwable $e): void { $message = $e->getMessage(); if ( - (!$e instanceof \Error && !$e instanceof \ErrorException) - || $e instanceof Nette\MemberAccessException - || strpos($e->getMessage(), 'did you mean') + !($e instanceof \Error || $e instanceof \ErrorException) + || str_contains($e->getMessage(), 'did you mean') ) { // do nothing } elseif (preg_match('#^Call to undefined function (\S+\\\\)?(\w+)\(#', $message, $m)) { $funcs = array_merge(get_defined_functions()['internal'], get_defined_functions()['user']); - $hint = self::getSuggestion($funcs, $m[1] . $m[2]) ?: self::getSuggestion($funcs, $m[2]); - $message = "Call to undefined function $m[2](), did you mean $hint()?"; - $replace = ["$m[2](", "$hint("]; + if ($hint = self::getSuggestion($funcs, $m[1] . $m[2]) ?: self::getSuggestion($funcs, $m[2])) { + $message = "Call to undefined function $m[2](), did you mean $hint()?"; + $replace = ["$m[2](", "$hint("]; + } } elseif (preg_match('#^Call to undefined method ([\w\\\\]+)::(\w+)#', $message, $m)) { - $hint = self::getSuggestion(get_class_methods($m[1]) ?: [], $m[2]); - $message .= ", did you mean $hint()?"; - $replace = ["$m[2](", "$hint("]; + if ($hint = self::getSuggestion(get_class_methods($m[1]) ?: [], $m[2])) { + $message .= ", did you mean $hint()?"; + $replace = ["$m[2](", "$hint("]; + } } elseif (preg_match('#^Undefined property: ([\w\\\\]+)::\$(\w+)#', $message, $m)) { $rc = new \ReflectionClass($m[1]); $items = array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($prop) => !$prop->isStatic()); - $hint = self::getSuggestion($items, $m[2]); - $message .= ", did you mean $$hint?"; - $replace = ["->$m[2]", "->$hint"]; + if ($hint = self::getSuggestion($items, $m[2])) { + $message .= ", did you mean $$hint?"; + $replace = ["->$m[2]", "->$hint"]; + } } elseif (preg_match('#^Access to undeclared static property:? ([\w\\\\]+)::\$(\w+)#', $message, $m)) { $rc = new \ReflectionClass($m[1]); $items = array_filter($rc->getProperties(\ReflectionProperty::IS_STATIC), fn($prop) => $prop->isPublic()); - $hint = self::getSuggestion($items, $m[2]); - $message .= ", did you mean $$hint?"; - $replace = ["::$$m[2]", "::$$hint"]; + if ($hint = self::getSuggestion($items, $m[2])) { + $message .= ", did you mean $$hint?"; + $replace = ["::$$m[2]", "::$$hint"]; + } } - if (isset($hint)) { - $loc = Debugger::mapSource($e->getFile(), $e->getLine()) ?? ['file' => $e->getFile(), 'line' => $e->getLine()]; + if ($message !== $e->getMessage()) { $ref = new \ReflectionProperty($e, 'message'); $ref->setAccessible(true); $ref->setValue($e, $message); + } + + if (isset($replace)) { + $loc = Debugger::mapSource($e->getFile(), $e->getLine()) ?? ['file' => $e->getFile(), 'line' => $e->getLine()]; @$e->tracyAction = [ // dynamic properties are deprecated since PHP 8.2 'link' => self::editorUri($loc['file'], $loc['line'], 'fix', $replace[0], $replace[1]), 'label' => 'fix it', From 086c977540497f0a76a5fec6b0f30ca3b7697c33 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 3 Jan 2024 22:09:42 +0100 Subject: [PATCH 02/10] Helpers::improveException() improved 'invalid callable errors' related to https://github.com/php/php-src/issues/8039 --- src/Tracy/Helpers.php | 19 +++++++ tests/Tracy/Helpers.improveException.phpt | 61 +++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/Tracy/Helpers.php b/src/Tracy/Helpers.php index 8315c136c..be962df9f 100644 --- a/src/Tracy/Helpers.php +++ b/src/Tracy/Helpers.php @@ -198,6 +198,25 @@ public static function improveException(\Throwable $e): void || str_contains($e->getMessage(), 'did you mean') ) { // 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; + if (is_string($arg) && str_contains($arg, '::')) { + $arg = explode('::', $arg, 2); + } + if (!is_callable($arg, syntax_only: true)) { + // do nothing + } elseif (is_array($arg) && is_string($arg[0]) && !class_exists($arg[0]) && !trait_exists($arg[0])) { + $message = str_replace($m[2], "but class '$arg[0]' does not exist", $message); + } elseif (is_array($arg) && !method_exists($arg[0], $arg[1])) { + $hint = self::getSuggestion(get_class_methods($arg[0]) ?: [], $arg[1]); + $class = is_object($arg[0]) ? get_class($arg[0]) : $arg[0]; + $message = str_replace($m[2], "but method $class::$arg[1]() does not exist" . ($hint ? " (did you mean $hint?)" : ''), $message); + } elseif (is_string($arg) && !function_exists($arg)) { + $funcs = array_merge(get_defined_functions()['internal'], get_defined_functions()['user']); + $hint = self::getSuggestion($funcs, $arg); + $message = str_replace($m[2], "but function '$arg' does not exist" . ($hint ? " (did you mean $hint?)" : ''), $message); + } + } elseif (preg_match('#^Call to undefined function (\S+\\\\)?(\w+)\(#', $message, $m)) { $funcs = array_merge(get_defined_functions()['internal'], get_defined_functions()['user']); if ($hint = self::getSuggestion($funcs, $m[1] . $m[2]) ?: self::getSuggestion($funcs, $m[2])) { diff --git a/tests/Tracy/Helpers.improveException.phpt b/tests/Tracy/Helpers.improveException.phpt index 8ee2ba7e8..32e78325a 100644 --- a/tests/Tracy/Helpers.improveException.phpt +++ b/tests/Tracy/Helpers.improveException.phpt @@ -218,3 +218,64 @@ test('do not suggest anything when accessing anonymous class', function () { Assert::same('Undefined property: class@anonymous::$property', $e->getMessage()); Assert::false(isset($e->tracyAction)); }); + + +test('callable error: ignore syntax mismatch', function () { + try { + (fn(callable $a) => null)(null); + } catch (Error $e) { + } + + Helpers::improveException($e); + Assert::match('{closure}(): Argument #1 ($a) must be of type callable, null given, called in %a%', $e->getMessage()); +}); + +test('callable error: typo in class name', function () { + try { + (fn(callable $a) => null)([PhpTokn::class, 'tokenize']); + } catch (Error $e) { + } + + Helpers::improveException($e); + Assert::match("{closure}(): Argument #1 (\$a) must be of type callable, but class 'PhpTokn' does not exist, called in %a%", $e->getMessage()); +}); + +test('callable error: typo in class name', function () { + try { + (fn(callable $a) => null)('PhpTokn::tokenize'); + } catch (Error $e) { + } + + Helpers::improveException($e); + Assert::match("{closure}(): Argument #1 (\$a) must be of type callable, but class 'PhpTokn' does not exist, called in %a%", $e->getMessage()); +}); + +test('callable error: typo in method name', function () { + try { + (fn(callable $a) => null)([PhpToken::class, 'tokenze']); + } catch (Error $e) { + } + + Helpers::improveException($e); + Assert::match('{closure}(): Argument #1 ($a) must be of type callable, but method PhpToken::tokenze() does not exist (did you mean tokenize?), called in %a%', $e->getMessage()); +}); + +test('callable error: typo in method name', function () { + try { + (fn(callable $a) => null)('PhpToken::tokenze'); + } catch (Error $e) { + } + + Helpers::improveException($e); + Assert::match('{closure}(): Argument #1 ($a) must be of type callable, but method PhpToken::tokenze() does not exist (did you mean tokenize?), called in %a%', $e->getMessage()); +}); + +test('callable error: typo in function name', function () { + try { + (fn(callable $a) => null)('trm'); + } catch (Error $e) { + } + + Helpers::improveException($e); + Assert::match("{closure}(): Argument #1 (\$a) must be of type callable, but function 'trm' does not exist (did you mean trim?), called in %a%", $e->getMessage()); +}); From a92e70a334c7e18bba9f96c1b902345434345fa2 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 2 Jan 2024 22:25:16 +0100 Subject: [PATCH 03/10] removed Helpers::fixStack() xdebug_get_function_stack() is available only if xdebug.mode=develop is set. Inside a shutdown handler, it returns the complete callstack only when called from the CLI. When used in a browser, the callstack is printed out and the function then does not return it. The callstack output can be disabled using html_errors=0, but the function still won't return it. --- src/Tracy/Debugger/Debugger.php | 2 +- src/Tracy/Helpers.php | 31 ------------------- .../Debugger.E_COMPILE_ERROR.console.phpt | 11 +------ 3 files changed, 2 insertions(+), 42 deletions(-) diff --git a/src/Tracy/Debugger/Debugger.php b/src/Tracy/Debugger/Debugger.php index 7e0fdeef5..fe45c539d 100644 --- a/src/Tracy/Debugger/Debugger.php +++ b/src/Tracy/Debugger/Debugger.php @@ -289,7 +289,7 @@ public static function shutdownHandler(): void { $error = error_get_last(); if (in_array($error['type'] ?? null, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, E_RECOVERABLE_ERROR, E_USER_ERROR], true)) { - self::exceptionHandler(Helpers::fixStack(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']))); + self::exceptionHandler(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'])); } elseif (($error['type'] ?? null) === E_COMPILE_WARNING) { error_clear_last(); self::errorHandler($error['type'], $error['message'], $error['file'], $error['line']); diff --git a/src/Tracy/Helpers.php b/src/Tracy/Helpers.php index be962df9f..d90f22971 100644 --- a/src/Tracy/Helpers.php +++ b/src/Tracy/Helpers.php @@ -116,37 +116,6 @@ public static function findTrace(array $trace, array|string $method, ?int &$inde } - /** @internal */ - public static function fixStack(\Throwable $exception): \Throwable - { - if (function_exists('xdebug_get_function_stack')) { - $stack = []; - $trace = @xdebug_get_function_stack(); // @ xdebug compatibility warning - $trace = array_slice(array_reverse($trace), 2, -1); - foreach ($trace as $row) { - $frame = [ - 'file' => $row['file'], - 'line' => $row['line'], - 'function' => $row['function'] ?? '*unknown*', - 'args' => [], - ]; - if (!empty($row['class'])) { - $frame['type'] = isset($row['type']) && $row['type'] === 'dynamic' ? '->' : '::'; - $frame['class'] = $row['class']; - } - - $stack[] = $frame; - } - - $ref = new \ReflectionProperty('Exception', 'trace'); - $ref->setAccessible(true); - $ref->setValue($exception, $stack); - } - - return $exception; - } - - /** @internal */ public static function errorTypeToString(int $type): string { diff --git a/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt b/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt index ab566eacf..bcf29eb2b 100644 --- a/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt +++ b/tests/Tracy/Debugger.E_COMPILE_ERROR.console.phpt @@ -24,16 +24,7 @@ $onFatalErrorCalled = false; register_shutdown_function(function () use (&$onFatalErrorCalled) { Assert::true($onFatalErrorCalled); - Assert::match(extension_loaded('xdebug') ? -'ErrorException: Cannot re-assign $this in %a% -Stack trace: -#0 %a%: third() -#1 %a%: second() -#2 %a%: first() -#3 {main} -Tracy is unable to log error: Logging directory is not specified. -' : -'ErrorException: Cannot re-assign $this in %a% + Assert::match('ErrorException: Cannot re-assign $this in %a% Stack trace: #0 [internal function]: Tracy\\Debugger::shutdownHandler() #1 {main} From b5d26b3dcdf511e52b375c2b44b39ca4af256293 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 7 Dec 2023 23:31:26 +0100 Subject: [PATCH 04/10] BlueScreen: displays real arguments and local variables using xdebug [WIP] - requires xdebug 3.3 and xdebug.mode=develop - stores information only for the last 8 exceptions, so we don't want to generate new exceptions during rendering --- examples/xdebug.php | 66 +++++++++++++++++++ src/Tracy/BlueScreen/BlueScreen.php | 14 ++++ src/Tracy/BlueScreen/assets/bluescreen.css | 8 ++- .../assets/section-stack-callStack.phtml | 34 +++++++++- .../assets/section-stack-exception.phtml | 2 + .../assets/section-stack-variables.phtml | 30 +++++++++ src/Tracy/Dumper/Describer.php | 26 ++++---- 7 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 examples/xdebug.php create mode 100644 src/Tracy/BlueScreen/assets/section-stack-variables.phtml diff --git a/examples/xdebug.php b/examples/xdebug.php new file mode 100644 index 000000000..2282d9203 --- /dev/null +++ b/examples/xdebug.php @@ -0,0 +1,66 @@ + + + +

Tracy: exception demo

+ +second(); + } + + + public function second() + { + self::third([1, 2, 3]); + } + + + public static function third($arg5) + { + //require __DIR__ . '/assets/E_COMPILE_WARNING-1.php'; + //require __DIR__ . '/assets/E_COMPILE_ERROR.php'; +// trigger_error('jo', E_USER_ERROR); +// dump(new Exception); +// dumpe(xdebug_get_function_stack( [ 'local_vars' => true, 'params_as_values' => true ] )); + try { + throw new Exception('Original'); + } catch (Exception $e) { + throw new Exception('The my exception', 123, $e); + } + $a++; + } +} + + + +function demo($a, $b) +{ + $demo = new DemoClass; + $demo->first($a, $b); +} + + +if (Debugger::$productionMode) { + echo '

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

'; +} + +demo(10, 'any string'); diff --git a/src/Tracy/BlueScreen/BlueScreen.php b/src/Tracy/BlueScreen/BlueScreen.php index 656058d67..fb220a05b 100644 --- a/src/Tracy/BlueScreen/BlueScreen.php +++ b/src/Tracy/BlueScreen/BlueScreen.php @@ -499,4 +499,18 @@ private function findGeneratorsAndFibers(object $object): array Helpers::traverseValue($object, $add); return [$generators, $fibers]; } + + + public function getRealArgsAndVariables(\Throwable $exception): array + { + $args = $variables = []; + if (function_exists('xdebug_get_function_stack') && version_compare(phpversion('xdebug'), '3.3.0', '>=')) { + $stack = xdebug_get_function_stack(['from_exception' => $exception]); + foreach (array_reverse($stack) as $k => $row) { + $args[$k] = $row['params'] ?? []; + $variables[$k - 1] = $row['variables'] ?? []; + } + } + return [$args, $variables]; + } } diff --git a/src/Tracy/BlueScreen/assets/bluescreen.css b/src/Tracy/BlueScreen/assets/bluescreen.css index bbe86c943..f63c29569 100644 --- a/src/Tracy/BlueScreen/assets/bluescreen.css +++ b/src/Tracy/BlueScreen/assets/bluescreen.css @@ -350,11 +350,15 @@ html.tracy-bs-visible body { grid-column-end: 3; } -#tracy-bs .tracy-callstack-args tr:first-child > * { +#tracy-bs .tracy-callstack-args tr > :first-child { + width: 10em; +} + +#tracy-bs .tracy-callstack-args-warning tr:first-child > * { position: relative; } -#tracy-bs .tracy-callstack-args tr:first-child td:before { +#tracy-bs .tracy-callstack-args-warning tr:first-child td:before { position: absolute; right: .3em; content: 'may not be true'; diff --git a/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml b/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml index 74b3744d8..c38da5a40 100644 --- a/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml +++ b/src/Tracy/BlueScreen/assets/section-stack-callStack.phtml @@ -8,6 +8,8 @@ namespace Tracy; * @var callable $dump * @var int $expanded * @var array $stack + * @var ?array $realArgs + * @var ?array $variables */ if (!$stack) { @@ -66,15 +68,27 @@ if (!$stack) { - + $v) { + echo '\n"; + } +?> +
', Helpers::escapeHtml((is_string($argName) ? '$' : '#') . $argName), ''; + echo $dump($v, $argName); + echo "
+ + + +getParameters(); - } catch (\Exception) { + } else { $params = []; } + foreach ($row['args'] as $k => $v) { $argName = isset($params[$k]) && !$params[$k]->isVariadic() ? $params[$k]->name : $k; echo '
', Helpers::escapeHtml((is_string($argName) ? '$' : '#') . $argName), ''; @@ -84,6 +98,20 @@ if (!$stack) { ?>
+ + +

Local Variables

+ + + $v) { + echo '\n"; + } +?> +
$', Helpers::escapeHtml($k), ''; + echo $dump($v, $k); + echo "
+ diff --git a/src/Tracy/BlueScreen/assets/section-stack-exception.phtml b/src/Tracy/BlueScreen/assets/section-stack-exception.phtml index 9e9bc66de..e3d459832 100644 --- a/src/Tracy/BlueScreen/assets/section-stack-exception.phtml +++ b/src/Tracy/BlueScreen/assets/section-stack-exception.phtml @@ -33,6 +33,8 @@ if (($stack[0]['class'] ?? null) === Debugger::class && in_array($stack[0]['func } $file = $ex->getFile(); $line = $ex->getLine(); +[$realArgs, $variables] = $this->getRealArgsAndVariables($ex); require __DIR__ . '/section-stack-sourceFile.phtml'; +require __DIR__ . '/section-stack-variables.phtml'; require __DIR__ . '/section-stack-callStack.phtml'; diff --git a/src/Tracy/BlueScreen/assets/section-stack-variables.phtml b/src/Tracy/BlueScreen/assets/section-stack-variables.phtml new file mode 100644 index 000000000..6fc766421 --- /dev/null +++ b/src/Tracy/BlueScreen/assets/section-stack-variables.phtml @@ -0,0 +1,30 @@ + + +
+ + +
+ + $v) { + echo '\n"; + } +?> +
$', Helpers::escapeHtml($k), ''; + echo $dump($v, $k); + echo "
+
+
diff --git a/src/Tracy/Dumper/Describer.php b/src/Tracy/Dumper/Describer.php index e841a3fa4..a0d293c68 100644 --- a/src/Tracy/Dumper/Describer.php +++ b/src/Tracy/Dumper/Describer.php @@ -326,19 +326,19 @@ private static function findLocation(): ?array if (isset($item['class']) && ($item['class'] === self::class || $item['class'] === Tracy\Dumper::class)) { $location = $item; continue; - } elseif (isset($item['function'])) { - try { - $reflection = isset($item['class']) - ? new \ReflectionMethod($item['class'], $item['function']) - : new \ReflectionFunction($item['function']); - if ( - $reflection->isInternal() - || preg_match('#\s@tracySkipLocation\s#', (string) $reflection->getDocComment()) - ) { - $location = $item; - continue; - } - } catch (\ReflectionException) { + } elseif ( + isset($item['function']) + && (isset($item['class']) ? method_exists($item['class'], $item['function']) : function_exists($item['function'])) + ) { + $reflection = isset($item['class']) + ? new \ReflectionMethod($item['class'], $item['function']) + : new \ReflectionFunction($item['function']); + if ( + $reflection->isInternal() + || preg_match('#\s@tracySkipLocation\s#', (string) $reflection->getDocComment()) + ) { + $location = $item; + continue; } } From 282fbf87d37842fa6b98c28aaa607e43ebe97bbd Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 10 Dec 2021 05:33:06 +0100 Subject: [PATCH 05/10] opened 3.0-dev --- composer.json | 2 +- readme.md | 1 + src/Tracy/Debugger/Debugger.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9be9a9415..09c813c44 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-master": "3.0-dev" } } } diff --git a/readme.md b/readme.md index f726dc45c..5b47bf203 100644 --- a/readme.md +++ b/readme.md @@ -50,6 +50,7 @@ Alternatively, you can download the whole package or [tracy.phar](https://github | Tracy | compatible with PHP | compatible with browsers |-----------|---------------|---------- +| Tracy 3.0 | PHP 8.0 – 8.3 | Chrome 64+, Firefox 69+, Safari 15.4+ and iOS Safari 15.4+ | Tracy 2.10| PHP 8.0 – 8.3 | 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+ diff --git a/src/Tracy/Debugger/Debugger.php b/src/Tracy/Debugger/Debugger.php index fe45c539d..407e52433 100644 --- a/src/Tracy/Debugger/Debugger.php +++ b/src/Tracy/Debugger/Debugger.php @@ -17,7 +17,7 @@ */ class Debugger { - public const Version = '2.10.5'; + public const Version = '3.0-dev'; /** server modes for Debugger::enable() */ public const From 3d9e2730d9dc0051acedbd4adcc4090fb7df5296 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 5 Dec 2022 01:21:34 +0100 Subject: [PATCH 06/10] uses PascalCase constants --- src/Bridges/Nette/TracyExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Nette/TracyExtension.php b/src/Bridges/Nette/TracyExtension.php index 3f0c6ac2b..809ef50e4 100644 --- a/src/Bridges/Nette/TracyExtension.php +++ b/src/Bridges/Nette/TracyExtension.php @@ -130,7 +130,7 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class): void 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); } From 01037c7b455b45d7e45fa1087d8058dd6ba07d24 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 20 Dec 2021 13:36:00 +0100 Subject: [PATCH 07/10] removed bridge for Latte --- src/Bridges/Nette/Bridge.php | 72 +----------------------------------- 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/src/Bridges/Nette/Bridge.php b/src/Bridges/Nette/Bridge.php index ccb2f1c1c..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,87 +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(fn(string $file) => 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, php: 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) { From 37ebfd39b4ccc417c6438c2988b506cf0772f467 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 3 Feb 2022 17:37:54 +0100 Subject: [PATCH 08/10] Logger: added typehints --- src/Tracy/Logger/Logger.php | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/Tracy/Logger/Logger.php b/src/Tracy/Logger/Logger.php index 4ef677798..7c9e65bc5 100644 --- a/src/Tracy/Logger/Logger.php +++ b/src/Tracy/Logger/Logger.php @@ -15,23 +15,22 @@ */ class Logger implements ILogger { - /** @var string|null 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|array|null email or emails to which send error notifications */ - public $email; + /** email or emails to which send error notifications */ + public string|array|null $email = null; - /** @var string|null 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 mixed $emailSnooze = '2 days'; /** @var callable handler for sending emails */ public $mailer; - /** @var BlueScreen|null */ - private $blueScreen; + private ?BlueScreen $blueScreen = null; public function __construct(?string $directory, string|array|null $email = null, ?BlueScreen $blueScreen = null) @@ -78,10 +77,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) { foreach (Helpers::getExceptionChain($message) as $exception) { @@ -101,10 +97,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]'), @@ -152,10 +145,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 @@ -174,10 +164,9 @@ protected function sendEmail($message): void /** * Default mailer. - * @param mixed $message * @internal */ - public function defaultMailer($message, string $email): void + public function defaultMailer(mixed $message, string $email): void { $host = preg_replace('#[^\w.-]+#', '', $_SERVER['SERVER_NAME'] ?? php_uname('n')); $parts = str_replace( From e26bb832ae7ff97b0bb2981d93d03a124fd7971a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 28 Sep 2023 19:05:31 +0200 Subject: [PATCH 09/10] css: uses nesting --- readme.md | 6 +++--- src/Tracy/Helpers.php | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 5b47bf203..6148b11fa 100644 --- a/readme.md +++ b/readme.md @@ -50,9 +50,9 @@ Alternatively, you can download the whole package or [tracy.phar](https://github | Tracy | compatible with PHP | compatible with browsers |-----------|---------------|---------- -| Tracy 3.0 | PHP 8.0 – 8.3 | Chrome 64+, Firefox 69+, Safari 15.4+ and iOS Safari 15.4+ -| Tracy 2.10| PHP 8.0 – 8.3 | 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 3.0 | PHP 8.0 – 8.3 | Chrome 112+, Firefox 117+, Safari 16.5+ +| Tracy 2.10| PHP 8.0 – 8.3 | Chrome 64+, Firefox 69+, Safari 15.4+ +| Tracy 2.9 | PHP 7.2 – 8.2 | Chrome 64+, Firefox 69+, Safari 13.1+ Usage diff --git a/src/Tracy/Helpers.php b/src/Tracy/Helpers.php index d90f22971..4bbd6cf7e 100644 --- a/src/Tracy/Helpers.php +++ b/src/Tracy/Helpers.php @@ -538,6 +538,7 @@ function ($match) use (&$last) { /** @internal */ public static function minifyCss(string $s): string { + return $s; $last = ''; return preg_replace_callback( <<<'XX' From a59b200d90c51cd844df054c846e2d41eb2b622e Mon Sep 17 00:00:00 2001 From: patrickkusebauch Date: Fri, 9 Jun 2023 19:35:14 +0200 Subject: [PATCH 10/10] Added panel sorter --- src/Tracy/Bar/Bar.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Tracy/Bar/Bar.php b/src/Tracy/Bar/Bar.php index 037bc3cee..5b635e379 100644 --- a/src/Tracy/Bar/Bar.php +++ b/src/Tracy/Bar/Bar.php @@ -15,10 +15,13 @@ */ class Bar { - /** @var IBarPanel[] */ + /** @var array */ private array $panels = []; private bool $loaderRendered = false; + /** @var ?callable(string, string): int */ + private $panelSorter; + /** * Add custom panel. @@ -37,6 +40,13 @@ public function addPanel(IBarPanel $panel, ?string $id = null): self return $this; } + /** + * @param callable(string, string): int $sorter + */ + public function replacePanelSorter(callable $sorter): void + { + $this->panelSorter = $sorter; + } /** * Returns panel with given id @@ -136,6 +146,10 @@ private function renderPanels(string $suffix = ''): array $obLevel = ob_get_level(); $panels = []; + if($this->panelSorter !== null) { + uksort($this->panels, $this->panelSorter); + } + foreach ($this->panels as $id => $panel) { $idHtml = preg_replace('#[^a-z0-9]+#i', '-', $id) . $suffix; try {