[Debug] cleanup interfaces before 2.5-final

This commit is contained in:
Nicolas Grekas 2014-05-19 12:15:59 +02:00
parent b2855a55dd
commit dfa8ff87f3
8 changed files with 156 additions and 173 deletions

View File

@ -9,7 +9,7 @@
<parameter key="debug.stopwatch.class">Symfony\Component\Stopwatch\Stopwatch</parameter>
<parameter key="debug.container.dump">%kernel.cache_dir%/%kernel.container_class%.xml</parameter>
<parameter key="debug.controller_resolver.class">Symfony\Component\HttpKernel\Controller\TraceableControllerResolver</parameter>
<parameter key="debug.fatal_error_exceptions_listener.class">Symfony\Component\HttpKernel\EventListener\FatalErrorExceptionsListener</parameter>
<parameter key="debug.debug_handlers_listener.class">Symfony\Component\HttpKernel\EventListener\DebugHandlersListener</parameter>
</parameters>
<services>
@ -41,11 +41,11 @@
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="debug.fatal_error_exceptions_listener" class="%debug.fatal_error_exceptions_listener.class%">
<service id="debug.debug_handlers_listener" class="%debug.debug_handlers_listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="collection">
<argument type="service" id="http_kernel" on-invalid="null" />
<argument>handleFatalErrorException</argument>
<argument>terminateWithException</argument>
</argument>
</service>
</services>

View File

@ -4,9 +4,8 @@ CHANGELOG
2.5.0
-----
* added ErrorHandler::setFatalErrorExceptionHandler()
* added ExceptionHandler::setHandler()
* added UndefinedMethodFatalErrorHandler
* deprecated ExceptionHandlerInterface
* deprecated DummyException
2.4.0

View File

@ -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 @@ class ErrorHandler
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 @@ class ErrorHandler
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 @@ class ErrorHandler
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 @@ class ErrorHandler
}
}
// 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;
try {
call_user_func($exceptionHandler, $exception);
} catch (\Exception $e) {
// The handler failed. Let PHP handle that now.
throw $exception;
}
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;
}
}
return $buffer;
}
}

View File

@ -29,10 +29,12 @@ if (!defined('ENT_SUBSTITUTE')) {
*
* @author Fabien Potencier <fabien@symfony.com>
*/
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 @@ class ExceptionHandler implements ExceptionHandlerInterface
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 @@ class ExceptionHandler implements ExceptionHandlerInterface
*/
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 @@ EOF;
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;
}
}

View File

@ -1,29 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <me@andrewmoore.ca>
*
* @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);
}

View File

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <p@tchwork.com>
*/
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));
}
}

View File

@ -1,47 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <p@tchwork.com>
*/
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));
}
}

View File

@ -25,7 +25,6 @@ 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\FatalErrorException;
/**
* HttpKernel notifies events to convert a Request object to a Response one.
@ -87,11 +86,16 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface
}
/**
* @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();