diff --git a/.gitattributes b/.gitattributes
index 9670e954e..ed8103553 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,9 +1,10 @@
-.gitattributes export-ignore
-.gitignore export-ignore
-.github export-ignore
-ncs.* export-ignore
-phpstan.neon export-ignore
-tests/ export-ignore
+.gitattributes export-ignore
+.github/ export-ignore
+.gitignore export-ignore
+CLAUDE.md export-ignore
+ncs.* export-ignore
+phpstan*.neon export-ignore
+tests/ export-ignore
-*.sh eol=lf
-*.php* diff=php linguist-language=PHP
+*.php* diff=php
+*.sh text eol=lf
diff --git a/.gitignore b/.gitignore
index bb7a71c46..bb2775cb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
/node_modules
/package-lock.json
/tools/create-phar/tracy.phar
+tests/lock
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..d95b0fa5f
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,523 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Tracy is a debugging and error visualization library for PHP (8.2-8.5). It provides beautiful error pages (BlueScreen), an interactive debug toolbar (Bar), advanced variable dumping, and production-ready error logging.
+
+**Key features:**
+- BlueScreen: Beautiful error/exception visualization with stack traces
+- Tracy Bar: Floating debug toolbar with extensible panel system
+- Dumper: Advanced variable dumping with multiple output formats
+- Logger: Error logging with email notifications
+- Production/development mode auto-detection
+
+## Essential Commands
+
+### Testing
+```bash
+# Run all tests
+composer run tester
+
+# Run tests manually with specific SAPI
+vendor/bin/tester tests -p php -s -C
+vendor/bin/tester tests -p php-cgi -s -C
+
+# Run single test file
+vendor/bin/tester tests/Tracy/Debugger.timer().phpt -s -C
+
+# Run tests in specific directory
+vendor/bin/tester tests/Dumper/ -s -C
+```
+
+### Code Quality
+```bash
+# Run PHPStan static analysis (level 5)
+composer run phpstan
+
+# Lint JavaScript assets
+npm run lint
+npm run lint:fix
+```
+
+## Core Architecture
+
+### Main Components (Facade Pattern)
+
+**Tracy\Debugger** (src/Tracy/Debugger/Debugger.php) - Central facade
+- `enable()` - Initialize Tracy
+- `dump()` / `bdump()` - Variable dumping
+- `log()` - Error logging
+- `timer()` - Performance profiling
+- Global functions available: `dump()`, `dumpe()`, `bdump()`
+
+**Strategy Pattern:**
+- `DevelopmentStrategy` - Shows full debug info, Tracy Bar
+- `ProductionStrategy` - Logs errors, shows user-friendly messages
+- Auto-detection: localhost = development, otherwise production
+
+### Core Components
+
+**BlueScreen** (src/Tracy/BlueScreen/)
+- Error/exception page rendering with stack traces
+- Multiple output formats (HTML, CLI, text)
+- Extensible via `addPanel()` callbacks
+- Main class: `BlueScreen.php` (513 lines)
+
+**Bar** (src/Tracy/Bar/)
+- Debug toolbar with panel system
+- Interface: `IBarPanel` for custom panels
+- Built-in panels: dumps, info, warnings
+- AJAX request tracking via session storage
+- Main class: `Bar.php` (164 lines)
+
+**Dumper** (src/Tracy/Dumper/)
+- Component architecture:
+ - `Describer` - Analyzes variable structure
+ - `Exposer` - Extracts object properties (including private/protected)
+ - `Renderer` - Formats output (HTML/CLI/Text)
+ - `Value` - Represents dumped values
+- Supports lazy loading, themes (light/dark)
+- Main class: `Dumper.php` (264 lines)
+
+**Logger** (src/Tracy/Logger/)
+- File-based error logging
+- Email notifications via `ILogger` interface
+- Severity filtering
+- PSR-3 compatible via adapters in `Bridges/Psr/`
+- Main class: `Logger.php` (198 lines)
+
+**Session Storage** (src/Tracy/Session/)
+- `FileSession` - Custom file-based storage (default)
+- `NativeSession` - PHP session integration
+- Used for AJAX/redirect request tracking
+
+### Directory Structure
+
+```
+src/
+├── Bridges/ # Framework integrations
+│ ├── Nette/ # Nette DI, Mail integration
+│ └── Psr/ # PSR-3 logger adapters
+└── Tracy/ # Core library
+ ├── Bar/ # Debug toolbar
+ ├── BlueScreen/ # Error visualization
+ ├── Debugger/ # Main facade & strategies
+ ├── Dumper/ # Variable dumping engine
+ ├── Logger/ # Error logging
+ ├── Session/ # Session storage
+ ├── OutputDebugger/ # Output buffer debugging
+ └── assets/ # Shared JavaScript utilities
+
+tests/ # 118 .phpt test files
+examples/ # Live examples and demos
+tools/ # Utilities (phar creation, editor integration)
+```
+
+## Testing Conventions
+
+**Test Framework:** Nette Tester (not PHPUnit)
+
+**Test file structure:**
+```php
+ $object->method(),
+ ExpectedException::class,
+ 'Expected message with %a% placeholders',
+);
+```
+
+**CI Testing:**
+- Tests run on PHP 8.2, 8.3, 8.4, 8.5
+- Both `php` and `php-cgi` SAPI
+- Ubuntu and Windows
+
+## Code Style
+
+**Nette Coding Standard** (based on PSR-12):
+- `declare(strict_types=1)` in every PHP file
+- Tab indentation
+- Return type and opening brace on separate lines:
+```php
+public function example(
+ string $param,
+ array $options,
+): ReturnType
+{
+ // method body
+}
+```
+- Configuration: `ncs.php`
+- No space before parentheses in arrow functions: `fn($a) => $b`
+
+**JavaScript:**
+- ESLint with `@nette/eslint-plugin`
+- Consistent with Nette JavaScript patterns
+
+## Configuration
+
+### Logger Configuration
+```php
+$logger = Debugger::getLogger();
+
+// Email notifications
+$logger->email = 'dev@example.com'; // (string|string[]) email(s) for error notifications
+$logger->fromEmail = 'me@example.com'; // (string) sender email
+$logger->mailer = /* callable */; // custom email sender, defaults to mail()
+$logger->emailSnooze = '2 days'; // minimum interval for sending emails
+
+// Log severity - which error levels are logged with HTML report
+Debugger::$logSeverity = E_WARNING | E_NOTICE;
+```
+
+### Dumper Configuration
+```php
+Debugger::$maxLength = 150; // maximum string length in dumps
+Debugger::$maxDepth = 10; // maximum nesting depth
+Debugger::$keysToHide = ['password', 'secret', 'token']; // hide sensitive keys
+Debugger::$dumpTheme = 'dark'; // 'light' or 'dark'
+Debugger::$showLocation = true; // show dump() call location
+```
+
+### Other Configuration
+```php
+Debugger::$strictMode = true; // display notices/warnings as BlueScreen
+Debugger::$scream = true; // display silenced (@) errors
+Debugger::$editor = 'editor://open/?file=%file&line=%line'; // editor link format
+Debugger::$errorTemplate = 'path/to/500.phtml'; // custom error 500 page
+Debugger::$showBar = true; // show Tracy Bar
+
+// Editor path mapping (e.g., for Docker/remote servers)
+Debugger::$editorMapping = [
+ '/var/www/html' => '/local/project/path',
+ '/home/web' => '/Users/dev/projects',
+];
+```
+
+### Nette Framework Configuration
+
+In `config/common.neon`:
+```neon
+tracy:
+ # Logging
+ email: dev@example.com
+ fromEmail: robot@example.com
+ emailSnooze: 2 days
+ logSeverity: [E_WARNING, E_NOTICE]
+
+ # Dumper
+ maxLength: 150
+ maxDepth: 10
+ keysToHide: [password, pass, secret]
+ dumpTheme: dark
+ showLocation: true
+
+ # Other
+ strictMode: true
+ scream: false
+ editor: 'editor://open/?file=%file&line=%line'
+ showBar: true
+
+ # Custom panels
+ bar:
+ - MyPanel(@MyService)
+ - Nette\Bridges\DITracy\ContainerPanel
+
+ # BlueScreen extensions
+ blueScreen:
+ - DoctrinePanel::renderException
+
+ editorMapping:
+ /var/www/html: /local/path
+```
+
+**DI Services available:**
+- `tracy.logger` (Tracy\ILogger)
+- `tracy.blueScreen` (Tracy\BlueScreen)
+- `tracy.bar` (Tracy\Bar)
+
+## Extension Points
+
+### Custom Tracy Bar Panels
+
+Implement `Tracy\IBarPanel` interface:
+```php
+class MyPanel implements Tracy\IBarPanel
+{
+ public function getTab(): string
+ {
+ // Tab HTML (small label on Bar)
+ return <<
+
+ My Panel
+
+ HTML;
+ }
+
+ public function getPanel(): string
+ {
+ // Panel HTML (popup content)
+ return <<My Panel Title
+
+
+
+
Info
Value
+
+
+
+ HTML;
+ }
+}
+
+// Register
+Tracy\Debugger::getBar()->addPanel(new MyPanel);
+```
+
+**Panel styling:**
+- Use classes, not IDs: `tracy-addons-[-optional]`
+- Prefix selectors: `#tracy-debug .your-class`
+- Elements ``, `
`, `
`, `` have predefined styles
+- Toggle elements: use `tracy-toggle` class with matching `href` and `id`
+
+### BlueScreen Extensions
+
+Add custom sections to error pages:
+```php
+Tracy\Debugger::getBlueScreen()->addPanel(function (?Throwable $e) {
+ // Called twice: first with exception, then with null
+ // First call renders at top, second call below call stack
+ return [
+ 'tab' => 'Database Queries',
+ 'panel' => '
Queries
' . implode("\n", $queries) . '
',
+ 'bottom' => true, // render at very bottom
+ ];
+});
+```
+
+### Custom Loggers
+
+Implement `Tracy\ILogger` interface:
+```php
+class SlackLogger implements Tracy\ILogger
+{
+ public function log($value, $priority = self::INFO)
+ {
+ // Send to Slack, Sentry, etc.
+ }
+}
+
+Tracy\Debugger::setLogger(new SlackLogger);
+```
+
+**Monolog integration:**
+```php
+$monolog = new Monolog\Logger('main-channel');
+$monolog->pushHandler(new Monolog\Handler\StreamHandler($logFilePath));
+
+$tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($monolog);
+Debugger::setLogger($tracyLogger);
+```
+
+### Custom Scrubber (Hide Sensitive Data)
+
+```php
+// Prevent dumping password values
+$scrubber = function(string $key, $value, ?string $class): bool {
+ return preg_match('#password|secret|token#i', $key) && $value !== null;
+};
+
+Tracy\Debugger::getBlueScreen()->scrubber = $scrubber;
+```
+
+### Custom Dump Formatting
+
+Add object exporters via `Dumper::addExporter()`
+
+## Integration Patterns
+
+**Basic usage:**
+```php
+Tracy\Debugger::enable(); // Auto-detect mode
+Tracy\Debugger::enable(Tracy\Debugger::Development); // Force mode
+Tracy\Debugger::enable('secret@123.45.67.89'); // IP + cookie
+```
+
+**Nette Framework:**
+- `TracyExtension` provides DI integration
+- Automatic configuration via NEON
+
+**PSR-3 Logging:**
+- `PsrToTracyLoggerAdapter` - Use PSR-3 logger with Tracy
+- `TracyToPsrLoggerAdapter` - Use Tracy as PSR-3 logger
+
+## Asset Management
+
+JavaScript and templates are embedded in PHP source files:
+- Bar assets: `src/Tracy/Bar/assets/`
+- BlueScreen assets: `src/Tracy/BlueScreen/assets/`
+- Dumper assets: `src/Tracy/Dumper/assets/`
+- Shared utilities: `src/Tracy/assets/`
+
+## Important Notes
+
+**Current version:** 2.11.0 (branch: v2.11)
+
+**Development directories:**
+- `exam/` - Experimental/development files
+- `x/` - Scratch work (git-ignored)
+
+**Mode detection:**
+- Development: localhost (127.0.0.1 or ::1) without proxy
+- Production: all other environments
+- Override with `Debugger::enable($mode)` or IP addresses
+
+**Error handling:**
+- Tracy changes error reporting to E_ALL on enable
+- Use `Debugger::$strictMode` to display notices as errors
+- Production mode logs errors instead of displaying them
+
+## Practical Recipes
+
+### AJAX Request Debugging
+
+Tracy automatically captures AJAX requests made with jQuery or native `fetch` API. They appear as additional rows in Tracy Bar.
+
+**Disable automatic capture:**
+```js
+window.TracyAutoRefresh = false;
+```
+
+**Manual AJAX monitoring:**
+```js
+fetch(url, {
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-Tracy-Ajax': Tracy.getAjaxHeader(),
+ }
+})
+```
+
+### Content Security Policy (CSP)
+
+Tracy requires CSP adjustments to work properly:
+
+**Nette Framework:**
+```neon
+http:
+ csp:
+ script-src: [nonce, strict-dynamic]
+```
+
+**Pure PHP:**
+```php
+$nonce = base64_encode(random_bytes(20));
+header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic';");
+```
+
+**Note:** `style-src` doesn't support nonce; use `'unsafe-inline'` (avoid in production)
+
+### Performance Optimization
+
+If slow scripts delay Tracy loading, render the loader early:
+
+```html
+
+
+
+ Page Title
+
+
+
+
+```
+
+### Session Storage
+
+**Use native PHP session:**
+```php
+session_start();
+Debugger::setSessionStorage(new Tracy\NativeSession);
+Debugger::enable();
+```
+
+**Complex session initialization:**
+```php
+Debugger::setSessionStorage(new Tracy\NativeSession);
+Debugger::enable();
+
+// Custom session initialization
+session_start();
+
+Debugger::dispatch(); // Inform Tracy session is ready
+```
+
+### nginx Configuration
+
+If Tracy doesn't work on nginx, fix the `try_files` directive:
+
+```nginx
+# Wrong
+try_files $uri $uri/ /index.php;
+
+# Correct
+try_files $uri $uri/ /index.php$is_args$args;
+```
+
+### IDE Integration
+
+Tracy can open files directly in your editor when clicking file names in error pages.
+
+**Editor integration scripts:**
+- Windows: `tools/open-in-editor/windows/`
+- Linux: `tools/open-in-editor/linux/`
+- macOS: Use built-in URL schemes
+
+**Built-in editor URLs (macOS):**
+```php
+// PhpStorm
+Tracy\Debugger::$editor = 'phpstorm://open?file=%file&line=%line';
+
+// VS Code
+Tracy\Debugger::$editor = 'vscode://file/%file:%line';
+
+// TextMate
+Tracy\Debugger::$editor = 'txmt://open/?url=file://%file&line=%line';
+```
+
+**Editor path mapping for remote/Docker:**
+```php
+Debugger::$editorMapping = [
+ '/var/www/html' => 'W:\\Projects\\myapp', // Docker to Windows
+ '/app' => '/Users/dev/projects/myapp', // Container to macOS
+];
+```
diff --git a/composer.json b/composer.json
index 00df1da0a..9e04b3a90 100644
--- a/composer.json
+++ b/composer.json
@@ -46,7 +46,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "2.11-dev"
+ "dev-master": "3.0-dev"
}
}
}
diff --git a/examples/dump.php b/examples/dump.php
index fc007ea74..0037d1365 100644
--- a/examples/dump.php
+++ b/examples/dump.php
@@ -35,6 +35,14 @@
echo "
Objects
\n";
echo "
Hover over the name \$baz to see property declaring class and over the hash #5 to see same objects.
\n";
+enum Suit
+{
+ case Clubs;
+ case Diamonds;
+ case Hearts;
+ case Spades;
+}
+
class ParentClass
{
public $foo = [10, 20];
@@ -42,6 +50,8 @@ class ParentClass
protected $bar = 30;
private $baz = 'parent';
+
+ public Suit $enum = Suit::Clubs;
}
#[\AllowDynamicProperties]
@@ -87,7 +97,8 @@ class ChildClass extends ParentClass
$arr = [
fopen(__FILE__, 'r'),
- new class {},
+ new class {
+},
function ($x, $y) use (&$arr, $obj) {},
];
diff --git a/src/Bridges/Nette/Bridge.php b/src/Bridges/Nette/Bridge.php
index 1f095716c..daeed1000 100644
--- a/src/Bridges/Nette/Bridge.php
+++ b/src/Bridges/Nette/Bridge.php
@@ -24,11 +24,12 @@ class Bridge
public static function initialize(): void
{
$blueScreen = Tracy\Debugger::getBlueScreen();
- $blueScreen->addAction([self::class, 'renderMemberAccessException']);
- $blueScreen->addPanel([self::class, 'renderNeonError']);
+ $blueScreen->addAction(self::renderMemberAccessException(...));
+ $blueScreen->addPanel(self::renderNeonError(...));
}
+ /** @return array{link: string, label: string}|null */
public static function renderMemberAccessException(?\Throwable $e): ?array
{
if (!$e instanceof Nette\MemberAccessException && !$e instanceof \LogicException) {
@@ -61,6 +62,7 @@ public static function renderMemberAccessException(?\Throwable $e): ?array
}
+ /** @return array{tab: string, panel: string}|null */
public static function renderNeonError(?\Throwable $e): ?array
{
if (!$e instanceof Nette\Neon\Exception || !preg_match('#line (\d+)#', $e->getMessage(), $m)) {
diff --git a/src/Bridges/Nette/MailSender.php b/src/Bridges/Nette/MailSender.php
index fdb8bc081..23f212ab3 100644
--- a/src/Bridges/Nette/MailSender.php
+++ b/src/Bridges/Nette/MailSender.php
@@ -18,20 +18,13 @@
*/
class MailSender
{
- private Nette\Mail\Mailer $mailer;
-
- /** sender of email notifications */
- private ?string $fromEmail = null;
-
- /** actual host on which notification occurred */
- private ?string $host = null;
-
-
- public function __construct(Nette\Mail\Mailer $mailer, ?string $fromEmail = null, ?string $host = null)
- {
- $this->mailer = $mailer;
- $this->fromEmail = $fromEmail;
- $this->host = $host;
+ public function __construct(
+ private readonly Nette\Mail\Mailer $mailer,
+ /** sender of email notifications */
+ private readonly ?string $fromEmail = null,
+ /** actual host on which notification occurred */
+ private readonly ?string $host = null,
+ ) {
}
@@ -42,7 +35,7 @@ public function send(mixed $message, string $email): void
$mail = new Nette\Mail\Message;
$mail->setHeader('X-Mailer', 'Tracy');
if ($this->fromEmail || Nette\Utils\Validators::isEmail("noreply@$host")) {
- $mail->setFrom($this->fromEmail ?: "noreply@$host");
+ $mail->setFrom($this->fromEmail ?? "noreply@$host");
}
foreach (explode(',', $email) as $item) {
diff --git a/src/Bridges/Nette/TracyExtension.php b/src/Bridges/Nette/TracyExtension.php
index d4244cc16..30185bc6e 100644
--- a/src/Bridges/Nette/TracyExtension.php
+++ b/src/Bridges/Nette/TracyExtension.php
@@ -25,8 +25,8 @@ class TracyExtension extends Nette\DI\CompilerExtension
public function __construct(
- private bool $debugMode = false,
- private bool $cliMode = false,
+ private readonly bool $debugMode = false,
+ private readonly bool $cliMode = false,
) {
}
@@ -130,8 +130,8 @@ 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)]);
+ if (is_string($item) && str_starts_with($item, '@')) {
+ $item = new Statement(['@' . $builder::ThisContainer, 'getService'], [substr($item, 1)]);
} elseif (is_string($item)) {
$item = new Statement($item);
}
diff --git a/src/Bridges/Psr/PsrToTracyLoggerAdapter.php b/src/Bridges/Psr/PsrToTracyLoggerAdapter.php
index 8bf66781a..33a843a32 100644
--- a/src/Bridges/Psr/PsrToTracyLoggerAdapter.php
+++ b/src/Bridges/Psr/PsrToTracyLoggerAdapter.php
@@ -31,12 +31,12 @@ class PsrToTracyLoggerAdapter implements Tracy\ILogger
public function __construct(
- private Psr\Log\LoggerInterface $psrLogger,
+ private readonly Psr\Log\LoggerInterface $psrLogger,
) {
}
- public function log(mixed $value, string $level = self::INFO)
+ public function log(mixed $value, string $level = self::INFO): void
{
if ($value instanceof \Throwable) {
$message = get_debug_type($value) . ': ' . $value->getMessage() . ($value->getCode() ? ' #' . $value->getCode() : '') . ' in ' . $value->getFile() . ':' . $value->getLine();
diff --git a/src/Bridges/Psr/TracyToPsrLoggerAdapter.php b/src/Bridges/Psr/TracyToPsrLoggerAdapter.php
index 7b1f578e9..a52fe283a 100644
--- a/src/Bridges/Psr/TracyToPsrLoggerAdapter.php
+++ b/src/Bridges/Psr/TracyToPsrLoggerAdapter.php
@@ -32,7 +32,7 @@ class TracyToPsrLoggerAdapter extends Psr\Log\AbstractLogger
public function __construct(
- private Tracy\ILogger $tracyLogger,
+ private readonly Tracy\ILogger $tracyLogger,
) {
}
diff --git a/src/Tracy/Bar/Bar.php b/src/Tracy/Bar/Bar.php
index ddc3a47a8..994715422 100644
--- a/src/Tracy/Bar/Bar.php
+++ b/src/Tracy/Bar/Bar.php
@@ -105,13 +105,14 @@ public function render(DeferredContent $defer): void
} else {
$nonceAttr = Helpers::getNonceAttr();
$async = false;
- Debugger::removeOutputBuffers(false);
+ Debugger::removeOutputBuffers(errorOccurred: false);
require __DIR__ . '/assets/loader.phtml';
}
}
}
+ /** @return array{bar: string, panels: string} */
private function renderPartial(string $type, string $suffix = ''): array
{
$panels = $this->renderPanels($suffix);
@@ -127,6 +128,7 @@ private function renderPartial(string $type, string $suffix = ''): array
}
+ /** @return \stdClass[] */
private function renderPanels(string $suffix = ''): array
{
set_error_handler(function (int $severity, string $message, string $file, int $line) {
diff --git a/src/Tracy/Bar/DefaultBarPanel.php b/src/Tracy/Bar/DefaultBarPanel.php
index 262bc4c13..d968b8ee2 100644
--- a/src/Tracy/Bar/DefaultBarPanel.php
+++ b/src/Tracy/Bar/DefaultBarPanel.php
@@ -19,12 +19,10 @@ class DefaultBarPanel implements IBarPanel
{
public $data;
- private $id;
-
- public function __construct(string $id)
- {
- $this->id = $id;
+ public function __construct(
+ private readonly string $id,
+ ) {
}
diff --git a/src/Tracy/BlueScreen/BlueScreen.php b/src/Tracy/BlueScreen/BlueScreen.php
index 4596b7bb7..5753a2751 100644
--- a/src/Tracy/BlueScreen/BlueScreen.php
+++ b/src/Tracy/BlueScreen/BlueScreen.php
@@ -30,7 +30,7 @@ class BlueScreen
public int $maxLength = 150;
public int $maxItems = 100;
- /** @var callable|null a callable returning true for sensitive data; fn(string $key, mixed $val): bool */
+ /** @var (callable(string $key, mixed $value, ?string $class): bool)|null */
public $scrubber;
/** @var string[] */
@@ -41,12 +41,16 @@ class BlueScreen
public bool $showEnvironment = true;
- /** @var callable[] */
+ /** @var array<\Closure(?\Throwable): ?array{tab: string, panel: string}> */
private array $panels = [];
- /** @var callable[] functions that returns action for exceptions */
+ /** @var array<\Closure(\Throwable): ?array{link: string, label: string}> */
private array $actions = [];
+
+ /** @var array<\Closure(string, ?string): ?string> */
private array $fileGenerators = [];
+
+ /** @var Dumper\Value[]|null */
private ?array $snapshot = null;
/** @var \WeakMap<\Fiber|\Generator> */
@@ -58,18 +62,20 @@ public function __construct()
$this->collapsePaths = preg_match('#(.+/vendor)/tracy/tracy/src/Tracy/BlueScreen$#', strtr(__DIR__, '\\', '/'), $m)
? [$m[1] . '/tracy', $m[1] . '/nette', $m[1] . '/latte']
: [dirname(__DIR__)];
- $this->fileGenerators[] = [self::class, 'generateNewPhpFileContents'];
+ $this->fileGenerators[] = self::generateNewPhpFileContents(...);
$this->fibers = new \WeakMap;
}
/**
- * Add custom panel as function (?\Throwable $e): ?array
+ * Add custom panel.
+ * @param callable(?\Throwable): ?array{tab: string, panel: string} $panel
* @return static
*/
public function addPanel(callable $panel): self
{
- if (!in_array($panel, $this->panels, true)) {
+ $panel = $panel(...);
+ if (!in_array($panel, $this->panels, strict: true)) {
$this->panels[] = $panel;
}
@@ -79,23 +85,24 @@ public function addPanel(callable $panel): self
/**
* Add action.
+ * @param callable(\Throwable): ?array{link: string, label: string} $action
* @return static
*/
public function addAction(callable $action): self
{
- $this->actions[] = $action;
+ $this->actions[] = $action(...);
return $this;
}
/**
* Add new file generator.
- * @param callable(string): ?string $generator
+ * @param callable(string, ?string): ?string $generator
* @return static
*/
public function addFileGenerator(callable $generator): self
{
- $this->fileGenerators[] = $generator;
+ $this->fileGenerators[] = $generator(...);
return $this;
}
@@ -167,7 +174,7 @@ private function renderTemplate(\Throwable $exception, string $template, bool $t
if (function_exists('apache_request_headers')) {
$httpHeaders = apache_request_headers();
} else {
- $httpHeaders = array_filter($_SERVER, fn($k) => strncmp($k, 'HTTP_', 5) === 0, ARRAY_FILTER_USE_KEY);
+ $httpHeaders = array_filter($_SERVER, fn($k) => str_starts_with($k, 'HTTP_'), ARRAY_FILTER_USE_KEY);
$httpHeaders = array_combine(array_map(fn($k) => strtolower(strtr(substr($k, 5), '_', '-')), array_keys($httpHeaders)), $httpHeaders);
}
@@ -175,7 +182,7 @@ private function renderTemplate(\Throwable $exception, string $template, bool $t
$snapshot = [];
$dump = $this->getDumper();
- $css = array_map('file_get_contents', array_merge([
+ $css = array_map(file_get_contents(...), array_merge([
__DIR__ . '/../assets/reset.css',
__DIR__ . '/assets/bluescreen.css',
__DIR__ . '/../assets/toggle.css',
@@ -250,7 +257,7 @@ private function renderActions(\Throwable $ex): array
if (preg_match('# ([\'"])(\w{3,}(?:\\\\\w{2,})+)\1#i', $ex->getMessage(), $m)) {
$class = $m[2];
if (
- !class_exists($class, false) && !interface_exists($class, false) && !trait_exists($class, false)
+ !class_exists($class, autoload: false) && !interface_exists($class, autoload: false) && !trait_exists($class, autoload: false)
&& ($file = Helpers::guessClassFile($class)) && !@is_file($file) // @ - may trigger error
) {
[$content, $line] = $this->generateNewFileContents($file, $class);
@@ -321,7 +328,7 @@ public static function highlightFile(
? CodeHighlighter::highlightPhp($source, $line, $column)
: '