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); |
||
97 | } |
||
98 | |||
99 | /** |
||
100 | * Register this error handler. |
||
101 | */ |
||
102 | public function register(): void |
||
103 | { |
||
104 | ini_set('display_errors', false); |
||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
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 | /** |
||
115 | * Unregisters this error handler by restoring the PHP error and exception handlers. |
||
116 | */ |
||
117 | public function unregister(): void |
||
118 | { |
||
119 | restore_error_handler(); |
||
120 | restore_exception_handler(); |
||
121 | } |
||
122 | |||
123 | public function handleFatalError(): void |
||
124 | { |
||
125 | unset($this->_memoryReserve); |
||
0 ignored issues
–
show
|
|||
126 | $error = error_get_last(); |
||
127 | if ($this->isFatalError($error)) { |
||
128 | $exception = new \ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']); |
||
129 | $this->handleThrowable($exception); |
||
130 | exit(1); |
||
131 | } |
||
132 | } |
||
133 | |||
134 | private function isFatalError(array $error): bool |
||
135 | { |
||
136 | 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); |
||
137 | } |
||
138 | |||
139 | private function log(\Throwable $t/*, ServerRequestInterface $request*/): void |
||
140 | { |
||
141 | $renderer = new PlainTextRenderer(); |
||
142 | $this->logger->error($renderer->render($t), [ |
||
143 | 'throwable' => $t, |
||
144 | //'request' => $request, |
||
145 | ]); |
||
146 | } |
||
147 | } |
||
148 |