*/ -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
+ */
+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 a6ab82ea28..33866e94ea 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": {