diff --git a/composer.json b/composer.json
index 4ab3bfc..5ae6d70 100644
--- a/composer.json
+++ b/composer.json
@@ -5,8 +5,8 @@
"license": "MIT",
"autoload": {
"psr-4": {
- "Mensbeam\\Framework\\": "lib/",
- "Mensbeam\\Framework\\Test\\": "test/"
+ "MensBeam\\Framework\\": "lib/",
+ "MensBeam\\Framework\\Test\\": "test/"
}
},
"authors": [
@@ -21,5 +21,8 @@
},
"suggest": {
"ext-dom": "For HTMLHandler"
+ },
+ "require-dev": {
+ "mensbeam/html-dom": "^1.0"
}
}
diff --git a/composer.lock b/composer.lock
index 7fc2812..ba844a4 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": "8998cce6e05b3ccc6aca7c736416f570",
+ "content-hash": "e8aa70aef9ba115203828355980ff484",
"packages": [
{
"name": "psr/log",
@@ -57,7 +57,491 @@
"time": "2021-07-14T16:46:02+00:00"
}
],
- "packages-dev": [],
+ "packages-dev": [
+ {
+ "name": "mensbeam/framework",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mensbeam/Framework.git",
+ "reference": "af4b8e72d50c01cf78d3d1b12b5a65f714f9d73b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mensbeam/Framework/zipball/af4b8e72d50c01cf78d3d1b12b5a65f714f9d73b",
+ "reference": "af4b8e72d50c01cf78d3d1b12b5a65f714f9d73b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MensBeam\\Framework\\": [
+ "lib/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Dustin Wilson",
+ "email": "dustin@dustinwilson.com",
+ "homepage": "https://dustinwilson.com/"
+ }
+ ],
+ "description": "Common classes and traits used in many MensBeam projects",
+ "support": {
+ "issues": "https://github.com/mensbeam/Framework/issues",
+ "source": "https://github.com/mensbeam/Framework/tree/1.0.5"
+ },
+ "time": "2022-01-05T16:09:09+00:00"
+ },
+ {
+ "name": "mensbeam/html-dom",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mensbeam/HTML-DOM.git",
+ "reference": "8a9f0e18b2bc14dc6e451690be9ff0aeeb428496"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mensbeam/HTML-DOM/zipball/8a9f0e18b2bc14dc6e451690be9ff0aeeb428496",
+ "reference": "8a9f0e18b2bc14dc6e451690be9ff0aeeb428496",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "mensbeam/framework": "^1.0.4",
+ "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/1.0.8"
+ },
+ "time": "2022-02-12T15:46:39+00:00"
+ },
+ {
+ "name": "mensbeam/html-parser",
+ "version": "1.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mensbeam/HTML-Parser.git",
+ "reference": "02ec5df3fe985b3b6635c0cfd37df10ccd023e4c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mensbeam/HTML-Parser/zipball/02ec5df3fe985b3b6635c0cfd37df10ccd023e4c",
+ "reference": "02ec5df3fe985b3b6635c0cfd37df10ccd023e4c",
+ "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.5"
+ },
+ "time": "2022-02-15T20:47:49+00:00"
+ },
+ {
+ "name": "mensbeam/intl",
+ "version": "0.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mensbeam/intl.git",
+ "reference": "07d26e3f45c3a3167eb6389572419d3bda7ff5e1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mensbeam/intl/zipball/07d26e3f45c3a3167eb6389572419d3bda7ff5e1",
+ "reference": "07d26e3f45c3a3167eb6389572419d3bda7ff5e1",
+ "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.1"
+ },
+ "time": "2021-10-24T14:37:46+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"
+ },
+ {
+ "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"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v5.4.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "c1681789f059ab756001052164726ae88512ae3d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/c1681789f059ab756001052164726ae88512ae3d",
+ "reference": "c1681789f059ab756001052164726ae88512ae3d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "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/v5.4.11"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-06-27T16:58:25+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.26.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
+ "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.26-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-05-10T07:21:04+00:00"
+ }
+ ],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
diff --git a/lib/Catcher.php b/lib/Catcher.php
index ebd85c0..fbc56be 100644
--- a/lib/Catcher.php
+++ b/lib/Catcher.php
@@ -6,8 +6,8 @@
*/
declare(strict_types=1);
-namespace Mensbeam\Framework;
-use Mensbeam\Framework\Catcher\{
+namespace MensBeam\Framework;
+use MensBeam\Framework\Catcher\{
ThrowableController,
Handler
};
@@ -20,18 +20,14 @@ class Catcher {
* @var Handler[]
*/
protected array $handlers = [];
- protected array $handlerClasses = [];
/** Flag set when the shutdown handler is run */
protected bool $isShuttingDown = false;
- protected Map $results;
-
- public function __construct(string ...$handlerClasses) {
- $this->handlerClasses = $handlerClasses;
- $this->results = new Map();
+ public function __construct(Handler ...$handlers) {
+ $this->handlers = $handlers;
set_error_handler([ $this, 'handleError' ]);
set_exception_handler([ $this, 'handleThrowable' ]);
@@ -69,30 +65,29 @@ class Catcher {
*/
public function handleThrowable(\Throwable $throwable): void {
$controller = new ThrowableController($throwable);
- foreach ($this->handlerClasses as $h) {
- $handler = $h::create($controller);
- $code = $handler->getOutputCode();
-
- if ($code & Handler::OUTPUT_NOW) {
- $handler->output();
- } else {
- $this->handlers[] = $handler;
+ foreach ($this->handlers as $h) {
+ $output = $h->handle($controller);
+ if ($output->outputCode & Handler::OUTPUT_NOW) {
+ $h->dispatch();
}
- if ($code & Handler::CONTINUE) {
+ $controlCode = $output->controlCode;
+ if ($controlCode !== Handler::CONTINUE) {
break;
}
}
- /*if ($this->isShuttingDown || $throwable instanceof \Exception || in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ]) {
-
- }*/
-
if (
- $this->isShuttingDown ||
$throwable instanceof \Exception ||
- in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ])
+ ($throwable instanceof Error && in_array($throwable->getCode(), [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR ])) ||
+ $throwable instanceof \Error
) {
+ foreach ($this->handlers as $h) {
+ $h->dispatch();
+ }
+
+ exit($throwable->getCode());
+ } elseif ($controlCode === Handler::EXIT) {
exit($throwable->getCode());
}
}
@@ -104,8 +99,10 @@ class Catcher {
*/
public function handleShutdown() {
$this->isShuttingDown = true;
- if (error_get_last() && in_array($error['type'], [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_CORE_WARNING, \E_COMPILE_ERROR, \E_COMPILE_WARNING ])) {
- $this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
+ if ($error = error_get_last()) {
+ if (in_array($error['type'], [ \E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_CORE_WARNING, \E_COMPILE_ERROR, \E_COMPILE_WARNING ])) {
+ $this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
+ }
}
}
diff --git a/lib/Catcher/HTMLHandler.php b/lib/Catcher/HTMLHandler.php
index 32a9346..f20486b 100644
--- a/lib/Catcher/HTMLHandler.php
+++ b/lib/Catcher/HTMLHandler.php
@@ -6,81 +6,145 @@
*/
declare(strict_types=1);
-namespace Mensbeam\Framework\Catcher;
+namespace MensBeam\Framework\Catcher;
class HTMLHandler extends Handler {
public const CONTENT_TYPE = 'text/html';
- /** The number of backtrace frames in which to print arguments; defaults to 5 */
- protected static int $_backtraceArgFrameLimit = 5;
- /** If true the handler will output backtraces; defaults to false */
- protected static bool $_outputBacktrace = false;
- /** If true the handler will output previous throwables; defaults to true */
- protected static bool $_outputPrevious = true;
+ protected ?\DOMDocument $_document = null;
+ protected static array $bullshit = [];
/** If true the handler will output times to the output; defaults to true */
- protected static bool $_outputTime = true;
+ protected bool $_outputTime = true;
/** The PHP-standard date format which to use for times printed to output */
- protected static string $_timeFormat = 'H:i:s';
+ protected string $_timeFormat = 'H:i:s';
- public static function handle(\Throwable $throwable, ThrowableController $controller): bool {
- $document = new \DOMDocument();
- $frag = $document->createDocumentFragment();
+ public function __construct(array $options = []) {
+ parent::__construct($options);
+
+ if ($this->_document === null) {
+ $this->_document = new \DOMDocument();
+ $this->_document->loadHTML(<<
+
+
HTTP {$this->_httpCode}
+
+
+ HTML);
+ }
+ }
+
+
+
+
+ protected function buildThrowable(ThrowableController $controller): \DOMElement {
+ $throwable = $controller->getThrowable();
+ $p = $this->_document->createElement('p');
+
+ $class = $throwable::class;
+ if ($throwable instanceof \Error) {
+ $type = $controller->getErrorType();
+ if ($type !== null) {
+ $b = $this->_document->createElement('b');
+ $b->appendChild($this->_document->createTextNode($type));
+ $p->appendChild($b);
+ $p->appendChild($this->_document->createTextNode(' ('));
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode($throwable::class));
+ $p->appendChild($code);
+ $p->appendChild($this->_document->createTextNode(')'));
+ } else {
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode($throwable::class));
+ $p->appendChild($code);
+ }
+ }
+
+ $p->appendChild($this->_document->createTextNode(': '));
+ $i = $this->_document->createElement('i');
+ $i->appendChild($this->_document->createTextNode($throwable->getMessage()));
+ $p->appendChild($i);
+ $p->appendChild($this->_document->createTextNode(' in file '));
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode($throwable->getFile()));
+ $p->appendChild($code);
+ $p->appendChild($this->_document->createTextNode(' on line ' . $throwable->getLine()));
+ return $p;
+ }
+
+ protected function dispatchCallback(): void {
+ $body = $this->_document->getElementsByTagName('body')[0];
+ foreach ($this->outputBuffer as $o) {
+ if ($o->outputCode & self::SILENT) {
+ continue;
+ }
+
+ $body->appendChild($o->output);
+ }
- if (self::$_outputTime && self::$_timeFormat !== '') {
- $p = $document->createElement('p');
- $time = $document->createElement('time');
- $time->appendChild($document->createTextNode((new \DateTime())->format(self::$_timeFormat)));
+ $output = $this->_document->saveHTML();
+ if (\PHP_SAPI === 'CLI') {
+ fprintf(\STDERR, "$output\n");
+ } else {
+ echo $output;
+ }
+ }
+
+ protected function handleCallback(ThrowableController $controller): HandlerOutput {
+ $frag = $this->_document->createDocumentFragment();
+
+ if ($this->_outputTime && $this->_timeFormat !== '') {
+ $p = $this->_document->createElement('p');
+ $time = $this->_document->createElement('time');
+ $time->appendChild($this->_document->createTextNode((new \DateTime())->format($this->_timeFormat)));
$p->appendChild($time);
$frag->appendChild($p);
}
- $frag->appendChild(self::$buildThrowable($document, $throwable, $controller));
- if (self::$_outputPrevious) {
- $prev = $throwable->getPrevious();
+ $frag->appendChild($this->buildThrowable($controller));
+ if ($this->_outputPrevious) {
$prevController = $controller->getPrevious();
- while ($prev) {
- $p = $document->createElement('p');
- $small = $document->createElement('small');
- $small->appendChild($document->createTextNode('Caused by ↴'));
+ while ($prevController) {
+ $p = $this->_document->createElement('p');
+ $small = $this->_document->createElement('small');
+ $small->appendChild($this->_document->createTextNode('Caused by ↴'));
$p->appendChild($small);
$frag->appendChild($p);
- $frag->appendChild(self::$buildThrowable($document, $prev, $prevController));
- $prev = $prev->getPrevious();
+ $frag->appendChild($this->buildThrowable($prevController));
$prevController = $prevController->getPrevious();
}
}
- if (self::$_outputBacktrace) {
+ if ($this->_outputBacktrace) {
$frames = $controller->getFrames();
- $p = $document->createElement('p');
- $p->appendChild($document->createTextNode('Stack trace:'));
+ $p = $this->_document->createElement('p');
+ $p->appendChild($this->_document->createTextNode('Stack trace:'));
$frag->appendChild($p);
if (count($frames) > 0) {
- $ol = $document->createElement('ol');
+ $ol = $this->_document->createElement('ol');
$p->appendChild($ol);
$num = 1;
foreach ($frames as $frame) {
- $li = $document->createElement('li');
- $args = (!empty($frame['args']) && self::$_backtraceArgFrameLimit >= $num);
- $t = ($args) ? $document->createElement('p') : $li;
+ $li = $this->_document->createElement('li');
+ $args = (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num);
+ $t = ($args) ? $this->_document->createElement('p') : $li;
if (!empty($frame['error'])) {
- $b = $document->createElement('b');
- $b->appendChild($document->createTextNode($frame['error']));
+ $b = $this->_document->createElement('b');
+ $b->appendChild($this->_document->createTextNode($frame['error']));
$t->appendChild($b);
- $t->appendChild($document->createTextNode(' ('));
- $code = $document->createElement('code');
- $code->appendChild($document->createTextNode($frame['class']));
+ $t->appendChild($this->_document->createTextNode(' ('));
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode($frame['class']));
$t->appendChild($code);
- $t->appendChild($document->createTextNode(')'));
+ $t->appendChild($this->_document->createTextNode(')'));
} elseif (!empty($frame['class'])) {
- $code = $document->createElement('code');
- $code->appendChild($document->createTextNode($frame['class']));
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode($frame['class']));
$t->appendChild($code);
}
@@ -90,22 +154,22 @@ class HTMLHandler extends Handler {
if ($class) {
$code->firstChild->textContent .= "::{$function}()";
} else {
- $code = $document->createElement('code');
- $code->appendChild($document->createTextNode("{$function}()"));
+ $code = $this->_document->createElement('code');
+ $code->appendChild($this->_document->createTextNode("{$function}()"));
$t->appendChild($code);
}
}
- $t->appendChild($document->createTextNode(' '));
- $i = $document->createElement('i');
- $i->appendChild($document->createTextNode($frame['file']));
+ $t->appendChild($this->_document->createTextNode(' '));
+ $i = $this->_document->createElement('i');
+ $i->appendChild($this->_document->createTextNode($frame['file']));
$t->appendChild($i);
- $t->appendChild($document->createTextNode(":{$frame['line']}"));
+ $t->appendChild($this->_document->createTextNode(":{$frame['line']}"));
if ($args) {
$li->appendChild($t);
- $pre = $document->createElement('pre');
- $pre->appendChild($document->createTextNode(var_export($frame['args'], true)));
+ $pre = $this->_document->createElement('pre');
+ $pre->appendChild($this->_document->createTextNode(var_export($frame['args'], true)));
$li->appendChild($pre);
}
@@ -114,57 +178,6 @@ class HTMLHandler extends Handler {
}
}
- $this->result = $frag;
-
- if (\PHP_SAPI !== 'cli' && self::$_output) {
- $document->loadHTML(sprintf(
- '%s%s',
- (isset($_SERVER['protocol'])) ? "{$_SERVER['protocol']} " : '',
- '500 Internal Server Error'
- ));
- $document->getElementsByTagName('body')[0]->appendChild($document->importNode($frag, true));
-
- self::$sendContentTypeHeader();
- http_response_code(500);
- echo $document->saveHTML();
- return (!self::$_passthrough);
- }
-
- return false;
- }
-
-
- protected static function buildThrowable(\DOMDocument $document, \Throwable $throwable, ThrowableController $controller): \DOMElement {
- $p = $document->createElement('p');
-
- $class = $throwable::class;
- if ($throwable instanceof \Error) {
- $type = $controller->getErrorType();
- if ($type !== null) {
- $b = $document->createElement('b');
- $b->appendChild($document->createTextNode($type));
- $p->appendChild($b);
- $p->appendChild($document->createTextNode(' ('));
- $code = $document->createElement('code');
- $code->appendChild($document->createTextNode($throwable::class));
- $p->appendChild($code);
- $p->appendChild($document->createTextNode(')'));
- } else {
- $code = $document->createElement('code');
- $code->appendChild($document->createTextNode($throwable::class));
- $p->appendChild($code);
- }
- }
-
- $p->appendChild($document->createTextNode(': '));
- $i = $document->createElement('i');
- $i->appendChild($document->createTextNode($throwable->getMessage()));
- $p->appendChild($i);
- $p->appendChild($document->createTextNode(' in file '));
- $code = $document->createElement('code');
- $code->appendChild($document->createTextNode($throwable->getFile()));
- $p->appendChild($code);
- $p->appendChild($document->createTextNode(' on line ' . $throwable->getLine()));
- return $p;
+ return new HandlerOutput($this->getControlCode(), $this->getOutputCode(), $frag);
}
}
\ No newline at end of file
diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php
index 6e054d3..c667f0f 100644
--- a/lib/Catcher/Handler.php
+++ b/lib/Catcher/Handler.php
@@ -6,46 +6,87 @@
*/
declare(strict_types=1);
-namespace Mensbeam\Framework\Catcher;
+namespace MensBeam\Framework\Catcher;
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 = 16;
public const OUTPUT_NOW = 32;
public const SILENT = 64;
protected ThrowableController $controller;
- protected array $data;
- /** The handler's result (bitmask) */
- protected int $outputCode;
/**
- * If true the handler will break the handler stack and won't continue onto the
- * next handler regardless
+ * Array of HandlerOutputs the handler creates
+ *
+ * @var HandlerOutput[]
+ */
+ protected array $outputBuffer = [];
+ /**
+ * Array of option property names; used when overloading
+ *
+ * @var string[]
*/
- protected static bool $_forceBreak = false;
+ protected array $optionNames;
+
+ /** The number of backtrace frames in which to print arguments; defaults to 5 */
+ protected int $_backtraceArgFrameLimit = 5;
+ /**
+ * 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 continue onto the next handler regardless */
- protected static bool $_forceContinue = false;
+ protected bool $_forceContinue = false;
/** If true the handler will force an exit */
- protected static bool $_forceExit = false;
+ protected bool $_forceExit = false;
/**
* If true the handler will output as soon as possible; however, if silent
* is true the handler will output nothing
*/
- protected static bool $_forceOutputNow = false;
+ protected bool $_forceOutputNow = false;
+ /** The HTTP code to be sent */
+ protected int $_httpCode = 500;
+ /** 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 */
+ protected bool $_outputPrevious = true;
/** If true the handler will be silent and won't output */
- protected static bool $_silent = false;
+ protected bool $_silent = false;
+
+
+
+ public function __construct(array $options = []) {
+ foreach ($options as $key => $value) {
+ $key = "_$key";
+ if ($key === '_httpCode' && is_int($value) && ($value < 400 || $value >= 600)) {
+ throw new \InvalidArgumentException('Option "httpCode" can only be an integer between 400 and 599');
+ }
+ $this->$key = $value;
+ }
+ $properties = (new \ReflectionClass($this))->getProperties(\ReflectionProperty::IS_PROTECTED);
+ $this->optionNames = [];
+ foreach ($properties as $p) {
+ $name = $p->getName();
+ if ($name[0] === '_') {
+ $this->optionNames[] = $name;
+ }
+ }
+ }
- protected function __construct(ThrowableController $controller, array $data = []) {
+ /*protected function __construct(ThrowableController $controller, array $data = []) {
$this->controller = $controller;
$this->data = $data;
@@ -73,30 +114,80 @@ abstract class Handler {
$this->outputCode |= ($this->outputCode === self::SILENT) ? self::CONTINUE : self::BREAK;
return;
- }
-
+ }*/
+ public function dispatch(): void {
+ if (count($this->outputBuffer) === 0) {
+ return;
+ }
- public static function config(array $config = []) {
- foreach ($config as $key => $value) {
- $key = "_$key";
- self::$$key = $value;
+ // Send the headers if possible and necessary
+ if (isset($_SERVER['REQUEST_URI'])) {
+ if (!headers_sent()) {
+ header_remove('location');
+ header(sprintf('Content-type: %s; charset=%s', static::CONTENT_TYPE, $this->_charset));
+ }
+ http_response_code($this->_httpCode);
}
- return __CLASS__;
+ $this->dispatchCallback();
+ $this->outputBuffer = [];
+ }
+
+ public function handle(ThrowableController $controller): HandlerOutput {
+ $output = $this->handleCallback($controller);
+ $this->outputBuffer[] = $output;
+ return $output;
}
- abstract public static function create(ThrowableController $controller): self;
+ abstract protected function dispatchCallback(): void;
- public function getOutputCode(): int {
- return $this->outputCode;
+
+ /*protected function createOutput(mixed $output): HandlerOutput {
+ return new HandlerOutput($this->getControlCode(), $this->getOutputCode(), $output);
+ }*/
+
+ protected function getControlCode(): int {
+ $code = self::BREAK;
+ if ($this->_forceExit) {
+ $code = self::EXIT;
+ } elseif ($this->_forceContinue) {
+ $code = self::CONTINUE;
+ }
+
+ return $code;
}
- public function getThrowable(): \Throwable {
- return $this->throwable;
+ protected function getOutputCode(): int {
+ $code = self::OUTPUT;
+ if ($this->_silent) {
+ $code = self::SILENT;
+ if ($this->_forceOutputNow) {
+ $code |= self::OUTPUT_NOW;
+ }
+ } elseif ($this->_forceOutputNow) {
+ $code = self::OUTPUT_NOW;
+ }
+
+ return $code;
+ }
+
+ abstract protected function handleCallback(ThrowableController $controller): HandlerOutput;
+
+
+ /*public function __get(string $name): mixed {
+ $name = "_$name";
+ if (in_array($name, $this->optionNames)) {
+ return $this->$name;
+ }
}
- abstract public function output(): void;
+ public function __set(string $name, mixed $value): void {
+ $name = "_$name";
+ if (in_array($name, $this->optionNames)) {
+ $this->$name = $value;
+ }
+ }*/
}
\ No newline at end of file
diff --git a/lib/Catcher/HandlerOutput.php b/lib/Catcher/HandlerOutput.php
new file mode 100644
index 0000000..6a39c77
--- /dev/null
+++ b/lib/Catcher/HandlerOutput.php
@@ -0,0 +1,23 @@
+controlCode = $controlCode;
+ $this->outputCode = $outputCode;
+ $this->output = $output;
+ }
+}
\ No newline at end of file
diff --git a/lib/Catcher/PlainTextHandler.php b/lib/Catcher/PlainTextHandler.php
index 6ca37e0..21081c9 100644
--- a/lib/Catcher/PlainTextHandler.php
+++ b/lib/Catcher/PlainTextHandler.php
@@ -6,151 +6,68 @@
*/
declare(strict_types=1);
-namespace Mensbeam\Framework\Catcher;
-use \Psr\Log\{
- LoggerAwareInterface,
- LoggerInterface
-};
+namespace MensBeam\Framework\Catcher;
+use \Psr\Log\LoggerInterface;
-class PlainTextHandler extends Handler implements LoggerAwareInterface {
+class PlainTextHandler extends Handler {
public const CONTENT_TYPE = 'text/plain';
- /** The number of backtrace frames in which to print arguments; defaults to 5 */
- protected static int $_backtraceArgFrameLimit = 5;
/** The PSR-3 compatible logger in which to log to; defaults to null (no logging) */
- protected static ?LoggerInterface $_logger = null;
- /** If true the handler will output backtraces; defaults to false */
- protected static bool $_outputBacktrace = false;
- /** If true the handler will output previous throwables; defaults to true */
- protected static bool $_outputPrevious = true;
- /**
- * If true the handler will output times to the output. This is ignored by the
- * logger which should have its own timestamping methods; defaults to true
- */
- protected static bool $_outputTime = true;
- /** The PHP-standard date format which to use for times printed to output */
- protected static string $_timeFormat = '[H:i:s]';
-
-
- public static function create(ThrowableController $controller): self {
- $message = null;
- if (self::$_logger !== null) {
- $message = self::serialize($controller);
- self::$log($controller->getThrowable(), $message);
- }
-
- return new self(
- controller: $controller,
- data: [ 'message' => $message ]
- );
- }
-
- public function output(): void {
- $message = $this->data['message'] ?? self::serialize($this->controller);
- if (self::$_outputTime && self::$_timeFormat !== '') {
- $time = (new \DateTime())->format(self::$_timeFormat) . ' ';
- $timeStrlen = strlen($time);
-
- $message = preg_replace('/^/m', str_repeat(' ', $timeStrlen), $message);
- $message = preg_replace('/^ {' . $timeStrlen . '}/', $time, $message);
- }
+ protected ?LoggerInterface $_logger = null;
+ /** If true the handler will output times to the output; defaults to true */
+ protected bool $_outputTime = true;
+ /** The PHP-standard date format which to use for timestamps in output */
+ protected string $_timeFormat = '[H:i:s]';
- if (!Catcher::isHTTPRequest()) {
- fprintf(\STDERR, "$message\n");
- } else {
- echo "$message\n";
- }
- }
-
- public function getOutputCode(): int {
- if ($this->outputCode !== null) {
- return $this->outputCode;
- }
-
- $code = parent::getOutputCode();
- // When the sapi is CLI we want to output as soon as possible if
- $this->outputCode = ($code & self::OUTPUT === 0 && \PHP_SAPI === 'CLI') ? $code &~ self::OUTPUT | self::OUTPUT_NOW : $code;
- return $this->outputCode;
- }
-
-
- protected static function log(\Throwable $throwable, string $message): void {
- if ($throwable instanceof \Error) {
- switch ($throwable->getCode()) {
- case \E_NOTICE:
- case \E_USER_NOTICE:
- case \E_STRICT:
- self::$_logger->notice($message);
- break;
- case \E_WARNING:
- case \E_COMPILE_WARNING:
- case \E_USER_WARNING:
- case \E_DEPRECATED:
- case \E_USER_DEPRECATED:
- self::$_logger->warning($message);
- break;
- case \E_RECOVERABLE_ERROR:
- self::$_logger->error($message);
- break;
- case \E_PARSE:
- case \E_CORE_ERROR:
- case \E_COMPILE_ERROR:
- self::$_logger->alert($message);
- break;
+ protected function dispatchCallback(): void {
+ foreach ($this->outputBuffer as $o) {
+ if ($o->outputCode & self::SILENT) {
+ continue;
}
- } elseif ($throwable instanceof \Exception) {
- if ($throwable instanceof \PharException || $throwable instanceof \RuntimeException) {
- self::$_logger->alert($message);
+
+ if (\PHP_SAPI === 'CLI') {
+ fprintf(\STDERR, "{$o->output}\n");
+ } else {
+ echo "{$o->output}\n";
}
- } else {
- self::$_logger->critical($message);
}
}
- protected function prependTimestamps(string $message): string {
- $time = (new \DateTime())->format(self::$_timeFormat) . ' ';
- $timeStrlen = strlen($time);
-
- $message = preg_replace('/^/m', str_repeat(' ', $timeStrlen), $message);
- return preg_replace('/^ {' . $timeStrlen . '}/', $time, $message);
- }
-
- protected static function serialize(ThrowableController $controller): string {
- $message = self::$serializeThrowable($controller);
- if (self::$_outputPrevious) {
- $prev = $throwable->getPrevious();
+ protected function handleCallback(ThrowableController $controller): HandlerOutput {
+ $output = $this->serializeThrowable($controller);
+ if ($this->_outputPrevious) {
$prevController = $controller->getPrevious();
- while ($prev) {
- $message .= sprintf("\n\nCaused by ↴\n%s", self::$serializeThrowable($prev, $prevController));
- $prev = $prev->getPrevious();
+ while ($prevController) {
+ $output .= sprintf("\n\nCaused by ↴\n%s", $this->serializeThrowable($prevController));
$prevController = $prevController->getPrevious();
}
}
- if (self::$_outputBacktrace) {
+ if ($this->_outputBacktrace) {
$frames = $controller->getFrames();
- $message .= "\nStack trace:";
+ $output .= "\nStack trace:";
$num = 1;
+ $maxDigits = strlen((string)count($frames));
foreach ($frames as $frame) {
$class = (!empty($frame['error'])) ? "{$frame['error']} ({$frame['class']})" : $frame['class'] ?? '';
$function = $frame['function'] ?? '';
$args = '';
- if (!empty($frame['args']) && self::$_backtraceArgFrameLimit >= $num) {
- $args = "\n" . preg_replace('/^/m', str_repeat(' ', strlen((string)$num) + 2) . '| ', var_export($frame['args'], true));
+ if (!empty($frame['args']) && $this->_backtraceArgFrameLimit >= $num) {
+ $args = "\n" . preg_replace('/^/m', str_repeat(' ', $maxDigits) . '| ', var_export($frame['args'], true));
}
- $template = "\n%3d. %s";
+ $template = "\n%{$maxDigits}d. %s";
if ($class && $function) {
$template .= '::';
}
$template .= ($function) ? '%s()' : '%s';
$template .= ' %s:%d%s';
- $message .= sprintf(
+ $output .= sprintf(
"$template\n",
$num++,
$class,
@@ -159,17 +76,68 @@ class PlainTextHandler extends Handler implements LoggerAwareInterface {
$frame['line'],
$args
);
+
+ die($output);
}
}
- return $message;
+ // The logger will handle timestamps itself.
+ if ($this->_logger !== null) {
+ $this->log($controller->getThrowable(), $message);
+ }
+
+ if (!$this->_silent && $this->_outputTime && $this->_timeFormat !== '') {
+ $time = (new \DateTime())->format($this->_timeFormat) . ' ';
+ $timeStrlen = strlen($time);
+
+ $output = preg_replace('/^/m', str_repeat(' ', $timeStrlen), $output);
+ $output = preg_replace('/^ {' . $timeStrlen . '}/', $time, $output);
+ }
+
+ $outputCode = $this->getOutputCode();
+ return new HandlerOutput($this->getControlCode(), ($outputCode === self::OUTPUT && \PHP_SAPI === 'CLI') ? self::OUTPUT_NOW : $outputCode, $output);
+ }
+
+
+
+ protected function log(\Throwable $throwable, string $message): void {
+ if ($throwable instanceof \Error) {
+ switch ($throwable->getCode()) {
+ case \E_NOTICE:
+ case \E_USER_NOTICE:
+ case \E_STRICT:
+ $this->_logger->notice($message);
+ break;
+ case \E_WARNING:
+ case \E_COMPILE_WARNING:
+ case \E_USER_WARNING:
+ case \E_DEPRECATED:
+ case \E_USER_DEPRECATED:
+ $this->_logger->warning($message);
+ break;
+ case \E_RECOVERABLE_ERROR:
+ $this->_logger->error($message);
+ break;
+ case \E_PARSE:
+ case \E_CORE_ERROR:
+ case \E_COMPILE_ERROR:
+ $this->_logger->alert($message);
+ break;
+ default: $this->_logger->critical($message);
+ }
+ } elseif ($throwable instanceof \Exception && ($throwable instanceof \PharException || $throwable instanceof \RuntimeException)) {
+ $this->_logger->alert($message);
+ } else {
+ $this->_logger->critical($message);
+ }
}
- protected static function serializeThrowable(ThrowableController $controller): string {
+ protected function serializeThrowable(ThrowableController $controller): string {
$throwable = $controller->getThrowable();
$class = $throwable::class;
- if ($throwable instanceof \Error) {
+ if ($throwable instanceof Error) {
$type = $controller->getErrorType();
+ $type = ($throwable instanceof Error) ? $controller->getErrorType() : null;
$class = ($type !== null) ? "$type (" . $throwable::class . ")" : $throwable::class;
}
diff --git a/lib/Catcher/ThrowableController.php b/lib/Catcher/ThrowableController.php
index 9a1e85c..c523dc3 100644
--- a/lib/Catcher/ThrowableController.php
+++ b/lib/Catcher/ThrowableController.php
@@ -6,7 +6,7 @@
*/
declare(strict_types=1);
-namespace Mensbeam\Framework\Catcher;
+namespace MensBeam\Framework\Catcher;
class ThrowableController {
@@ -31,7 +31,7 @@ class ThrowableController {
return $this->errorType;
}
- if (!$this->throwable instanceof \Error) {
+ if (!$this->throwable instanceof Error) {
$this->errorType = null;
return null;
}
diff --git a/lib/Error.php b/lib/Error.php
index a69d753..85abb4f 100644
--- a/lib/Error.php
+++ b/lib/Error.php
@@ -6,7 +6,7 @@
*/
declare(strict_types=1);
-namespace Mensbeam\Framework;
+namespace MensBeam\Framework;
class Error extends \Error {
public function __construct(string $message = '', int $code = 0, ?string $file = null, ?int $line = line, ?\Throwable $previous = null) {