Completed
Pull Request — master (#151)
by
unknown
02:00
created

ErrorCatcher::getRenderer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
1
<?php
2
namespace Yiisoft\Yii\Web\Middleware;
3
4
use Psr\Container\ContainerInterface;
5
use Psr\Http\Message\ResponseFactoryInterface;
6
use Psr\Http\Message\ResponseInterface;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Psr\Http\Server\MiddlewareInterface;
9
use Psr\Http\Server\RequestHandlerInterface;
10
use Yiisoft\Yii\Web\ErrorHandler\ErrorHandler;
11
use Yiisoft\Yii\Web\ErrorHandler\ThrowableRendererInterface;
12
use Yiisoft\Yii\Web\ErrorHandler\HtmlRenderer;
13
use Yiisoft\Yii\Web\ErrorHandler\JsonRenderer;
14
use Yiisoft\Yii\Web\ErrorHandler\PlainTextRenderer;
15
use Yiisoft\Yii\Web\ErrorHandler\XmlRenderer;
16
use Yiisoft\Yii\Web\Helper\HeaderHelper;
17
18
/**
19
 * ErrorCatcher catches all throwables from the next middlewares and renders it
20
 * accoring to the content type passed by the client.
21
 */
22
final class ErrorCatcher implements MiddlewareInterface
23
{
24
    private $responseFactory;
25
    private $errorHandler;
26
    private $container;
27
28
    private $renderers = [
29
        'application/json' => JsonRenderer::class,
30
        'application/xml' => XmlRenderer::class,
31
        'text/xml' => XmlRenderer::class,
32
        'text/plain' => PlainTextRenderer::class,
33
        'text/html' => HtmlRenderer::class,
34
    ];
35
36 4
    public function __construct(ResponseFactoryInterface $responseFactory, ErrorHandler $errorHandler, ContainerInterface $container)
37
    {
38 4
        $this->responseFactory = $responseFactory;
39 4
        $this->errorHandler = $errorHandler;
40 4
        $this->container = $container;
41
    }
42
43 2
    public function withAddedRenderer(string $mimeType, string $rendererClass): self
44
    {
45 2
        if (strlen($mimeType) === 0) {
46
            throw new \InvalidArgumentException('The mime type cannot be an empty string!');
47
        }
48 2
        if (strlen($rendererClass) === 0) {
49
            throw new \InvalidArgumentException('The renderer class cannot be an empty string!');
50
        }
51 2
        if (strpos($mimeType, '/') === false) {
52
            throw new \InvalidArgumentException('Invalid mime type!');
53
        }
54 2
        $new = clone $this;
55 2
        $new->renderers[strtolower($mimeType)] = $rendererClass;
56 2
        return $new;
57
    }
58
59
    /**
60
     * @param string... $mimeTypes MIME types or, if not specified, all will be removed.
0 ignored issues
show
Documentation Bug introduced by
The doc comment string... at position 0 could not be parsed: Unknown type name 'string...' at position 0 in string....
Loading history...
61
     */
62 2
    public function withoutRenderers(string... $mimeTypes): self
63
    {
64 2
        $new = clone $this;
65 2
        if (count($mimeTypes) === 0) {
66 1
            $new->renderers = [];
67 1
            return $new;
68
        }
69 1
        foreach ($mimeTypes as $mimeType) {
70 1
            if (strlen($mimeType) === 0) {
71
                throw new \InvalidArgumentException('The mime type cannot be an empty string!');
72
            }
73 1
            unset($new->renderers[strtolower($mimeType)]);
74
        }
75 1
        return $new;
76
    }
77
78 4
    private function handleException(\Throwable $e, ServerRequestInterface $request): ResponseInterface
79
    {
80 4
        $contentType = $this->getContentType($request);
81 4
        $renderer = $this->getRenderer(strtolower($contentType));
82 4
        if ($renderer !== null) {
83 2
            $renderer->setRequest($request);
84
        }
85 4
        $content = $this->errorHandler->handleCaughtThrowable($e, $renderer);
86 4
        $response = $this->responseFactory->createResponse(500)
87 4
            ->withHeader('Content-type', $contentType);
88 4
        $response->getBody()->write($content);
89 4
        return $response;
90
    }
91
92 4
    private function getRenderer(string $contentType): ?ThrowableRendererInterface
93
    {
94 4
        if (isset($this->renderers[$contentType])) {
95 2
            return $this->container->get($this->renderers[$contentType]);
96
        }
97
98 2
        return null;
99
    }
100
101 4
    private function getContentType(ServerRequestInterface $request): string
102
    {
103
        try {
104 4
            $acceptHeaders = HeaderHelper::getSortedAcceptTypesFromRequest($request);
105 4
            foreach ($acceptHeaders as $header) {
106 4
                if (array_key_exists($header, $this->renderers)) {
107 2
                    return $header;
108
                }
109
            }
110
        } catch (\InvalidArgumentException $e) {
111
            // The Accept header contains an invalid q factor
112
        }
113 2
        return 'text/html';
114
    }
115
116 4
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
117
    {
118
        try {
119 4
            return $handler->handle($request);
120 4
        } catch (\Throwable $e) {
121 4
            return $this->handleException($e, $request);
122
        }
123
    }
124
}
125