Passed
Push — main ( 9492df...30ab62 )
by Thomas
02:31
created

Handler::logger()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Conia\Error;
6
7
use ErrorException;
8
use Psr\Http\Message\ResponseFactoryInterface as ResponseFactory;
9
use Psr\Http\Message\ResponseInterface as Response;
10
use Psr\Http\Message\ServerRequestInterface as Request;
11
use Psr\Http\Server\MiddlewareInterface as Middleware;
12
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
13
use Psr\Log\LoggerInterface as Logger;
14
use Throwable;
15
16
/** @psalm-api */
17
class Handler implements Middleware
18
{
19
    protected ?Logger $logger = null;
20
    protected ?DebugHandler $debugHandler = null;
21
22
    /** @var RendererEntry[] */
23
    protected array $renderers = [];
24
25
    protected ?RendererEntry $defaultRenderer = null;
26
27 17
    public function __construct(
28
        protected readonly ResponseFactory $responseFactory,
29
        protected readonly bool $debug = false,
30
    ) {
31 17
        set_error_handler([$this, 'handleError'], E_ALL);
32 17
        set_exception_handler([$this, 'emitException']);
33
    }
34
35 1
    public function debugHandler(DebugHandler $debugHandler): void
36
    {
37 1
        $this->debugHandler = $debugHandler;
38
    }
39
40 17
    public function __destruct()
41
    {
42 17
        restore_error_handler();
43 17
        restore_exception_handler();
44
    }
45
46 3
    public function logger(?Logger $logger = null): void
47
    {
48 3
        $this->logger = $logger;
49
    }
50
51 1
    public function process(Request $request, RequestHandler $handler): Response
52
    {
53
        try {
54 1
            return $handler->handle($request);
55 1
        } catch (Throwable $e) {
56 1
            return $this->getResponse($e, $request);
57
        }
58
    }
59
60
    /**
61
     * @param class-string<Throwable>|class-string<Throwable>[] $exceptions
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Throwable>|class-string<Throwable>[] at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Throwable>|class-string<Throwable>[].
Loading history...
62
     */
63 10
    public function renderer(Renderer $renderer, string|array|null $exceptions = null): RendererEntry
64
    {
65 10
        if ($exceptions === null) {
66 1
            $rendererEntry =  new RendererEntry([], $renderer);
67 1
            $this->defaultRenderer = $rendererEntry;
68
69 1
            return $rendererEntry;
70
        }
71
72 9
        $renderEntry = new RendererEntry((array)$exceptions, $renderer);
73 9
        $this->renderers[] = $renderEntry;
74
75 9
        return $renderEntry;
76
    }
77
78 2
    public function handleError(
79
        int $level,
80
        string $message,
81
        string $file = '',
82
        int $line = 0,
83
    ): bool {
84 2
        if ($level & error_reporting()) {
85 1
            throw new ErrorException($message, $level, $level, $file, $line);
86
        }
87
88 1
        return false;
89
    }
90
91 2
    public function emitException(Throwable $exception): void
92
    {
93 2
        $response = $this->getResponse($exception, null);
94
95 2
        echo (string)$response->getBody();
96
    }
97
98 15
    public function getResponse(Throwable $exception, ?Request $request): Response
99
    {
100 15
        $renderer = null;
101 15
        $logLevel = null;
102
103 15
        foreach ($this->renderers as $rendererEntry) {
104 9
            if ($rendererEntry->matches($exception)) {
105 8
                $renderer = $rendererEntry->renderer;
106 8
                $logLevel = $rendererEntry->getLogLevel();
107 8
                break;
108
            }
109
        }
110
111 15
        if (!is_null($logLevel)) {
112 1
            $this->log($logLevel, $exception);
113
        }
114
115 15
        if ($renderer) {
116 8
            return $renderer->render(
117 8
                $exception,
118 8
                $this->responseFactory,
119 8
                $request,
120 8
                $this->debug,
121 8
            );
122
        }
123
124 7
        if ($this->debug) {
125 2
            if ($this->debugHandler) {
126 1
                return $this->debugHandler->handle($exception, $this->responseFactory);
127
            }
128
129 1
            throw $exception;
130
        }
131
132 5
        $this->logUnmatched($exception);
133
134 5
        if ($this->defaultRenderer) {
135 1
            return $this->defaultRenderer->renderer->render(
136 1
                $exception,
137 1
                $this->responseFactory,
138 1
                $request,
139 1
                $this->debug,
140 1
            );
141
        }
142
143 4
        $response = $this->responseFactory->createResponse(500)->withHeader('Content-Type', 'text/html') ;
144 4
        $response->getBody()->write('<h1>500 Internal Server Error</h1>');
145
146 4
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
147
    }
148
149 1
    protected function log(string|int $logLevel, Throwable $exception): void
150
    {
151 1
        if ($this->logger) {
152 1
            $this->logger->log($logLevel, 'Matched Exception:', ['exception' => $exception]);
153
        }
154
    }
155
156 5
    protected function logUnmatched(Throwable $exception): void
157
    {
158 5
        if ($this->logger) {
159 1
            $this->logger->alert('Unmatched Exception:', ['exception' => $exception]);
160
        }
161
    }
162
}
163