Passed
Push — master ( 5a8407...ff879d )
by Daniel
12:59
created

src/Middleware/ExceptionMiddleware.php (3 issues)

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
The method getStatusCode() 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 ignore-call  annotation

96
            $this->html((string)$response->/** @scrutinizer ignore-call */ getStatusCode()),
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 ignore-call  annotation

97
            $this->html($response->/** @scrutinizer ignore-call */ getReasonPhrase()),
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
Bug Best Practice introduced by
The expression return $response returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
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