A DI tag for resettable services.
This commit is contained in:
parent
30e3b6d27e
commit
d9a6b76dbd
@ -33,6 +33,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\AddCacheWarmerPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueResolverPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
|
||||
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass;
|
||||
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
|
||||
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
|
||||
@ -117,6 +118,7 @@ class FrameworkBundle extends Bundle
|
||||
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$this->addCompilerPassIfExists($container, FormPass::class);
|
||||
$container->addCompilerPass(new WorkflowGuardListenerPass());
|
||||
$container->addCompilerPass(new ResettableServicePass());
|
||||
|
||||
if ($container->getParameter('kernel.debug')) {
|
||||
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
||||
|
@ -74,5 +74,11 @@
|
||||
<service id="Symfony\Component\Config\Resource\SelfCheckingResourceChecker">
|
||||
<tag name="config_cache.resource_checker" priority="-990" />
|
||||
</service>
|
||||
|
||||
<service id="Symfony\Component\HttpKernel\EventListener\ServiceResetListener">
|
||||
<argument /> <!-- ResettableServicePass will inject an iterator of initialized services here ($serviceId => $serviceInstance) -->
|
||||
<argument type="collection" /> <!-- ResettableServicePass will inject an array of reset methods here ($serviceId => $method) -->
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\EventListener\ServiceResetListener;
|
||||
|
||||
/**
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*/
|
||||
class ResettableServicePass implements CompilerPassInterface
|
||||
{
|
||||
private $tagName;
|
||||
|
||||
/**
|
||||
* @param string $tagName
|
||||
*/
|
||||
public function __construct($tagName = 'kernel.reset')
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->has(ServiceResetListener::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$services = $methods = array();
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->tagName, true) as $id => $tags) {
|
||||
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE);
|
||||
$attributes = $tags[0];
|
||||
|
||||
if (!isset($attributes['method'])) {
|
||||
throw new RuntimeException(sprintf('Tag %s requires the "method" attribute to be set.', $this->tagName));
|
||||
}
|
||||
|
||||
$methods[$id] = $attributes['method'];
|
||||
}
|
||||
|
||||
if (empty($services)) {
|
||||
$container->removeDefinition(ServiceResetListener::class);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$container->findDefinition(ServiceResetListener::class)
|
||||
->replaceArgument(0, new IteratorArgument($services))
|
||||
->replaceArgument(1, $methods);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\EventListener;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Clean up services between requests.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*/
|
||||
class ServiceResetListener implements EventSubscriberInterface
|
||||
{
|
||||
private $services;
|
||||
private $resetMethods;
|
||||
|
||||
public function __construct(\Traversable $services, array $resetMethods)
|
||||
{
|
||||
$this->services = $services;
|
||||
$this->resetMethods = $resetMethods;
|
||||
}
|
||||
|
||||
public function onKernelTerminate()
|
||||
{
|
||||
foreach ($this->services as $id => $service) {
|
||||
$method = $this->resetMethods[$id];
|
||||
$service->$method();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
KernelEvents::TERMINATE => array('onKernelTerminate', -2048),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
|
||||
use Symfony\Component\HttpKernel\EventListener\ServiceResetListener;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
|
||||
|
||||
class ResettableServicePassTest extends TestCase
|
||||
{
|
||||
public function testCompilerPass()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('one', ResettableService::class)
|
||||
->addTag('kernel.reset', array('method' => 'reset'));
|
||||
$container->register('two', ClearableService::class)
|
||||
->addTag('kernel.reset', array('method' => 'clear'));
|
||||
|
||||
$container->register(ServiceResetListener::class)
|
||||
->setArguments(array(null, array()));
|
||||
$container->addCompilerPass(new ResettableServicePass('kernel.reset'));
|
||||
|
||||
$container->compile();
|
||||
|
||||
$definition = $container->getDefinition(ServiceResetListener::class);
|
||||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
new IteratorArgument(array(
|
||||
'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
|
||||
'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
|
||||
)),
|
||||
array(
|
||||
'one' => 'reset',
|
||||
'two' => 'clear',
|
||||
),
|
||||
),
|
||||
$definition->getArguments()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
|
||||
* @expectedExceptionMessage Tag kernel.reset requires the "method" attribute to be set.
|
||||
*/
|
||||
public function testMissingMethod()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(ResettableService::class)
|
||||
->addTag('kernel.reset');
|
||||
$container->register(ServiceResetListener::class)
|
||||
->setArguments(array(null, array()));
|
||||
$container->addCompilerPass(new ResettableServicePass('kernel.reset'));
|
||||
|
||||
$container->compile();
|
||||
}
|
||||
|
||||
public function testCompilerPassWithoutResetters()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register(ServiceResetListener::class)
|
||||
->setArguments(array(null, array()));
|
||||
$container->addCompilerPass(new ResettableServicePass());
|
||||
|
||||
$container->compile();
|
||||
|
||||
$this->assertFalse($container->has(ServiceResetListener::class));
|
||||
}
|
||||
|
||||
public function testCompilerPassWithoutListener()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->addCompilerPass(new ResettableServicePass());
|
||||
|
||||
$container->compile();
|
||||
|
||||
$this->assertFalse($container->has(ServiceResetListener::class));
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\EventListener;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\EventListener\ServiceResetListener;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService;
|
||||
use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
|
||||
|
||||
class ServiceResetListenerTest extends TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
ResettableService::$counter = 0;
|
||||
ClearableService::$counter = 0;
|
||||
}
|
||||
|
||||
public function testResetServicesNoOp()
|
||||
{
|
||||
$container = $this->buildContainer();
|
||||
$container->get('reset_subscriber')->onKernelTerminate();
|
||||
|
||||
$this->assertEquals(0, ResettableService::$counter);
|
||||
$this->assertEquals(0, ClearableService::$counter);
|
||||
}
|
||||
|
||||
public function testResetServicesPartially()
|
||||
{
|
||||
$container = $this->buildContainer();
|
||||
$container->get('one');
|
||||
$container->get('reset_subscriber')->onKernelTerminate();
|
||||
|
||||
$this->assertEquals(1, ResettableService::$counter);
|
||||
$this->assertEquals(0, ClearableService::$counter);
|
||||
}
|
||||
|
||||
public function testResetServicesTwice()
|
||||
{
|
||||
$container = $this->buildContainer();
|
||||
$container->get('one');
|
||||
$container->get('reset_subscriber')->onKernelTerminate();
|
||||
$container->get('two');
|
||||
$container->get('reset_subscriber')->onKernelTerminate();
|
||||
|
||||
$this->assertEquals(2, ResettableService::$counter);
|
||||
$this->assertEquals(1, ClearableService::$counter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContainerBuilder
|
||||
*/
|
||||
private function buildContainer()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('one', ResettableService::class);
|
||||
$container->register('two', ClearableService::class);
|
||||
|
||||
$container->register('reset_subscriber', ServiceResetListener::class)
|
||||
->addArgument(new IteratorArgument(array(
|
||||
'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
|
||||
'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
|
||||
)))
|
||||
->addArgument(array(
|
||||
'one' => 'reset',
|
||||
'two' => 'clear',
|
||||
));
|
||||
|
||||
$container->compile();
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
|
||||
|
||||
class ClearableService
|
||||
{
|
||||
public static $counter = 0;
|
||||
|
||||
public function clear()
|
||||
{
|
||||
++self::$counter;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Fixtures;
|
||||
|
||||
class ResettableService
|
||||
{
|
||||
public static $counter = 0;
|
||||
|
||||
public function reset()
|
||||
{
|
||||
++self::$counter;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user