Completed
Push — master ( b09865...c05ece )
by Alexander
03:58
created

ErrorHandler::disableDisplayErrors()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 4
rs 10
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
            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 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...
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 && $this->isFatalError($error)) {
135
            $exception = new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
136
            $this->handleThrowable($exception);
137
            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...
138
        }
139
    }
140
141
    private function isFatalError(array $error): bool
142
    {
143
        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);
144
    }
145
146
    private function log(\Throwable $t/*, ServerRequestInterface $request*/): void
147
    {
148
        $renderer = new PlainTextRenderer();
149
        $this->logger->error($renderer->render($t), [
150
            'throwable' => $t,
151
            //'request' => $request,
152
        ]);
153
    }
154
}
155