diff --git a/src/webfiori/error/AbstractExceptionHandler.php b/src/webfiori/error/AbstractHandler.php similarity index 63% rename from src/webfiori/error/AbstractExceptionHandler.php rename to src/webfiori/error/AbstractHandler.php index 5e4994c..7e12a47 100644 --- a/src/webfiori/error/AbstractExceptionHandler.php +++ b/src/webfiori/error/AbstractHandler.php @@ -7,14 +7,53 @@ * * @author Ibrahim */ -abstract class AbstractExceptionHandler { +abstract class AbstractHandler { private $exception; private $traceArr; + private $name; + private $isCalled; /** * Creates new instance of the class. */ public function __construct() { $this->traceArr = []; + $this->name = 'New Handler'; + $this->isCalled = false; + } + /** + * Sets the handler as executed. + * + * This method is used to make sure that same handler won't get executed twice. + * + * @param bool $bool True to set it as executed, false to not. + */ + public function setIsExecuted(bool $bool) { + $this->isCalled = $bool; + } + /** + * Checks if the handler was executed once or not. + * + * @return bool If the method returned true, then this means the handler + * was executed. + */ + public function isExecuted() : bool { + return $this->isCalled; + } + /** + * Gives the handler a specific name. + * + * @param string $name The custom name of the handler. + */ + public function setName(string $name) { + $this->name = trim($name); + } + /** + * Returns the name of the handler. + * + * @return string The name of the handler. + */ + public function getName() : string { + return $this->name; } /** * Returns a string that represents the name of the class that an exception @@ -65,6 +104,17 @@ public function getTrace() : array { * The developer can implement this method to handle all thrown exceptions. */ public abstract function handle(); + /** + * Checks if the handler will be used to handle errors or not. + * + * The developer must implement this method in a way it returns true if the + * handler will get executed. False otherwise. + */ + public abstract function isActive() : bool; + /** + * Checks if the handler will be called in case of error after shutdown. + */ + public abstract function isShutdownHandler() : bool; /** * Sets the exception which was thrown by an error on the code. * diff --git a/src/webfiori/error/DefaultExceptionsHandler.php b/src/webfiori/error/DefaultHandler.php similarity index 80% rename from src/webfiori/error/DefaultExceptionsHandler.php rename to src/webfiori/error/DefaultHandler.php index 49131e8..4f2a4b3 100644 --- a/src/webfiori/error/DefaultExceptionsHandler.php +++ b/src/webfiori/error/DefaultHandler.php @@ -9,12 +9,13 @@ * * @author Ibrahim */ -class DefaultExceptionsHandler extends AbstractExceptionHandler { +class DefaultHandler extends AbstractHandler { /** * Creates new instance of the class. */ public function __construct() { parent::__construct(); + $this->setName('Default'); } /** * Handles the exception. @@ -38,4 +39,13 @@ public function handle() { } echo ''; } + + public function isActive(): bool { + return true; + } + + public function isShutdownHandler(): bool { + return true; + } + } diff --git a/src/webfiori/error/Handler.php b/src/webfiori/error/Handler.php index 8be5a69..db3c800 100644 --- a/src/webfiori/error/Handler.php +++ b/src/webfiori/error/Handler.php @@ -8,6 +8,7 @@ * @author Ibrahim */ class Handler { + private $handlersPool; /** * An array which holds one constant that is used to hold the meanings of different * PHP errors. @@ -78,7 +79,7 @@ class Handler { ]; /** * - * @var AbstractExceptionHandler + * @var AbstractHandler */ private $handler; /** @@ -99,11 +100,36 @@ private function __construct() { }); set_exception_handler(function (Throwable $ex) { - $class = Handler::get()->handler; - $class->setException($ex); - $class->handle(); + foreach (Handler::get()->handlersPool as $h) { + + if ($h->isActive()) { + $h->setException($ex); + $h->handle(); + $h->setIsExecuted(true); + } + } }); - $this->handler = new DefaultExceptionsHandler(); + register_shutdown_function(function () { + $lastErr = error_get_last(); + + if ($lastErr !== null) { + ob_clean(); + $errClass = TraceEntry::extractClassName($lastErr['file']); + $errType = Handler::ERR_TYPES[$lastErr['type']]; + $message = $errType['description'].': '.$lastErr['message'].' At '.$errClass.' Line '.$lastErr['line']; + $ex = new ErrorHandlerException($message, $lastErr['type'], $lastErr['file']); + foreach (Handler::get()->handlersPool as $h) { + + if ($h->isActive() && $h->isShutdownHandler() && !$h->isExecuted()) { + $h->setException($ex); + $h->handle(); + $h->setIsExecuted(true); + } + } + } + }); + $this->handlersPool = []; + $this->handlersPool[] = new DefaultHandler(); } /** * Returns the instance which is used to handle exceptions and errors. @@ -120,19 +146,68 @@ public static function get() { /** * Sets a custom handler to handle exceptions. * - * @param AbstractExceptionHandler $h A class that implements a custom + * @param AbstractHandler $h A class that implements a custom * handler. */ - public static function setHandler(AbstractExceptionHandler $h) { - self::get()->handler = $h; + public static function registerHandler(AbstractHandler $h) { + if (!self::hasHandler($h->getName())) { + self::get()->handlersPool[] = $h; + } } /** - * Returns the handler instance which is used to handle exceptions. + * Remove a registered errors handler. * - * @return AbstractExceptionHandler The handler instance which is used to - * handle exceptions. + * @param AbstractHandler $h A class that implements a custom + * handler. */ - public function getHandler() : AbstractExceptionHandler { - return self::get()->handler; + public static function unregisterHandler(AbstractHandler $h) : bool { + $tempPool = []; + $removed = false; + foreach (self::get()->handlersPool as $handler) { + if ($handler->getName() != $h->getName()) { + $tempPool[] = $handler; + continue; + } + $removed = true; + } + self::get()->handlersPool = $tempPool; + return $removed; + } + /** + * Returns a handler given its name. + * + * @param string $name The name of the handler. + * + * @return AbstractHandler|null If a handler which has the given name is found, + * it will be returned as an object. Other than that, null is returned. + */ + public static function &getHandler(string $name) { + $h = null; + $trimmed = trim($name); + + foreach (self::get()->handlersPool as $handler) { + if ($handler->getName() == $trimmed) { + $h = $handler; + break; + } + } + return $h; + } + /** + * Checks if a handler is registered or not given its name. + * + * @param string $name The name of the handler. + * + * @return bool If such handler is registered, the method will return true. + * Other than that, the method will return false. + */ + public static function hasHandler(string $name) : bool { + $trimmed = trim($name); + foreach (self::get()->handlersPool as $handler) { + if ($handler->getName() == $trimmed) { + return true; + } + } + return false; } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1c6beaa..3fdef71 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -21,8 +21,8 @@ $classesPath = $rootDir.'src'.DS.'webfiori'.DS.'error'.DS; require_once $classesPath . 'ErrorHandlerException.php'; -require_once $classesPath . 'AbstractExceptionHandler.php'; -require_once $classesPath . 'DefaultExceptionsHandler.php'; +require_once $classesPath . 'AbstractHandler.php'; +require_once $classesPath . 'DefaultHandler.php'; require_once $classesPath . 'TraceEntry.php'; require_once $classesPath . 'Handler.php';