slickframework /
error-handler
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * This file is part of error-handler |
||
| 5 | * |
||
| 6 | * For the full copyright and license information, please view the LICENSE |
||
| 7 | * file that was distributed with this source code. |
||
| 8 | */ |
||
| 9 | |||
| 10 | declare(strict_types=1); |
||
| 11 | |||
| 12 | namespace Slick\ErrorHandler; |
||
| 13 | |||
| 14 | use ErrorException; |
||
| 15 | use Slick\ErrorHandler\Exception\ExceptionInspector; |
||
| 16 | use Slick\ErrorHandler\Handler\HandlerInterface; |
||
| 17 | use Slick\ErrorHandler\Util\SystemFacade; |
||
| 18 | use Throwable; |
||
| 19 | |||
| 20 | /** |
||
| 21 | * Runner |
||
| 22 | * |
||
| 23 | * @package Slick\ErrorHandler |
||
| 24 | */ |
||
| 25 | final class Runner implements RunnerInterface |
||
| 26 | { |
||
| 27 | |||
| 28 | /** @var array<callable|HandlerInterface> */ |
||
| 29 | private array $handlers = []; |
||
| 30 | |||
| 31 | public function __construct(private readonly SystemFacade $system, private readonly ?string $path = '/') |
||
| 32 | { |
||
| 33 | } |
||
| 34 | |||
| 35 | /** |
||
| 36 | * @inheritDoc |
||
| 37 | */ |
||
| 38 | public function pushHandler(callable|HandlerInterface $handler): self |
||
| 39 | { |
||
| 40 | $this->handlers[] = $handler; |
||
| 41 | return $this; |
||
| 42 | } |
||
| 43 | |||
| 44 | /** |
||
| 45 | * @inheritDoc |
||
| 46 | */ |
||
| 47 | public function getHandlers(): array |
||
| 48 | { |
||
| 49 | return $this->handlers; |
||
| 50 | } |
||
| 51 | |||
| 52 | /** |
||
| 53 | * @inheritDoc |
||
| 54 | */ |
||
| 55 | public function clearHandlers(): self |
||
| 56 | { |
||
| 57 | $this->handlers = []; |
||
| 58 | return $this; |
||
| 59 | } |
||
| 60 | |||
| 61 | /** |
||
| 62 | * @inheritDoc |
||
| 63 | */ |
||
| 64 | public function register(): self |
||
| 65 | { |
||
| 66 | $this->system->setExceptionHandler([$this, 'handleException']); |
||
| 67 | $this->system->setErrorHandler([$this, 'handleError']); |
||
| 68 | $this->system->registerShutdownFunction([$this, 'handleShutdown']); |
||
| 69 | return $this; |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * @inheritDoc |
||
| 74 | */ |
||
| 75 | public function unregister(): self |
||
| 76 | { |
||
| 77 | $this->system->restoreExceptionHandler(); |
||
| 78 | $this->system->restoreErrorHandler(); |
||
| 79 | return $this; |
||
| 80 | } |
||
| 81 | |||
| 82 | /** |
||
| 83 | * @inheritDoc |
||
| 84 | */ |
||
| 85 | public function handleException(Throwable $exception): string |
||
| 86 | { |
||
| 87 | $inspector = new ExceptionInspector($exception, (string) $this->path); |
||
| 88 | $this->system->startOutputBuffering(); |
||
| 89 | // Just in case there are no handlers: |
||
| 90 | $handlerResponse = null; |
||
| 91 | |||
| 92 | try { |
||
| 93 | foreach (array_reverse($this->handlers) as $handler) { |
||
| 94 | $handlerResponse = is_callable($handler) |
||
| 95 | ? $handler($exception, $inspector, $this) |
||
| 96 | : $handler->handle($exception, $inspector, $this); |
||
| 97 | |||
| 98 | if (in_array($handlerResponse, [HandlerInterface::LAST_HANDLER, HandlerInterface::QUIT])) { |
||
| 99 | break; |
||
| 100 | } |
||
| 101 | } |
||
| 102 | } finally { |
||
| 103 | $output = $this->system->cleanOutputBuffer(); |
||
| 104 | } |
||
| 105 | |||
| 106 | if ($handlerResponse === HandlerInterface::QUIT) { |
||
| 107 | // Cleanup all other output buffers before sending our output: |
||
| 108 | while ($this->system->getOutputBufferLevel() > 0) { |
||
| 109 | $this->system->endOutputBuffering(); |
||
| 110 | } |
||
| 111 | |||
| 112 | echo $output; |
||
| 113 | $this->system->flushOutputBuffer(); |
||
| 114 | $this->system->stopExecution(1); |
||
| 115 | } |
||
| 116 | |||
| 117 | return is_string($output) ? $output : ''; |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 118 | } |
||
| 119 | |||
| 120 | /** |
||
| 121 | * @inheritDoc |
||
| 122 | */ |
||
| 123 | public function handleError(int $level, string $message, ?string $file = null, ?int $line = null): bool |
||
| 124 | { |
||
| 125 | $shouldHandle = $level & $this->system->getErrorReportingLevel(); |
||
| 126 | if (!($shouldHandle)) { |
||
| 127 | // Propagate error to the next handler, allows error_get_last() to |
||
| 128 | // work on silenced errors. |
||
| 129 | return false; |
||
| 130 | } |
||
| 131 | |||
| 132 | $exception = new ErrorException($this->clearMessage($message), 0, $level, $file, $line); |
||
| 133 | $this->handleException($exception); |
||
| 134 | return true; |
||
| 135 | } |
||
| 136 | |||
| 137 | /** |
||
| 138 | * @inheritDoc |
||
| 139 | */ |
||
| 140 | public function handleShutdown(): void |
||
| 141 | { |
||
| 142 | $error = $this->system->getLastError(); |
||
| 143 | if ($error && $this->isLevelFatal($error['type'])) { |
||
| 144 | $this->handleError($error['type'], $error['message'], $error['file'], $error['line']); |
||
| 145 | } |
||
| 146 | } |
||
| 147 | |||
| 148 | /** |
||
| 149 | * Determine if an error level is fatal (halts execution) |
||
| 150 | * |
||
| 151 | * @param int $level |
||
| 152 | * @return bool |
||
| 153 | */ |
||
| 154 | private function isLevelFatal($level): bool |
||
| 155 | { |
||
| 156 | $errors = E_ERROR; |
||
| 157 | $errors |= E_PARSE; |
||
| 158 | $errors |= E_CORE_ERROR; |
||
| 159 | $errors |= E_CORE_WARNING; |
||
| 160 | $errors |= E_COMPILE_ERROR; |
||
| 161 | $errors |= E_COMPILE_WARNING; |
||
| 162 | return ($level & $errors) > 0; |
||
| 163 | } |
||
| 164 | |||
| 165 | /** |
||
| 166 | * @inheritDoc |
||
| 167 | * @param array<string, string> $headers |
||
| 168 | */ |
||
| 169 | public function outputHeaders(array $headers): void |
||
| 170 | { |
||
| 171 | $this->system->sendHeaders($headers); |
||
| 172 | } |
||
| 173 | |||
| 174 | public function sendResponseCode(int $code): void |
||
| 175 | { |
||
| 176 | $this->system->setHttpResponseCode($code); |
||
| 177 | } |
||
| 178 | |||
| 179 | private function clearMessage(string $message): string |
||
| 180 | { |
||
| 181 | $parts = explode("in /", $message, 2); |
||
| 182 | return trim($parts[0]); |
||
| 183 | } |
||
| 184 | } |
||
| 185 |