Passed
Pull Request — master (#281)
by
unknown
13:12
created

ErrorCatcher::withAddedRenderer()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.432

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 2
dl 0
loc 14
ccs 7
cts 10
cp 0.7
crap 4.432
rs 9.9666
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A ErrorCatcher::withRenderer() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web\ErrorHandler;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\Http\Message\ResponseFactoryInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use Psr\Http\Server\MiddlewareInterface;
12
use Psr\Http\Server\RequestHandlerInterface;
13
use Yiisoft\Http\Header;
14
use Yiisoft\Http\HeaderHelper;
15
use Yiisoft\Http\Status;
16
17
/**
18
 * ErrorCatcher catches all throwables from the next middlewares and renders it
19
 * according to the content type passed by the client.
20
 */
21
final class ErrorCatcher implements MiddlewareInterface
22
{
23
    private array $renderers = [
24
        'application/json' => JsonRenderer::class,
25
        'application/xml' => XmlRenderer::class,
26
        'text/xml' => XmlRenderer::class,
27
        'text/plain' => PlainTextRenderer::class,
28
        'text/html' => HtmlRenderer::class,
29
        '*/*' => HtmlRenderer::class,
30
    ];
31
32
    private ResponseFactoryInterface $responseFactory;
33
    private ErrorHandler $errorHandler;
34
    private ContainerInterface $container;
35
36 5
    public function __construct(
37
        ResponseFactoryInterface $responseFactory,
38 5
        ErrorHandler $errorHandler,
39 5
        ContainerInterface $container
40 5
    ) {
41 5
        $this->responseFactory = $responseFactory;
42
        $this->errorHandler = $errorHandler;
43 3
        $this->container = $container;
44
    }
45 3
46
    public function withRenderer(string $mimeType, string $rendererClass): self
47
    {
48 3
        $this->validateMimeType($mimeType);
49
        if (trim($rendererClass) === '') {
50
            throw new \InvalidArgumentException('The renderer class cannot be an empty string.');
51 3
        }
52
        $new = clone $this;
53
        $new->renderers[$this->normalizeMimeType($mimeType)] = $rendererClass;
54 3
        return $new;
55 3
    }
56 3
57
    /**
58
     * @param string[] $mimeTypes MIME types or, if not specified, all will be removed.
59
     */
60
    public function withoutRenderers(string ...$mimeTypes): self
61
    {
62 2
        $new = clone $this;
63
        if (count($mimeTypes) === 0) {
64 2
            $new->renderers = [];
65 2
            return $new;
66 1
        }
67 1
        foreach ($mimeTypes as $mimeType) {
68
            $this->validateMimeType($mimeType);
69 1
            unset($new->renderers[$this->normalizeMimeType($mimeType)]);
70 1
        }
71
        return $new;
72
    }
73 1
74
    private function handleException(\Throwable $e, ServerRequestInterface $request): ResponseInterface
75 1
    {
76
        $contentType = $this->getContentType($request);
77
        $renderer = $this->getRenderer(strtolower($contentType));
78 5
        if ($renderer !== null) {
79
            $renderer->setRequest($request);
80 5
        }
81 5
        $content = $this->errorHandler->handleCaughtThrowable($e, $renderer);
82 5
        $response = $this->responseFactory->createResponse(Status::INTERNAL_SERVER_ERROR)
83 3
            ->withHeader(Header::CONTENT_TYPE, $contentType);
84
        $response->getBody()->write($content);
85 5
        return $response;
86 5
    }
87 5
88 5
    private function getRenderer(string $contentType): ?ThrowableRendererInterface
89 5
    {
90
        if (isset($this->renderers[$contentType])) {
91
            return $this->container->get($this->renderers[$contentType]);
92 5
        }
93
        return null;
94 5
    }
95 3
96
    private function getContentType(ServerRequestInterface $request): string
97
    {
98 2
        try {
99
            foreach (HeaderHelper::getSortedAcceptTypesFromRequest($request) as $header) {
100
                if (array_key_exists($header, $this->renderers)) {
101 5
                    return $header;
102
                }
103
            }
104 5
        } catch (\InvalidArgumentException $e) {
105 5
            // The Accept header contains an invalid q factor
106 2
        }
107
        return '*/*';
108
    }
109
110
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
111
    {
112 3
        try {
113
            return $handler->handle($request);
114
        } catch (\Throwable $e) {
115 5
            return $this->handleException($e, $request);
116
        }
117
    }
118 5
119 5
    /**
120 5
     * @throws \InvalidArgumentException
121
     */
122
    private function validateMimeType(string $mimeType): void
123
    {
124 1
        if (strpos($mimeType, '/') === false) {
125
            throw new \InvalidArgumentException('Invalid mime type.');
126
        }
127
    }
128
129
    private function normalizeMimeType(string $mimeType): string
130
    {
131
        return strtolower($mimeType);
132
    }
133
}
134