From 3c2461272f565d495105acd87e14242fd7d662f7 Mon Sep 17 00:00:00 2001 From: Dustin Wilson Date: Fri, 21 Apr 2023 23:17:24 -0500 Subject: [PATCH] Should have committed something long ago... :) --- .gitignore | 2 +- README.md | 17 +- composer.json | 16 +- composer.lock | 1183 +++++++---------------- lib/Catcher.php | 109 +-- lib/Catcher/ArgumentCountError.php | 11 + lib/Catcher/HTMLHandler.php | 196 ---- lib/Catcher/Handler.php | 90 +- lib/Catcher/JSONHandler.php | 32 - lib/Catcher/PlainTextHandler.php | 68 +- lib/Catcher/UnderflowException.php | 11 + run | 100 -- test | 37 + tests/bootstrap.php | 17 +- tests/cases/TestCatcher.php | 677 +++++-------- tests/cases/TestHTMLHandler.php | 89 -- tests/cases/TestHandler.php | 173 ---- tests/cases/TestJSONHandler.php | 48 - tests/cases/TestPlainTextHandler.php | 145 --- tests/cases/TestThrowableController.php | 189 ---- tests/lib/TestingHandler.php | 41 + tests/phpunit.dist.xml | 28 - tests/phpunit.xml | 21 + 23 files changed, 806 insertions(+), 2494 deletions(-) create mode 100644 lib/Catcher/ArgumentCountError.php delete mode 100644 lib/Catcher/HTMLHandler.php delete mode 100644 lib/Catcher/JSONHandler.php create mode 100644 lib/Catcher/UnderflowException.php delete mode 100755 run create mode 100755 test delete mode 100644 tests/cases/TestHTMLHandler.php delete mode 100644 tests/cases/TestHandler.php delete mode 100644 tests/cases/TestJSONHandler.php delete mode 100644 tests/cases/TestPlainTextHandler.php delete mode 100644 tests/cases/TestThrowableController.php create mode 100644 tests/lib/TestingHandler.php delete mode 100644 tests/phpunit.dist.xml create mode 100644 tests/phpunit.xml diff --git a/.gitignore b/.gitignore index 6335909..3366cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -72,6 +72,6 @@ $RECYCLE.BIN/ /vendor/ /vendor-bin/*/vendor /tests/html5lib-tests -/tests/.phpunit.result.cache +/tests/.phpunit.cache /tests/coverage cachegrind.out.* diff --git a/README.md b/README.md index 3673272..837ba81 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [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 +[g]: https://github.com/php-fig/log # Catcher # @@ -11,20 +12,20 @@ Catcher is a Throwable catcher and error handling library for PHP. Error handlin Catcher uses classes called _handlers_ to handle throwables sent its way. PHP is currently in a state of flux when it comes to errors. There are traditional PHP errors which are triggered in userland by using `trigger_error()` which can't be caught using `try`/`catch` and are generally a pain to work with. PHP has begun to remedy this problem by introducing the `\Error` class and its various child classes. However, a lot of functions and core aspects of the language itself continue to use legacy errors. This class does away with this pain point in PHP by turning all errors into throwables. When Catcher converts legacy errors into throwables it will only exit if PHP would, so warnings, notices, etc. won't cause the program to exit unless you configure it to do so. Non user-level fatal errors are picked up by Catcher using its shutdown handler. This means that simply by invoking Catcher one may now... catch (almost) any error PHP then handles. - + ## Requirements ## -* PHP 8.1 or newer with the following _optional_ extensions: - * [dom][a] extension (for HTMLHandler) +* PHP >= 8.1 +* [psr/log][g] ^3.0 + - ## Installation ## ```shell composer require mensbeam/catcher ``` - + ## Usage ## For most use cases this library requires no configuration and little effort to integrate into non-complex environments: @@ -88,7 +89,7 @@ This is accomplished internally because of [`pcntl_fork`][d]. The throw is done PHP by default won't allow fatal errors to be handled by error handlers. It will instead print the error and exit. However, before code execution halts any shutdown functions are run. Catcher will retrieve the last error and manually process it. This causes multiple instances of the same error to be output. Because of this Catcher alters the error reporting level by always removing `E_ERROR` from it when registering the handlers. `E_ERROR` is bitwise or'd back to the error reporting level when unregistering. If this behavior is undesirable then `E_ERROR` can be manually included back into error reporting at any time after Catcher instantiates. Keep in mind Catcher _will not_ include `E_ERROR` back into the error reporting level bitmask if the error reporting level was modified after Catcher was instantiated or if the error reporting level didn't have it when Catcher registered its handlers. ## Documentation ## - + ### MensBeam\Catcher ### This is the main class in the library. Unless you have a need to configure a handler or use multiple handlers there usually isn't a need to interact with the rest of the library at all. @@ -163,7 +164,7 @@ Unregisters the Catcher instance as an error, exception and shutdown handler. Unshifts the specified handler(s) onto the beginning of the stack - + ### MensBeam\Catcher\Handler ### All handlers inherit from this abstract class. Since it is an abstract class meant for constructing handlers protected methods and properties will be documented here as well. @@ -457,7 +458,7 @@ throw new \Exception('Ook!'); Output: ``` [21:12:00] 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 | [ diff --git a/composer.json b/composer.json index 14c26a8..c865354 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,11 @@ "MensBeam\\": "lib/" } }, + "autoload-dev": { + "psr-4": { + "MensBeam\\Catcher\\Test\\": "tests/lib/" + } + }, "authors": [ { "name": "Dustin Wilson", @@ -18,15 +23,8 @@ "php": ">=8.1", "psr/log": "^3.0" }, - "suggest": { - "ext-dom": "For HTMLHandler", - "ext-pcntl": "For allowing catching of notices, warnings, etc." - }, "require-dev": { - "ext-dom": "*", - "mensbeam/html-dom": ">=1.0", - "phpunit/phpunit": ">=9.5", - "nikic/php-parser": ">=4.15", - "eloquent/phony-phpunit": ">=7.1" + "phpunit/phpunit": "^10", + "phake/phake": "^4.4" } } diff --git a/composer.lock b/composer.lock index 3bfa5e0..38f7c76 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": "60a855adaf02eabdaca59112cbc1586d", + "content-hash": "264b81873cadf8449e400b7902f0ef98", "packages": [ { "name": "psr/log", @@ -60,30 +60,30 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -110,7 +110,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -126,445 +126,20 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" - }, - { - "name": "eloquent/phony", - "version": "5.0.2", - "source": { - "type": "git", - "url": "https://github.com/eloquent/phony.git", - "reference": "f34d67d6db6b6f351ea7e8aa8066107e756ec26b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/eloquent/phony/zipball/f34d67d6db6b6f351ea7e8aa8066107e756ec26b", - "reference": "f34d67d6db6b6f351ea7e8aa8066107e756ec26b", - "shasum": "" - }, - "require": { - "php": "^7.3 || ^8" - }, - "require-dev": { - "eloquent/code-style": "^1.0", - "eloquent/phpstan-phony": "^0.7", - "errors/exceptions": "^0.2", - "ext-pdo": "*", - "friendsofphp/php-cs-fixer": "^2", - "hamcrest/hamcrest-php": "^2", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1.x-dev" - } - }, - "autoload": { - "files": [ - "src/initialize.php", - "src/functions.php" - ], - "psr-4": { - "Eloquent\\Phony\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Erin Millard", - "email": "ezzatron@gmail.com", - "homepage": "http://ezzatron.com/" - } - ], - "description": "Mocks, stubs, and spies for PHP.", - "homepage": "http://eloquent-software.com/phony/", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "mocking", - "spy", - "stub", - "stubbing", - "test" - ], - "support": { - "issues": "https://github.com/eloquent/phony/issues", - "source": "https://github.com/eloquent/phony/tree/5.0.2" - }, - "time": "2021-02-17T01:45:10+00:00" - }, - { - "name": "eloquent/phony-phpunit", - "version": "7.1.0", - "source": { - "type": "git", - "url": "https://github.com/eloquent/phony-phpunit.git", - "reference": "e77ff95ea6235211d4aae7e5f53488a5faebc2e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/eloquent/phony-phpunit/zipball/e77ff95ea6235211d4aae7e5f53488a5faebc2e0", - "reference": "e77ff95ea6235211d4aae7e5f53488a5faebc2e0", - "shasum": "" - }, - "require": { - "eloquent/phony": "^5", - "php": "^7.3 || ^8", - "phpunit/phpunit": "^9" - }, - "require-dev": { - "eloquent/code-style": "^1", - "eloquent/phpstan-phony": "^0.7", - "errors/exceptions": "^0.2", - "friendsofphp/php-cs-fixer": "^2", - "phpstan/extension-installer": "^1", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.2.x-dev" - } - }, - "autoload": { - "files": [ - "src/initialize.php", - "src/functions.php" - ], - "psr-4": { - "Eloquent\\Phony\\Phpunit\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Erin Millard", - "email": "ezzatron@gmail.com", - "homepage": "http://ezzatron.com/" - } - ], - "description": "Phony for PHPUnit.", - "homepage": "http://eloquent-software.com/phony/", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "mocking", - "spy", - "stub", - "stubbing", - "test" - ], - "support": { - "issues": "https://github.com/eloquent/phony-phpunit/issues", - "source": "https://github.com/eloquent/phony-phpunit/tree/7.1.0" - }, - "time": "2020-12-21T09:36:47+00:00" - }, - { - "name": "mensbeam/getters-and-setters", - "version": "v1.1.0", - "source": { - "type": "git", - "url": "https://github.com/mensbeam/Getters-and-Setters.git", - "reference": "141a946eb476b1f897315aa5faf5ade64a306e88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mensbeam/Getters-and-Setters/zipball/141a946eb476b1f897315aa5faf5ade64a306e88", - "reference": "141a946eb476b1f897315aa5faf5ade64a306e88", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "require-dev": { - "nikic/php-parser": "^4.15", - "phpunit/phpunit": "^9.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "MensBeam\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dustin Wilson", - "email": "dustin@dustinwilson.com" - } - ], - "description": "Getter and setter method trait for PHP", - "support": { - "source": "https://github.com/mensbeam/Getters-and-Setters/tree/v1.1.0" - }, - "time": "2023-02-19T19:17:19+00:00" - }, - { - "name": "mensbeam/html-dom", - "version": "v1.0.11", - "source": { - "type": "git", - "url": "https://github.com/mensbeam/HTML-DOM.git", - "reference": "cbc152e6d02cd41d8777074139d4cd727d067302" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mensbeam/HTML-DOM/zipball/cbc152e6d02cd41d8777074139d4cd727d067302", - "reference": "cbc152e6d02cd41d8777074139d4cd727d067302", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "mensbeam/getters-and-setters": ">=1.1", - "mensbeam/html-parser": ">=1.2.1", - "php": ">=8.0.2", - "symfony/css-selector": ">=5.3" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.3", - "mikey179/vfsstream": "^1.6", - "nikic/php-parser": "^4.13" - }, - "type": "library", - "autoload": { - "psr-4": { - "MensBeam\\HTML\\DOM\\": [ - "lib/", - "lib/HTMLElement" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dustin Wilson", - "email": "dustin@dustinwilson.com", - "homepage": "https://dustinwilson.com/" - }, - { - "name": "J. King", - "email": "jking@jkingweb.ca", - "homepage": "https://jkingweb.ca/" - } - ], - "description": "Modern DOM library written in PHP for HTML documents", - "support": { - "issues": "https://github.com/mensbeam/HTML-DOM/issues", - "source": "https://github.com/mensbeam/HTML-DOM/tree/v1.0.11" - }, - "time": "2023-02-19T19:28:08+00:00" - }, - { - "name": "mensbeam/html-parser", - "version": "1.2.6", - "source": { - "type": "git", - "url": "https://github.com/mensbeam/HTML-Parser.git", - "reference": "fa0591bd9ce241631894bda290cb99f30b7c288d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mensbeam/HTML-Parser/zipball/fa0591bd9ce241631894bda290cb99f30b7c288d", - "reference": "fa0591bd9ce241631894bda290cb99f30b7c288d", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "mensbeam/intl": ">=0.9.1", - "mensbeam/mimesniff": ">=0.2.0", - "php": ">=7.1" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.3" - }, - "suggest": { - "ext-ctype": "Improved performance" - }, - "type": "library", - "autoload": { - "files": [ - "lib/Parser/ctype.php" - ], - "psr-4": { - "MensBeam\\HTML\\": [ - "lib/" - ] - }, - "classmap": [ - "lib/Parser/Token.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dustin Wilson", - "email": "dustin@dustinwilson.com", - "homepage": "https://dustinwilson.com/" - }, - { - "name": "J. King", - "email": "jking@jkingweb.ca", - "homepage": "https://jkingweb.ca/" - } - ], - "description": "Parser and serializer for modern HTML documents", - "keywords": [ - "HTML5", - "WHATWG", - "dom", - "html", - "parser", - "parsing" - ], - "support": { - "issues": "https://github.com/mensbeam/HTML-Parser/issues", - "source": "https://github.com/mensbeam/HTML-Parser/tree/1.2.6" - }, - "time": "2023-01-25T22:49:58+00:00" - }, - { - "name": "mensbeam/intl", - "version": "0.9.2", - "source": { - "type": "git", - "url": "https://github.com/mensbeam/intl.git", - "reference": "88dbf8398ab69e71164ac073f9ec011be2baa4ae" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mensbeam/intl/zipball/88dbf8398ab69e71164ac073f9ec011be2baa4ae", - "reference": "88dbf8398ab69e71164ac073f9ec011be2baa4ae", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "*", - "ext-intl": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "MensBeam\\Intl\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "J. King", - "email": "jking@jkingweb.ca", - "homepage": "https://jkingweb.ca/" - } - ], - "description": "A set of dependency-free basic internationalization tools", - "keywords": [ - "WHATWG", - "charset", - "encoding", - "internationalization", - "intl", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "issues": "https://github.com/mensbeam/intl/issues", - "source": "https://github.com/mensbeam/intl/tree/0.9.2" - }, - "time": "2023-01-25T22:12:58+00:00" - }, - { - "name": "mensbeam/mimesniff", - "version": "0.2.1", - "source": { - "type": "git", - "url": "https://github.com/mensbeam/mime.git", - "reference": "c19be2496ab1e27fbf9c3483c2a9faa2781796cd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mensbeam/mime/zipball/c19be2496ab1e27fbf9c3483c2a9faa2781796cd", - "reference": "c19be2496ab1e27fbf9c3483c2a9faa2781796cd", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "psr/http-message": "^1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.3", - "ext-intl": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "MensBeam\\Mime\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "J. King", - "email": "jking@jkingweb.ca", - "homepage": "https://jkingweb.ca/" - } - ], - "description": "An implementation of the WHATWG MIME Sniffing specification", - "keywords": [ - "WHATWG", - "mime", - "mimesniff" - ], - "support": { - "issues": "https://github.com/mensbeam/mime/issues", - "source": "https://github.com/mensbeam/mime/tree/0.2.1" - }, - "time": "2021-03-07T03:58:00+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -602,7 +177,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -610,20 +185,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.3", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { @@ -664,9 +239,79 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + }, + "time": "2023-03-05T19:49:14+00:00" + }, + { + "name": "phake/phake", + "version": "v4.4.0", + "source": { + "type": "git", + "url": "https://github.com/phake/phake.git", + "reference": "5c8954791645d9b7fc027bf76822a221a5a4de8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phake/phake/zipball/5c8954791645d9b7fc027bf76822a221a5a4de8a", + "reference": "5c8954791645d9b7fc027bf76822a221a5a4de8a", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.4", + "php": "^7.1|^8.0", + "sebastian/comparator": "^1.1|^2.0|^3.0|^4.0|^5.0" + }, + "require-dev": { + "doctrine/annotations": "^1.13", + "hamcrest/hamcrest-php": "1.1.*", + "phpunit/phpunit": "^6.5|^7.0|^8.0|^9.0|^10.0", + "psalm/phar": "^4.18" + }, + "suggest": { + "doctrine/annotations": "Allows mock annotations to use import statements for classes.", + "hamcrest/hamcrest-php": "Use Hamcrest matchers." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "files": [ + "src/Phake.php" + ], + "psr-4": { + "Phake\\": "src/Phake" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Mike Lively", + "email": "m@digitalsandwich.com" + } + ], + "description": "The Phake mock testing library", + "homepage": "https://phake.github.io", + "keywords": [ + "mock", + "phake", + "spy", + "stub", + "test-doubles", + "testing" + ], + "support": { + "docs": "https://phake.github.io/doc/", + "issues": "https://github.com/phake/phake/issues", + "source": "https://github.com/phake/phake/tree/v4.4.0" }, - "time": "2023-01-16T22:05:37+00:00" + "time": "2023-02-10T20:32:41+00:00" }, { "name": "phar-io/manifest", @@ -781,44 +426,44 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.24", + "version": "10.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" + "reference": "20800e84296ea4732f9a125e08ce86b4004ae3e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/20800e84296ea4732f9a125e08ce86b4004ae3e4", + "reference": "20800e84296ea4732f9a125e08ce86b4004ae3e4", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", + "nikic/php-parser": "^4.15", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "10.0-dev" } }, "autoload": { @@ -846,7 +491,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.0.2" }, "funding": [ { @@ -854,32 +499,32 @@ "type": "github" } ], - "time": "2023-01-26T08:26:55+00:00" + "time": "2023-03-06T13:00:19+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "fd9329ab3368f59fe1fe808a189c51086bd4b6bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/fd9329ab3368f59fe1fe808a189c51086bd4b6bd", + "reference": "fd9329ab3368f59fe1fe808a189c51086bd4b6bd", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -906,7 +551,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.0.1" }, "funding": [ { @@ -914,28 +559,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2023-02-10T16:53:14+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-pcntl": "*" @@ -943,7 +588,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -969,7 +614,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, "funding": [ { @@ -977,32 +622,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/9f3d3709577a527025f55bcf0f7ab8052c8bb37d", + "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1028,7 +673,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.0" }, "funding": [ { @@ -1036,32 +681,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2023-02-03T06:56:46+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1087,7 +732,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, "funding": [ { @@ -1095,24 +740,23 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.3", + "version": "10.0.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" + "reference": "20c23e85c86e5c06d63538ba464e8054f4744e62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", - "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20c23e85c86e5c06d63538ba464e8054f4744e62", + "reference": "20c23e85c86e5c06d63538ba464e8054f4744e62", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1122,27 +766,26 @@ "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.0", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-invoker": "^4.0", + "phpunit/php-text-template": "^3.0", + "phpunit/php-timer": "^6.0", + "sebastian/cli-parser": "^2.0", + "sebastian/code-unit": "^2.0", + "sebastian/comparator": "^5.0", + "sebastian/diff": "^5.0", + "sebastian/environment": "^6.0", + "sebastian/exporter": "^5.0", + "sebastian/global-state": "^6.0", + "sebastian/object-enumerator": "^5.0", + "sebastian/recursion-context": "^5.0", + "sebastian/type": "^4.0", + "sebastian/version": "^4.0" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -1150,7 +793,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "10.0-dev" } }, "autoload": { @@ -1181,7 +824,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.19" }, "funding": [ { @@ -1197,85 +841,32 @@ "type": "tidelift" } ], - "time": "2023-02-04T13:37:15+00:00" - }, - { - "name": "psr/http-message", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/master" - }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-03-27T11:46:33+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae", + "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1298,7 +889,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0" }, "funding": [ { @@ -1306,32 +897,32 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2023-02-03T06:58:15+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1354,7 +945,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { @@ -1362,32 +953,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1409,7 +1000,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -1417,34 +1008,36 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/72f01e6586e0caf6af81297897bd112eb7e9627c", + "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1483,7 +1076,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.0" }, "funding": [ { @@ -1491,33 +1084,33 @@ "type": "github" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2023-02-03T07:07:16+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/e67d240970c9dc7ea7b2123a6d520e334dd61dc6", + "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^4.10", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1540,7 +1133,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/3.0.0" }, "funding": [ { @@ -1548,33 +1141,33 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-02-03T06:59:47+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/aae9a0a43bff37bd5d8d0311426c87bf36153f02", + "reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^10.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1606,7 +1199,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.0.1" }, "funding": [ { @@ -1614,27 +1208,27 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-03-23T05:12:41+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "b6f3694c6386c7959915a0037652e0c40f6f69cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/b6f3694c6386c7959915a0037652e0c40f6f69cc", + "reference": "b6f3694c6386c7959915a0037652e0c40f6f69cc", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-posix": "*" @@ -1642,7 +1236,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1661,7 +1255,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -1669,7 +1263,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "source": "https://github.com/sebastianbergmann/environment/tree/6.0.0" }, "funding": [ { @@ -1677,34 +1271,34 @@ "type": "github" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2023-02-03T07:03:04+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", + "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1746,7 +1340,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.0.0" }, "funding": [ { @@ -1754,38 +1348,35 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2023-02-03T07:06:49+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "aab257c712de87b90194febd52e4d184551c2d44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/aab257c712de87b90194febd52e4d184551c2d44", + "reference": "aab257c712de87b90194febd52e4d184551c2d44", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1810,7 +1401,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.0" }, "funding": [ { @@ -1818,33 +1409,33 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-02-03T07:07:38+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/17c4d940ecafb3d15d2cf916f4108f664e28b130", + "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^4.10", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1867,7 +1458,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.0" }, "funding": [ { @@ -1875,34 +1466,34 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-02-03T07:08:02+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1924,7 +1515,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { @@ -1932,32 +1523,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1979,7 +1570,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "funding": [ { @@ -1987,32 +1578,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2042,62 +1633,7 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" }, "funding": [ { @@ -2105,32 +1641,32 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2023-02-03T07:05:40+00:00" }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2153,7 +1689,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -2161,29 +1697,29 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2206,7 +1742,7 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -2214,72 +1750,7 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" - }, - { - "name": "symfony/css-selector", - "version": "v6.2.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1", - "reference": "bf1b9d4ad8b1cf0dbde8b08e0135a2f6259b9ba1", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Converts CSS selectors to XPath expressions", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.2.5" - }, - "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": "2023-01-01T08:38:09+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { "name": "theseer/tokenizer", @@ -2340,8 +1811,6 @@ "platform": { "php": ">=8.1" }, - "platform-dev": { - "ext-dom": "*" - }, + "platform-dev": [], "plugin-api-version": "2.3.0" } diff --git a/lib/Catcher.php b/lib/Catcher.php index 192b4f1..021d67a 100644 --- a/lib/Catcher.php +++ b/lib/Catcher.php @@ -8,26 +8,30 @@ declare(strict_types=1); namespace MensBeam; use MensBeam\Catcher\{ + ArgumentCountError, Error, Handler, PlainTextHandler, ThrowableController, + UnderflowException }; class Catcher { - /** Fork when throwing non-exiting errors, if available */ - public bool $forking = true; + public const THROW_NO_ERRORS = 0; + public const THROW_FATAL_ERRORS = 1; + public const THROW_ALL_ERRORS = 2; + /** When set to true Catcher won't exit when instructed */ public bool $preventExit = false; - /** When set to true Catcher will throw errors as throwables */ - public bool $throwErrors = true; + /** Determines how errors are handled; THROW_* constants exist to control */ + public int $errorHandlingMethod = self::THROW_FATAL_ERRORS; /** * Stores the error reporting level set by Catcher to compare against when * unregistering */ - protected ?int $errorReporting = null; + public ?int $errorReporting = null; /** * Array of handlers the exceptions are passed to * @@ -69,7 +73,7 @@ class Catcher { public function popHandler(): Handler { if (count($this->handlers) === 1) { - throw new \Exception("Popping the last handler will cause the Catcher to have zero handlers; there must be at least one\n"); + throw new UnderflowException('Popping the last handler will cause the Catcher to have zero handlers; there must be at least one'); } return array_pop($this->handlers); @@ -77,19 +81,10 @@ class Catcher { public function pushHandler(Handler ...$handlers): void { if (count($handlers) === 0) { - throw new \ArgumentCountError(__METHOD__ . "expects at least 1 argument, 0 given\n"); + throw new ArgumentCountError(__METHOD__ . 'expects at least 1 argument, 0 given'); } - $prev = []; - foreach ($handlers as $h) { - if (in_array($h, $this->handlers, true) || in_array($h, $prev, true)) { - trigger_error("Handlers must be unique; skipping\n", \E_USER_WARNING); - continue; - } - - $prev[] = $h; - $this->handlers[] = $h; - } + $this->handlers = [ ...$this->handlers, ...$handlers ]; } public function register(): bool { @@ -108,6 +103,7 @@ class Catcher { set_error_handler([ $this, 'handleError' ]); set_exception_handler([ $this, 'handleThrowable' ]); register_shutdown_function([ $this, 'handleShutdown' ]); + $this->registered = true; return true; } @@ -119,7 +115,7 @@ class Catcher { public function shiftHandler(): Handler { if (count($this->handlers) === 1) { - throw new \Exception("Shifting the last handler will cause the Catcher to have zero handlers; there must be at least one\n"); + throw new UnderflowException('Shifting the last handler will cause the Catcher to have zero handlers; there must be at least one'); } return array_shift($this->handlers); @@ -147,28 +143,10 @@ class Catcher { public function unshiftHandler(Handler ...$handlers): void { if (count($handlers) === 0) { - throw new \ArgumentCountError(__METHOD__ . "expects at least 1 argument, 0 given\n"); + throw new ArgumentCountError(__METHOD__ . 'expects at least 1 argument, 0 given'); } - $modified = false; - $prev = []; - foreach ($handlers as $v => $h) { - if (in_array($h, $this->handlers, true) || in_array($h, $prev, true)) { - trigger_error("Handlers must be unique; skipping\n", \E_USER_WARNING); - unset($handlers[$v]); - $modified = true; - continue; - } - - $prev[] = $h; - } - if ($modified) { - $handlers = array_values($handlers); - } - - if (count($handlers) > 0) { - $this->handlers = [ ...$handlers, ...$this->handlers ]; - } + $this->handlers = [ ...$handlers, ...$this->handlers ]; } @@ -181,32 +159,13 @@ class Catcher { public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool { if ($code && $code & error_reporting()) { $error = new Error($message, $code, $file, $line); - if ($this->throwErrors) { - // The point of this library is to allow treating of errors as if they were - // exceptions but instead have things like warnings, notices, etc. not stop - // execution. You normally can't have it both ways. So, what's going on here is - // that if the error wouldn't normally stop execution the newly-created Error - // throwable is thrown in a fork instead, allowing execution to resume in the - // parent process. - if ($this->isErrorFatal($code)) { - throw $error; - } elseif ($this->forking && \PHP_SAPI === 'cli' && function_exists('pcntl_fork')) { - $pid = pcntl_fork(); - if ($pid === -1) { - // This can't be covered unless it is possible to fake a misconfigured system - throw new \Exception(message: 'Could not create fork to throw Error', previous: $error); // @codeCoverageIgnore - } elseif (!$pid) { - // This can't be covered because it happens in the fork - throw $error; // @codeCoverageIgnore - } - - pcntl_wait($status); - return true; - } + if ($this->errorHandlingMethod > self::THROW_NO_ERRORS && ($this->errorHandlingMethod === self::THROW_ALL_ERRORS || $this->isErrorFatal($code)) && !$this->isShuttingDown) { + $this->lastThrowable = $error; + throw $error; + } else { + $this->handleThrowable($error); + return true; } - - $this->handleThrowable($error); - return true; } // If preventing exit we don't want a false here to halt processing @@ -220,21 +179,24 @@ class Catcher { */ public function handleThrowable(\Throwable $throwable): void { $controller = new ThrowableController($throwable); + $exit = false; foreach ($this->handlers as $h) { $output = $h->handle($controller); - if ($output['outputCode'] & Handler::NOW) { - $h->dispatch(); - } - $controlCode = $output['controlCode']; - if ($controlCode & Handler::BREAK) { + if (!$this->isShuttingDown && $output['code'] & Handler::NOW) { + $h(); + } + if ($output['code'] & Handler::EXIT) { + $exit = true; + } + if (($output['code'] & Handler::BUBBLES) === 0) { break; } } if ( + $exit || $this->isShuttingDown || - $controlCode & Handler::EXIT || $throwable instanceof \Exception || ($throwable instanceof Error && $this->isErrorFatal($throwable->getCode())) || (!$throwable instanceof Error && $throwable instanceof \Error) @@ -243,11 +205,9 @@ class Catcher { if ($this->isShuttingDown) { $h->setOption('outputBacktrace', false); } - $h->dispatch(); + $h(); } - $this->lastThrowable = $throwable; - // Don't want to exit here when shutting down so any shutdown functions further // down the stack still run. if (!$this->preventExit && !$this->isShuttingDown) { @@ -268,19 +228,18 @@ class Catcher { return; } - $this->throwErrors = false; $this->isShuttingDown = true; if ($error = $this->getLastError()) { if ($this->isErrorFatal($error['type'])) { $errorReporting = error_reporting(); - if ($this->errorReporting !== null && $this->errorReporting === $errorReporting && !($this->errorReporting & \E_ERROR)) { + if ($this->errorReporting !== null && $this->errorReporting === $errorReporting && ($this->errorReporting & \E_ERROR) === 0) { error_reporting($errorReporting | \E_ERROR); } $this->handleError($error['type'], $error['message'], $error['file'], $error['line']); } } else { foreach ($this->handlers as $h) { - $h->dispatch(); + $h(); } } } diff --git a/lib/Catcher/ArgumentCountError.php b/lib/Catcher/ArgumentCountError.php new file mode 100644 index 0000000..21dfb9d --- /dev/null +++ b/lib/Catcher/ArgumentCountError.php @@ -0,0 +1,11 @@ +_document === null) { - $this->_document = new \DOMDocument(); - $this->_document->loadHTML(<< - - HTTP {$this->_httpCode} - - - HTML); - } - - $this->xpath = new \DOMXPath($this->_document); - $location = $this->xpath->query($this->_errorPath); - if (count($location) === 0 || (!$location->item(0) instanceof \DOMElement && !$location->item(0) instanceof \DOMDocumentFragment)) { - throw new \InvalidArgumentException('Option "errorPath" must correspond to a location that is an instance of \DOMElement or \DOMDocumentFragment'); - } - $this->errorLocation = $location->item(0); - } - - - - - protected function buildOutputThrowable(array $outputThrowable, bool $previous = false): \DOMDocumentFragment { - $frag = $this->_document->createDocumentFragment(); - $tFrag = $this->_document->createDocumentFragment(); - $ip = $frag; - $hasSiblings = false; - - if ($previous === false) { - if (isset($outputThrowable['time'])) { - $p = $this->_document->createElement('p'); - $time = $this->_document->createElement('time'); - $time->setAttribute('datetime', $outputThrowable['time']->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d\TH:i:s.vO')); - $time->appendChild($this->_document->createTextNode($outputThrowable['time']->format($this->_timeFormat))); - $p->appendChild($time); - $frag->appendChild($p); - - $div = $this->_document->createElement('div'); - $frag->appendChild($div); - $ip = $div; - } - } else { - $span = $this->_document->createElement('span'); - $span->appendChild($this->_document->createTextNode('Caused by:')); - $tFrag->appendChild($span); - $tFrag->appendChild($this->_document->createTextNode(' ')); - } - - $b = $this->_document->createElement('b'); - $code = $this->_document->createElement('code'); - $code->appendChild($this->_document->createTextNode($outputThrowable['class'])); - $b->appendChild($code); - if (isset($outputThrowable['errorType'])) { - $b->insertBefore($this->_document->createTextNode("{$outputThrowable['errorType']} ("), $code); - $b->appendChild($this->_document->createTextNode(')')); - } - $tFrag->appendChild($b); - $tFrag->appendChild($this->_document->createTextNode(': ')); - $i = $this->_document->createElement('i'); - $i->appendChild($this->_document->createTextNode($outputThrowable['message'])); - $tFrag->appendChild($i); - $tFrag->appendChild($this->_document->createTextNode(' in file ')); - $code = $this->_document->createElement('code'); - $code->appendChild($this->_document->createTextNode($outputThrowable['file'])); - $tFrag->appendChild($code); - $tFrag->appendChild($this->_document->createTextNode(" on line {$outputThrowable['line']}")); - - if (isset($outputThrowable['previous'])) { - $ul = $this->_document->createElement('ul'); - $li = $this->_document->createElement('li'); - $li->appendChild($this->buildOutputThrowable($outputThrowable['previous'], true)); - $ul->appendChild($li); - $ip->appendChild($ul); - $hasSiblings = true; - } - - if ($previous === false && isset($outputThrowable['frames'])) { - $p = $this->_document->createElement('p'); - $p->appendChild($this->_document->createTextNode('Stack trace:')); - $ip->appendChild($p); - - $ol = $this->_document->createElement('ol'); - $ip->appendChild($ol); - foreach ($outputThrowable['frames'] as $frame) { - $li = $this->_document->createElement('li'); - $ol->appendChild($li); - if (isset($frame['args'])) { - $t = $this->_document->createElement('p'); - $li->appendChild($t); - } else { - $t = $li; - } - $b = $this->_document->createElement('b'); - $code = $this->_document->createElement('code'); - $b->appendChild($code); - $t->appendChild($b); - - if (isset($frame['class'])) { - $code->appendChild($this->_document->createTextNode($frame['class'])); - - if (isset($frame['errorType'])) { - $b->insertBefore($this->_document->createTextNode("{$frame['errorType']} ("), $code); - $b->appendChild($this->_document->createTextNode(')')); - } elseif (isset($frame['function'])) { - $code->firstChild->appendData("::{$frame['function']}"); - } - } elseif (!empty($frame['function'])) { - $code->appendChild($this->_document->createTextNode($frame['function'])); - } - - $t->appendChild($this->_document->createTextNode("\u{00a0}\u{00a0}")); - $code = $this->_document->createElement('code'); - $code->appendChild($this->_document->createTextNode($frame['file'])); - $t->appendChild($code); - $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($varExporter($frame['args'])))); - $li->appendChild($pre); - } - } - - $hasSiblings = true; - } - - if ($hasSiblings) { - $p = $this->_document->createElement('p'); - $p->appendChild($tFrag); - $ip->insertBefore($p, $ip->firstChild); - } else { - $ip->appendChild($tFrag); - } - - return $frag; - } - - protected function dispatchCallback(): void { - $frag = $this->_document->createDocumentFragment(); - $allSilent = true; - foreach ($this->outputBuffer as $o) { - if ($o['outputCode'] & self::SILENT) { - continue; - } - - $li = $this->_document->createElement('li'); - $li->appendChild($this->buildOutputThrowable($o)); - $frag->appendChild($li); - - $allSilent = false; - } - - if (!$allSilent) { - $ul = $this->_document->createElement('ul'); - $ul->appendChild($frag); - $this->errorLocation->appendChild($ul); - $this->print($this->serializeDocument()); - } - } - - protected function serializeDocument() { - return $this->_document->saveHTML(); - } -} \ No newline at end of file diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php index e581aa7..53a1de9 100644 --- a/lib/Catcher/Handler.php +++ b/lib/Catcher/Handler.php @@ -7,21 +7,18 @@ declare(strict_types=1); namespace MensBeam\Catcher; +use Psr\Log\LoggerInterface; abstract class Handler { public const CONTENT_TYPE = null; // Control constants - public const CONTINUE = 1; - public const BREAK = 2; - public const EXIT = 4; - - // Output constants - public const OUTPUT = 8; - public const SILENT = 16; - public const NOW = 32; - + public const BUBBLES = 1; + public const EXIT = 2; + public const LOG = 4; + public const NOW = 8; + public const OUTPUT = 16; /** * Array of HandlerOutputs the handler creates @@ -32,22 +29,23 @@ abstract class Handler { /** The number of backtrace frames in which to print arguments; defaults to 5 */ protected int $_backtraceArgFrameLimit = 5; + /** If true the handler will move onto the next item in the stack of handlers */ + protected bool $_bubbles = true; /** * The character encoding used for errors; only used if headers weren't sent before * an error occurred */ protected string $_charset = 'UTF-8'; - /** If true the handler will force break the loop through the stack of handlers */ - protected bool $_forceBreak = false; /** If true the handler will force an exit */ protected bool $_forceExit = false; - /** - * If true the handler will output as soon as possible; however, if silent - * is true the handler will output nothing - */ + /** If true the handler will output as soon as possible, unless silenced */ protected bool $_forceOutputNow = false; /** The HTTP code to be sent; possible values: 200, 400-599 */ protected int $_httpCode = 500; + /** The PSR-3 compatible logger in which to log to; defaults to null (no logging) */ + protected ?LoggerInterface $_logger = null; + /** Still send logs when silent */ + protected bool $_logWhenSilent = true; /** If true the handler will output backtraces; defaults to false */ protected bool $_outputBacktrace = false; /** If true the handler will output previous throwables; defaults to true */ @@ -88,14 +86,14 @@ abstract class Handler { - public function dispatch(): void { + public function __invoke(): void { if (count($this->outputBuffer) === 0) { return; } // Send the headers if possible and necessary if (isset($_SERVER['REQUEST_URI'])) { - // Can't figure out a way to test coverage here, but the logic is tested thoroughly + // Can't figure out a way to test coverage here, but the logic is tested thoroughly // when running tests in HTTP // @codeCoverageIgnoreStart if (!headers_sent()) { @@ -106,7 +104,7 @@ abstract class Handler { // @codeCoverageIgnoreEnd } - $this->dispatchCallback(); + $this->invokeCallback(); $this->outputBuffer = []; } @@ -131,23 +129,23 @@ abstract class Handler { $output['time'] = new \DateTimeImmutable(); } - $code = self::CONTINUE; - if ($this->_forceBreak) { - $code = self::BREAK; + $code = 0; + if ($this->_bubbles) { + $code = self::BUBBLES; } if ($this->_forceExit) { $code |= self::EXIT; } - $output['controlCode'] = $code; - - $code = self::OUTPUT; - if ($this->_silent) { - $code = self::SILENT; + if ($this->_logger !== null && (!$this->_silent || ($this->_silent && $this->_logWhenSilent))) { + $code |= self::LOG; } if ($this->_forceOutputNow) { $code |= self::NOW; } - $output['outputCode'] = $code; + if (!$this->_silent) { + $code |= self::OUTPUT; + } + $output['code'] = $code; $output = $this->handleCallback($output); $this->outputBuffer[] = $output; @@ -158,6 +156,7 @@ abstract class Handler { $class = get_class($this); if (!property_exists($class, "_$name")) { trigger_error(sprintf('Undefined option in %s: %s', $class, $name), \E_USER_WARNING); + return; } $name = "_$name"; @@ -206,12 +205,45 @@ abstract class Handler { return $outputThrowable; } - abstract protected function dispatchCallback(): void; - protected function handleCallback(array $output): array { return $output; } + abstract protected function invokeCallback(): void; + + protected function log(\Throwable $throwable, string $message): void { + $context = [ 'exception' => $throwable ]; + if ($throwable instanceof \Error) { + switch ($throwable->getCode()) { + case \E_NOTICE: + case \E_USER_NOTICE: + case \E_STRICT: + $this->_logger->notice($message, $context); + break; + case \E_WARNING: + case \E_COMPILE_WARNING: + case \E_USER_WARNING: + case \E_DEPRECATED: + case \E_USER_DEPRECATED: + $this->_logger->warning($message, $context); + break; + case \E_RECOVERABLE_ERROR: + $this->_logger->error($message, $context); + break; + case \E_PARSE: + case \E_CORE_ERROR: + case \E_COMPILE_ERROR: + $this->_logger->alert($message, $context); + break; + default: $this->_logger->critical($message, $context); + } + } elseif ($throwable instanceof \Exception && ($throwable instanceof \PharException || $throwable instanceof \RuntimeException)) { + $this->_logger->alert($message, $context); + } else { + $this->_logger->critical($message, $context); + } + } + protected function print(string $string): void { $string = "$string\n"; if (strtolower(\PHP_SAPI) === 'cli' && $this->_outputToStderr) { diff --git a/lib/Catcher/JSONHandler.php b/lib/Catcher/JSONHandler.php deleted file mode 100644 index 09e87be..0000000 --- a/lib/Catcher/JSONHandler.php +++ /dev/null @@ -1,32 +0,0 @@ -outputBuffer as $key => $value) { - if ($value['outputCode'] & self::SILENT) { - unset($this->outputBuffer[$key]); - continue; - } - - $this->outputBuffer[$key] = $this->cleanOutputThrowable($this->outputBuffer[$key]); - } - - if (count($this->outputBuffer) > 0) { - $this->print(json_encode([ - 'errors' => $this->outputBuffer - ], \JSON_INVALID_UTF8_SUBSTITUTE | \JSON_PARTIAL_OUTPUT_ON_ERROR | \JSON_UNESCAPED_SLASHES)); - } - } -} \ No newline at end of file diff --git a/lib/Catcher/PlainTextHandler.php b/lib/Catcher/PlainTextHandler.php index 06bb443..0468ad9 100644 --- a/lib/Catcher/PlainTextHandler.php +++ b/lib/Catcher/PlainTextHandler.php @@ -7,75 +7,31 @@ declare(strict_types=1); namespace MensBeam\Catcher; -use Psr\Log\LoggerInterface; class PlainTextHandler extends Handler { public const CONTENT_TYPE = 'text/plain'; - /** The PSR-3 compatible logger in which to log to; defaults to null (no logging) */ - protected ?LoggerInterface $_logger = null; /** The PHP-standard date format which to use for timestamps in output */ protected string $_timeFormat = '[H:i:s]'; - - protected function dispatchCallback(): void { - if ($this->_logger) { - foreach ($this->outputBuffer as $o) { - $output = $this->serializeOutputThrowable($o); - if ($o['outputCode'] & self::SILENT) { - continue; - } - - $this->print($output); - } - } else { - foreach ($this->outputBuffer as $o) { - if ($o['outputCode'] & self::SILENT) { - continue; - } - - $this->print($this->serializeOutputThrowable($o)); - } - } - } - protected function handleCallback(array $output): array { - $output['outputCode'] = (\PHP_SAPI === 'cli') ? $output['outputCode'] | self::NOW : $output['outputCode']; + $output['code'] = (\PHP_SAPI === 'cli') ? $output['code'] | self::NOW : $output['code']; return $output; } - protected function log(\Throwable $throwable, string $message): void { - $context = [ 'exception' => $throwable ]; - if ($throwable instanceof \Error) { - switch ($throwable->getCode()) { - case \E_NOTICE: - case \E_USER_NOTICE: - case \E_STRICT: - $this->_logger->notice($message, $context); - break; - case \E_WARNING: - case \E_COMPILE_WARNING: - case \E_USER_WARNING: - case \E_DEPRECATED: - case \E_USER_DEPRECATED: - $this->_logger->warning($message, $context); - break; - case \E_RECOVERABLE_ERROR: - $this->_logger->error($message, $context); - break; - case \E_PARSE: - case \E_CORE_ERROR: - case \E_COMPILE_ERROR: - $this->_logger->alert($message, $context); - break; - default: $this->_logger->critical($message, $context); + protected function invokeCallback(): void { + foreach ($this->outputBuffer as $o) { + if (($o['code'] & self::OUTPUT) === 0) { + if ($o['code'] & self::LOG) { + $this->serializeOutputThrowable($o); + } + + continue; } - } elseif ($throwable instanceof \Exception && ($throwable instanceof \PharException || $throwable instanceof \RuntimeException)) { - $this->_logger->alert($message, $context); - } else { - $this->_logger->critical($message, $context); + + $this->print($this->serializeOutputThrowable($o)); } } @@ -136,7 +92,7 @@ class PlainTextHandler extends Handler { $output = rtrim($output) . \PHP_EOL; } - if (!empty($this->_logger)) { + if ($outputThrowable['code'] & self::LOG) { $this->log($outputThrowable['controller']->getThrowable(), $output); } diff --git a/lib/Catcher/UnderflowException.php b/lib/Catcher/UnderflowException.php new file mode 100644 index 0000000..e184a20 --- /dev/null +++ b/lib/Catcher/UnderflowException.php @@ -0,0 +1,11 @@ + ['pipe', 'w'], - 2 => ['pipe', 'w'] -], $pipes); - -if ($process === false) { - error('Failed to execute phpunit'); -} - -$stderr = trim(stream_get_contents($pipes[2])); -$output = trim(stream_get_contents($pipes[1])); - -fclose($pipes[1]); -fclose($pipes[2]); -proc_close($process); - -echo "$output\n"; -if ($stderr !== '') { - error($stderr); -} \ No newline at end of file diff --git a/test b/test new file mode 100755 index 0000000..1cbf2ae --- /dev/null +++ b/test @@ -0,0 +1,37 @@ +#!/usr/bin/env php + $v) { + if (in_array($v, ['--coverage', '--coverage-html'])) { + $argv[$k] = '--coverage-html tests/coverage'; + } +} + +$cmd = [ + $php, + '-d opcache.enable_cli=0', +]; + +if (!extension_loaded('xdebug')) { + $cmd[] = '-d zend_extension=xdebug.so'; +} + +$cmd = implode(' ', [ + ...$cmd, + '-d xdebug.mode=coverage,develop,trace', + escapeshellarg(__DIR__ . '/vendor/bin/phpunit'), + '--configuration tests/phpunit.xml', + ...$argv +]); +passthru($cmd); \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 117d01c..cff9451 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,23 +1,18 @@ preventExit = true; - $c->throwErrors = false; - $this->assertSame(1, count($c->getHandlers())); - $this->assertSame(PlainTextHandler::class, $c->getHandlers()[0]::class); - $c->unregister(); - - $c = new Catcher( - new PlainTextHandler(), - new HTMLHandler(), - new JSONHandler() - ); - $c->preventExit = true; - $c->throwErrors = false; - $this->assertSame('MensBeam\Catcher', $c::class); - $this->assertSame(3, count($c->getHandlers())); - $h = $c->getHandlers(); - $this->assertSame(PlainTextHandler::class, $h[0]::class); - $this->assertSame(HTMLHandler::class, $h[1]::class); - $this->assertSame(JSONHandler::class, $h[2]::class); - $c->unregister(); - } + protected ?Catcher $catcher = null; - /** - * @covers \MensBeam\Catcher::getLastThrowable - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::handleError - * @covers \MensBeam\Catcher::handleThrowable - * @covers \MensBeam\Catcher::isErrorFatal - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback - * @covers \MensBeam\Catcher\PlainTextHandler::handleCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod_getLastThrowable(): void { - $c = new Catcher(new PlainTextHandler([ 'silent' => true ])); - $c->preventExit = true; - $c->throwErrors = false; - trigger_error('Ook!', \E_USER_WARNING); - $this->assertSame(\E_USER_WARNING, $c->getLastThrowable()->getCode()); - $c->unregister(); - } - /** - * @covers \MensBeam\Catcher::pushHandler - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Handler::__construct - */ - public function testMethod_pushHandler(): void { - $e = null; - set_error_handler(function($errno) use (&$e) { - $e = $errno; - }); - - $h = new PlainTextHandler(); - $c = new Catcher($h, $h); - $c->preventExit = true; - $c->throwErrors = false; - $c->unregister(); - $this->assertSame(\E_USER_WARNING, $e); - $e = null; - - $c = new Catcher(); - $c->preventExit = true; - $c->throwErrors = false; - $c->unregister(); - $c->pushHandler($h, $h); - $this->assertSame(\E_USER_WARNING, $e); - - restore_error_handler(); - - $c = new Catcher(); - $c->unregister(); - - $e = null; - try { - $c->pushHandler(); - } catch (\Throwable $t) { - $e = $t::class; - } finally { - $this->assertSame(\ArgumentCountError::class, $e); + public function setUp(): void { + if ($this->catcher !== null) { + $this->catcher->unregister(); } - } + $this->catcher = new Catcher(); + $this->catcher->preventExit = true; - /** - * @covers \MensBeam\Catcher::popHandler - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\HTMLHandler::__construct - */ - public function testMethod_popHandler(): void { - $h = [ - new HTMLHandler(), - new PlainTextHandler(), - new JSONHandler() - ]; - $c = new Catcher(...$h); - $c->preventExit = true; - $c->throwErrors = false; - $hh = $c->popHandler(); - $this->assertSame($h[2], $hh); - $hh = $c->popHandler(); - $this->assertSame($h[1], $hh); - - $e = null; - try { - $c->popHandler(); - } catch (\Throwable $t) { - $e = $t::class; - } finally { - $c->unregister(); - $this->assertSame(\Exception::class, $e); - } + // Do this instead of specifying the option in the constructor for coverage + // purposes... + $handlers = $this->catcher->getHandlers(); + $handlers[0]->setOption('silent', true); } - /** - * @covers \MensBeam\Catcher::isRegistered - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Handler::__construct - */ - public function testMethod_register(): void { - $c = new Catcher(); - $c->preventExit = true; - $c->throwErrors = false; - $this->assertTrue($c->isRegistered()); - $this->assertFalse($c->register()); - $c->unregister(); - $this->assertFalse($c->isRegistered()); + public function tearDown(): void { + $this->catcher->unregister(); + $this->catcher = null; + error_reporting(\E_ALL); } - /** - * @covers \MensBeam\Catcher::setHandlers - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::getHandlers - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Handler::__construct - */ - public function testMethod_setHandlers(): void { - $c = new Catcher(); - $c->preventExit = true; - $c->throwErrors = false; - $c->setHandlers(new PlainTextHandler()); - $h = $c->getHandlers(); - $this->assertSame(1, count($h)); - $this->assertSame(PlainTextHandler::class, $h[0]::class); - $c->unregister(); + + public function testConstructor(): void { + $h = $this->catcher->getHandlers(); + $this->assertEquals(1, count($h)); + $this->assertInstanceOf(PlainTextHandler::class, $h[0]); } - /** - * @covers \MensBeam\Catcher::shiftHandler - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\HTMLHandler::__construct - */ - public function testMethod_shiftHandler(): void { - $h = [ - new HTMLHandler(), - new PlainTextHandler(), - new JSONHandler() - ]; - $c = new Catcher(...$h); - $c->preventExit = true; - $c->throwErrors = false; - $c->unregister(); - $hh = $c->shiftHandler(); - $this->assertSame($h[0], $hh); - $hh = $c->shiftHandler(); - $this->assertSame($h[1], $hh); - - $e = null; + /** @dataProvider provideErrorHandlingTests */ + public function testErrorHandling(int $code): void { + $t = null; try { - $c->shiftHandler(); - } catch (\Throwable $t) { - $e = $t::class; - } finally { - $this->assertSame(\Exception::class, $e); + trigger_error('Ook!', $code); + } catch (\Throwable $t) {} finally { + $t = ($t === null) ? $this->catcher->getLastThrowable() : $t; + $this->assertSame(Error::class, $t::class); + $this->assertSame($code, $t->getCode()); + $this->assertSame($t, $this->catcher->getLastThrowable()); } } - /** - * @covers \MensBeam\Catcher::unregister - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher\Handler::__construct - */ - public function testMethod_unregister(): void { - $c = new Catcher(); - $c->preventExit = true; - $c->throwErrors = false; - $c->unregister(); - $this->assertFalse($c->unregister()); - } + public function testExit(): void { + $this->catcher->unregister(); + $h = Phake::partialMock(TestingHandler::class); + $this->catcher = $m = Phake::partialMock(Catcher::class, $h); + $m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; + Phake::when($m)->exit->thenReturn(null); + Phake::when($m)->handleShutdown()->thenReturn(null); - /** - * @covers \MensBeam\Catcher::unshiftHandler - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::getHandlers - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\HTMLHandler::__construct - */ - public function testMethod_unshiftHandler(): void { - $c = new Catcher(new PlainTextHandler()); - $c->preventExit = true; - $c->throwErrors = false; - $c->unshiftHandler(new JSONHandler(), new HTMLHandler(), new PlainTextHandler()); - $h = $c->getHandlers(); - $this->assertSame(4, count($h)); - $this->assertSame(JSONHandler::class, $h[0]::class); - $this->assertSame(HTMLHandler::class, $h[1]::class); - $this->assertSame(PlainTextHandler::class, $h[2]::class); - $this->assertSame(PlainTextHandler::class, $h[3]::class); - - $e = null; - set_error_handler(function($errno) use (&$e) { - $e = $errno; - }); - - $c->unshiftHandler($h[0]); - $this->assertSame(\E_USER_WARNING, $e); - $e = null; - $h = new PlainTextHandler(); - $c->unshiftHandler($h, $h); - $this->assertSame(\E_USER_WARNING, $e); - - restore_error_handler(); - $c->unregister(); - - $c = new Catcher(); - $c->preventExit = true; - $c->throwErrors = false; - $c->unregister(); - - $e = null; - try { - $c->unshiftHandler(); - } catch (\Throwable $t) { - $e = $t::class; - } finally { - $this->assertSame(\ArgumentCountError::class, $e); - } + trigger_error('Ook!', \E_USER_ERROR); + + Phake::verify($h, Phake::times(1))->invokeCallback(); } - /** - * @covers \MensBeam\Catcher::handleError - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::getLastThrowable - * @covers \MensBeam\Catcher::handleThrowable - * @covers \MensBeam\Catcher::isErrorFatal - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback - * @covers \MensBeam\Catcher\PlainTextHandler::handleCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod_handleError(): void { - $c = new Catcher(new PlainTextHandler([ 'silent' => true ])); - $c->preventExit = true; - $c->throwErrors = false; - - trigger_error('Ook!', \E_USER_NOTICE); - $t = $c->getLastThrowable(); - $this->assertSame(Error::class, $t::class); - $this->assertSame(\E_USER_NOTICE, $t->getCode()); - - trigger_error('Ook!', \E_USER_DEPRECATED); - $t = $c->getLastThrowable(); - $this->assertSame(Error::class, $t::class); - $this->assertSame(\E_USER_DEPRECATED, $t->getCode()); - - trigger_error('Ook!', \E_USER_WARNING); - $t = $c->getLastThrowable(); - $this->assertSame(Error::class, $t::class); - $this->assertSame(\E_USER_WARNING, $t->getCode()); + public function testHandlerBubbling(): void { + $this->catcher->unregister(); - trigger_error('Ook!', \E_USER_ERROR); - $t = $c->getLastThrowable(); - $this->assertSame(Error::class, $t::class); - $this->assertSame(\E_USER_ERROR, $t->getCode()); + $h1 = Phake::partialMock(TestingHandler::class, [ 'bubbles' => false ]); + $h2 = Phake::partialMock(TestingHandler::class); + $this->catcher = $m = Phake::partialMock(Catcher::class, $h1, $h2); + $m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; + $m->preventExit = true; - $er = error_reporting(); - error_reporting(0); trigger_error('Ook!', \E_USER_ERROR); - error_reporting($er); + Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', __FILE__, __LINE__ - 1); + Phake::verify($m)->handleThrowable($m->getLastThrowable()); + Phake::verify($h1)->invokeCallback(); + Phake::verify($h2, Phake::never())->invokeCallback(); + } - $c->unregister(); + public function testHandlerForceExiting(): void { + $this->catcher->setHandlers(new TestingHandler([ 'forceExit' => true ])); + $this->catcher->errorHandlingMethod = Catcher::THROW_NO_ERRORS; + $this->catcher->preventExit = true; - $h1 = Phony::partialMock(PlainTextHandler::class, [ [ 'silent' => true ] ]); - $h2 = Phony::partialMock(HTMLHandler::class, [ [ 'silent' => true ] ]); - $h3 = Phony::partialMock(JSONHandler::class, [ [ 'silent' => true ] ]); + trigger_error('Ook', \E_USER_ERROR); + $this->assertSame(Error::class, $this->catcher->getLastThrowable()::class); + } - $h = Phony::partialMock(Catcher::class, [ - $h1->get(), - $h2->get(), - $h3->get() - ]); - $c = $h->get(); - $c->preventExit = true; - $c->throwErrors = false; + public function testRegistration(): void { + $this->assertTrue($this->catcher->isRegistered()); + $this->assertFalse($this->catcher->register()); + $this->assertTrue($this->catcher->unregister()); + $this->assertFalse($this->catcher->unregister()); + $this->assertFalse($this->catcher->isRegistered()); + } - trigger_error('Ook!', \E_USER_ERROR); + /** @dataProvider provideShutdownTests */ + public function testShutdownHandling(\Closure $closure): void { + $this->catcher->unregister(); - $h1->dispatch->called(); - $h2->dispatch->called(); - $h3->dispatch->called(); + $h1 = Phake::partialMock(TestingHandler::class); + $this->catcher = $m = Phake::partialMock(Catcher::class, $h1); + $closure($m, $h1); + } - $c->throwErrors = true; + public function testStackManipulation(): void { + $c = $this->catcher; + $c->pushHandler(new TestingHandler(options: [ 'name' => 'ook' ]), new TestingHandler(options: [ 'name' => 'eek' ])); + $h = $c->getHandlers(); + $this->assertEquals(3, count($h)); + $this->assertSame('ook', $h[1]->getOption('name')); + $this->assertSame('eek', $h[2]->getOption('name')); + + $this->assertInstanceOf(PlainTextHandler::class, $c->shiftHandler()); + $h = $c->getHandlers(); + $this->assertEquals(2, count($h)); + $this->assertSame('ook', $h[0]->getOption('name')); + $this->assertSame('eek', $h[1]->getOption('name')); + + $p = $c->popHandler(); + $this->assertInstanceOf(TestingHandler::class, $p); + $h = $c->getHandlers(); + $this->assertEquals(1, count($h)); + $this->assertSame('eek', $p->getOption('name')); + $this->assertSame('ook', $h[0]->getOption('name')); + + $c->unshiftHandler($p); + $h = $c->getHandlers(); + $this->assertEquals(2, count($h)); + $this->assertSame('eek', $h[0]->getOption('name')); + $this->assertSame('ook', $h[1]->getOption('name')); + + $c->setHandlers(new PlainTextHandler()); + $this->assertEquals(1, count($c->getHandlers())); + } + + public function testWeirdErrorReporting(): void { + error_reporting(\E_ERROR); + $t = null; try { trigger_error('Ook!', \E_USER_WARNING); - } catch (\Throwable $t) { - $this->assertInstanceOf(Error::class, $t); - $this->assertSame(\E_USER_WARNING, $t->getCode()); + } catch (\Throwable $t) {} finally { + $this->assertNull($t); + $this->assertNull($this->catcher->getLastThrowable()); } + } - try { - trigger_error('Ook!', \E_USER_ERROR); - } catch (\Throwable $t) { - $this->assertInstanceOf(Error::class, $t); - $this->assertSame(\E_USER_ERROR, $t->getCode()); - } - - $c->unregister(); - $c->throwErrors = false; + + /** @dataProvider provideFatalErrorTests */ + public function testFatalErrors(string $throwableClassName, \Closure $closure): void { + $this->expectException($throwableClassName); + $closure($this->catcher); } - /** - * @covers \MensBeam\Catcher::handleThrowable - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::getLastThrowable - * @covers \MensBeam\Catcher::handleError - * @covers \MensBeam\Catcher::isErrorFatal - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback - * @covers \MensBeam\Catcher\PlainTextHandler::handleCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod_handleThrowable(): void { - $c = new Catcher(new PlainTextHandler([ 'silent' => true, 'forceBreak' => true ])); - $c->preventExit = true; - $c->throwErrors = false; - trigger_error('Ook!', \E_USER_ERROR); - $t = $c->getLastThrowable(); - $this->assertSame(Error::class, $t::class); - $this->assertSame(\E_USER_ERROR, $t->getCode()); - $c->unregister(); - $h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]); - $h->exit->returns(); - $c = $h->get(); - $c->preventExit = false; - $c->throwErrors = false; + public static function provideFatalErrorTests(): iterable { + $iterable = [ + [ + UnderflowException::class, + function (Catcher $c): void { + $c->popHandler(); + } + ], + [ + ArgumentCountError::class, + function (Catcher $c): void { + $c->pushHandler(); + } + ], + [ + UnderflowException::class, + function (Catcher $c): void { + $c->shiftHandler(); + } + ], + [ + ArgumentCountError::class, + function (Catcher $c): void { + $c->unshiftHandler(); + } + ], + ]; - trigger_error('Ook!', \E_USER_ERROR); - $t = $c->getLastThrowable(); - $this->assertSame(Error::class, $t::class); - $this->assertSame(\E_USER_ERROR, $t->getCode()); - $c->unregister(); + foreach ($iterable as $i) { + yield $i; + } } - /** - * @covers \MensBeam\Catcher::handleShutdown - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::getLastError - * @covers \MensBeam\Catcher::handleError - * @covers \MensBeam\Catcher::isErrorFatal - * @covers \MensBeam\Catcher::handleThrowable - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback - * @covers \MensBeam\Catcher\PlainTextHandler::handleCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod_handleShutdown(): void { - $c = new Catcher(); - $c->preventExit = true; - $c->throwErrors = false; - $c->handleShutdown(); - $p = new \ReflectionProperty($c, 'isShuttingDown'); - $p->setAccessible(true); - $this->assertTrue($p->getValue($c)); - $c->unregister(); - - $c = new Catcher(); - $c->preventExit = true; - $c->throwErrors = false; - $c->unregister(); - $c->handleShutdown(); - $p = new \ReflectionProperty($c, 'isShuttingDown'); - $p->setAccessible(true); - $this->assertFalse($p->getValue($c)); - $c->unregister(); - - $h = Phony::partialMock(Catcher::class, [ new PlainTextHandler([ 'silent' => true ]) ]); - $h->getLastError->returns([ - 'type' => \E_ERROR, - 'message' => 'Ook!', - 'file' => '/dev/null', - 'line' => 2112 - ]); - $c = $h->get(); - $c->handleShutdown(); - $h->handleError->called(); - $h->handleThrowable->called(); + public static function provideErrorHandlingTests(): iterable { + foreach ([ \E_USER_NOTICE, \E_USER_DEPRECATED, \E_USER_WARNING, \E_USER_ERROR ] as $i) { + yield [ $i ]; + } + } + + public static function provideShutdownTests(): iterable { + $iterable = [ + [ + function (IMock $m, IMock $h): void { + $m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; + Phake::when($m)->getLastError()->thenReturn([ + 'type' => \E_ERROR, + 'message' => 'Ook!', + 'file' => '/dev/null', + 'line' => 2112 + ]); + $m->handleShutdown(); + Phake::verify($m)->getLastError(); + Phake::verify($m)->handleError(\E_ERROR, 'Ook!', '/dev/null', 2112); + Phake::verify($h, Phake::times(1))->invokeCallback(); + } + ], + [ + function (IMock $m, IMock $h): void { + $m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; + Phake::when($m)->getLastError()->thenReturn([ + 'type' => \E_USER_ERROR, + 'message' => 'Ook!', + 'file' => '/dev/null', + 'line' => 2112 + ]); + $m->handleShutdown(); + Phake::verify($m)->getLastError(); + Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', '/dev/null', 2112); + Phake::verify($h, Phake::times(1))->invokeCallback(); + } + ], + [ + function (IMock $m, IMock $h): void { + $m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; + Phake::when($m)->getLastError()->thenReturn([ + 'type' => \E_USER_ERROR, + 'message' => 'Ook!', + 'file' => '/dev/null', + 'line' => 2112 + ]); + $m->handleShutdown(); + Phake::verify($m)->getLastError(); + Phake::verify($m)->handleError(\E_USER_ERROR, 'Ook!', '/dev/null', 2112); + Phake::verify($h, Phake::times(1))->invokeCallback(); + } + ], + [ + function (IMock $m, IMock $h): void { + $m->errorHandlingMethod = Catcher::THROW_NO_ERRORS; + $m->handleShutdown(); + Phake::verify($m)->getLastError(); + // Handler wouldn't be invoked because there aren't any errors in the output buffer. + Phake::verify($h, Phake::never())->invokeCallback(); + } + ], + [ + function (IMock $m, IMock $h): void { + // Nothing in the shutdown handler runs if Catcher is unregistered + $m->unregister(); + $m->handleShutdown(); + Phake::verify($m, Phake::never())->getLastError(); + Phake::verify($h, Phake::never())->invokeCallback(); + } + ] + ]; + + foreach ($iterable as $i) { + yield $i; + } } } \ No newline at end of file diff --git a/tests/cases/TestHTMLHandler.php b/tests/cases/TestHTMLHandler.php deleted file mode 100644 index c0b5bb7..0000000 --- a/tests/cases/TestHTMLHandler.php +++ /dev/null @@ -1,89 +0,0 @@ -expectException(\InvalidArgumentException::class); - new HTMLHandler([ 'errorPath' => '/html/body/fail' ]); - } - - /** - * @covers \MensBeam\Catcher\HTMLHandler::buildOutputThrowable - * - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\HTMLHandler::__construct - * @covers \MensBeam\Catcher\HTMLHandler::dispatchCallback - * @covers \MensBeam\Catcher\HTMLHandler::handleCallback - * @covers \MensBeam\Catcher\HTMLHandler::serializeDocument - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getFrames - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod_buildOutputThrowable(): void { - $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR, previous: new Error(message: 'Ack!')))); - $h = new HTMLHandler([ - 'backtraceArgFrameLimit' => 1, - 'outputBacktrace' => true, - 'outputToStderr' => false - ]); - $o = $h->handle($c); - $this->assertSame(Handler::CONTINUE, $o['controlCode']); - - ob_start(); - $h->dispatch(); - ob_end_clean(); - } - - /** - * @covers \MensBeam\Catcher\HTMLHandler::dispatchCallback - * - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\Handler::handleCallback - * @covers \MensBeam\Catcher\HTMLHandler::__construct - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod_dispatchCallback(): void { - $c = new ThrowableController(new \Exception(message: 'Ook!')); - $h = new HTMLHandler([ - 'backtraceArgFrameLimit' => 1, - 'outputToStderr' => false, - 'silent' => true - ]); - $h->handle($c); - - ob_start(); - $h->dispatch(); - $o = ob_get_clean(); - $this->assertEmpty($o); - } -} \ No newline at end of file diff --git a/tests/cases/TestHandler.php b/tests/cases/TestHandler.php deleted file mode 100644 index e3ce51f..0000000 --- a/tests/cases/TestHandler.php +++ /dev/null @@ -1,173 +0,0 @@ -expectException(\RangeException::class); - new PlainTextHandler([ 'httpCode' => 42 ]); - } - - /** - * @covers \MensBeam\Catcher\Handler::cleanOutputThrowable - * - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\Handler::handleCallback - * @covers \MensBeam\Catcher\Handler::print - * @covers \MensBeam\Catcher\JSONHandler::dispatchCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getFrames - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod__cleanOutputThrowable(): void { - // Just need to test coverage here; TestJSONHandler covers this one thoroughly. - $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error('Eek!'))); - $h = new JSONHandler([ - 'outputBacktrace' => true, - 'outputToStderr' => false - ]); - $o = $h->handle($c); - - ob_start(); - $h->dispatch(); - ob_end_clean(); - - $this->assertTrue(isset($o['frames'])); - } - - /** - * @covers \MensBeam\Catcher\Handler::handle - * - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::handleCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getFrames - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod__handle(): void { - // Just need to test backtrace handling. The rest has already been covered by prior tests. - $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new Error(message: 'Eek!', code: \E_USER_ERROR)))); - $h = new HTMLHandler([ - 'outputBacktrace' => true, - 'outputToStderr' => true - ]); - $this->assertTrue(isset($h->handle($c)['frames'])); - } - - /** - * @covers \MensBeam\Catcher\Handler::getOption - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::handleError - * @covers \MensBeam\Catcher::handleThrowable - * @covers \MensBeam\Catcher::isErrorFatal - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\PlainTextHandler::handleCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod__getOption(): void { - $h = new PlainTextHandler([ 'forceExit' => true, 'silent' => true ]); - $this->assertTrue($h->getOption('forceExit')); - - $c = new Catcher($h); - $c->preventExit = true; - $c->throwErrors = false; - $this->assertNull($h->getOption('ook')); - $c->unregister(); - } - - /** - * @covers \MensBeam\Catcher\Handler::setOption - * - * @covers \MensBeam\Catcher\Handler::__construct - */ - public function testMethod__setOption(): void { - $h = new PlainTextHandler([ 'forceExit' => true, 'silent' => true ]); - $h->setOption('forceExit', false); - $r = new \ReflectionProperty($h, '_forceExit'); - $r->setAccessible(true); - $this->assertFalse($r->getValue($h)); - - $m = Phony::partialMock(Catcher::class, [ - $h - ]); - $c = $m->get(); - $c->preventExit = true; - $c->throwErrors = false; - - $h->setOption('ook', 'FAIL'); - $m->handleError->called(); - - $c->unregister(); - } - - /** - * @covers \MensBeam\Catcher\Handler::print - * - * @covers \MensBeam\Catcher::__construct - * @covers \MensBeam\Catcher::handleError - * @covers \MensBeam\Catcher::isErrorFatal - * @covers \MensBeam\Catcher::handleThrowable - * @covers \MensBeam\Catcher::pushHandler - * @covers \MensBeam\Catcher::register - * @covers \MensBeam\Catcher::unregister - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::dispatch - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback - * @covers \MensBeam\Catcher\PlainTextHandler::handleCallback - * @covers \MensBeam\Catcher\PlainTextHandler::serializeOutputThrowable - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod__print(): void { - // Just need to test forceOutputNow for coverage purposes - $c = new Catcher(new PlainTextHandler([ 'forceOutputNow' => true, 'outputToStderr' => false ])); - $c->preventExit = true; - $c->throwErrors = false; - ob_start(); - trigger_error('Ook!', \E_USER_NOTICE); - ob_end_clean(); - $this->assertSame(\E_USER_NOTICE, $c->getLastThrowable()->getCode()); - $c->unregister(); - } -} \ No newline at end of file diff --git a/tests/cases/TestJSONHandler.php b/tests/cases/TestJSONHandler.php deleted file mode 100644 index b5b6766..0000000 --- a/tests/cases/TestJSONHandler.php +++ /dev/null @@ -1,48 +0,0 @@ - true, - 'outputToStderr' => false - ]); - $o = $h->handle($c); - - ob_start(); - $h->dispatch(); - $o = ob_get_clean(); - $this->assertEmpty($o); - } -} \ No newline at end of file diff --git a/tests/cases/TestPlainTextHandler.php b/tests/cases/TestPlainTextHandler.php deleted file mode 100644 index 8391ef4..0000000 --- a/tests/cases/TestPlainTextHandler.php +++ /dev/null @@ -1,145 +0,0 @@ - $l->get(), - 'outputBacktrace' => true, - 'outputToStderr' => false - ]); - $o = $h->handle($c); - $this->assertSame(Handler::CONTINUE, $o['controlCode']); - $this->assertSame(Handler::OUTPUT | Handler::NOW, $o['outputCode']); - $this->assertTrue(isset($o['previous'])); - - ob_start(); - $h->dispatch(); - ob_end_clean(); - - $l->critical->called(); - - $c = new ThrowableController(new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new Error(message: 'Ack!', code: \E_USER_ERROR)))); - $l = Phony::mock(LoggerInterface::class); - $h = new PlainTextHandler([ - 'logger' => $l->get(), - 'silent' => true - ]); - $o = $h->handle($c); - $this->assertSame(Handler::CONTINUE, $o['controlCode']); - $this->assertSame(Handler::SILENT | Handler::NOW, $o['outputCode']); - $this->assertTrue(isset($o['previous'])); - - ob_start(); - $h->dispatch(); - ob_end_clean(); - - $l->critical->called(); - } - - - /** - * @covers \MensBeam\Catcher\PlainTextHandler::log - * - * @covers \MensBeam\Catcher\Error::__construct - * @covers \MensBeam\Catcher\Handler::__construct - * @covers \MensBeam\Catcher\Handler::buildOutputArray - * @covers \MensBeam\Catcher\Handler::handle - * @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback - * @covers \MensBeam\Catcher\PlainTextHandler::handleCallback - * @covers \MensBeam\Catcher\PlainTextHandler::dispatchCallback - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - * @covers \MensBeam\Catcher\ThrowableController::getThrowable - */ - public function testMethod_log(): void { - $l = Phony::mock(LoggerInterface::class); - $h = new PlainTextHandler([ - 'logger' => $l->get(), - 'outputToStderr' => false - ]); - - $e = [ - 'notice' => [ - \E_NOTICE, - \E_USER_NOTICE, - \E_STRICT - ], - 'warning' => [ - \E_WARNING, - \E_COMPILE_WARNING, - \E_USER_WARNING, - \E_DEPRECATED, - \E_USER_DEPRECATED - ], - 'error' => [ - \E_RECOVERABLE_ERROR - ], - 'alert' => [ - \E_PARSE, - \E_CORE_ERROR, - \E_COMPILE_ERROR - ] - ]; - - foreach ($e as $k => $v) { - foreach ($v as $vv) { - $h->handle(new ThrowableController(new Error('Ook!', $vv))); - - ob_start(); - $h->dispatch(); - ob_end_clean(); - - $l->$k->called(); - } - } - - $h->handle(new ThrowableController(new \PharException('Ook!'))); - ob_start(); - $h->dispatch(); - ob_end_clean(); - $l->alert->called(); - - $h->handle(new ThrowableController(new \RuntimeException('Ook!'))); - ob_start(); - $h->dispatch(); - ob_end_clean(); - $l->alert->called(); - } -} \ No newline at end of file diff --git a/tests/cases/TestThrowableController.php b/tests/cases/TestThrowableController.php deleted file mode 100644 index dd28967..0000000 --- a/tests/cases/TestThrowableController.php +++ /dev/null @@ -1,189 +0,0 @@ - false ])); - $c->preventExit = true; - $c->throwErrors = false; - ob_start(); - trigger_error('Ook!', \E_USER_DEPRECATED); - ob_end_clean(); - $this->assertSame(\E_USER_DEPRECATED, $c->getLastThrowable()->getCode()); - $c->unregister(); - - $c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ])); - $c->preventExit = true; - $c->throwErrors = false; - ob_start(); - trigger_error('Ook!', \E_USER_WARNING); - ob_end_clean(); - $this->assertSame(\E_USER_WARNING, $c->getLastThrowable()->getCode()); - $c->unregister(); - - $c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ])); - $c->preventExit = true; - $c->throwErrors = false; - ob_start(); - trigger_error('Ook!', \E_USER_NOTICE); - ob_end_clean(); - $this->assertSame(\E_USER_NOTICE, $c->getLastThrowable()->getCode()); - $c->unregister(); - - $c = new Catcher(new PlainTextHandler([ 'outputToStderr' => false ])); - $c->preventExit = true; - $c->throwErrors = false; - ob_start(); - trigger_error('Ook!', \E_USER_ERROR); - ob_end_clean(); - $this->assertSame(\E_USER_ERROR, $c->getLastThrowable()->getCode()); - $c->unregister(); - - // These others will be tested by invoking the method directly - $c = new ThrowableController(new Error('Ook!', \E_ERROR)); - $this->assertSame('PHP Fatal Error', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_WARNING)); - $this->assertSame('PHP Warning', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_PARSE)); - $this->assertSame('PHP Parsing Error', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_NOTICE)); - $this->assertSame('PHP Notice', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_DEPRECATED)); - $this->assertSame('Deprecated', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_CORE_ERROR)); - $this->assertSame('PHP Core Error', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_CORE_WARNING)); - $this->assertSame('PHP Core Warning', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_COMPILE_ERROR)); - $this->assertSame('Compile Error', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_COMPILE_WARNING)); - $this->assertSame('Compile Warning', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_STRICT)); - $this->assertSame('Runtime Notice', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!', \E_RECOVERABLE_ERROR)); - $this->assertSame('Recoverable Error', $c->getErrorType()); - $c = new ThrowableController(new Error('Ook!')); - $this->assertNull($c->getErrorType()); - $c = new ThrowableController(new \Exception('Ook!')); - $this->assertNull($c->getErrorType()); - - // For code coverage purposes. - $this->assertNull($c->getErrorType()); - } - - /** - * @covers \MensBeam\Catcher\ThrowableController::getFrames - * - * @covers \MensBeam\Catcher\ThrowableController::__construct - * @covers \MensBeam\Catcher\ThrowableController::getErrorType - * @covers \MensBeam\Catcher\ThrowableController::getPrevious - */ - public function testMethod_getFrames(): void { - $f = false; - try { - throw new \Exception('Ook!'); - } catch (\Throwable $t) { - $c = new ThrowableController($t); - $f = $c->getFrames(); - } finally { - $this->assertSame(\Exception::class, $f[0]['class']); - } - - $f = false; - try { - throw new Error('Ook!', \E_ERROR); - } catch (\Throwable $t) { - $c = new ThrowableController($t); - $f = $c->getFrames(); - } finally { - $this->assertSame(Error::class, $f[0]['class']); - } - - $f = false; - try { - throw new \Exception(message: 'Ook!', previous: new Error(message: 'Ook!', code: \E_ERROR, previous: new \Exception('Ook!'))); - } catch (\Throwable $t) { - $c = new ThrowableController($t); - $f = $c->getFrames(); - } finally { - $this->assertSame(\Exception::class, $f[0]['class']); - $this->assertSame(Error::class, $f[count($f) - 2]['class']); - } - - $f = false; - try { - call_user_func_array(function () { - throw new \Exception('Ook!'); - }, []); - } catch (\Throwable $t) { - $c = new ThrowableController($t); - $f = $c->getFrames(); - } finally { - $this->assertSame(\Exception::class, $f[0]['class']); - $this->assertArrayHasKey('file', $f[2]); - $this->assertMatchesRegularExpression('/TestThrowableController\.php$/', $f[2]['file']); - $this->assertSame('call_user_func_array', $f[2]['function']); - $this->assertArrayHasKey('line', $f[2]); - $this->assertNotSame(0, $f[2]['line']); - } - - // This is mostly here for code coverage: to delete userland error handling from - // the backtrace - $f = false; - try { - function ook() {} - call_user_func('ook', []); - } catch (\Throwable $t) { - $c = new ThrowableController($t); - $f = $c->getFrames(); - } finally { - $this->assertSame(\TypeError::class, $f[0]['class']); - } - - // For code coverage purposes; should use the cached value instead of calculating - // the frames over again. - $f = $c->getFrames(); - - // Lastly test for a RangeException - $this->expectException(\RangeException::class); - $c = new ThrowableController(new \Exception('Ook!')); - $c->getFrames(-1); - } -} \ No newline at end of file diff --git a/tests/lib/TestingHandler.php b/tests/lib/TestingHandler.php new file mode 100644 index 0000000..da13e52 --- /dev/null +++ b/tests/lib/TestingHandler.php @@ -0,0 +1,41 @@ +outputBuffer as $o) { + if (($o['code'] & self::OUTPUT) === 0) { + continue; + } + + if ($o['code'] & self::LOG) { + $this->log($o['controller']->getThrowable(), json_encode([ + 'class' => $o['class'], + 'code' => $o['code'], + 'file' => $o['file'], + 'line' => $o['line'], + 'message' => $o['message'] + ])); + } + + //$this->print($this->serializeOutputThrowable($o)); + } + } +} \ No newline at end of file diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml deleted file mode 100644 index 672fd0c..0000000 --- a/tests/phpunit.dist.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - ../lib - - - - - cases/TestCatcher.php - cases/TestHandler.php - cases/TestHTMLHandler.php - cases/TestJSONHandler.php - cases/TestPlainTextHandler.php - cases/TestThrowableController.php - - - \ No newline at end of file diff --git a/tests/phpunit.xml b/tests/phpunit.xml new file mode 100644 index 0000000..92e62e7 --- /dev/null +++ b/tests/phpunit.xml @@ -0,0 +1,21 @@ + + + + + ./cases + + + + + ../lib + + +