Passed
Pull Request — master (#168)
by Alexander
11:02
created

ErrorCatcher::withoutRenderers()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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