1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Conia\Error; |
||
6 | |||
7 | use ErrorException; |
||
8 | use Psr\Http\Message\ResponseFactoryInterface as ResponseFactory; |
||
9 | use Psr\Http\Message\ResponseInterface as Response; |
||
10 | use Psr\Http\Message\ServerRequestInterface as Request; |
||
11 | use Psr\Http\Server\MiddlewareInterface as Middleware; |
||
12 | use Psr\Http\Server\RequestHandlerInterface as RequestHandler; |
||
13 | use Psr\Log\LoggerInterface as Logger; |
||
14 | use Throwable; |
||
15 | |||
16 | /** @psalm-api */ |
||
17 | class Handler implements Middleware |
||
18 | { |
||
19 | protected ?Logger $logger = null; |
||
20 | protected ?DebugHandler $debugHandler = null; |
||
21 | |||
22 | /** @var RendererEntry[] */ |
||
23 | protected array $renderers = []; |
||
24 | |||
25 | protected ?RendererEntry $defaultRenderer = null; |
||
26 | |||
27 | 17 | public function __construct( |
|
28 | protected readonly ResponseFactory $responseFactory, |
||
29 | protected readonly bool $debug = false, |
||
30 | ) { |
||
31 | 17 | set_error_handler([$this, 'handleError'], E_ALL); |
|
32 | 17 | set_exception_handler([$this, 'emitException']); |
|
33 | } |
||
34 | |||
35 | 1 | public function debugHandler(DebugHandler $debugHandler): void |
|
36 | { |
||
37 | 1 | $this->debugHandler = $debugHandler; |
|
38 | } |
||
39 | |||
40 | 17 | public function __destruct() |
|
41 | { |
||
42 | 17 | restore_error_handler(); |
|
43 | 17 | restore_exception_handler(); |
|
44 | } |
||
45 | |||
46 | 3 | public function logger(?Logger $logger = null): void |
|
47 | { |
||
48 | 3 | $this->logger = $logger; |
|
49 | } |
||
50 | |||
51 | 1 | public function process(Request $request, RequestHandler $handler): Response |
|
52 | { |
||
53 | try { |
||
54 | 1 | return $handler->handle($request); |
|
55 | 1 | } catch (Throwable $e) { |
|
56 | 1 | return $this->getResponse($e, $request); |
|
57 | } |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * @param class-string<Throwable>|class-string<Throwable>[] $exceptions |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||
62 | */ |
||
63 | 10 | public function renderer(Renderer $renderer, string|array|null $exceptions = null): RendererEntry |
|
64 | { |
||
65 | 10 | if ($exceptions === null) { |
|
66 | 1 | $rendererEntry = new RendererEntry([], $renderer); |
|
67 | 1 | $this->defaultRenderer = $rendererEntry; |
|
68 | |||
69 | 1 | return $rendererEntry; |
|
70 | } |
||
71 | |||
72 | 9 | $renderEntry = new RendererEntry((array)$exceptions, $renderer); |
|
73 | 9 | $this->renderers[] = $renderEntry; |
|
74 | |||
75 | 9 | return $renderEntry; |
|
76 | } |
||
77 | |||
78 | 2 | public function handleError( |
|
79 | int $level, |
||
80 | string $message, |
||
81 | string $file = '', |
||
82 | int $line = 0, |
||
83 | ): bool { |
||
84 | 2 | if ($level & error_reporting()) { |
|
85 | 1 | throw new ErrorException($message, $level, $level, $file, $line); |
|
86 | } |
||
87 | |||
88 | 1 | return false; |
|
89 | } |
||
90 | |||
91 | 2 | public function emitException(Throwable $exception): void |
|
92 | { |
||
93 | 2 | $response = $this->getResponse($exception, null); |
|
94 | |||
95 | 2 | echo (string)$response->getBody(); |
|
96 | } |
||
97 | |||
98 | 15 | public function getResponse(Throwable $exception, ?Request $request): Response |
|
99 | { |
||
100 | 15 | $renderer = null; |
|
101 | 15 | $logLevel = null; |
|
102 | |||
103 | 15 | foreach ($this->renderers as $rendererEntry) { |
|
104 | 9 | if ($rendererEntry->matches($exception)) { |
|
105 | 8 | $renderer = $rendererEntry->renderer; |
|
106 | 8 | $logLevel = $rendererEntry->getLogLevel(); |
|
107 | 8 | break; |
|
108 | } |
||
109 | } |
||
110 | |||
111 | 15 | if (!is_null($logLevel)) { |
|
112 | 1 | $this->log($logLevel, $exception); |
|
113 | } |
||
114 | |||
115 | 15 | if ($renderer) { |
|
116 | 8 | return $renderer->render( |
|
117 | 8 | $exception, |
|
118 | 8 | $this->responseFactory, |
|
119 | 8 | $request, |
|
120 | 8 | $this->debug, |
|
121 | 8 | ); |
|
122 | } |
||
123 | |||
124 | 7 | if ($this->debug) { |
|
125 | 2 | if ($this->debugHandler) { |
|
126 | 1 | return $this->debugHandler->handle($exception, $this->responseFactory); |
|
127 | } |
||
128 | |||
129 | 1 | throw $exception; |
|
130 | } |
||
131 | |||
132 | 5 | $this->logUnmatched($exception); |
|
133 | |||
134 | 5 | if ($this->defaultRenderer) { |
|
135 | 1 | return $this->defaultRenderer->renderer->render( |
|
136 | 1 | $exception, |
|
137 | 1 | $this->responseFactory, |
|
138 | 1 | $request, |
|
139 | 1 | $this->debug, |
|
140 | 1 | ); |
|
141 | } |
||
142 | |||
143 | 4 | $response = $this->responseFactory->createResponse(500)->withHeader('Content-Type', 'text/html') ; |
|
144 | 4 | $response->getBody()->write('<h1>500 Internal Server Error</h1>'); |
|
145 | |||
146 | 4 | return $response; |
|
0 ignored issues
–
show
|
|||
147 | } |
||
148 | |||
149 | 1 | protected function log(string|int $logLevel, Throwable $exception): void |
|
150 | { |
||
151 | 1 | if ($this->logger) { |
|
152 | 1 | $this->logger->log($logLevel, 'Matched Exception:', ['exception' => $exception]); |
|
153 | } |
||
154 | } |
||
155 | |||
156 | 5 | protected function logUnmatched(Throwable $exception): void |
|
157 | { |
||
158 | 5 | if ($this->logger) { |
|
159 | 1 | $this->logger->alert('Unmatched Exception:', ['exception' => $exception]); |
|
160 | } |
||
161 | } |
||
162 | } |
||
163 |