Passed
Pull Request — master (#296)
by
unknown
23:32 queued 07:56
created

ErrorCatcher::validateRenderer()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 12
rs 10
c 1
b 0
f 1
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
    private ?string $onlyContentType = null;
36 7
37
    public function __construct(
38
        ResponseFactoryInterface $responseFactory,
39
        ErrorHandler $errorHandler,
40
        ContainerInterface $container
41 7
    ) {
42 7
        $this->responseFactory = $responseFactory;
43 7
        $this->errorHandler = $errorHandler;
44 7
        $this->container = $container;
45
    }
46 5
47
    public function withRenderer(string $mimeType, string $rendererClass): self
48 5
    {
49
        $this->validateMimeType($mimeType);
50 4
        $this->validateRenderer($rendererClass);
51
52
        $new = clone $this;
53
        $new->renderers[$this->normalizeMimeType($mimeType)] = $rendererClass;
54 4
        return $new;
55 1
    }
56
57
    /**
58 3
     * @param string[] $mimeTypes MIME types or, if not specified, all will be removed.
59 3
     */
60 3
    public function withoutRenderers(string ...$mimeTypes): self
61
    {
62
        $new = clone $this;
63
        if (count($mimeTypes) === 0) {
64
            $new->renderers = [];
65
            return $new;
66 2
        }
67
        foreach ($mimeTypes as $mimeType) {
68 2
            $this->validateMimeType($mimeType);
69 2
            unset($new->renderers[$this->normalizeMimeType($mimeType)]);
70 1
        }
71 1
        return $new;
72
    }
73 1
74 1
    public function withOnlyContentType(string $contentType): self
75 1
    {
76
        $this->validateMimeType($contentType);
77 1
        if (!isset($this->renderers[$contentType])) {
78
            throw new \InvalidArgumentException(sprintf('The renderer for %s cannot be set.', $contentType));
79
        }
80 5
81
        $new = clone $this;
82 5
        $new->onlyContentType = $contentType;
83 5
        return $new;
84 5
    }
85 3
86
    private function handleException(\Throwable $e, ServerRequestInterface $request): ResponseInterface
87 5
    {
88 5
        $contentType = $this->getContentType($request);
89 5
        $renderer = $this->getRenderer(strtolower($contentType));
90 5
        if ($renderer !== null) {
91 5
            $renderer->setRequest($request);
92
        }
93
        $content = $this->errorHandler->handleCaughtThrowable($e, $renderer);
94 5
        $response = $this->responseFactory->createResponse(Status::INTERNAL_SERVER_ERROR)
95
            ->withHeader(Header::CONTENT_TYPE, $contentType);
96 5
        $response->getBody()->write($content);
97 3
        return $response;
98
    }
99 2
100
    private function getRenderer(string $contentType): ?ThrowableRendererInterface
101
    {
102 5
        if (isset($this->renderers[$contentType])) {
103
            return $this->container->get($this->renderers[$contentType]);
104
        }
105 5
        return null;
106 5
    }
107 2
108
    private function getContentType(ServerRequestInterface $request): string
109
    {
110
        if ($this->onlyContentType !== null) {
111
            return $this->onlyContentType;
112
        }
113 3
114
        try {
115
            foreach (HeaderHelper::getSortedAcceptTypes($request->getHeader('accept')) as $header) {
116 5
                if (array_key_exists($header, $this->renderers)) {
117
                    return $header;
118
                }
119 5
            }
120 5
        } catch (\InvalidArgumentException $e) {
121 5
            // The Accept header contains an invalid q factor
122
        }
123
        return '*/*';
124
    }
125
126
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
127
    {
128 6
        try {
129
            return $handler->handle($request);
130 6
        } catch (\Throwable $e) {
131 1
            return $this->handleException($e, $request);
132
        }
133 5
    }
134
135 4
    /**
136
     * @throws \InvalidArgumentException
137 4
     */
138
    private function validateMimeType(string $mimeType): void
139
    {
140 1
        if (strpos($mimeType, '/') === false) {
141
            throw new \InvalidArgumentException('Invalid mime type.');
142
        }
143
    }
144
145
    private function normalizeMimeType(string $mimeType): string
146
    {
147
        return strtolower(trim($mimeType));
148
    }
149
150
    private function validateRenderer(string $rendererClass): void
151
    {
152
        if (trim($rendererClass) === '') {
153
            throw new \InvalidArgumentException('The renderer class cannot be an empty string.');
154
        }
155
156
        if ($this->container->has($rendererClass) === false) {
157
            throw new \InvalidArgumentException("The renderer \"$rendererClass\" cannot be found.");
158
        }
159
    }
160
}
161