Passed
Pull Request — master (#833)
by Maxim
07:21
created

ErrorHandlerMiddleware::getRenderer()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 8.7414

Importance

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