Completed
Push — master ( 632840...f0c648 )
by Alexander
02:14
created

ErrorHandler::handleThrowable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 10
ccs 0
cts 5
cp 0
crap 2
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 3
    public function __construct(LoggerInterface $logger, ThrowableRendererInterface $defaultRenderer)
24
    {
25 3
        $this->logger = $logger;
26 3
        $this->defaultRenderer = $defaultRenderer;
27
    }
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
        // in case error appeared in __toString method we can't throw any exception
49
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
50
        array_shift($trace);
51
        foreach ($trace as $frame) {
52
            if ($frame['function'] === '__toString') {
53
                trigger_error($message, $severity);
54
                return;
55
            }
56
        }
57
58
        throw new ErrorException($message, $severity, $severity, $file, $line);
59
    }
60
61
    /**
62
     * Handle throwable and return output
63
     *
64
     * @param \Throwable $t
65
     * @param ThrowableRendererInterface|null $renderer
66
     * @return string
67
     */
68 3
    public function handleCaughtThrowable(\Throwable $t, ThrowableRendererInterface $renderer = null): string
69
    {
70 3
        if ($renderer === null) {
71 2
            $renderer = $this->defaultRenderer;
72
        }
73
74
        try {
75 3
            $this->log($t);
76 3
            return $renderer->render($t);
77
        } catch (\Throwable $t) {
78
            return nl2br($t);
79
        }
80
    }
81
82
    /**
83
     * Handle throwable, echo output and exit
84
     *
85
     * @param \Throwable $t
86
     */
87
    public function handleThrowable(\Throwable $t): void
88
    {
89
        // disable error capturing to avoid recursive errors while handling exceptions
90
        $this->unregister();
91
92
        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
93
        http_response_code(500);
94
95
        echo $this->handleCaughtThrowable($t);
96
        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...
97
    }
98
99
    /**
100
     * Register this error handler.
101
     */
102
    public function register(): void
103
    {
104
        $this->disableDisplayErrors();
105
        set_exception_handler([$this, 'handleThrowable']);
106
        set_error_handler([$this, 'handleError']);
107
108
        if ($this->memoryReserveSize > 0) {
109
            $this->memoryReserve = str_repeat('x', $this->memoryReserveSize);
110
        }
111
        register_shutdown_function([$this, 'handleFatalError']);
112
    }
113
114
    private function disableDisplayErrors(): void
115
    {
116
        if (function_exists('ini_set')) {
117
            ini_set('display_errors', '0');
118
        }
119
    }
120
121
    /**
122
     * Unregisters this error handler by restoring the PHP error and exception handlers.
123
     */
124
    public function unregister(): void
125
    {
126
        restore_error_handler();
127
        restore_exception_handler();
128
    }
129
130
    public function handleFatalError(): void
131
    {
132
        unset($this->memoryReserve);
133
        $error = error_get_last();
134
        if ($error !== null && ErrorException::isFatalError($error)) {
135
            $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
136
            $this->handleThrowable($exception);
137
            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...
138
        }
139
    }
140
141 3
    private function log(\Throwable $t/*, ServerRequestInterface $request*/): void
142
    {
143 3
        $renderer = new PlainTextRenderer();
144 3
        $this->logger->error($renderer->render($t), [
145 3
            'throwable' => $t,
146
            //'request' => $request,
147
        ]);
148
    }
149
}
150