Passed
Push — main ( d72029...35e57d )
by Thomas
03:10
created

ErrorHandler   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 89
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 13
eloc 45
dl 0
loc 89
ccs 49
cts 49
cp 1
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
B handleException() 0 41 6
A __construct() 0 2 1
A handleError() 0 11 2
A getLoggerMethod() 0 10 1
A setup() 0 5 1
A log() 0 7 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Conia\Chuck;
6
7
use Conia\Chuck\Config;
8
use Conia\Chuck\Exception\ExitException;
9
use Conia\Chuck\Exception\HttpBadRequest;
10
use Conia\Chuck\Exception\HttpError;
11
use Conia\Chuck\Exception\HttpForbidden;
12
use Conia\Chuck\Exception\HttpMethodNotAllowed;
13
use Conia\Chuck\Exception\HttpNotFound;
14
use Conia\Chuck\Exception\HttpServerError;
15
use Conia\Chuck\Exception\HttpUnauthorized;
16
use Conia\Chuck\Registry\Registry;
17
use Conia\Chuck\ResponseFactory;
18
use ErrorException;
19
use Throwable;
20
21
class ErrorHandler
22
{
23 27
    public function __construct(protected Config $config, protected Registry $registry)
24
    {
25 27
    }
26
27 25
    public function setup(): callable|null
28
    {
29 25
        set_error_handler([$this, 'handleError'], E_ALL);
30
31 25
        return set_exception_handler([$this, 'handleException']);
32
    }
33
34 15
    public function handleError(
35
        int $level,
36
        string $message,
37
        string $file = '',
38
        int $line = 0,
39
    ): bool {
40 15
        if ($level & error_reporting()) {
41 14
            throw new ErrorException($message, $level, $level, $file, $line);
42
        }
43
44 1
        return false;
45
    }
46
47 3
    public function handleException(Throwable $exception): void
48
    {
49 3
        $response = (new ResponseFactory($this->registry))->html(null);
50 3
        $debug = $this->config->debug();
51
52 3
        if ($exception instanceof HttpError) {
53 2
            $code = $exception->getCode();
54 2
            $response->status($code);
55 2
            $body = '<h1>' . htmlspecialchars($exception->getTitle()) . '</h1>';
56 2
            $subTitle = $exception->getSubtitle();
57
58 2
            if ($subTitle) {
59 1
                $body .= '<h2>' . htmlspecialchars($subTitle) . '</h2>';
60
            } else {
61 2
                $body .= '<h2>HTTP Error</h2>';
62
            }
63 1
        } elseif ($exception instanceof ExitException) {
64
            // Would stop the test suit
65
            // @codeCoverageIgnoreStart
66
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
67
        // @codeCoverageIgnoreEnd
68
        } else {
69 1
            $code = 500;
70 1
            $response->status($code);
71 1
            $body = '<h1>500 Internal Server Error</h1>';
72 1
            $body .= '<h2>' . htmlspecialchars($exception->getMessage()) . '</h2>';
73
        }
74
75 3
        if ($debug && $code === 500) {
76 1
            $trace = str_replace(
77 1
                ['<', '>', '"'],
78 1
                ['&lt;', '&gt', '&quot;'],
79 1
                $exception->getTraceAsString()
80 1
            );
81 1
            $trace = implode('<br>#', explode('#', $trace));
82 1
            $body .= preg_replace('/^<br>/', '', $trace);
83
        }
84
85 3
        $this->log($exception);
86
87 3
        (new Emitter())->emit($response->body($body)->psr7());
88
    }
89
90 3
    public function log(Throwable $exception): void
91
    {
92 3
        $logger = $this->config->logger();
93
94 3
        if ($logger) {
0 ignored issues
show
introduced by
$logger is of type Psr\Log\LoggerInterface, thus it always evaluated to true.
Loading history...
95 3
            $method = $this->getLoggerMethod($exception);
96 3
            ([$logger, $method])('Uncaught Exception:', ['exception' => $exception]);
97
        }
98
    }
99
100 3
    protected function getLoggerMethod(Throwable $exception): string
101
    {
102 3
        return match ($exception::class) {
103 3
            HttpNotFound::class => 'info',
104 3
            HttpMethodNotAllowed::class => 'info',
105 3
            HttpForbidden::class => 'notice',
106 3
            HttpUnauthorized::class => 'notice',
107 3
            HttpBadRequest::class => 'warning',
108 3
            HttpServerError::class => 'error',
109 3
            default => 'error',
110 3
        };
111
    }
112
}
113