Completed
Push — master ( fcdf89...4a0ecc )
by Alexander
14:58
created

ErrorHandler::handleFatalError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Yiisoft\Yii\Web\ErrorHandler;
4
5
use Psr\Log\LoggerInterface;
6
7
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
    public function __construct(LoggerInterface $logger, ThrowableRendererInterface $defaultRenderer)
24
    {
25
        $this->logger = $logger;
26
        $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, 0, $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
    public function handleCaughtThrowable(\Throwable $t, ThrowableRendererInterface $renderer = null): string
69
    {
70
        if ($renderer === null) {
71
            $renderer = $this->defaultRenderer;
72
        }
73
74
        try {
75
            $this->log($t);
76
            return $renderer->render($t);
77
        } catch (\Throwable $t) {
78
79
            return nl2br($t);
80
        }
81
    }
82
83
    /**
84
     * Handle throwable, echo output and exit
85
     *
86
     * @param \Throwable $t
87
     */
88
    public function handleThrowable(\Throwable $t): void
89
    {
90
        // disable error capturing to avoid recursive errors while handling exceptions
91
        $this->unregister();
92
93
        // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
94
        http_response_code(500);
95
96
        echo $this->handleCaughtThrowable($t);
97
        exit(1);
0 ignored issues
show
Best Practice introduced by Alexander Makarov
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...
98
    }
99
100
    /**
101
     * Register this error handler.
102
     */
103
    public function register(): void
104
    {
105
        ini_set('display_errors', false);
0 ignored issues
show
Bug introduced by Alexander Makarov
false of type false is incompatible with the type string expected by parameter $newvalue of ini_set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
        ini_set('display_errors', /** @scrutinizer ignore-type */ false);
Loading history...
106
        set_exception_handler([$this, 'handleThrowable']);
107
        set_error_handler([$this, 'handleError']);
108
109
        if ($this->memoryReserveSize > 0) {
110
            $this->memoryReserve = str_repeat('x', $this->memoryReserveSize);
111
        }
112
        register_shutdown_function([$this, 'handleFatalError']);
113
    }
114
115
    /**
116
     * Unregisters this error handler by restoring the PHP error and exception handlers.
117
     */
118
    public function unregister(): void
119
    {
120
        restore_error_handler();
121
        restore_exception_handler();
122
    }
123
124
    public function handleFatalError(): void
125
    {
126
        unset($this->_memoryReserve);
0 ignored issues
show
Bug introduced by Alexander Makarov
The property _memoryReserve does not exist on Yiisoft\Yii\Web\ErrorHandler\ErrorHandler. Did you mean memoryReserve?
Loading history...
127
        $error = error_get_last();
128
        if ($this->isFatalError($error)) {
129
            $exception = new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
130
            $this->handleThrowable($exception);
131
            exit(1);
0 ignored issues
show
Best Practice introduced by Alexander Makarov
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...
132
        }
133
    }
134
135
    private function isFatalError(array $error): bool
136
    {
137
        return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING], true);
138
    }
139
140
    private function log(\Throwable $t/*, ServerRequestInterface $request*/): void
141
    {
142
        $renderer = new PlainTextRenderer();
143
        $this->logger->error($renderer->render($t), [
144
            'throwable' => $t,
145
            //'request' => $request,
146
        ]);
147
    }
148
}
149