Completed
Push — master ( 632840...f0c648 )
by Alexander
02:14
created

ErrorCatcher::handleException()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
c 2
b 0
f 0
nc 2
nop 2
dl 0
loc 12
ccs 10
cts 10
cp 1
crap 2
rs 9.9666
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
17
/**
18
 * ErrorCatcher catches all throwables from the next middlewares and renders it
19
 * accoring to the content type passed by the client.
20
 */
21
final class ErrorCatcher implements MiddlewareInterface
22
{
23
    private $responseFactory;
24
    private $errorHandler;
25
    private $container;
26
27
    private $renderers = [
28
        'application/json' => JsonRenderer::class,
29
        'application/xml' => XmlRenderer::class,
30
        'text/xml' => XmlRenderer::class,
31
        'text/plain' => PlainTextRenderer::class,
32
        'text/html' => HtmlRenderer::class,
33
    ];
34
35 3
    public function __construct(ResponseFactoryInterface $responseFactory, ErrorHandler $errorHandler, ContainerInterface $container)
36
    {
37 3
        $this->responseFactory = $responseFactory;
38 3
        $this->errorHandler = $errorHandler;
39 3
        $this->container = $container;
40
    }
41
42 1
    public function withAddedRenderer(string $mimeType, string $rendererClass): self
43
    {
44 1
        if (strlen($mimeType) === 0) {
45
            throw new \InvalidArgumentException('The mime type cannot be an empty string!');
46
        }
47 1
        if (strlen($rendererClass) === 0) {
48
            throw new \InvalidArgumentException('The renderer class cannot be an empty string!');
49
        }
50 1
        if (strpos($mimeType, '/') === false) {
51
            throw new \InvalidArgumentException('Invalid mime type!');
52
        }
53 1
        $new = clone $this;
54 1
        $new->renderers[strtolower($mimeType)] = $rendererClass;
55 1
        return $new;
56
    }
57
58
    /**
59
     * @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...
60
     */
61 2
    public function withoutRenderers(string... $mimeTypes): self
62
    {
63 2
        $new = clone $this;
64 2
        if (count($mimeTypes) === 0) {
65 1
            $new->renderers = [];
66 1
            return $new;
67
        }
68 1
        foreach ($mimeTypes as $mimeType) {
69 1
            if (strlen($mimeType) === 0) {
70
                throw new \InvalidArgumentException('The mime type cannot be an empty string!');
71
            }
72 1
            unset($new->renderers[strtolower($mimeType)]);
73
        }
74 1
        return $new;
75
    }
76
77 3
    private function handleException(\Throwable $e, ServerRequestInterface $request): ResponseInterface
78
    {
79 3
        $contentType = $this->getContentType($request);
80 3
        $renderer = $this->getRenderer(strtolower($contentType));
81 3
        if ($renderer !== null) {
82 1
            $renderer->setRequest($request);
83
        }
84 3
        $content = $this->errorHandler->handleCaughtThrowable($e, $renderer);
85 3
        $response = $this->responseFactory->createResponse(500)
86 3
            ->withHeader('Content-type', $contentType);
87 3
        $response->getBody()->write($content);
88 3
        return $response;
89
    }
90
91 3
    private function getRenderer(string $contentType): ?ThrowableRendererInterface
92
    {
93 3
        if (isset($this->renderers[$contentType])) {
94 1
            return $this->container->get($this->renderers[$contentType]);
95
        }
96
97 2
        return null;
98
    }
99
100 3
    private function getContentType(ServerRequestInterface $request): string
101
    {
102 3
        $acceptHeaders = preg_split('~\s*,\s*~', $request->getHeaderLine('Accept'), PREG_SPLIT_NO_EMPTY);
103 3
        foreach ($acceptHeaders as $header) {
104 3
            if (array_key_exists($header, $this->renderers)) {
105 1
                return $header;
106
            }
107
        }
108 2
        return 'text/html';
109
    }
110
111 3
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
112
    {
113
        try {
114 3
            return $handler->handle($request);
115 3
        } catch (\Throwable $e) {
116 3
            return $this->handleException($e, $request);
117
        }
118
    }
119
}
120