1 | <?php |
||||
2 | |||||
3 | namespace App\Middleware; |
||||
4 | |||||
5 | use App\Renderer\JsonRenderer; |
||||
6 | use DomainException; |
||||
7 | use Fig\Http\Message\StatusCodeInterface; |
||||
8 | use InvalidArgumentException; |
||||
9 | use Psr\Http\Message\ResponseFactoryInterface; |
||||
10 | use Psr\Http\Message\ResponseInterface; |
||||
11 | use Psr\Http\Message\ServerRequestInterface; |
||||
12 | use Psr\Http\Server\MiddlewareInterface; |
||||
13 | use Psr\Http\Server\RequestHandlerInterface; |
||||
14 | use Psr\Log\LoggerInterface; |
||||
15 | use Slim\Exception\HttpException; |
||||
16 | use Throwable; |
||||
17 | |||||
18 | final class ExceptionMiddleware implements MiddlewareInterface |
||||
19 | { |
||||
20 | private ResponseFactoryInterface $responseFactory; |
||||
21 | private JsonRenderer $renderer; |
||||
22 | private ?LoggerInterface $logger; |
||||
23 | private bool $displayErrorDetails; |
||||
24 | |||||
25 | 2 | public function __construct( |
|||
26 | ResponseFactoryInterface $responseFactory, |
||||
27 | JsonRenderer $jsonRenderer, |
||||
28 | LoggerInterface $logger = null, |
||||
29 | bool $displayErrorDetails = false, |
||||
30 | ) { |
||||
31 | 2 | $this->responseFactory = $responseFactory; |
|||
32 | 2 | $this->renderer = $jsonRenderer; |
|||
33 | 2 | $this->displayErrorDetails = $displayErrorDetails; |
|||
34 | 2 | $this->logger = $logger; |
|||
35 | } |
||||
36 | |||||
37 | 2 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface |
|||
38 | { |
||||
39 | try { |
||||
40 | 2 | return $handler->handle($request); |
|||
41 | 1 | } catch (Throwable $exception) { |
|||
42 | 1 | return $this->render($exception, $request); |
|||
43 | } |
||||
44 | } |
||||
45 | |||||
46 | 1 | private function render( |
|||
47 | Throwable $exception, |
||||
48 | ServerRequestInterface $request, |
||||
49 | ): ResponseInterface { |
||||
50 | 1 | $httpStatusCode = $this->getHttpStatusCode($exception); |
|||
51 | 1 | $response = $this->responseFactory->createResponse($httpStatusCode); |
|||
52 | |||||
53 | // Log error |
||||
54 | 1 | if (isset($this->logger)) { |
|||
55 | 1 | $this->logger->error( |
|||
56 | 1 | sprintf( |
|||
57 | 1 | '%s;Code %s;File: %s;Line: %s', |
|||
58 | 1 | $exception->getMessage(), |
|||
59 | 1 | $exception->getCode(), |
|||
60 | 1 | $exception->getFile(), |
|||
61 | 1 | $exception->getLine() |
|||
62 | 1 | ), |
|||
63 | 1 | $exception->getTrace() |
|||
64 | 1 | ); |
|||
65 | } |
||||
66 | |||||
67 | // Content negotiation |
||||
68 | 1 | if (str_contains($request->getHeaderLine('Accept'), 'application/json')) { |
|||
69 | $response = $response->withAddedHeader('Content-Type', 'application/json'); |
||||
70 | |||||
71 | // JSON |
||||
72 | return $this->renderJson($exception, $response); |
||||
73 | } |
||||
74 | |||||
75 | // HTML |
||||
76 | 1 | return $this->renderHtml($response, $exception); |
|||
77 | } |
||||
78 | |||||
79 | public function renderJson(Throwable $exception, ResponseInterface $response): ResponseInterface |
||||
80 | { |
||||
81 | $data = [ |
||||
82 | 'error' => [ |
||||
83 | 'message' => $exception->getMessage(), |
||||
84 | ], |
||||
85 | ]; |
||||
86 | |||||
87 | return $this->renderer->json($response, $data); |
||||
88 | } |
||||
89 | |||||
90 | 1 | public function renderHtml(ResponseInterface $response, Throwable $exception): ResponseInterface |
|||
91 | { |
||||
92 | 1 | $response = $response->withAddedHeader('Content-Type', 'text/html'); |
|||
93 | |||||
94 | 1 | $message = sprintf( |
|||
95 | 1 | "\n<br>Error %s (%s)\n<br>Message: %s\n<br>", |
|||
96 | 1 | $this->html((string)$response->getStatusCode()), |
|||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
97 | 1 | $this->html($response->getReasonPhrase()), |
|||
0 ignored issues
–
show
The method
getReasonPhrase() does not exist on Psr\Http\Message\MessageInterface . It seems like you code against a sub-type of Psr\Http\Message\MessageInterface such as Psr\Http\Message\ResponseInterface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
98 | 1 | $this->html($exception->getMessage()), |
|||
99 | 1 | ); |
|||
100 | |||||
101 | 1 | if ($this->displayErrorDetails) { |
|||
102 | 1 | $message .= sprintf( |
|||
103 | 1 | 'File: %s, Line: %s', |
|||
104 | 1 | $this->html($exception->getFile()), |
|||
105 | 1 | $this->html((string)$exception->getLine()) |
|||
106 | 1 | ); |
|||
107 | } |
||||
108 | |||||
109 | 1 | $response->getBody()->write($message); |
|||
110 | |||||
111 | 1 | return $response; |
|||
0 ignored issues
–
show
|
|||||
112 | } |
||||
113 | |||||
114 | 1 | private function getHttpStatusCode(Throwable $exception): int |
|||
115 | { |
||||
116 | 1 | $statusCode = StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR; |
|||
117 | |||||
118 | 1 | if ($exception instanceof HttpException) { |
|||
119 | 1 | $statusCode = $exception->getCode(); |
|||
120 | } |
||||
121 | |||||
122 | 1 | if ($exception instanceof DomainException || $exception instanceof InvalidArgumentException) { |
|||
123 | $statusCode = StatusCodeInterface::STATUS_BAD_REQUEST; |
||||
124 | } |
||||
125 | |||||
126 | 1 | return $statusCode; |
|||
127 | } |
||||
128 | |||||
129 | 1 | private function html(string $text): string |
|||
130 | { |
||||
131 | 1 | return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); |
|||
132 | } |
||||
133 | } |
||||
134 |