From 0279fbfdadc71f7940e41fdf41ee1d34792fd616 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 17 Apr 2014 09:44:42 +0200 Subject: [PATCH] [Debug] Handled errors --- .../Resources/config/debug.xml | 9 ++ .../Bundle/FrameworkBundle/composer.json | 2 +- src/Symfony/Component/Debug/CHANGELOG.md | 5 ++ src/Symfony/Component/Debug/ErrorHandler.php | 62 ++++++++----- .../Exception/ClassNotFoundException.php | 1 + .../Debug/Exception/ContextErrorException.php | 19 +--- .../Debug/Exception/DummyException.php | 4 +- .../Debug/Exception/FatalErrorException.php | 48 ++++++++++- .../Debug/Exception/FlattenException.php | 31 +------ .../Debug/Exception/HandledErrorException.php | 86 +++++++++++++++++++ .../Exception/UndefinedFunctionException.php | 1 + .../Exception/UndefinedMethodException.php | 10 ++- .../Component/Debug/ExceptionHandler.php | 4 +- .../Debug/ExceptionHandlerInterface.php | 2 + .../Debug/Tests/DebugClassLoaderTest.php | 12 +-- .../Debug/Tests/ErrorHandlerTest.php | 8 +- .../DataCollector/LoggerDataCollector.php | 2 +- .../EventListener/ErrorsLoggerListener.php | 2 +- .../FatalErrorExceptionsListener.php | 45 ++++++++++ .../Fragment/InlineFragmentRenderer.php | 12 ++- .../Component/HttpKernel/HttpKernel.php | 20 +++++ .../Component/HttpKernel/composer.json | 2 +- 22 files changed, 297 insertions(+), 90 deletions(-) create mode 100644 src/Symfony/Component/Debug/Exception/HandledErrorException.php create mode 100644 src/Symfony/Component/HttpKernel/EventListener/FatalErrorExceptionsListener.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index 875c556574..2366ac1f06 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -9,6 +9,7 @@ Symfony\Component\Stopwatch\Stopwatch %kernel.cache_dir%/%kernel.container_class%.xml Symfony\Component\HttpKernel\Controller\TraceableControllerResolver + Symfony\Component\HttpKernel\EventListener\FatalErrorExceptionsListener @@ -39,5 +40,13 @@ scream + + + + + + handleFatalErrorException + + diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index e1ec07d398..894f1f7869 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -21,7 +21,7 @@ "symfony/config" : "~2.4", "symfony/event-dispatcher": "~2.5", "symfony/http-foundation": "~2.4", - "symfony/http-kernel": "~2.4", + "symfony/http-kernel": "~2.5", "symfony/filesystem": "~2.3", "symfony/routing": "~2.2", "symfony/security-core": "~2.4", diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md index d20fde043b..3823fc8783 100644 --- a/src/Symfony/Component/Debug/CHANGELOG.md +++ b/src/Symfony/Component/Debug/CHANGELOG.md @@ -4,7 +4,12 @@ CHANGELOG 2.5.0 ----- +* added HandledErrorException +* added ErrorHandler::setFatalErrorExceptionHandler() * added UndefinedMethodFatalErrorHandler +* deprecated ExceptionHandlerInterface +* deprecated ContextErrorException +* deprecated DummyException 2.4.0 ----- diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index f90ba426ff..46296f45e7 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -13,9 +13,9 @@ namespace Symfony\Component\Debug; use Psr\Log\LogLevel; use Psr\Log\LoggerInterface; -use Symfony\Component\Debug\Exception\ContextErrorException; use Symfony\Component\Debug\Exception\FatalErrorException; use Symfony\Component\Debug\Exception\DummyException; +use Symfony\Component\Debug\Exception\HandledErrorException; use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; @@ -63,6 +63,8 @@ class ErrorHandler private static $stackedErrorLevels = array(); + private static $fatalHandler = false; + /** * Registers the error handler. * @@ -117,7 +119,17 @@ class ErrorHandler } /** - * @throws ContextErrorException When error_reporting returns error + * Sets a fatal error exception handler. + * + * @param callable $handler An handler that will be called on FatalErrorException + */ + public static function setFatalErrorExceptionHandler($handler) + { + self::$fatalHandler = $handler; + } + + /** + * @throws HandledErrorException When error_reporting returns error */ public function handle($level, $message, $file = 'unknown', $line = 0, $context = array()) { @@ -152,7 +164,7 @@ class ErrorHandler $exceptionHandler = set_exception_handler('var_dump'); restore_exception_handler(); - if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandlerInterface) { + if ($exceptionHandler) { if (self::$stackedErrorLevels) { self::$stackedErrors[] = func_get_args(); @@ -160,22 +172,22 @@ class ErrorHandler } $exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line); - $exception = new ContextErrorException($exception, 0, $level, $file, $line, $context); - $exceptionHandler[0]->handle($exception); + $exception = new HandledErrorException($exception, 0, $level, $file, $line, $context); + $exception->handleWith($exceptionHandler); // we must stop the PHP script execution, as the exception has // already been dealt with, so, let's throw an exception that // will be caught by a dummy exception handler set_exception_handler(function (\Exception $e) use ($exceptionHandler) { - if (!$e instanceof DummyException) { - // happens if our dummy exception is caught by a - // catch-all from user code, in which case, let's the + if (!$e instanceof HandledErrorException && !$e instanceof DummyException) { + // happens if our handled exception is caught by a + // catch-all from user code, in which case, let the // current handler handle this "new" exception call_user_func($exceptionHandler, $e); } }); - throw new DummyException(); + throw $exception; } } @@ -256,6 +268,7 @@ class ErrorHandler public function handleFatal() { $this->reservedMemory = ''; + gc_collect_cycles(); $error = error_get_last(); while (self::$stackedErrorLevels) { @@ -281,16 +294,14 @@ class ErrorHandler self::$loggers['emergency']->emergency($error['message'], $fatal); } - if (!$this->displayErrors) { - return; - } + if ($this->displayErrors) { + // get current exception handler + $exceptionHandler = set_exception_handler('var_dump'); + restore_exception_handler(); - // get current exception handler - $exceptionHandler = set_exception_handler('var_dump'); - restore_exception_handler(); - - if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandlerInterface) { - $this->handleFatalError($exceptionHandler[0], $error); + if ($exceptionHandler || self::$fatalHandler) { + $this->handleFatalError($exceptionHandler, $error); + } } } @@ -310,18 +321,25 @@ class ErrorHandler ); } - private function handleFatalError(ExceptionHandlerInterface $exceptionHandler, array $error) + private function handleFatalError($exceptionHandler, array $error) { $level = isset($this->levels[$error['type']]) ? $this->levels[$error['type']] : $error['type']; $message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']); - $exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line']); + $exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line'], 3); foreach ($this->getFatalErrorHandlers() as $handler) { if ($ex = $handler->handleError($error, $exception)) { - return $exceptionHandler->handle($ex); + $exception = $ex; + break; } } - $exceptionHandler->handle($exception); + if ($exceptionHandler) { + $exception->handleWith($exceptionHandler); + } + + if (self::$fatalHandler) { + call_user_func(self::$fatalHandler, $exception); + } } } diff --git a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php index 94169b45a2..b91bf46631 100644 --- a/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php +++ b/src/Symfony/Component/Debug/Exception/ClassNotFoundException.php @@ -28,5 +28,6 @@ class ClassNotFoundException extends FatalErrorException $previous->getLine(), $previous->getPrevious() ); + $this->setTrace($previous->getTrace()); } } diff --git a/src/Symfony/Component/Debug/Exception/ContextErrorException.php b/src/Symfony/Component/Debug/Exception/ContextErrorException.php index 54f0198f1b..d6a9eaf3da 100644 --- a/src/Symfony/Component/Debug/Exception/ContextErrorException.php +++ b/src/Symfony/Component/Debug/Exception/ContextErrorException.php @@ -15,22 +15,9 @@ namespace Symfony\Component\Debug\Exception; * Error Exception with Variable Context. * * @author Christian Sciberras + * + * @deprecated since version 2.5, to be removed in 3.0. */ -class ContextErrorException extends \ErrorException +class ContextErrorException extends HandledErrorException { - private $context = array(); - - public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) - { - parent::__construct($message, $code, $severity, $filename, $lineno); - $this->context = $context; - } - - /** - * @return array Array of variables that existed when the exception occurred - */ - public function getContext() - { - return $this->context; - } } diff --git a/src/Symfony/Component/Debug/Exception/DummyException.php b/src/Symfony/Component/Debug/Exception/DummyException.php index 8891f2fed6..967e033777 100644 --- a/src/Symfony/Component/Debug/Exception/DummyException.php +++ b/src/Symfony/Component/Debug/Exception/DummyException.php @@ -12,9 +12,9 @@ namespace Symfony\Component\Debug\Exception; /** - * Used to stop execution of a PHP script after handling a fatal error. - * * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0. */ class DummyException extends \ErrorException { diff --git a/src/Symfony/Component/Debug/Exception/FatalErrorException.php b/src/Symfony/Component/Debug/Exception/FatalErrorException.php index bf37ef8098..47375a3700 100644 --- a/src/Symfony/Component/Debug/Exception/FatalErrorException.php +++ b/src/Symfony/Component/Debug/Exception/FatalErrorException.php @@ -14,8 +14,54 @@ namespace Symfony\Component\Debug\Exception; /** * Fatal Error Exception. * + * @author Fabien Potencier * @author Konstanton Myakshin + * @author Nicolas Grekas */ -class FatalErrorException extends \ErrorException +class FatalErrorException extends HandledErrorException { + public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + + if (null !== $traceOffset) { + if (function_exists('xdebug_get_function_stack')) { + $trace = xdebug_get_function_stack(); + if (0 < $traceOffset) { + $trace = array_slice($trace, 0, -$traceOffset); + } + $trace = array_reverse($trace); + + foreach ($trace as $i => $frame) { + if (!isset($frame['type'])) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (isset($frame['class'])) { + $trace[$i]['type'] = '::'; + } + } elseif ('dynamic' === $frame['type']) { + $trace[$i]['type'] = '->'; + } elseif ('static' === $frame['type']) { + $trace[$i]['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (isset($frame['params']) && !isset($frame['args'])) { + $trace[$i]['args'] = $frame['params']; + unset($trace[$i]['params']); + } + } + } else { + $trace = array(); + } + + $this->setTrace($trace); + } + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } } diff --git a/src/Symfony/Component/Debug/Exception/FlattenException.php b/src/Symfony/Component/Debug/Exception/FlattenException.php index 878ac4d774..eb49d4609f 100644 --- a/src/Symfony/Component/Debug/Exception/FlattenException.php +++ b/src/Symfony/Component/Debug/Exception/FlattenException.php @@ -172,36 +172,7 @@ class FlattenException public function setTraceFromException(\Exception $exception) { - $trace = $exception->getTrace(); - - if ($exception instanceof FatalErrorException) { - if (function_exists('xdebug_get_function_stack')) { - $trace = array_slice(array_reverse(xdebug_get_function_stack()), 4); - - foreach ($trace as $i => $frame) { - // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 - if (!isset($frame['type'])) { - $trace[$i]['type'] = '??'; - } - - if ('dynamic' === $trace[$i]['type']) { - $trace[$i]['type'] = '->'; - } elseif ('static' === $trace[$i]['type']) { - $trace[$i]['type'] = '::'; - } - - // XDebug also has a different name for the parameters array - if (isset($frame['params']) && !isset($frame['args'])) { - $trace[$i]['args'] = $frame['params']; - unset($trace[$i]['params']); - } - } - } else { - $trace = array_slice(array_reverse($trace), 1); - } - } - - $this->setTrace($trace, $exception->getFile(), $exception->getLine()); + $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); } public function setTrace($trace, $file, $line) diff --git a/src/Symfony/Component/Debug/Exception/HandledErrorException.php b/src/Symfony/Component/Debug/Exception/HandledErrorException.php new file mode 100644 index 0000000000..83219d1196 --- /dev/null +++ b/src/Symfony/Component/Debug/Exception/HandledErrorException.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * @author Nicolas Grekas + */ +class HandledErrorException extends \ErrorException +{ + private $handlerOutput = false; + private $context = array(); + + public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + $this->context = $context; + } + + /** + * @return array Array of variables that existed when the exception occurred + */ + public function getContext() + { + return $this->context; + } + + public function handleWith($exceptionHandler) + { + $this->handlerOutput = false; + ob_start(array($this, 'catchOutput')); + call_user_func($exceptionHandler, $this); + if (false === $this->handlerOutput) { + ob_end_clean(); + } + ob_start(array(__CLASS__, 'flushOutput')); + echo $this->handlerOutput; + $this->handlerOutput = ob_get_length(); + } + + /** + * @internal + */ + public function catchOutput($buffer) + { + $this->handlerOutput = $buffer; + + return ''; + } + + /** + * @internal + */ + public static function flushOutput($buffer) + { + return $buffer; + } + + public function cleanOutput() + { + $status = ob_get_status(); + + if (isset($status['name']) && __CLASS__.'::flushOutput' === $status['name']) { + if ($this->handlerOutput) { + // use substr_replace() instead of substr() for mbstring overloading resistance + echo substr_replace(ob_get_clean(), '', 0, $this->handlerOutput); + } else { + ob_end_flush(); + } + } + } + + public function __destruct() + { + $this->handlerOutput = 0; + $this->cleanOutput(); + } +} diff --git a/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php b/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php index 572c8b30a1..a66ae2a387 100644 --- a/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php +++ b/src/Symfony/Component/Debug/Exception/UndefinedFunctionException.php @@ -28,5 +28,6 @@ class UndefinedFunctionException extends FatalErrorException $previous->getLine(), $previous->getPrevious() ); + $this->setTrace($previous->getTrace()); } } diff --git a/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php b/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php index d84031b442..350dc3187f 100644 --- a/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php +++ b/src/Symfony/Component/Debug/Exception/UndefinedMethodException.php @@ -20,6 +20,14 @@ class UndefinedMethodException extends FatalErrorException { public function __construct($message, \ErrorException $previous) { - parent::__construct($message, $previous->getCode(), $previous->getSeverity(), $previous->getFile(), $previous->getLine(), $previous->getPrevious()); + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); } } diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index da1cbe6268..91e904fbe2 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -71,7 +71,9 @@ class ExceptionHandler implements ExceptionHandlerInterface public function handle(\Exception $exception) { if (class_exists('Symfony\Component\HttpFoundation\Response')) { - $this->createResponse($exception)->send(); + $response = $this->createResponse($exception); + $response->sendHeaders(); + $response->sendContent(); } else { $this->sendPhpResponse($exception); } diff --git a/src/Symfony/Component/Debug/ExceptionHandlerInterface.php b/src/Symfony/Component/Debug/ExceptionHandlerInterface.php index 5ebefcf829..f1740184c6 100644 --- a/src/Symfony/Component/Debug/ExceptionHandlerInterface.php +++ b/src/Symfony/Component/Debug/ExceptionHandlerInterface.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Debug; * An ExceptionHandler does something useful with an exception. * * @author Andrew Moore + * + * @deprecated since version 2.5, to be removed in 3.0. */ interface ExceptionHandlerInterface { diff --git a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php index 0c071414f7..fd98a5116e 100644 --- a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php @@ -78,14 +78,14 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException \Symfony\Component\Debug\Exception\DummyException + * @expectedException \Symfony\Component\Debug\Exception\HandledErrorException */ public function testStacking() { - // the ContextErrorException must not be loaded to test the workaround + // the HandledErrorException must not be loaded to test the workaround // for https://bugs.php.net/65322. - if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) { - $this->markTestSkipped('The ContextErrorException class is already loaded.'); + if (class_exists('Symfony\Component\Debug\Exception\HandledErrorException', false)) { + $this->markTestSkipped('The HandledErrorException class is already loaded.'); } $exceptionHandler = $this->getMock('Symfony\Component\Debug\ExceptionHandler', array('handle')); @@ -93,7 +93,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase $that = $this; $exceptionCheck = function ($exception) use ($that) { - $that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception); + $that->assertInstanceOf('Symfony\Component\Debug\Exception\HandledErrorException', $exception); $that->assertEquals(E_STRICT, $exception->getSeverity()); $that->assertStringStartsWith(__FILE__, $exception->getFile()); $that->assertRegexp('/^Runtime Notice: Declaration/', $exception->getMessage()); @@ -107,7 +107,7 @@ class DebugClassLoaderTest extends \PHPUnit_Framework_TestCase try { // Trigger autoloading + E_STRICT at compile time // which in turn triggers $errorHandler->handle() - // that again triggers autoloading for ContextErrorException. + // that again triggers autoloading for HandledErrorException. // Error stacking works around the bug above and everything is fine. eval(' diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index 133dfa7af3..7786c14ef1 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Debug\Tests; use Symfony\Component\Debug\ErrorHandler; -use Symfony\Component\Debug\Exception\DummyException; +use Symfony\Component\Debug\Exception\HandledErrorException; /** * ErrorHandlerTest @@ -51,7 +51,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase $that = $this; $exceptionCheck = function ($exception) use ($that) { - $that->assertInstanceOf('Symfony\Component\Debug\Exception\ContextErrorException', $exception); + $that->assertInstanceOf('Symfony\Component\Debug\Exception\HandledErrorException', $exception); $that->assertEquals(E_NOTICE, $exception->getSeverity()); $that->assertEquals(__FILE__, $exception->getFile()); $that->assertRegexp('/^Notice: Undefined variable: (foo|bar)/', $exception->getMessage()); @@ -80,7 +80,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase try { self::triggerNotice($this); - } catch (DummyException $e) { + } catch (HandledErrorException $e) { // if an exception is thrown, the test passed } catch (\Exception $e) { restore_error_handler(); @@ -213,7 +213,7 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase $m = new \ReflectionMethod($handler, 'handleFatalError'); $m->setAccessible(true); - $m->invoke($handler, $exceptionHandler, $error); + $m->invoke($handler, array($exceptionHandler, 'handle'), $error); $this->assertInstanceof($class, $exceptionHandler->e); // class names are case insensitive and PHP/HHVM do not return the same diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index bf2dbf014c..48cde6695b 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -11,8 +11,8 @@ namespace Symfony\Component\HttpKernel\DataCollector; +use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Debug\ErrorHandler; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php index 13940ab7ac..ec6b7f9328 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php @@ -12,7 +12,7 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; -use Symfony\Component\HttpKernel\Debug\ErrorHandler; +use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; diff --git a/src/Symfony/Component/HttpKernel/EventListener/FatalErrorExceptionsListener.php b/src/Symfony/Component/HttpKernel/EventListener/FatalErrorExceptionsListener.php new file mode 100644 index 0000000000..39ccfaa0cc --- /dev/null +++ b/src/Symfony/Component/HttpKernel/EventListener/FatalErrorExceptionsListener.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Injects a fatal error exceptions handler into the ErrorHandler. + * + * @author Nicolas Grekas + */ +class FatalErrorExceptionsListener implements EventSubscriberInterface +{ + private $handler = null; + + public function __construct($handler) + { + if (is_callable($handler)) { + $this->handler = $handler; + } + } + + public function injectHandler() + { + if ($this->handler) { + ErrorHandler::setFatalErrorExceptionHandler($this->handler); + } + } + + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => 'injectHandler'); + } +} diff --git a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php index c6ca3d475e..8de07184e3 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Debug\Exception\HandledErrorException; /** * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. @@ -86,10 +87,15 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer } catch (\Exception $e) { // we dispatch the exception event to trigger the logging // the response that comes back is simply ignored - if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { - $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + if (isset($options['ignore_errors']) && $options['ignore_errors']) { + if ($e instanceof HandledErrorException) { + $e->cleanOutput(); + } + if ($this->dispatcher) { + $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); - $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + } } // let's clean up the output buffers that were created by the sub-request diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index fda4075a7e..46e8a1fe12 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -25,6 +25,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Debug\Exception\HandledErrorException; +use Symfony\Component\Debug\Exception\FatalErrorException; /** * HttpKernel notifies events to convert a Request object to a Response one. @@ -70,6 +72,9 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface throw $e; } + if ($e instanceof HandledErrorException) { + $e->cleanOutput(); + } return $this->handleException($e, $request, $type); } @@ -85,6 +90,21 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); } + /** + * @internal + */ + public function handleFatalErrorException(FatalErrorException $exception) + { + $request = $this->requestStack->getMasterRequest(); + $response = $this->handleException($exception, $request, self::MASTER_REQUEST); + + $response->sendHeaders(); + $response->sendContent(); + + $this->terminate($request, $response); + $exception->cleanOutput(); + } + /** * Handles a request to convert it to a response. * diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 3854dd389d..dd01364985 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -19,7 +19,7 @@ "php": ">=5.3.3", "symfony/event-dispatcher": "~2.1", "symfony/http-foundation": "~2.4", - "symfony/debug": "~2.3", + "symfony/debug": "~2.5", "psr/log": "~1.0" }, "require-dev": {