Passed
Pull Request — master (#281)
by
unknown
21:29 queued 06:32
created

ErrorCatcher::withRenderer()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.7085

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 2
dl 0
loc 11
ccs 4
cts 7
cp 0.5714
crap 3.7085
rs 10
c 0
b 0
f 0
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(ResponseFactoryInterface $responseFactory, ErrorHandler $errorHandler, ContainerInterface $container)
37
    {
38 5
        $this->responseFactory = $responseFactory;
39 5
        $this->errorHandler = $errorHandler;
40 5
        $this->container = $container;
41 5
    }
42
43 3
    public function withRenderer(string $mimeType, string $rendererClass): self
44
    {
45 3
        if ($mimeType === '') {
46
            throw new \InvalidArgumentException('The mime type cannot be an empty string.');
47
        }
48 3
        if ($rendererClass === '') {
49
            throw new \InvalidArgumentException('The renderer class cannot be an empty string.');
50
        }
51 3
        $new = clone $this;
52
        $new->renderers[strtolower($mimeType)] = $rendererClass;
53
        return $new;
54 3
    }
55 3
56 3
    /**
57
     * @param string[] $mimeTypes MIME types or, if not specified, all will be removed.
58
     */
59
    public function withoutRenderers(string ... $mimeTypes): self
60
    {
61
        $new = clone $this;
62 2
        if (count($mimeTypes) === 0) {
63
            $new->renderers = [];
64 2
            return $new;
65 2
        }
66 1
        foreach ($mimeTypes as $mimeType) {
67 1
            if (trim($mimeType) === '') {
68
                throw new \InvalidArgumentException('The mime type cannot be an empty string.');
69 1
            }
70 1
            unset($new->renderers[strtolower($mimeType)]);
71
        }
72
        return $new;
73 1
    }
74
75 1
    private function handleException(\Throwable $e, ServerRequestInterface $request): ResponseInterface
76
    {
77
        $contentType = $this->getContentType($request);
78 5
        $renderer = $this->getRenderer(strtolower($contentType));
79
        if ($renderer !== null) {
80 5
            $renderer->setRequest($request);
81 5
        }
82 5
        $content = $this->errorHandler->handleCaughtThrowable($e, $renderer);
83 3
        $response = $this->responseFactory->createResponse(Status::INTERNAL_SERVER_ERROR)
84
            ->withHeader(Header::CONTENT_TYPE, $contentType);
85 5
        $response->getBody()->write($content);
86 5
        return $response;
87 5
    }
88 5
89 5
    private function getRenderer(string $contentType): ?ThrowableRendererInterface
90
    {
91
        if (isset($this->renderers[$contentType])) {
92 5
            return $this->container->get($this->renderers[$contentType]);
93
        }
94 5
        return null;
95 3
    }
96
97
    private function getContentType(ServerRequestInterface $request): string
98 2
    {
99
        try {
100
            foreach (HeaderHelper::getSortedAcceptTypesFromRequest($request) as $header) {
101 5
                if (array_key_exists($header, $this->renderers)) {
102
                    return $header;
103
                }
104 5
            }
105 5
        } catch (\InvalidArgumentException $e) {
106 2
            // The Accept header contains an invalid q factor
107
        }
108
        return '*/*';
109
    }
110
111
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
112 3
    {
113
        try {
114
            return $handler->handle($request);
115 5
        } catch (\Throwable $e) {
116
            return $this->handleException($e, $request);
117
        }
118 5
    }
119
}
120