Passed
Push — main ( bdf69f...54bf20 )
by Thomas
13:06
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 16
    public function __construct(
26
        protected readonly ResponseFactory $responseFactory,
27
        protected readonly bool $debug = false,
28
    ) {
29 16
        set_error_handler([$this, 'handleError'], E_ALL);
30 16
        set_exception_handler([$this, 'emitException']);
31
    }
32
33 1
    public function debugHandler(DebugHandler $debugHandler): void
34
    {
35 1
        $this->debugHandler = $debugHandler;
36
    }
37
38 16
    public function __destruct()
39
    {
40 16
        restore_error_handler();
41 16
        restore_exception_handler();
42
    }
43
44 3
    public function logger(?Logger $logger = null): void
45
    {
46 3
        $this->logger = $logger;
47
    }
48
49 1
    public function process(Request $request, RequestHandler $handler): Response
50
    {
51
        try {
52 1
            return $handler->handle($request);
53 1
        } catch (Throwable $e) {
54 1
            return $this->getResponse($e, $request);
55
        }
56
    }
57
58
    /**
59
     * @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...
60
     */
61 9
    public function render(string|array $exceptions, Renderer $renderer): RendererEntry
62
    {
63 9
        $renderEntry = new RendererEntry(is_string($exceptions) ? [$exceptions] : $exceptions, $renderer);
0 ignored issues
show
introduced by
The condition is_string($exceptions) is always false.
Loading history...
64 9
        $this->renderers[] = $renderEntry;
65
66 9
        return $renderEntry;
67
    }
68
69 2
    public function handleError(
70
        int $level,
71
        string $message,
72
        string $file = '',
73
        int $line = 0,
74
    ): bool {
75 2
        if ($level & error_reporting()) {
76 1
            throw new ErrorException($message, $level, $level, $file, $line);
77
        }
78
79 1
        return false;
80
    }
81
82 2
    public function emitException(Throwable $exception): void
83
    {
84 2
        $response = $this->getResponse($exception, null);
85
86 2
        echo (string)$response->getBody();
87
    }
88
89 14
    public function getResponse(Throwable $exception, ?Request $request): Response
90
    {
91 14
        $renderer = null;
92 14
        $logLevel = null;
93
94 14
        foreach ($this->renderers as $rendererEntry) {
95 9
            if ($rendererEntry->matches($exception)) {
96 8
                $renderer = $rendererEntry->renderer;
97 8
                $logLevel = $rendererEntry->getLogLevel();
98 8
                break;
99
            }
100
        }
101
102 14
        if (!is_null($logLevel)) {
103 1
            $this->log($logLevel, $exception);
104
        }
105
106 14
        if ($renderer) {
107 8
            return $renderer->render(
108 8
                $exception,
109 8
                $this->responseFactory->createResponse(),
110 8
                $request,
111 8
                $this->debug,
112 8
            );
113
        }
114
115 6
        if ($this->debug) {
116 2
            if ($this->debugHandler) {
117 1
                return $this->debugHandler->handle($exception, $this->responseFactory);
118
            }
119
120 1
            throw $exception;
121
        }
122
123 4
        $this->logUnmatched($exception);
124
125 4
        $response = $this->responseFactory->createResponse(500)->withHeader('Content-Type', 'text/html') ;
126 4
        $response->getBody()->write('<h1>500 Internal Server Error</h1>');
127
128 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...
129
    }
130
131 1
    protected function log(string|int $logLevel, Throwable $exception): void
132
    {
133 1
        if ($this->logger) {
134 1
            $this->logger->log($logLevel, 'Matched Exception:', ['exception' => $exception]);
135
        }
136
    }
137
138 4
    protected function logUnmatched(Throwable $exception): void
139
    {
140 4
        if ($this->logger) {
141 1
            $this->logger->alert('Unmatched Exception:', ['exception' => $exception]);
142
        }
143
    }
144
}
145