Passed
Push — master ( 400934...9d586b )
by Anton
02:32
created

ErrorHandlerMiddleware::getOptional()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 6
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Http\Middleware;
13
14
use Psr\Container\ContainerExceptionInterface;
15
use Psr\Container\ContainerInterface;
16
use Psr\Http\Message\ResponseFactoryInterface;
17
use Psr\Http\Message\ResponseInterface as Response;
18
use Psr\Http\Message\ServerRequestInterface as Request;
19
use Psr\Http\Server\MiddlewareInterface;
20
use Psr\Http\Server\RequestHandlerInterface as Handler;
21
use Spiral\Debug\StateInterface;
22
use Spiral\Exceptions\HtmlHandler;
23
use Spiral\Exceptions\JsonHandler;
24
use Spiral\Http\ErrorHandler\RendererInterface;
25
use Spiral\Http\Exception\ClientException;
26
use Spiral\Logger\Traits\LoggerTrait;
27
use Spiral\Router\Exception\RouterException;
28
use Spiral\Snapshots\SnapshotterInterface;
29
30
/**
31
 * Wraps Client and Routing exceptions into proper response.
32
 */
33
final class ErrorHandlerMiddleware implements MiddlewareInterface
34
{
35
    use LoggerTrait;
36
37
    /** @var ResponseFactoryInterface */
38
    private $responseFactory;
39
40
    /** @var bool */
41
    private $suppressErrors;
42
43
    /** @var RendererInterface */
44
    private $renderer;
45
46
    /** @var ContainerInterface @internal */
47
    private $container;
48
49
    /**
50
     * @param bool                     $suppressErrors
51
     * @param RendererInterface        $renderer
52
     * @param ResponseFactoryInterface $responseFactory
53
     * @param ContainerInterface       $container
54
     */
55
    public function __construct(
56
        bool $suppressErrors,
57
        RendererInterface $renderer,
58
        ResponseFactoryInterface $responseFactory,
59
        ContainerInterface $container
60
    ) {
61
        $this->suppressErrors = $suppressErrors;
62
        $this->renderer = $renderer;
63
        $this->responseFactory = $responseFactory;
64
        $this->container = $container;
65
    }
66
67
    /**
68
     * @inheritdoc
69
     *
70
     * @throws \Throwable
71
     */
72
    public function process(Request $request, Handler $handler): Response
73
    {
74
        try {
75
            return $handler->handle($request);
76
        } catch (ClientException | RouterException $e) {
77
            if ($e instanceof ClientException) {
78
                $code = $e->getCode();
79
            } else {
80
                $code = 404;
81
            }
82
        } catch (\Throwable $e) {
83
            $snapshotter = $this->getOptional(SnapshotterInterface::class);
84
            if ($snapshotter !== null) {
85
                /** @var SnapshotterInterface $snapshotter */
86
                $snapshotter->register($e);
87
            }
88
89
            $code = 500;
90
91
            if (!$this->suppressErrors) {
92
                return $this->renderException($request, $e);
93
            }
94
        }
95
96
        $this->logError($request, $code, $e->getMessage());
97
98
        return $this->renderer->renderException($request, $code, $e->getMessage());
99
    }
100
101
    /**
102
     * @param Request    $request
103
     * @param \Throwable $e
104
     * @return Response
105
     *
106
     * @throws \Throwable
107
     */
108
    private function renderException(Request $request, \Throwable $e): Response
109
    {
110
        $response = $this->responseFactory->createResponse(500);
111
112
        if ($request->getHeaderLine('Accept') == 'application/json') {
113
            $response = $response->withHeader('Content-Type', 'application/json');
114
            $handler = new JsonHandler();
115
            $response->getBody()->write(json_encode(
116
                ['status' => 500]
117
                + json_decode(
118
                    $handler->renderException($e, HtmlHandler::VERBOSITY_VERBOSE),
119
                    true
120
                )
121
            ));
122
        } else {
123
            $handler = new HtmlHandler();
124
            $state = $this->getOptional(StateInterface::class);
125
            if ($state !== null) {
126
                $handler = $handler->withState($state);
0 ignored issues
show
Bug introduced by
The method withState() does not exist on Spiral\Exceptions\HtmlHandler. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

126
                /** @scrutinizer ignore-call */ 
127
                $handler = $handler->withState($state);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
127
            }
128
129
            $response->getBody()->write($handler->renderException($e, HtmlHandler::VERBOSITY_VERBOSE));
130
        }
131
132
        return $response;
133
    }
134
135
    /**
136
     * @param Request $request
137
     * @param int     $code
138
     * @param string  $message
139
     */
140
    private function logError(Request $request, int $code, string $message): void
141
    {
142
        $this->getLogger()->error(sprintf(
143
            '%s://%s%s caused the error %s (%s) by client %s.',
144
            $request->getUri()->getScheme(),
145
            $request->getUri()->getHost(),
146
            $request->getUri()->getPath(),
147
            $code,
148
            $message ?: '-not specified-',
149
            $request->getServerParams()['REMOTE_ADDR'] ?? '127.0.0.1'
150
        ));
151
    }
152
153
    /**
154
     * @param string $class
155
     * @return mixed|null
156
     */
157
    private function getOptional(string $class)
158
    {
159
        try {
160
            return $this->container->get($class);
161
        } catch (\Throwable | ContainerExceptionInterface $se) {
162
            return null;
163
        }
164
    }
165
}
166