diff --git a/README.md b/README.md index bc7e41d..0281e4d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [b]: https://github.com/Seldaek/monolog [c]: https://github.com/symfony/yaml [d]: https://www.php.net/manual/en/function.pcntl-fork.php +[e]: https://www.php.net/manual/en/function.print-r.php +[f]: https://github.com/symfony/var-exporter # Catcher @@ -195,6 +197,7 @@ abstract class Handler { protected bool $_outputToStderr = true; protected bool $_silent = false; protected string $_timeFormat = 'Y-m-d\TH:i:s.vO'; + protected ?\Closure $varExporter = null; public function __construct(array $options = []); @@ -244,7 +247,9 @@ _outputPrevious_: When true will output previous throwables. Defaults to _true_. _outputTime_: When true will output times to the output. Defaults to _true_. _outputToStderr_: When the SAPI is cli output errors to stderr. Defaults to _true_. _silent_: When true the handler won't output anything. Defaults to _false_. -_timeFormat_: The PHP-standard date format which to use for times in output. Defaults to _"Y-m-d\\TH:i:s.vO"_. +_timeFormat_: The PHP-standard date format which to use for times in output. Defaults to _"Y-m-d\\TH:i:s.vO"_. +_varExporter_: A user-defined closure to use when printing arguments in backtraces. Defaults to _null_. + #### MensBeam\Foundation\Catcher\Handler::dispatch @@ -414,12 +419,48 @@ class YamlHandler extends Handler { This theoretical class uses the [`symfony/yaml`][c] package in Composer and exposes a few of its options as options for the Handler. Using this class would be very similar to the examples provided at the beginning of this document: ```php +#!/usr/bin/env php + true ]); ``` -More complex handlers are possible by extending the various methods documented above. Examples can be seen by looking at the code for both `HTMLHandler` and `PlainTextHandler`. \ No newline at end of file +More complex handlers are possible by extending the various methods documented above. Examples can be seen by looking at the code for both `HTMLHandler` and `PlainTextHandler`. + +## Setting a Custom Variable Exporter + +By default internally [`print_r`][e] is used. This is due to tests made internally where it performed the best out of built-in options, including other functions which might otherwise be preferred. Starting in v1.0.2 `Handler`'s `varExporter` option allows for defining how arguments are printed in backtraces in Catcher. Here is an example: + +```php +#!/usr/bin/env php + true, + 'varExporter' => fn(mixed $value): string|bool => VarExporter::export($value) +])); + +throw new \Exception('Ook!'); +``` + +Output: +``` +[04:48:23] Exception: Ook! in file /home/mensbeam/super-awesome-project/ook.php on line 13 + + Stack trace: + 1. Exception /home/mensbeam/super-awesome-project/ook.php:13 + | [ + | 'Ook!', + | ] +``` + +This example above uses the symfony/var-exporter package for a more modern human-readable variable export. However, using any variable printer is possible. \ No newline at end of file diff --git a/composer.json b/composer.json index 0e26cf6..aa52bb6 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,6 @@ "mensbeam/html-dom": "^1.0", "phpunit/phpunit": "^9.5", "nikic/php-parser": "^4.15", - "eloquent/phony-phpunit": "^7.1", - "symfony/yaml": "^6.2" + "eloquent/phony-phpunit": "^7.1" } } diff --git a/composer.lock b/composer.lock index c4ae596..0251e6d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dd7f0dc703d14f61f5c0d12d9106338f", + "content-hash": "73069560c4ea9f687930120ab8363d8f", "packages": [ { "name": "psr/log", @@ -2285,88 +2285,6 @@ ], "time": "2022-12-23T11:40:44+00:00" }, - { - "name": "symfony/polyfill-ctype", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, { "name": "symfony/polyfill-php80", "version": "v1.27.0", @@ -2450,80 +2368,6 @@ ], "time": "2022-11-03T14:55:06+00:00" }, - { - "name": "symfony/yaml", - "version": "v6.2.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "6ed8243aa5f2cb5a57009f826b5e7fb3c4200cf3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6ed8243aa5f2cb5a57009f826b5e7fb3c4200cf3", - "reference": "6ed8243aa5f2cb5a57009f826b5e7fb3c4200cf3", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<5.4" - }, - "require-dev": { - "symfony/console": "^5.4|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v6.2.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-12-14T16:11:27+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.1", diff --git a/lib/Catcher/HTMLHandler.php b/lib/Catcher/HTMLHandler.php index aac314d..cf63ba7 100644 --- a/lib/Catcher/HTMLHandler.php +++ b/lib/Catcher/HTMLHandler.php @@ -146,8 +146,9 @@ class HTMLHandler extends Handler { $t->appendChild($this->_document->createTextNode(":{$frame['line']}")); if (isset($frame['args'])) { + $varExporter = $this->_varExporter; $pre = $this->_document->createElement('pre'); - $pre->appendChild($this->_document->createTextNode(trim(print_r($frame['args'], true)))); + $pre->appendChild($this->_document->createTextNode(trim($varExporter($frame['args'])))); $li->appendChild($pre); } } diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php index 7e4e5f3..eb46b6c 100644 --- a/lib/Catcher/Handler.php +++ b/lib/Catcher/Handler.php @@ -60,6 +60,12 @@ abstract class Handler { protected bool $_silent = false; /** The PHP-standard date format which to use for timestamps in output */ protected string $_timeFormat = 'Y-m-d\TH:i:s.vO'; + /** + * A user-defined closure to use when printing arguments in backtraces + * + * @var ?(mixed): string|bool + */ + protected ?\Closure $_varExporter = null; @@ -73,6 +79,10 @@ abstract class Handler { $this->$key = $value; } + + if ($this->_varExporter === null) { + $this->_varExporter = fn(mixed $value): string|bool => print_r($value, true); + } } diff --git a/lib/Catcher/PlainTextHandler.php b/lib/Catcher/PlainTextHandler.php index b727490..a08dffb 100644 --- a/lib/Catcher/PlainTextHandler.php +++ b/lib/Catcher/PlainTextHandler.php @@ -127,7 +127,8 @@ class PlainTextHandler extends Handler { ); if (!empty($frame['args']) && $this->_backtraceArgFrameLimit > $key) { - $output .= preg_replace('/^/m', "$indent| ", print_r($frame['args'], true)) . \PHP_EOL; + $varExporter = $this->_varExporter; + $output .= preg_replace('/^/m', "$indent| ", $varExporter($frame['args'])) . \PHP_EOL; } }