10000 feature #12081 [FrameworkBundle] enable ErrorHandler in prod (nicolas… · symfony/symfony@3da6fc2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3da6fc2

Browse files
committed
feature #12081 [FrameworkBundle] enable ErrorHandler in prod (nicolas-grekas)
This PR was merged into the 2.6-dev branch. Discussion ---------- [FrameworkBundle] enable ErrorHandler in prod | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11053, #8281 | License | MIT | Doc PR | - - a new debug.error_handler service is the registered PHP error handler, with ErrorHandler::register() as factory - ErrorHandler::register() is patched so that it checks if the currently registered error handler is an instance of ErrorHandler - in which case it returns this instance and don't create a new one. - DebugHandlersListener now listen to ConsoleEvents and re-injects fatal errors within the $app->renderException code path - DebugHandlersListener also has a new $scream parameter to control is silenced errors are logged or not Commits ------- fac3cc4 [FrameworkBundle] register ErrorHandler at boot time 4acf5d3 [Debug] make screaming configurable 4d0ab7d [FrameworkBundle] enable ErrorHandler in prod
2 parents b5c4d54 + fac3cc4 commit 3da6fc2

File tree

8 files changed

+159
-25
lines changed

8 files changed

+159
-25
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,12 @@ public function load(array $configs, ContainerBuilder $container)
127127
$definition = $container->findDefinition('debug.debug_handlers_listener');
128128

129129
if ($container->hasParameter('templating.helper.code.file_link_format')) {
130-
$definition->replaceArgument(4, '%templating.helper.code.file_link_format%');
130+
$definition->replaceArgument(5, '%templating.helper.code.file_link_format%');
131131
}
132132

