Completed
Pull Request — master (#188)
by Alexander
04:45
created

ErrorHandler::register()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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