From dfa8ff87f3cff4f33d9d003814bf4d9043c9ee1b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 19 May 2014 12:15:59 +0200 Subject: [PATCH 1/2] [Debug] cleanup interfaces before 2.5-final --- .../Resources/config/debug.xml | 6 +- src/Symfony/Component/Debug/CHANGELOG.md | 3 +- src/Symfony/Component/Debug/ErrorHandler.php | 89 ++--------------- .../Component/Debug/ExceptionHandler.php | 95 +++++++++++++++++-- .../Debug/ExceptionHandlerInterface.php | 29 ------ .../EventListener/DebugHandlersListener.php | 50 ++++++++++ .../FatalErrorExceptionsListener.php | 47 --------- .../Component/HttpKernel/HttpKernel.php | 10 +- 8 files changed, 156 insertions(+), 173 deletions(-) delete mode 100644 src/Symfony/Component/Debug/ExceptionHandlerInterface.php create mode 100644 src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php delete 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 2366ac1f0604e..c457e4f903a36 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -9,7 +9,7 @@ Symfony\Component\Stopwatch\Stopwatch %kernel.cache_dir%/%kernel.container_class%.xml Symfony\Component\HttpKernel\Controller\TraceableControllerResolver - Symfony\Component\HttpKernel\EventListener\FatalErrorExceptionsListener + Symfony\Component\HttpKernel\EventListener\DebugHandlersListener @@ -41,11 +41,11 @@ - + - handleFatalErrorException + terminateWithException diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md index b128efaaa8913..776468fb7a59e 100644 --- a/src/Symfony/Component/Debug/CHANGELOG.md +++ b/src/Symfony/Component/Debug/CHANGELOG.md @@ -4,9 +4,8 @@ CHANGELOG 2.5.0 ----- -* added ErrorHandler::setFatalErrorExceptionHandler() +* added ExceptionHandler::setHandler() * added UndefinedMethodFatalErrorHandler -* deprecated ExceptionHandlerInterface * deprecated DummyException 2.4.0 diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index b62e5b752150f..9a6f6fa1c3699 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -53,8 +53,6 @@ class ErrorHandler private $displayErrors; - private $caughtOutput = 0; - /** * @var LoggerInterface[] Loggers for channels */ @@ -64,8 +62,6 @@ class ErrorHandler private static $stackedErrorLevels = array(); - private static $fatalHandler = false; - /** * Registers the error handler. * @@ -119,16 +115,6 @@ public static function setLogger(LoggerInterface $logger, $channel = 'deprecatio self::$loggers[$channel] = $logger; } - /** - * 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 ContextErrorException When error_reporting returns error */ @@ -284,7 +270,7 @@ public function handleFatal() throw $exception; } - if (!$error || !$this->level || !in_array($error['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) { + if (!$error || !$this->level || !($error['type'] & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_PARSE))) { return; } @@ -298,7 +284,7 @@ public function handleFatal() self::$loggers['emergency']->emergency($error['message'], $fatal); } - if ($this->displayErrors && ($exceptionHandler || self::$fatalHandler)) { + if ($this->displayErrors && $exceptionHandler) { $this->handleFatalError($exceptionHandler, $error); } } @@ -336,73 +322,12 @@ private function handleFatalError($exceptionHandler, array $error) } } - // To be as fail-safe as possible, the FatalErrorException is first handled - // by the exception handler, then by the fatal error handler. The latter takes - // precedence and any output from the former is cancelled, if and only if - // nothing bad happens in this handling path. - - $caughtOutput = 0; - - if ($exceptionHandler) { - $this->caughtOutput = false; - ob_start(array($this, 'catchOutput')); - try { - call_user_func($exceptionHandler, $exception); - } catch (\Exception $e) { - // Ignore this exception, we have to deal with the fatal error - } - if (false === $this->caughtOutput) { - ob_end_clean(); - } - if (isset($this->caughtOutput[0])) { - ob_start(array($this, 'cleanOutput')); - echo $this->caughtOutput; - $caughtOutput = ob_get_length(); - } - $this->caughtOutput = 0; - } - - if (self::$fatalHandler) { - try { - call_user_func(self::$fatalHandler, $exception); - - if ($caughtOutput) { - $this->caughtOutput = $caughtOutput; - } - } catch (\Exception $e) { - if (!$caughtOutput) { - // Neither the exception nor the fatal handler succeeded. - // Let PHP handle that now. - throw $exception; - } - } - } - } - - /** - * @internal - */ - public function catchOutput($buffer) - { - $this->caughtOutput = $buffer; - - return ''; - } - - /** - * @internal - */ - public function cleanOutput($buffer) - { - if ($this->caughtOutput) { - // use substr_replace() instead of substr() for mbstring overloading resistance - $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtOutput); - if (isset($cleanBuffer[0])) { - $buffer = $cleanBuffer; - } + try { + call_user_func($exceptionHandler, $exception); + } catch (\Exception $e) { + // The handler failed. Let PHP handle that now. + throw $exception; } - - return $buffer; } } diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 91e904fbe25ce..4979899afcdfb 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -29,10 +29,12 @@ * * @author Fabien Potencier */ -class ExceptionHandler implements ExceptionHandlerInterface +class ExceptionHandler { private $debug; private $charset; + private $handler; + private $caughtOutput = 0; public function __construct($debug = true, $charset = 'UTF-8') { @@ -56,6 +58,22 @@ public static function register($debug = true) return $handler; } + /** + * Sets a user exception handler. + * + * @param callable $handler An handler that will be called on Exception + */ + public function setHandler($handler) + { + if (isset($handler) && !is_callable($handler)) { + throw new \LogicException('The exception handler must be a valid PHP callable.'); + } + $old = $this->handler; + $this->handler = $handler; + + return $old; + } + /** * {@inheritdoc} * @@ -70,12 +88,49 @@ public static function register($debug = true) */ public function handle(\Exception $exception) { - if (class_exists('Symfony\Component\HttpFoundation\Response')) { - $response = $this->createResponse($exception); - $response->sendHeaders(); - $response->sendContent(); - } else { - $this->sendPhpResponse($exception); + // To be as fail-safe as possible, the exception is first handled + // by our simple exception handler, then by the user exception handler. + // The latter takes precedence and any output from the former is cancelled, + // if and only if nothing bad happens in this handling path. + + $caughtOutput = 0; + + $this->caughtOutput = false; + ob_start(array($this, 'catchOutput')); + try { + if (class_exists('Symfony\Component\HttpFoundation\Response')) { + $response = $this->createResponse($exception); + $response->sendHeaders(); + $response->sendContent(); + } else { + $this->sendPhpResponse($exception); + } + } catch (\Exception $e) { + // Ignore this $e exception, we have to deal with $exception + } + if (false === $this->caughtOutput) { + ob_end_clean(); + } + if (isset($this->caughtOutput[0])) { + ob_start(array($this, 'cleanOutput')); + echo $this->caughtOutput; + $caughtOutput = ob_get_length(); + } + $this->caughtOutput = 0; + + if (!empty($this->handler)) { + try { + call_user_func($this->handler, $exception); + + if ($caughtOutput) { + $this->caughtOutput = $caughtOutput; + } + } catch (\Exception $e) { + if (!$caughtOutput) { + // All handlers failed. Let PHP handle that now. + throw $exception; + } + } } } @@ -317,4 +372,30 @@ private function formatArgs(array $args) return implode(', ', $result); } + + /** + * @internal + */ + public function catchOutput($buffer) + { + $this->caughtOutput = $buffer; + + return ''; + } + + /** + * @internal + */ + public function cleanOutput($buffer) + { + if ($this->caughtOutput) { + // use substr_replace() instead of substr() for mbstring overloading resistance + $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtOutput); + if (isset($cleanBuffer[0])) { + $buffer = $cleanBuffer; + } + } + + return $buffer; + } } diff --git a/src/Symfony/Component/Debug/ExceptionHandlerInterface.php b/src/Symfony/Component/Debug/ExceptionHandlerInterface.php deleted file mode 100644 index f1740184c6dfe..0000000000000 --- a/src/Symfony/Component/Debug/ExceptionHandlerInterface.php +++ /dev/null @@ -1,29 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -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 -{ - /** - * Handles an exception. - * - * @param \Exception $exception An \Exception instance - */ - public function handle(\Exception $exception); -} diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php new file mode 100644 index 0000000000000..f46ef71208bde --- /dev/null +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -0,0 +1,50 @@ + + * + * 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\ExceptionHandler; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Configures the ExceptionHandler. + * + * @author Nicolas Grekas + */ +class DebugHandlersListener implements EventSubscriberInterface +{ + private $exceptionHandler; + + public function __construct($exceptionHandler) + { + if (is_callable($exceptionHandler)) { + $this->exceptionHandler = $exceptionHandler; + } + } + + public function configure() + { + if ($this->exceptionHandler) { + $mainHandler = set_exception_handler('var_dump'); + restore_exception_handler(); + if ($mainHandler instanceof ExceptionHandler) { + $mainHandler->setHandler($this->exceptionHandler); + } + $this->exceptionHandler = null; + } + } + + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => array('configure', 2048)); + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/FatalErrorExceptionsListener.php b/src/Symfony/Component/HttpKernel/EventListener/FatalErrorExceptionsListener.php deleted file mode 100644 index 0677682810ea8..0000000000000 --- a/src/Symfony/Component/HttpKernel/EventListener/FatalErrorExceptionsListener.php +++ /dev/null @@ -1,47 +0,0 @@ - - * - * 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); - $this->handler = null; - } - } - - public static function getSubscribedEvents() - { - // Don't register early as e.g. the Router is generally required by the handler - return array(KernelEvents::REQUEST => array('injectHandler', 8)); - } -} diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index c3556972e97d1..68d89c94e9be3 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -25,7 +25,6 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\Debug\Exception\FatalErrorException; /** * HttpKernel notifies events to convert a Request object to a Response one. @@ -87,11 +86,16 @@ public function terminate(Request $request, Response $response) } /** + * @throws \LogicException If the request stack is empty + * * @internal */ - public function handleFatalErrorException(FatalErrorException $exception) + public function terminateWithException(\Exception $exception) { - $request = $this->requestStack->getMasterRequest(); + if (!$request = $this->requestStack->getMasterRequest()) { + throw new \LogicException('Request stack is empty', 0, $exception); + } + $response = $this->handleException($exception, $request, self::MASTER_REQUEST); $response->sendHeaders(); From e3255bf5259a87760e4702c2b0faca5f4f8ec6a8 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 May 2014 09:49:05 +0200 Subject: [PATCH 2/2] [Debug] better ouf of memory error handling --- src/Symfony/Component/Debug/ErrorHandler.php | 17 ++++++++----- .../Debug/Exception/FatalErrorException.php | 24 +++++++++++-------- .../Debug/Exception/OutOfMemoryException.php | 21 ++++++++++++++++ .../Component/Debug/ExceptionHandler.php | 9 +++++++ 4 files changed, 55 insertions(+), 16 deletions(-) create mode 100644 src/Symfony/Component/Debug/Exception/OutOfMemoryException.php diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index 9a6f6fa1c3699..850f7d9c55376 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -15,6 +15,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Debug\Exception\ContextErrorException; use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; @@ -313,12 +314,16 @@ 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'], 3); - - foreach ($this->getFatalErrorHandlers() as $handler) { - if ($e = $handler->handleError($error, $exception)) { - $exception = $e; - break; + if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + $exception = new OutOfMemoryException($message, 0, $error['type'], $error['file'], $error['line'], 3, false); + } else { + $exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line'], 3, true); + + foreach ($this->getFatalErrorHandlers() as $handler) { + if ($e = $handler->handleError($error, $exception)) { + $exception = $e; + break; + } } } diff --git a/src/Symfony/Component/Debug/Exception/FatalErrorException.php b/src/Symfony/Component/Debug/Exception/FatalErrorException.php index 4e29495f302cb..d5b58468c9c6d 100644 --- a/src/Symfony/Component/Debug/Exception/FatalErrorException.php +++ b/src/Symfony/Component/Debug/Exception/FatalErrorException.php @@ -20,7 +20,7 @@ */ class FatalErrorException extends \ErrorException { - public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null) + public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true) { parent::__construct($message, $code, $severity, $filename, $lineno); @@ -28,28 +28,32 @@ public function __construct($message, $code, $severity, $filename, $lineno, $tra if (function_exists('xdebug_get_function_stack')) { $trace = xdebug_get_function_stack(); if (0 < $traceOffset) { - $trace = array_slice($trace, 0, -$traceOffset); + array_splice($trace, -$traceOffset); } - $trace = array_reverse($trace); - foreach ($trace as $i => $frame) { + foreach ($trace as &$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'] = '::'; + $frame['type'] = '::'; } } elseif ('dynamic' === $frame['type']) { - $trace[$i]['type'] = '->'; + $frame['type'] = '->'; } elseif ('static' === $frame['type']) { - $trace[$i]['type'] = '::'; + $frame['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']); + if (!$traceArgs) { + unset($frame['params'], $frame['args']); + } elseif (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + unset($frame['params']); } } + + unset($frame); + $trace = array_reverse($trace); } else { $trace = array(); } diff --git a/src/Symfony/Component/Debug/Exception/OutOfMemoryException.php b/src/Symfony/Component/Debug/Exception/OutOfMemoryException.php new file mode 100644 index 0000000000000..fec1979836450 --- /dev/null +++ b/src/Symfony/Component/Debug/Exception/OutOfMemoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Out of memory exception. + * + * @author Nicolas Grekas + */ +class OutOfMemoryException extends FatalErrorException +{ +} diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php index 4979899afcdfb..bfbd78313fb2f 100644 --- a/src/Symfony/Component/Debug/ExceptionHandler.php +++ b/src/Symfony/Component/Debug/ExceptionHandler.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; if (!defined('ENT_SUBSTITUTE')) { define('ENT_SUBSTITUTE', 8); @@ -62,6 +63,8 @@ public static function register($debug = true) * Sets a user exception handler. * * @param callable $handler An handler that will be called on Exception + * + * @return callable|null The previous exception handler if any */ public function setHandler($handler) { @@ -88,6 +91,12 @@ public function setHandler($handler) */ public function handle(\Exception $exception) { + if ($exception instanceof OutOfMemoryException) { + $this->sendPhpResponse($exception); + + return; + } + // To be as fail-safe as possible, the exception is first handled // by our simple exception handler, then by the user exception handler. // The latter takes precedence and any output from the former is cancelled, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy