Passed
Push — master ( 5b3043...a2340d )
by butschster
09:28 queued 02:04
created

ErrorHandlerMiddleware::renderError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.0054

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 19
ccs 8
cts 9
cp 0.8889
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2.0054
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Http\Middleware;
6
7
use Psr\Http\Message\ResponseFactoryInterface;
8
use Psr\Http\Message\ResponseInterface as Response;
9
use Psr\Http\Message\ServerRequestInterface as Request;
10
use Psr\Http\Server\MiddlewareInterface;
11
use Psr\Http\Server\RequestHandlerInterface as Handler;
12
use Spiral\Exceptions\ExceptionHandlerInterface;
13
use Spiral\Exceptions\ExceptionRendererInterface;
14
use Spiral\Exceptions\Verbosity;
15
use Spiral\Http\ErrorHandler\RendererInterface;
16
use Spiral\Http\Exception\ClientException;
17
use Spiral\Http\Header\AcceptHeader;
18
use Spiral\Http\Middleware\ErrorHandlerMiddleware\SuppressErrorsInterface;
19
use Spiral\Logger\Traits\LoggerTrait;
20
use Spiral\Router\Exception\RouterException;
21
22
/**
23
 * Wraps Client and Routing exceptions into proper response.
24
 */
25
final class ErrorHandlerMiddleware implements MiddlewareInterface
26
{
27
    use LoggerTrait;
28
29 38
    public function __construct(
30
        private readonly SuppressErrorsInterface $suppressErrors,
31
        private readonly RendererInterface $renderer,
32
        private readonly ResponseFactoryInterface $responseFactory,
33
        private readonly ExceptionHandlerInterface $errorHandler,
34
        private readonly Verbosity $verbosity
35
    ) {
36
    }
37
38
    /**
39
     * @psalm-suppress UnusedVariable
40
     * @throws \Throwable
41
     */
42 37
    public function process(Request $request, Handler $handler): Response
43
    {
44
        try {
45 37
            return $handler->handle($request);
46 12
        } catch (ClientException|RouterException $e) {
47 6
            $code = $e instanceof ClientException ? $e->getCode() : 404;
48 6
        } catch (\Throwable $e) {
49 6
            $code = 500;
50
        }
51
52 12
        $this->errorHandler->report($e);
53
54 12
        if (!$this->suppressErrors->suppressed()) {
55 4
            return $this->renderError($request, $e, $code);
56
        }
57
58 8
        $this->logError($request, $code, $e->getMessage());
59
60 8
        return $this->renderer->renderException($request, $code, $e);
61
    }
62
63
    /**
64
     * @throws \Throwable
65
     */
66 4
    private function renderError(Request $request, \Throwable $e, int $code): Response
67
    {
68 4
        $response = $this->responseFactory->createResponse($code);
69
70 4
        [$format, $renderer] = $this->getRenderer($this->errorHandler, $request);
71
72 4
        if ($format !== null) {
73
            $response = $response->withHeader('Content-Type', $format . '; charset=UTF-8');
74
        }
75
76 4
        $response->getBody()->write(
77 4
            (string)$renderer?->render(
78
                exception: $e,
79 4
                verbosity: $this->verbosity,
80
                format: $format
81
            )
82
        );
83
84 4
        return $response;
85
    }
86
87
    /**
88
     * @return array{string|null, ExceptionRendererInterface|null}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{string|null, Excep...RendererInterface|null} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
89
     */
90 4
    private function getRenderer(ExceptionHandlerInterface $handler, Request $request): array
91
    {
92 4
        if ($request->hasHeader('Accept')) {
93
            $acceptItems = AcceptHeader::fromString($request->getHeaderLine('Accept'))->getAll();
94
            foreach ($acceptItems as $item) {
95
                $format = $item->getValue();
96
                $renderer = $handler->getRenderer($format);
97
                if ($renderer !== null) {
98
                    return [$format, $renderer];
99
                }
100
            }
101
        }
102 4
        return [null, $handler->getRenderer()];
103
    }
104
105 8
    private function logError(Request $request, int $code, string $message): void
106
    {
107 8
        $this->getLogger()->error(
108 8
            \sprintf(
109
                '%s://%s%s caused the error %s (%s) by client %s.',
110 8
                $request->getUri()->getScheme(),
111 8
                $request->getUri()->getHost(),
112 8
                $request->getUri()->getPath(),
113
                $code,
114 8
                $message ?: '-not specified-',
115 8
                $request->getServerParams()['REMOTE_ADDR'] ?? '127.0.0.1'
116
            )
117
        );
118
    }
119
}
120