2525 *
2626 * @author Fabien Potencier <fabien@symfony.com>
2727 * @author Konstantin Myakshin <koc-dp@yandex.ru>
28+ * @author Nicolas Grekas <p@tchwork.com>
2829 */
2930class ErrorHandler
3031{
@@ -57,6 +58,10 @@ class ErrorHandler
5758 */
5859 private static $ loggers = array ();
5960
61+ private static $ stackedErrors = array ();
62+
63+ private static $ stackedErrorLevels = array ();
64+
6065 /**
6166 * Registers the error handler.
6267 *
@@ -121,45 +126,46 @@ public function handle($level, $message, $file = 'unknown', $line = 0, $context
121126
122127 if ($ level & (E_USER_DEPRECATED | E_DEPRECATED )) {
123128 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 ();
133131 } 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+ }
136144
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+ }
138147 }
139148
140149 return true ;
141150 }
142151
143152 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-
151153 // Exceptions thrown from error handlers are sometimes not caught by the exception
152154 // 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 ' );
154156 restore_exception_handler ();
155157
156158 if (is_array ($ exceptionHandler ) && $ exceptionHandler [0 ] instanceof ExceptionHandler) {
157- $ exceptionHandler [0 ]->handle ($ exception );
159+ if (self ::$ stackedErrorLevels ) {
160+ self ::$ stackedErrors [] = func_get_args ();
158161
159- if (!class_exists ('Symfony\Component\Debug\Exception\DummyException ' )) {
160- require __DIR__ .'/Exception/DummyException.php ' ;
162+ return true ;
161163 }
162164
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+
163169 // we must stop the PHP script execution, as the exception has
164170 // already been dealt with, so, let's throw an exception that
165171 // will be caught by a dummy exception handler
@@ -179,13 +185,61 @@ function ($row) {
179185 return false ;
180186 }
181187
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+
182230 public function handleFatal ()
183231 {
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 ) {
185240 return ;
186241 }
187242
188- $ this ->reservedMemory = '' ;
189243 $ type = $ error ['type ' ];
190244 if (0 === $ this ->level || !in_array ($ type , array (E_ERROR , E_CORE_ERROR , E_COMPILE_ERROR , E_PARSE ))) {
191245 return ;
@@ -206,7 +260,7 @@ public function handleFatal()
206260 }
207261
208262 // get current exception handler
209- $ exceptionHandler = set_exception_handler (function () {} );
263+ $ exceptionHandler = set_exception_handler (' var_dump ' );
210264 restore_exception_handler ();
211265
212266 if (is_array ($ exceptionHandler ) && $ exceptionHandler [0 ] instanceof ExceptionHandler) {
0 commit comments