Skip to content

[Console][DI] Fail gracefully #25255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions src/Symfony/Component/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

Expand Down Expand Up @@ -118,28 +119,39 @@ public function run(InputInterface $input = null, OutputInterface $output = null
$output = new ConsoleOutput();
}

$renderException = function ($e) use ($output) {
if (!$e instanceof \Exception) {
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
}
if ($output instanceof ConsoleOutputInterface) {
$this->renderException($e, $output->getErrorOutput());
} else {
$this->renderException($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
restore_exception_handler();
if (!is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
$debugHandler = true;
} elseif ($debugHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($debugHandler);
}
}

if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
@trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED);
}

$this->configureIO($input, $output);

try {
$e = null;
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
}

if (null !== $e) {
if (!$this->catchExceptions) {
throw $e;
}

if ($output instanceof ConsoleOutputInterface) {
$this->renderException($e, $output->getErrorOutput());
} else {
$this->renderException($e, $output);
}
$renderException($e);

$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
Expand All @@ -150,6 +162,12 @@ public function run(InputInterface $input = null, OutputInterface $output = null
} else {
$exitCode = 1;
}
} finally {
if (!$phpHandler) {
restore_exception_handler();
} elseif (!$debugHandler) {
$phpHandler[0]->setExceptionHandler(null);
}
}

if ($this->autoExit) {
Expand Down
18 changes: 11 additions & 7 deletions src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,16 @@ public function dump(array $options = array())
{$namespaceLine}
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.

if (!class_exists(\\Container{$hash}\\{$options['class']}::class, false)) {
require __DIR__.'/Container{$hash}/{$options['class']}.php';
if (\\class_exists(\\Container{$hash}\\{$options['class']}::class, false)) {
// no-op
} elseif (!include __DIR__.'/Container{$hash}/{$options['class']}.php') {
touch(__DIR__.'/Container{$hash}.legacy');

return;
}

if (!class_exists({$options['class']}::class, false)) {
class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false);
if (!\\class_exists({$options['class']}::class, false)) {
\\class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false);
}

return new \\Container{$hash}\\{$options['class']}();
Expand Down Expand Up @@ -428,13 +432,13 @@ private function addServiceInclude($cId, Definition $definition, \SplObjectStora
}

foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
$code .= sprintf(" require_once %s;\n", $file);
$code .= sprintf(" include_once %s;\n", $file);
}
}

foreach ($inlinedDefinitions as $def) {
if ($file = $def->getFile()) {
$code .= sprintf(" require_once %s;\n", $this->dumpValue($file));
$code .= sprintf(" include_once %s;\n", $this->dumpValue($file));
}
}

