Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class UnusedTagsPass implements CompilerPassInterface
'json_streamer.value_transformer',
'kernel.cache_clearer',
'kernel.cache_warmer',
'kernel.close',
'kernel.event_listener',
'kernel.event_subscriber',
'kernel.fragment_renderer',
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
8.2
---

* Add the `kernel.close` tag and the `services_closer` service to close services on kernel shutdown
* Pass top-level extension values that are not arrays to the extension instead of replacing them with an empty array, so that a configuration tree can accept a scalar at its root
* Name the package to install when an extension is missing, for the configuration keys declared in the `.container.extension_packages` build parameter
* Add the `container.remove_if_missing` tag to drop a definition when a service, a class or a package it needs is not there
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;

/**
* Collects the services tagged "kernel.close" for "services_closer", which closes them when the kernel shuts down.
*/
class ClosableServicePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has('services_closer')) {
return;
}

$services = $methods = [];

foreach ($container->findTaggedServiceIds('kernel.close', true) as $id => $tags) {
if (!$container->getDefinition($id)->isShared()) {
throw new InvalidArgumentException(\sprintf('Service "%s" cannot be tagged "kernel.close" because it is not shared: the container keeps no instance of it to close.', $id));
}

// a service that was never used is not instantiated just to be closed
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE);

foreach ($tags as $attributes) {
$method = $attributes['method'] ?? 'close';
$methods[$id][] = 'ignore' === ($attributes['on_invalid'] ?? null) ? '?'.$method : $method;
}
}

if (!$services) {
$container->removeDefinition('services_closer');

return;
}

$container->findDefinition('services_closer')
->setArgument(0, new IteratorArgument($services))
->setArgument(1, $methods);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@ public function shutdown(): void
$bundle->setContainer(null);
}

$this->container = null;
try {
// after the bundles, which may still use the services they shut down
if ($this->container->has('services_closer')) {
$this->container->get('services_closer')->reset();
}
} finally {
$this->container = null;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
->public()
->alias(ServicesResetterInterface::class, 'services_resetter')

// calls the methods of the services tagged "kernel.close" when the kernel shuts down
->set('services_closer', ServicesResetter::class)
->public()

->set('container.env_var_processor', EnvVarProcessor::class)
->args([
service('service_container'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Symfony\Component\Config\ResourceCheckerInterface;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\AddBehaviorDescribingTagsPass;
use Symfony\Component\DependencyInjection\Compiler\ClosableServicePass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\Compiler\ResettableServicePass;
Expand Down Expand Up @@ -61,8 +62,10 @@ public function build(ContainerBuilder $container): void
'kernel.event_subscriber',
'kernel.event_listener',
'kernel.reset',
'kernel.close',
]), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
$container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
$container->addCompilerPass(new ClosableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
}

public function loadExtension(array $config, ContainerConfigurator $configurator, ContainerBuilder $container): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
use Symfony\Component\DependencyInjection\Kernel\BundleInterface;
use Symfony\Component\DependencyInjection\Kernel\KernelTrait;
use Symfony\Component\DependencyInjection\Kernel\RequiredBundle;
use Symfony\Component\DependencyInjection\Kernel\ServicesBundle;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Tests\Fixtures\ClosableService;
use Symfony\Component\Filesystem\Filesystem;

class AbstractKernelTest extends TestCase
Expand Down Expand Up @@ -211,6 +213,59 @@ public function testShutdownCallsShutdownOnBundles()
$kernel->shutdown();
}

public function testShutdownClosesTaggedServices()
{
ClosableService::$closed = 0;
$this->writeBundlesFile([ServicesBundle::class]);
$kernel = new ClosingKernel('test', true, $this->varDir);
$kernel->boot();
$kernel->getContainer()->get('closable');

$kernel->shutdown();

$this->assertSame(1, ClosableService::$closed);
}

public function testShutdownDoesNotInstantiateServicesToCloseThem()
{
ClosableService::$closed = 0;
$this->writeBundlesFile([ServicesBundle::class]);
$kernel = new ClosingKernel('test', true, $this->varDir);
$kernel->boot();

$kernel->shutdown();

$this->assertSame(0, ClosableService::$closed);
}

public function testShutdownClearsContainerWhenClosingFails()
{
ClosableService::$closed = 0;
$this->writeBundlesFile([ServicesBundle::class]);
$kernel = new ClosingKernel('test', true, $this->varDir);
$kernel->boot();
$kernel->getContainer()->get('failing');
$kernel->getContainer()->get('closable');

$closingFailed = false;

try {
$kernel->shutdown();
} catch (\RuntimeException $e) {
$closingFailed = true;
$this->assertSame('Cannot close.', $e->getMessage());
}

$this->assertTrue($closingFailed, 'The exception thrown while closing a service should not be swallowed.');

// one service failing to close does not prevent closing the others
$this->assertSame(1, ClosableService::$closed);
$this->assertFalse($kernel->isBooted());

$this->expectException(\LogicException::class);
$kernel->getContainer();
}

public function testGetBundlesAndGetBundle()
{
$bundle = $this->createStub(BundleInterface::class);
Expand Down Expand Up @@ -650,6 +705,19 @@ protected function build(ContainerBuilder $container): void
}
}

