From 4d0ab7ddc1088e6af7c0b3944b77e10b19bd09a0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 27 Sep 2014 09:00:54 +0200 Subject: [PATCH 1/3] [FrameworkBundle] enable ErrorHandler in prod --- .../FrameworkExtension.php | 6 ++- .../Resources/config/debug_prod.xml | 3 ++ src/Symfony/Component/Debug/ErrorHandler.php | 10 +++- .../Debug/Tests/ErrorHandlerTest.php | 24 +++++++++ .../EventListener/DebugHandlersListener.php | 42 ++++++++++++++- .../DebugHandlersListenerTest.php | 52 +++++++++++++++++++ 6 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index e02f927e7a..ecfd5531b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -133,8 +133,6 @@ class FrameworkExtension extends Extension if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); - $definition->replaceArgument(0, array(new Reference('http_kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'terminateWithException')); - $definition = $container->findDefinition('http_kernel'); $definition->replaceArgument(2, new Reference('debug.controller_resolver')); @@ -145,11 +143,15 @@ class FrameworkExtension extends Extension $container->setAlias('event_dispatcher', 'debug.event_dispatcher'); } else { $definition->replaceArgument(2, E_COMPILE_ERROR | E_PARSE | E_ERROR | E_CORE_ERROR); + + $container->findDefinition('debug.error_handler')->addMethodCall('throwAt', array(0)); } $this->addClassesToCompile(array( 'Symfony\\Component\\Config\\FileLocator', + 'Symfony\\Component\\Debug\\ErrorHandler', + 'Symfony\\Component\\EventDispatcher\\Event', 'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml index 983e9f4c82..d830006eca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml @@ -6,6 +6,7 @@ Symfony\Component\HttpKernel\EventListener\DebugHandlersListener + Symfony\Component\Debug\ErrorHandler Symfony\Component\Stopwatch\Stopwatch @@ -20,6 +21,8 @@ null + + diff --git a/src/Symfony/Component/Debug/ErrorHandler.php b/src/Symfony/Component/Debug/ErrorHandler.php index 5982b0c482..c9f1d6d6b3 100644 --- a/src/Symfony/Component/Debug/ErrorHandler.php +++ b/src/Symfony/Component/Debug/ErrorHandler.php @@ -124,9 +124,15 @@ class ErrorHandler $handler = new static(); $levels &= $handler->thrownErrors; - set_error_handler(array($handler, 'handleError'), $levels); + $prev = set_error_handler(array($handler, 'handleError'), $levels); + $prev = is_array($prev) ? $prev[0] : null; + if ($prev instanceof self) { + restore_error_handler(); + $handler = $prev; + } else { + $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException'))); + } $handler->throwAt($throw ? $levels : 0, true); - $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException'))); return $handler; } diff --git a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php index aaaefd54dd..9ed5966813 100644 --- a/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -46,6 +46,30 @@ class ErrorHandlerTest extends \PHPUnit_Framework_TestCase error_reporting($this->errorReporting); } + public function testRegister() + { + $handler = ErrorHandler::register(); + + try { + $this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler); + + try { + $this->assertSame($handler, ErrorHandler::register()); + } catch (\Exception $e) { + restore_error_handler(); + restore_exception_handler(); + } + } catch (\Exception $e) { + } + + restore_error_handler(); + restore_exception_handler(); + + if (isset($e)) { + throw $e; + } + } + public function testNotice() { ErrorHandler::register(); diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index 2c18bd143b..eba7859f44 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -14,8 +14,14 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; /** * Configures errors and exceptions handlers. @@ -28,6 +34,7 @@ class DebugHandlersListener implements EventSubscriberInterface private $logger; private $levels; private $debug; + private $fileLinkFormat; /** * @param callable $exceptionHandler A handler that will be called on Exception @@ -45,8 +52,20 @@ class DebugHandlersListener implements EventSubscriberInterface $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); } - public function configure() + /** + * Configures the error handler. + * + * @param Event|null $event The triggering event + * @param string|null $eventName The triggering event name + * @param EventDispatcherInterface|null $eventDispatcher The dispatcher used to trigger $event + */ + public function configure(Event $event = null, $eventName = null, EventDispatcherInterface $eventDispatcher = null) { + if (null !== $eventDispatcher) { + foreach (array_keys(static::getSubscribedEvents()) as $name) { + $eventDispatcher->removeListener($name, array($this, 'configure')); + } + } if ($this->logger) { $handler = set_error_handler('var_dump', 0); $handler = is_array($handler) ? $handler[0] : null; @@ -67,6 +86,19 @@ class DebugHandlersListener implements EventSubscriberInterface } $this->logger = $this->levels = null; } + if (!$this->exceptionHandler) { + if ($event instanceof KernelEvent) { + $this->exceptionHandler = array($event->getKernel(), 'terminateWithException'); + } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) { + $output = $event->getOutput(); + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->exceptionHandler = function ($e) use ($app, $output) { + $app->renderException($e, $output); + }; + } + } if ($this->exceptionHandler) { $handler = set_exception_handler('var_dump'); $handler = is_array($handler) ? $handler[0] : null; @@ -86,6 +118,12 @@ class DebugHandlersListener implements EventSubscriberInterface public static function getSubscribedEvents() { - return array(KernelEvents::REQUEST => array('configure', 2048)); + $events = array(KernelEvents::REQUEST => array('configure', 2048)); + + if (defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) { + $events[ConsoleEvents::COMMAND] = array('configure', 2048); + } + + return $events; } } diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php index dabc377af0..976d1e938c 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php @@ -12,9 +12,17 @@ namespace Symfony\Component\HttpKernel\Tests\EventListener; use Psr\Log\LogLevel; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener; +use Symfony\Component\HttpKernel\KernelEvents; /** * DebugHandlersListenerTest @@ -53,4 +61,48 @@ class DebugHandlersListenerTest extends \PHPUnit_Framework_TestCase $this->assertArrayHasKey(E_DEPRECATED, $loggers); $this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]); } + + public function testConsoleEvent() + { + $dispatcher = new EventDispatcher(); + $listener = new DebugHandlersListener(null); + $app = $this->getMock('Symfony\Component\Console\Application'); + $app->expects($this->once())->method('getHelperSet')->will($this->returnValue(new HelperSet())); + $command = new Command(__FUNCTION__); + $command->setApplication($app); + $event = new ConsoleEvent($command, new ArgvInput(), new ConsoleOutput()); + + $dispatcher->addSubscriber($listener); + + $xListeners = array( + KernelEvents::REQUEST => array(array($listener, 'configure')), + ConsoleEvents::COMMAND => array(array($listener, 'configure')), + ); + $this->assertSame($xListeners, $dispatcher->getListeners()); + + $exception = null; + $eHandler = new ErrorHandler(); + set_error_handler(array($eHandler, 'handleError')); + set_exception_handler(array($eHandler, 'handleException')); + try { + $dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + } catch (\Exception $exception) { + } + restore_exception_handler(); + restore_error_handler(); + + if (null !== $exception) { + throw $exception; + } + + $this->assertSame(array(), $dispatcher->getListeners()); + + $xHandler = $eHandler->setExceptionHandler('var_dump'); + $this->assertInstanceOf('Closure', $xHandler); + + $app->expects($this->once()) + ->method('renderException'); + + $xHandler(new \Exception()); + } } From 4acf5d3d0b8226a970dc14b79966f31de6c0d2c2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 30 Sep 2014 09:39:40 +0200 Subject: [PATCH 2/3] [Debug] make screaming configurable --- .../FrameworkExtension.php | 6 +-- .../Resources/config/debug_prod.xml | 3 +- .../EventListener/DebugHandlersListener.php | 42 +++++++++++-------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ecfd5531b8..093b48a649 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -127,12 +127,14 @@ class FrameworkExtension extends Extension $definition = $container->findDefinition('debug.debug_handlers_listener'); if ($container->hasParameter('templating.helper.code.file_link_format')) { - $definition->replaceArgument(4, '%templating.helper.code.file_link_format%'); + $definition->replaceArgument(5, '%templating.helper.code.file_link_format%'); } if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); + $definition->replaceArgument(3, E_ALL | E_STRICT); + $definition = $container->findDefinition('http_kernel'); $definition->replaceArgument(2, new Reference('debug.controller_resolver')); @@ -143,8 +145,6 @@ class FrameworkExtension extends Extension $container->setAlias('event_dispatcher', 'debug.event_dispatcher'); } else { $definition->replaceArgument(2, E_COMPILE_ERROR | E_PARSE | E_ERROR | E_CORE_ERROR); - - $container->findDefinition('debug.error_handler')->addMethodCall('throwAt', array(0)); } $this->addClassesToCompile(array( diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml index d830006eca..e5588b2d51 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml @@ -17,7 +17,8 @@ - %kernel.debug% + 0 + true null diff --git a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php index eba7859f44..9d4258d54a 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php @@ -33,22 +33,25 @@ class DebugHandlersListener implements EventSubscriberInterface private $exceptionHandler; private $logger; private $levels; - private $debug; + private $throwAt; + private $scream; private $fileLinkFormat; /** - * @param callable $exceptionHandler A handler that will be called on Exception + * @param callable|null $exceptionHandler A handler that will be called on Exception * @param LoggerInterface|null $logger A PSR-3 logger * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants - * @param bool $debug Enables/disables debug mode + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged * @param string $fileLinkFormat The format for links to source files */ - public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $debug = true, $fileLinkFormat = null) + public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $throwAt = -1, $scream = true, $fileLinkFormat = null) { $this->exceptionHandler = $exceptionHandler; $this->logger = $logger; $this->levels = $levels; - $this->debug = $debug; + $this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? -1 : null)); + $this->scream = (bool) $scream; $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); } @@ -66,25 +69,28 @@ class DebugHandlersListener implements EventSubscriberInterface $eventDispatcher->removeListener($name, array($this, 'configure')); } } - if ($this->logger) { - $handler = set_error_handler('var_dump', 0); - $handler = is_array($handler) ? $handler[0] : null; - restore_error_handler(); - if ($handler instanceof ErrorHandler) { - if ($this->debug) { - $handler->throwAt(-1); - } + $handler = set_error_handler('var_dump', 0); + $handler = is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler instanceof ErrorHandler) { + if ($this->logger) { $handler->setDefaultLogger($this->logger, $this->levels); if (is_array($this->levels)) { $scream = 0; foreach ($this->levels as $type => $log) { $scream |= $type; } - $this->levels = $scream; + } else { + $scream = null === $this->levels ? E_ALL | E_STRICT : $this->levels; } - $handler->screamAt($this->levels); + if ($this->scream) { + $handler->screamAt($scream); + } + $this->logger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); } - $this->logger = $this->levels = null; } if (!$this->exceptionHandler) { if ($event instanceof KernelEvent) { @@ -110,7 +116,9 @@ class DebugHandlersListener implements EventSubscriberInterface } if ($handler instanceof ExceptionHandler) { $handler->setHandler($this->exceptionHandler); - $handler->setFileLinkFormat($this->fileLinkFormat); + if (null !== $this->fileLinkFormat) { + $handler->setFileLinkFormat($this->fileLinkFormat); + } } $this->exceptionHandler = null; } From fac3cc4d035eb23681d8c35efccf9fd6071e3b78 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 2 Oct 2014 17:46:44 +0200 Subject: [PATCH 3/3] [FrameworkBundle] register ErrorHandler at boot time --- .../DependencyInjection/FrameworkExtension.php | 2 -- src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php | 3 +++ .../Bundle/FrameworkBundle/Resources/config/debug.xml | 1 + .../Bundle/FrameworkBundle/Resources/config/debug_prod.xml | 6 ++---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 093b48a649..dedb5b3418 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -133,8 +133,6 @@ class FrameworkExtension extends Extension if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); - $definition->replaceArgument(3, E_ALL | E_STRICT); - $definition = $container->findDefinition('http_kernel'); $definition->replaceArgument(2, new Reference('debug.controller_resolver')); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 08a1eee87a..cb7d1e125b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -30,6 +30,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtra use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FragmentRendererPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; +use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Scope; @@ -46,6 +47,8 @@ class FrameworkBundle extends Bundle { public function boot() { + ErrorHandler::register($this->container->getParameter('debug.error_handler.throw_at')); + if ($trustedProxies = $this->container->getParameter('kernel.trusted_proxies')) { Request::setTrustedProxies($trustedProxies); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index 1cdb0631ba..819f9623b0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -8,6 +8,7 @@ Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher %kernel.cache_dir%/%kernel.container_class%.xml Symfony\Component\HttpKernel\Controller\TraceableControllerResolver + -1 diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml index e5588b2d51..39266eab03 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml @@ -6,8 +6,8 @@ Symfony\Component\HttpKernel\EventListener\DebugHandlersListener - Symfony\Component\Debug\ErrorHandler Symfony\Component\Stopwatch\Stopwatch + 0 @@ -17,13 +17,11 @@ - 0 + null true null - -