Passed
Push — master ( 4b20bd...439edc )
by Alexander
06:09
created

ErrorHandler::withoutExposedDetails()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
ccs 4
cts 4
cp 1
crap 1
1
<?php
2
3
namespace Yiisoft\Yii\Web\ErrorHandler;
4
5
use Psr\Log\LoggerInterface;
6
use Yiisoft\Http\Status;
7
8
final class ErrorHandler
9
{
10
    /**
11
     * @var int the size of the reserved memory. A portion of memory is pre-allocated so that
12
     * when an out-of-memory issue occurs, the error handler is able to handle the error with
13
     * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
14
     * Defaults to 256KB.
15
     */
16
    private int $memoryReserveSize = 262_144;
0 ignored issues
show
Bug introduced by
The constant Yiisoft\Yii\Web\ErrorHandler\262_144 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
17
    private string $memoryReserve = '';
18
    private bool $exposeDetails = true;
19
20
    private LoggerInterface $logger;
21
    private ThrowableRendererInterface $defaultRenderer;
22
23 9
    public function __construct(LoggerInterface $logger, ThrowableRendererInterface $defaultRenderer)
24
    {
25 9
        $this->logger = $logger;
26 9
        $this->defaultRenderer = $defaultRenderer;
27 9
    }
28
29
    /**
30
     * Handles PHP execution errors such as warnings and notices.
31
     *
32
     * This method is used as a PHP error handler. It will raise an [[\ErrorException]].
33
     *
34
     * @param int $severity the level of the error raised.
35
     * @param string $message the error message.
36
     * @param string $file the filename that the error was raised in.
37
     * @param int $line the line number the error was raised at.
38
     *
39
     * @throws ErrorException
40
     */
41
    public function handleError(int $severity, string $message, string $file, int $line): void
42
    {
43
        if (!(error_reporting() & $severity)) {
44
            // This error code is not included in error_reporting
45
            return;
46
        }
47
48
        throw new ErrorException($message, $severity, $severity, $file, $line);
49
    }
50
51
    /**
52
     * Handle throwable and return output
53
     *
54
     * @param \Throwable $t
55
     * @param ThrowableRendererInterface|null $renderer
56
     * @return string
57
     */
58 9
    public function handleCaughtThrowable(\Throwable $t, ThrowableRendererInterface $renderer = null): string
59
    {
60 9
        if ($renderer === null) {
61 5
            $renderer = $this->defaultRenderer;
62
        }
63
64
        try {
65 9
            $this->log($t);
66 9
            return $this->exposeDetails ? $renderer->renderVerbose($t) : $renderer->render($t);
67
        } catch (\Throwable $t) {
68
            return nl2br($t);
69
        }
70
    }
71
72
    /**
73
     * Handle throwable, echo output and exit
74
     *
75
     * @param \Throwable $t
76
     */
77
    public function handleThrowable(\Throwable $t): void
78
    {
79
        // disable error capturing to avoid recursive errors while handling exceptions
80
        $this->unregister();
81
82
        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
83
        http_response_code(Status::INTERNAL_SERVER_ERROR);
84
85
        echo $this->handleCaughtThrowable($t);
86
        exit(1);
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...
87
    }
88
89
    /**
90
     * Register this error handler.
91
     */
92
    public function register(): void
93
    {
94
        $this->disableDisplayErrors();
95
        set_exception_handler([$this, 'handleThrowable']);
96
        set_error_handler([$this, 'handleError']);
97
98
        if ($this->memoryReserveSize > 0) {
99
            $this->memoryReserve = str_repeat('x', $this->memoryReserveSize);
100
        }
101
        register_shutdown_function([$this, 'handleFatalError']);
102
    }
103
104
    private function disableDisplayErrors(): void
105
    {
106
        if (function_exists('ini_set')) {
107
            ini_set('display_errors', '0');
108
        }
109
    }
110
111
    /**
112
     * Unregisters this error handler by restoring the PHP error and exception handlers.
113
     */
114
    public function unregister(): void
115
    {
116
        restore_error_handler();
117
        restore_exception_handler();
118
    }
119
120
    public function handleFatalError(): void
121
    {
122
        unset($this->memoryReserve);
123
        $error = error_get_last();
124
        if ($error !== null && ErrorException::isFatalError($error)) {
125
            $exception = new ErrorException(
126
                $error['message'],
127
                $error['type'],
128
                $error['type'],
129
                $error['file'],
130
                $error['line']
131
            );
132
            $this->handleThrowable($exception);
133
            exit(1);
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...
134
        }
135
    }
136
137 9
    private function log(\Throwable $t/*, ServerRequestInterface $request*/): void
138
    {
139 9
        $renderer = new PlainTextRenderer();
140 9
        $this->logger->error(
141 9
            $renderer->renderVerbose($t),
142
            [
143 9
                'throwable' => $t,
144
                //'request' => $request,
145
            ]
146
        );
147 9
    }
148
149 1
    public function withExposedDetails(): self
150
    {
151 1
        $new = clone $this;
152 1
        $new->exposeDetails = true;
153 1
        return $new;
154
    }
155
156 1
    public function withoutExposedDetails(): self
157
    {
158 1
        $new = clone $this;
159 1
        $new->exposeDetails = false;
160 1
        return $new;
161
    }
162
}
163