25
25
*
26
26
* @author Fabien Potencier <fabien@symfony.com>
27
27
* @author Konstantin Myakshin <koc-dp@yandex.ru>
28
+ * @author Nicolas Grekas <p@tchwork.com>
28
29
*/
29
30
class ErrorHandler
30
31
{
@@ -57,6 +58,10 @@ class ErrorHandler
57
58
*/
58
59
private static $ loggers = array ();
59
60
61
+ private static $ stackedErrors = array ();
62
+
63
+ private static $ stackedErrorLevels = array ();
64
+
60
65
/**
61
66
* Registers the error handler.
62
67
*
@@ -121,45 +126,46 @@ public function handle($level, $message, $file = 'unknown', $line = 0, $context
121
126
122
127
if ($ level & (E_USER_DEPRECATED | E_DEPRECATED )) {
123
128
if (isset (self ::$ loggers ['deprecation ' ])) {
124
- if (version_compare (PHP_VERSION , '5.4 ' , '< ' )) {
125
- $ stack = array_map (
126
- function ($ row ) {
127
- unset($ row ['args ' ]);
128
-
129
- return $ row ;
130
- },
131
- array_slice (debug_backtrace (false ), 0 , 10 )
132
- );
129
+ if (self ::$ stackedErrorLevels ) {
130
+ self ::$ stackedErrors [] = func_get_args ();
133
131
} else {
134
- $ stack = debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS , 10 );
135
- }
132
+ if (version_compare (PHP_VERSION , '5.4 ' , '< ' )) {
133
+ $ stack = array_map (
134
+ function ($ row ) {
135
+ unset($ row ['args ' ]);
136
+
137
+ return $ row ;
138
+ },
139
+ array_slice (debug_backtrace (false ), 0 , 10 )
140
+ );
141
+ } else {
142
+ $ stack = debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS , 10 );
143
+ }
136
144
137
- self ::$ loggers ['deprecation ' ]->warning ($ message , array ('type ' => self ::TYPE_DEPRECATION , 'stack ' => $ stack ));
145
+ self ::$ loggers ['deprecation ' ]->warning ($ message , array ('type ' => self ::TYPE_DEPRECATION , 'stack ' => $ stack ));
146
+ }
138
147
}
139
148
140
149
return true ;
141
150
}
142
151
143
152
if ($ this ->displayErrors && error_reporting () & $ level && $ this ->level & $ level ) {
144
- // make sure the ContextErrorException class is loaded (https://bugs.php.net/bug.php?id=65322)
145
- if (!class_exists ('Symfony\Component\Debug\Exception\ContextErrorException ' )) {
146
- require __DIR__ .'/Exception/ContextErrorException.php ' ;
147
- }
148
-
149
- $ exception = new ContextErrorException (sprintf ('%s: %s in %s line %d ' , isset ($ this ->levels [$ level ]) ? $ this ->levels [$ level ] : $ level , $ message , $ file , $ line ), 0 , $ level , $ file , $ line , $ context );
150
-
151
153
// Exceptions thrown from error handlers are sometimes not caught by the exception
152
154
// handler, so we invoke it directly (https://bugs.php.net/bug.php?id=54275)
153
- $ exceptionHandler = set_exception_handler (function () {} );
155
+ $ exceptionHandler = set_exception_handler (' var_dump ' );
154
156
restore_exception_handler ();
155
157
156
158
if (is_array ($ exceptionHandler ) && $ exceptionHandler [0 ] instanceof ExceptionHandler) {
157
- $ exceptionHandler [0 ]->handle ($ exception );
159
+ if (self ::$ stackedErrorLevels ) {
160
+ self ::$ stackedErrors [] = func_get_args ();
158
161
159
- if (!class_exists ('Symfony\Component\Debug\Exception\DummyException ' )) {
160
- require __DIR__ .'/Exception/DummyException.php ' ;
162
+ return true ;
161
163
}
162
164
165
+ $ exception = sprintf ('%s: %s in %s line %d ' , isset ($ this ->levels [$ level ]) ? $ this ->levels [$ level ] : $ level , $ message , $ file , $ line );
166
+ $ exception = new ContextErrorException ($ exception , 0 , $ level , $ file , $ line , $ context );
167
+ $ exceptionHandler [0 ]->handle ($ exception );
168
+
163
169
// we must stop the PHP script execution, as the exception has
164
170
// already been dealt with, so, let's throw an exception that
165
171
// will be caught by a dummy exception handler
@@ -179,13 +185,61 @@ function ($row) {
179
185
return false ;
180
186
}
181
187
188
+ /**
189
+ * Configure the error handler for delayed handling.
190
+ * Ensures also that non-catchable fatal errors are never silenced.
191
+ *
192
+ * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
193
+ * PHP has a compile stage where it behaves unusually. To workaround it,
194
+ * we plug an error handler that only stacks errors for later.
195
+ *
196
+ * The most important feature of this is to prevent
197
+ * autoloading until unstackErrors() is called.
198
+ */
199
+ public static function stackErrors ()
200
+ {
201
+ self ::$ stackedErrorLevels [] = error_reporting (error_reporting () | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR );
202
+ }
203
+
204
+ /**
205
+ * Unstacks stacked errors and forwards to the regular handler
206
+ */
207
+ public static function unstackErrors ()
208
+ {
209
+ $ level = array_pop (self ::$ stackedErrorLevels );
210
+
211
+ if (null !== $ level ) {
212
+ error_reporting ($ level );
213
+ }
214
+
215
+ if (empty (self ::$ stackedErrorLevels )) {
216
+ $ errors = self ::$ stackedErrors ;
217
+ self ::$ stackedErrors = array ();
218
+
219
+ $ errorHandler = set_error_handler ('var_dump ' );
220
+ restore_error_handler ();
221
+
222
+ if ($ errorHandler ) {
223
+ foreach ($ errors as $ e ) {
224
+ call_user_func_array ($ errorHandler , $ e );
225
+ }
226
+ }
227
+ }
228
+ }
229
+
182
230
public function handleFatal ()
183
231
{
184
- if (null === $ error = error_get_last ()) {
232
+ $ this ->reservedMemory = '' ;
233
+ $ error = error_get_last ();
234
+
235
+ while (self ::$ stackedErrorLevels ) {
236
+ static ::unstackErrors ();
237
+ }
238
+
239
+ if (null === $ error ) {
185
240
return ;
186
241
}
187
242
188
- $ this ->reservedMemory = '' ;
189
243
$ type = $ error ['type ' ];
190
244
if (0 === $ this ->level || !in_array ($ type , array (E_ERROR , E_CORE_ERROR , E_COMPILE_ERROR , E_PARSE ))) {
191
245
return ;
@@ -206,7 +260,7 @@ public function handleFatal()
206
260
}
207
261
208
262
// get current exception handler
209
- $ exceptionHandler = set_exception_handler (function () {} );
263
+ $ exceptionHandler = set_exception_handler (' var_dump ' );
210
264
restore_exception_handler ();
211
265
212
266
if (is_array ($ exceptionHandler ) && $ exceptionHandler [0 ] instanceof ExceptionHandler) {
0 commit comments