Expand Down Expand Up @@ -1233,7 +1237,7 @@ private function addInlineRequires()
foreach ($lineage as $file) {
if (!isset($this->inlinedRequires[$file])) {
$this->inlinedRequires[$file] = true;
$code .= sprintf(" require_once %s;\n", $file);
$code .= sprintf(" include_once %s;\n", $file);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ protected function getLazyContextIgnoreInvalidRefService()
*/
protected function getMethodCall1Service()
{
require_once '%path%foo.php';
include_once '%path%foo.php';

$this->services['method_call1'] = $instance = new \Bar\FooClass();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the public 'method_call1' shared service.

require_once ($this->targetDirs[0].'/Fixtures/includes/foo.php');
include_once ($this->targetDirs[0].'/Fixtures/includes/foo.php');

$this->services['method_call1'] = $instance = new \Bar\FooClass();

Expand Down Expand Up @@ -471,12 +471,16 @@ class ProjectServiceContainer extends Container

// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.

if (!class_exists(\Container%s\ProjectServiceContainer::class, false)) {
require __DIR__.'/Container%s/ProjectServiceContainer.php';
if (\class_exists(\Container%s\ProjectServiceContainer::class, false)) {
// no-op
} elseif (!include __DIR__.'/Container%s/ProjectServiceContainer.php') {
touch(__DIR__.'/Container%s.legacy');

return;
}

if (!class_exists(ProjectServiceContainer::class, false)) {
class_alias(\Container%s\ProjectServiceContainer::class, ProjectServiceContainer::class, false);
if (!\class_exists(ProjectServiceContainer::class, false)) {
\class_alias(\Container%s\ProjectServiceContainer::class, ProjectServiceContainer::class, false);
}

return new \Container%s\ProjectServiceContainer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ protected function getLazyContextIgnoreInvalidRefService()
*/
protected function getMethodCall1Service()
{
require_once '%path%foo.php';
include_once '%path%foo.php';

$this->services['method_call1'] = $instance = new \Bar\FooClass();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public function __construct()

$this->aliases = array();

require_once $this->targetDirs[1].'/includes/HotPath/I1.php';
require_once $this->targetDirs[1].'/includes/HotPath/P1.php';
require_once $this->targetDirs[1].'/includes/HotPath/T1.php';
require_once $this->targetDirs[1].'/includes/HotPath/C1.php';
include_once $this->targetDirs[1].'/includes/HotPath/I1.php';
include_once $this->targetDirs[1].'/includes/HotPath/P1.php';
include_once $this->targetDirs[1].'/includes/HotPath/T1.php';
include_once $this->targetDirs[1].'/includes/HotPath/C1.php';
}

public function getRemovedIds()
Expand Down Expand Up @@ -104,8 +104,8 @@ protected function getC1Service()
*/
protected function getC2Service()
{
require_once $this->targetDirs[1].'/includes/HotPath/C2.php';
require_once $this->targetDirs[1].'/includes/HotPath/C3.php';
include_once $this->targetDirs[1].'/includes/HotPath/C2.php';
include_once $this->targetDirs[1].'/includes/HotPath/C3.php';

return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()) && false ?: '_'});
}
Expand All @@ -117,7 +117,7 @@ protected function getC2Service()
*/
protected function getC3Service()
{
require_once $this->targetDirs[1].'/includes/HotPath/C3.php';
include_once $this->targetDirs[1].'/includes/HotPath/C3.php';

return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3();
}
Expand Down
24 changes: 17 additions & 7 deletions src/Symfony/Component/HttpKernel/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -582,15 +582,21 @@ protected function initializeContainer()
$cacheDir = $this->warmupDir ?: $this->getCacheDir();
$cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug);
if ($fresh = $cache->isFresh()) {
$this->container = require $cache->getPath();
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
try {
$this->container = include $cache->getPath();
} finally {
error_reporting($errorLevel);
}
$fresh = \is_object($this->container);
}
if (!$fresh) {
if ($this->debug) {
$collectedLogs = array();
$previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) {
if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) {
return $previousHandler ? $previousHandler($type, $message, $file, $line) : false;
return $previousHandler ? $previousHandler($type & ~E_WARNING, $message, $file, $line) : E_WARNING === $type;
}

if (isset($collectedLogs[$message])) {
Expand All @@ -617,23 +623,27 @@ protected function initializeContainer()
'count' => 1,
);
});
} else {
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
}

try {
$container = null;
$container = $this->buildContainer();
$container->compile();

$oldContainer = file_exists($cache->getPath()) && is_object($oldContainer = include $cache->getPath()) ? new \ReflectionClass($oldContainer) : false;
} finally {
if ($this->debug) {
restore_error_handler();

file_put_contents($cacheDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs)));
file_put_contents($cacheDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : '');
} else {
error_reporting($errorLevel);
}
}

$oldContainer = file_exists($cache->getPath()) && is_object($oldContainer = @include $cache->getPath()) ? new \ReflectionClass($oldContainer) : false;

$this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass());
$this->container = require $cache->getPath();
}
Expand All @@ -649,13 +659,13 @@ protected function initializeContainer()
// old container files are not removed immediately,
// but on a next dump of the container.
$oldContainerDir = dirname($oldContainer->getFileName());
foreach (glob(dirname($oldContainerDir).'/*.legacyContainer') as $legacyContainer) {
if ($oldContainerDir.'.legacyContainer' !== $legacyContainer && @unlink($legacyContainer)) {
foreach (glob(dirname($oldContainerDir).'/*.legacy') as $legacyContainer) {
if ($oldContainerDir.'.legacy' !== $legacyContainer && @unlink($legacyContainer)) {
(new Filesystem())->remove(substr($legacyContainer, 0, -16));
}
}

touch($oldContainerDir.'.legacyContainer');
touch($oldContainerDir.'.legacy');
}

if ($this->container->has('cache_warmer')) {
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpKernel/Tests/KernelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ public function testKernelReset()

$this->assertTrue(get_class($kernel->getContainer()) !== $containerClass);
$this->assertFileExists($containerFile);
$this->assertFileExists(dirname($containerFile).'.legacyContainer');
$this->assertFileExists(dirname($containerFile).'.legacy');
}

public function testKernelPass()
Expand Down