class ClosingKernel extends TestKernel
{
protected function build(ContainerBuilder $container): void
{
$container->register('failing', ClosableService::class)
->setPublic(true)
->addTag('kernel.close', ['method' => 'fail']);
$container->register('closable', ClosableService::class)
->setPublic(true)
->addTag('kernel.close');
}
}

class ConfigureContainerKernel extends TestKernel
{
private function configureContainer(ContainerConfigurator $container): void
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Compiler;

use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\ClosableServicePass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServicesResetter;
use Symfony\Component\DependencyInjection\Tests\Fixtures\ClosableService;

class ClosableServicePassTest extends TestCase
{
protected function setUp(): void
{
ClosableService::$closed = 0;
}

public function testCompilerPass()
{
$container = new ContainerBuilder();
$container->register('default_method', ClosableService::class)
->setPublic(true)
->addTag('kernel.close');
$container->register('custom_methods', ClosableService::class)
->setPublic(true)
->addTag('kernel.close', ['method' => 'close'])
->addTag('kernel.close', ['method' => 'missing', 'on_invalid' => 'ignore']);
$this->registerCloser($container);

$container->compile();

$this->assertEquals(
[
new IteratorArgument([
'default_method' => new Reference('default_method', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
'custom_methods' => new Reference('custom_methods', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
]),
[
'default_method' => ['close'],
'custom_methods' => ['close', '?missing'],
],
],
$container->getDefinition('services_closer')->getArguments()
);
}

public function testOnlyInstantiatedServicesAreClosed()
{
$container = new ContainerBuilder();
$container->register('used', ClosableService::class)
->setPublic(true)
->addTag('kernel.close');
$container->register('unused', ClosableService::class)
->setPublic(true)
->addTag('kernel.close');
$this->registerCloser($container);

$container->compile();
$container->get('used');
$container->get('services_closer')->reset();

$this->assertSame(1, ClosableService::$closed);
}

public function testNonSharedServiceIsRejected()
{
$container = new ContainerBuilder();
$container->register('non_shared', ClosableService::class)
->setShared(false)
->addTag('kernel.close');
$this->registerCloser($container);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Service "non_shared" cannot be tagged "kernel.close" because it is not shared');

$container->compile();
}

public function testCloserIsRemovedWithoutTaggedServices()
{
$container = new ContainerBuilder();
$this->registerCloser($container);

$container->compile();

$this->assertFalse($container->has('services_closer'));
}

private function registerCloser(ContainerBuilder $container): void
{
$container->register('services_closer', ServicesResetter::class)
->setPublic(true)
->setArguments([null, []]);
$container->addCompilerPass(new ClosableServicePass());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Fixtures;

class ClosableService
{
public static int $closed = 0;

public function close(): void
{
++self::$closed;
}

public function fail(): void
{
throw new \RuntimeException('Cannot close.');
}
}