@@ -100,6 +100,7 @@ class ErrorHandler
100
100
private static $ reservedMemory ;
101
101
private static $ stackedErrors = array ();
102
102
private static $ stackedErrorLevels = array ();
103
+ private static $ toStringException = null ;
103
104
104
105
/**
105
106
* Same init value as thrownErrors.
@@ -377,7 +378,10 @@ public function handleError($type, $message, $file, $line, array $context, array
377
378
}
378
379
379
380
if ($ throw ) {
380
- if (($ this ->scopedErrors & $ type ) && class_exists ('Symfony\Component\Debug\Exception\ContextErrorException ' )) {
381
+ if (null !== self ::$ toStringException ) {
382
+ $ throw = self ::$ toStringException ;
383
+ self ::$ toStringException = null ;
384
+ } elseif (($ this ->scopedErrors & $ type ) && class_exists ('Symfony\Component\Debug\Exception\ContextErrorException ' )) {
381
385
// Checking for class existence is a work around for https://bugs.php.net/42098
382
386
$ throw = new ContextErrorException ($ this ->levels [$ type ].': ' .$ message , 0 , $ type , $ file , $ line , $ context );
383
387
} else {
@@ -392,6 +396,47 @@ public function handleError($type, $message, $file, $line, array $context, array
392
396
$ throw ->errorHandlerCanary = new ErrorHandlerCanary ();
393
397
}
394
398
399
+ if (E_USER_ERROR & $ type ) {
400
+ $ backtrace = $ backtrace ?: $ throw ->getTrace ();
401
+
402
+ for ($ i = 1 ; isset ($ backtrace [$ i ]); ++$ i ) {
403
+ if (isset ($ backtrace [$ i ]['function ' ], $ backtrace [$ i ]['type ' ], $ backtrace [$ i - 1 ]['function ' ])
404
+ && '__toString ' === $ backtrace [$ i ]['function ' ]
405
+ && '-> ' === $ backtrace [$ i ]['type ' ]
406
+ && !isset ($ backtrace [$ i - 1 ]['class ' ])
407
+ && ('trigger_error ' === $ backtrace [$ i - 1 ]['function ' ] || 'user_error ' === $ backtrace [$ i - 1 ]['function ' ])
408
+ ) {
409
+ // Here, we know trigger_error() has been called from __toString().
410
+ // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
411
+ // A small convention allows working around the limitation:
412
+ // given a caught $e exception in __toString(), quitting the method with
413
+ // `return trigger_error($e, E_USER_ERROR);` allows this error handler
414
+ // to make $e get through the __toString() barrier.
415
+
416
+ foreach ($ context as $ e ) {
417
+ if (($ e instanceof \Exception || $ e instanceof \Throwable) && $ e ->__toString () === $ message ) {
418
+ if (1 === $ i ) {
419
+ // On HHVM
420
+ $ throw = $ e ;
421
+ break ;
422
+ }
423
+ self ::$ toStringException = $ e ;
424
+
425
+ return true ;
426
+ }
427
+ }
428
+
429
+ if (1 < $ i ) {
430
+ // On PHP (not on HHVM), display the original error message instead of the default one.
431
+ $ this ->handleException ($ throw );
432
+
433
+ // Stop the process by giving back the error to the native handler.
434
+ return false ;
435
+ }
436
+ }
437
+ }
438
+ }
439
+
395
440
throw $ throw ;
396
441
}
397
442
0 commit comments