133133
if ($container->getParameter('kernel.debug')) {
134134
$loader->load('debug.xml');
135135

136-
$definition->replaceArgument(0, array(new Reference('http_kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE), 'terminateWithException'));
137-
138136
$definition = $container->findDefinition('http_kernel');
139137
$definition->replaceArgument(2, new Reference('debug.controller_resolver'));
140138

@@ -150,6 +148,8 @@ public function load(array $configs, ContainerBuilder $container)
150148
$this->addClassesToCompile(array(
151149
'Symfony\\Component\\Config\\FileLocator',
152150

151+
'Symfony\\Component\\Debug\\ErrorHandler',
152+
153153
'Symfony\\Component\\EventDispatcher\\Event',
154154
'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher',
155155

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
3131
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FragmentRendererPass;
3232
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass;
33+
use Symfony\Component\Debug\ErrorHandler;
3334
use Symfony\Component\DependencyInjection\ContainerBuilder;
3435
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
3536
use Symfony\Component\DependencyInjection\Scope;
@@ -46,6 +47,8 @@ class FrameworkBundle extends Bundle
4647
{
4748
public function boot()
4849
{
50+
ErrorHandler::register($this->container->getParameter('debug.error_handler.throw_at'));
51+
4952
if ($trustedProxies = $this->container->getParameter('kernel.trusted_proxies')) {
5053
Request::setTrustedProxies($trustedProxies);
5154
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<parameter key="debug.event_dispatcher.class">Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher</parameter>
99
<parameter key="debug.container.dump">%kernel.cache_dir%/%kernel.container_class%.xml</parameter>
1010
<parameter key="debug.controller_resolver.class">Symfony\Component\HttpKernel\Controller\TraceableControllerResolver</parameter>
11+
<parameter key="debug.error_handler.throw_at">-1</parameter>
1112
</parameters>
1213

1314
<services>

src/Symfony/Bundle/FrameworkBundle/Resources/config/debug_prod.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<parameters>
88
<parameter key="debug.debug_handlers_listener.class">Symfony\Component\HttpKernel\EventListener\DebugHandlersListener</parameter>
99
<parameter key="debug.stopwatch.class">Symfony\Component\Stopwatch\Stopwatch</parameter>
10+
<parameter key="debug.error_handler.throw_at">0</parameter>
1011
</parameters>
1112

1213
<services>
@@ -16,7 +17,8 @@
1617
<argument /><!-- Exception handler -->
1718
<argument type="service" id="logger" on-invalid="null" />
1819
<argument /><!-- Log levels map for enabled error levels -->
19-
<argument>%kernel.debug%</argument>
20+
<argument>null</argument>
21+
<argument>true</argument>
2022
<argument>null</argument><!-- %templating.helper.code.file_link_format% -->
2123
</service>
2224

src/Symfony/Component/Debug/ErrorHandler.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,15 @@ public static function register($levels = -1, $throw = true)
124124

125125
$handler = new static();
126126
$levels &= $handler->thrownErrors;
127-
set_error_handler(array($handler, 'handleError'), $levels);
127+
$prev = set_error_handler(array($handler, 'handleError'), $levels);
128+
$prev = is_array($prev) ? $prev[0] : null;
129+
if ($prev instanceof self) {
130+
restore_error_handler();
131+
$handler = $prev;
132+
} else {
133+
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
134+
}
128135
$handler->throwAt($throw ? $levels : 0, true);
129-
$handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
130136

131137
return $handler;
132138
}

src/Symfony/Component/Debug/Tests/ErrorHandlerTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,30 @@ public function tearDown()
4646
error_reporting($this->errorReporting);
4747
}
4848

49+
public function testRegister()
50+
{
51+
$handler = ErrorHandler::register();
52+
53+
try {
54+
$this->assertInstanceOf('Symfony\Component\Debug\ErrorHandler', $handler);
55+
56+
try {
57+
$this->assertSame($handler, ErrorHandler::register());
58+
} catch (\Exception $e) {
59+
restore_error_handler();
60+
restore_exception_handler();
61+
}
62+
} catch (\Exception $e) {
63+
}
64+
65+
restore_error_handler();
66+
restore_exception_handler();
67+
68+
if (isset($e)) {
69+
throw $e;
70+
}
71+
}
72+
4973
public function testNotice()
5074
{
5175
ErrorHandler::register();

src/Symfony/Component/HttpKernel/EventListener/DebugHandlersListener.php

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@
1414
use Psr\Log\LoggerInterface;
1515
use Symfony\Component\Debug\ErrorHandler;
1616
use Symfony\Component\Debug\ExceptionHandler;
17+
use Symfony\Component\EventDispatcher\Event;
18+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1719
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
20+
use Symfony\Component\HttpKernel\Event\KernelEvent;
1821
use Symfony\Component\HttpKernel\KernelEvents;
22+
use Symfony\Component\Console\ConsoleEvents;
23+
use Symfony\Component\Console\Event\ConsoleEvent;
24+
use Symfony\Component\Console\Output\ConsoleOutputInterface;
1925

2026
/**
2127
* Configures errors and exceptions handlers.
@@ -27,45 +33,77 @@ class DebugHandlersListener implements EventSubscriberInterface
2733
private $exceptionHandler;
2834
private $logger;
2935
private $levels;
30-
private $debug;
36+
private $throwAt;
37+
private $scream;
38+
private $fileLinkFormat;
3139

3240
/**
33-
* @param callable $exceptionHandler A handler that will be called on Exception
41+
* @param callable|null $exceptionHandler A handler that will be called on Exception
3442
* @param LoggerInterface|null $logger A PSR-3 logger
3543
* @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
36-
* @param bool $debug Enables/disables debug mode
44+
* @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value
45+
* @param bool $scream Enables/disables screaming mode, where even silenced errors are logged
3746
* @param string $fileLinkFormat The format for links to source files
3847
*/
39-
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $debug = true, $fileLinkFormat = null)
48+
public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $throwAt = -1, $scream = true, $fileLinkFormat = null)
4049
{
4150
$this->exceptionHandler = $exceptionHandler;
4251
$this->logger = $logger;
4352
$this->levels = $levels;
44-
$this->debug = $debug;
53+
$this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? -1 : null));
54+
$this->scream = (bool) $scream;
4555
$this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
4656
}
4757

48-
public function configure()
58+
/**
59+
* Configures the error handler.
60+
*
61+
* @param Event|null $event The triggering event
62+
* @param string|null $eventName The triggering event name
63+
* @param EventDispatcherInterface|null $eventDispatcher The dispatcher used to trigger $event
64+
*/
65+
public function configure(Event $event = null, $eventName = null, EventDispatcherInterface $eventDispatcher = null)
4966
{
50-
if ($this->logger) {
51-
$handler = set_error_handler('var_dump', 0);
52-
$handler = is_array($handler) ? $handler[0] : null;
53-
restore_error_handler();
54-
if ($handler instanceof ErrorHandler) {
55-
if ($this->debug) {
56-
$handler->throwAt(-1);
57-
}
67+
if (null !== $eventDispatcher) {
68+
foreach (array_keys(static::getSubscribedEvents()) as $name) {
69+
$eventDispatcher->removeListener($name, array($this, 'configure'));
70+
}
71+
}
72+
$handler = set_error_handler('var_dump', 0);
73+
$handler = is_array($handler) ? $handler[0] : null;
74+
restore_error_handler();
75+
if ($handler instanceof ErrorHandler) {
76+
if ($this->logger) {
5877
$handler->setDefaultLogger($this->logger, $this->levels);
5978
if (is_array($this->levels)) {
6079
$scream = 0;
6180
foreach ($this->levels as $type => $log) {
6281
$scream |= $type;
6382
}
64-
$this->levels = $scream;
83+
} else {
84+
$scream = null === $this->levels ? E_ALL | E_STRICT : $this->levels;
85+
}
86+
if ($this->scream) {
87+
$handler->screamAt($scream);
6588
}
66-
$handler->screamAt($this->levels);
89+
$this->logger = $this->levels = null;
90+
}
91+
if (null !== $this->throwAt) {
92+
$handler->throwAt($this->throwAt, true);
93 10000 +
}
94+
}
95+
if (!$this->exceptionHandler) {
96+
if ($event instanceof KernelEvent) {
97+
$this->exceptionHandler = array($event->getKernel(), 'terminateWithException');
98+
} elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
99+
$output = $event->getOutput();
100+
if ($output instanceof ConsoleOutputInterface) {
101+
$output = $output->getErrorOutput();
102+
}
103+
$this->exceptionHandler = function ($e) use ($app, $output) {
104+
$app->renderException($e, $output);
105+
};
67106
}
68-
$this->logger = $this->levels = null;
69107
}
70108
if ($this->exceptionHandler) {
71109
$handler = set_exception_handler('var_dump');
@@ -78,14 +116,22 @@ public function configure()
78116
}
79117
if ($handler instanceof ExceptionHandler) {
80118
$handler->setHandler($this->exceptionHandler);
81-
$handler->setFileLinkFormat($this->fileLinkFormat);
119+
if (null !== $this->fileLinkFormat) {
120+
$handler->setFileLinkFormat($this->fileLinkFormat);
121+
}
82122
}
83123
$this->exceptionHandler = null;
84124
}
85125
}
86126

87127
public static function getSubscribedEvents()
88128
{
89-
return array(KernelEvents::REQUEST => array('configure', 2048));
129+
$events = array(KernelEvents::REQUEST => array('configure', 2048));
130+
131+
if (defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) {
132+
$events[ConsoleEvents::COMMAND] = array('configure', 2048);
133+
}
134+
135+
return $events;
90136
}
91137
}

src/Symfony/Component/HttpKernel/Tests/EventListener/DebugHandlersListenerTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@
1212
namespace Symfony\Component\HttpKernel\Tests\EventListener;
1313

1414
use Psr\Log\LogLevel;
15+
use Symfony\Component\Console\Event\ConsoleEvent;
16+
use Symfony\Component\Console\Command\Command;
17+
use Symfony\Component\Console\ConsoleEvents;
18+
use Symfony\Component\Console\Helper\HelperSet;
19+
use Symfony\Component\Console\Input\ArgvInput;
20+
use Symfony\Component\Console\Output\ConsoleOutput;
1521
use Symfony\Component\Debug\ErrorHandler;
1622
use Symfony\Component\Debug\ExceptionHandler;
23+
use Symfony\Component\EventDispatcher\EventDispatcher;
1724
use Symfony\Component\HttpKernel\EventListener\DebugHandlersListener;
25+
use Symfony\Component\HttpKernel\KernelEvents;
1826

1927
/**
2028
* DebugHandlersListenerTest
@@ -53,4 +61,48 @@ public function testConfigure()
5361
$this->assertArrayHasKey(E_DEPRECATED, $loggers);
5462
$this->assertSame(array($logger, LogLevel::INFO), $loggers[E_DEPRECATED]);
5563
}
64+
65+
public function testConsoleEvent()
66+
{
67+
$dispatcher = new EventDispatcher();
68+
$listener = new DebugHandlersListener(null);
69+
$app = $this->getMock('Symfony\Component\Console\Application');
70+
$app->expects($this->once())->method('getHelperSet')->will($this->returnValue(new HelperSet()));
71+
$command = new Command(__FUNCTION__);
72+
$command->setApplication($app);
73+
$event = new ConsoleEvent($command, new ArgvInput(), new ConsoleOutput());
74+
75+
$dispatcher->addSubscriber($listener);
76+
77+
$xListeners = array(
78+
KernelEvents::REQUEST => array(array($listener, 'configure')),
79+
ConsoleEvents::COMMAND => array(array($listener, 'configure')),
80+
);
81+
$this->assertSame($xListeners, $dispatcher->getListeners());
82+
83+
$exception = null;
84+
$eHandler = new ErrorHandler();
85+
set_error_handler(array($eHandler, 'handleError'));
86+
set_exception_handler(array($eHandler, 'handleException'));
87+
try {
88+
$dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
89+
} catch (\Exception $exception) {
90+
}
91+
restore_exception_handler();
92+
restore_error_handler();
93+
94+
if (null !== $exception) {
95+
throw $exception;
96+
}
97+
98+
$this->assertSame(array(), $dispatcher->getListeners());
99+
100+
$xHandler = $eHandler->setExceptionHandler('var_dump');
101+
$this->assertInstanceOf('Closure', $xHandler);
102+
103+
$app->expects($this->once())
104+
->method('renderException');
105+
106+
$xHandler(new \Exception());
107+
}
56108
}

0 commit comments

Comments
 (0)
0