8000 A DI tag for resettable services. · symfony/symfony@9847c51 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9847c51

Browse files
committed
A DI tag for resettable services.
1 parent b1b6860 commit 9847c51

File tree

9 files changed

+301
-0
lines changed

9 files changed

+301
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
4+
5+
use Symfony\Bundle\FrameworkBundle\EventListener\ServiceResetSubscriber;
6+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
7+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
use Symfony\Component\DependencyInjection\ContainerInterface;
10+
use Symfony\Component\DependencyInjection\Reference;
11+
12+
/**
13+
* @author Alexander M. Turek <me@derrabus.de>
14+
*
15+
* @internal
16+
*/
17+
class ResettableServicePass implements CompilerPassInterface
18+
{
19+
const RESET_TAG = 'kernel.reset';
20+
const METHOD_ATTRIBUTE = 'method';
21+
22+
/**
23+
* {@inheritdoc}
24+
*/
25+
public function process(ContainerBuilder $container)
26+
{
27+
$services = $methods = array();
28+
29+
foreach ($container->findTaggedServiceIds(self::RESET_TAG, true) as $id => $tags) {
30+
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE);
31+
$methods[$id] = array();
32+
33+
foreach ($tags as $attributes) {
34+
if (!isset($attributes[self::METHOD_ATTRIBUTE])) {
35+
throw new \RuntimeException(
36+
sprintf('Tag %s requires the attribute %s to be set.', self::RESET_TAG, self::METHOD_ATTRIBUTE)
37+
);
38+
}
39+
40+
$methods[$id][] = $attributes[self::METHOD_ATTRIBUTE];
41+
}
42+
}
43+
44+
$container->getDefinition(ServiceResetSubscriber::class)
45+
->setArguments(array(
46+
new IteratorArgument($services),
47+
$methods,
48+
));
49+
}
50+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\EventListener;
4+
5+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
6+
use Symfony\Component\HttpKernel\KernelEvents;
7+
8+
/**
9+
* Clean up services between requests.
10+
*
11+
* @author Alexander M. Turek <me@derrabus.de>
12+
*
13+
* @internal
14+
*/
15+
class ServiceResetSubscriber implements EventSubscriberInterface
16+
{
17+
/**
18+
* @var \Traversable
19+
*/
20+
private $services;
21+
22+
/**
23+
* @var array
24+
*/
25+
private $resetMethods;
26+
27+
public function __construct(\Traversable $services, array $resetMethods)
28+
{
29+
$this->services = $services;
30+
$this->resetMethods = $resetMethods;
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public static function getSubscribedEvents()
37+
{
38+
return array(
39+
KernelEvents::TERMINATE => array('onKernelTerminate', -2048),
40+
);
41+
}
42+
43+
public function onKernelTerminate()
44+
{
45+
foreach ($this->services as $id => $service) {
46+
foreach ($this->resetMethods[$id] as $method) {
47+
call_user_func(array($service, $method));
48+
}
49+
}
50+
}
51+
}

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass;
1919
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPrunerPass;
2020
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass;
21+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ResettableServicePass;
2122
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
2223
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
2324
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass;
@@ -117,6 +118,7 @@ public function build(ContainerBuilder $container)
117118
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
118119
$this->addCompilerPassIfExists($container, FormPass::class);
119120
$container->addCompilerPass(new WorkflowGuardListenerPass());
121+
$container->addCompilerPass(new ResettableServicePass());
120122 10000

121123
if ($container->getParameter('kernel.debug')) {
122124
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,9 @@
7474
<service id="Symfony\Component\Config\Resource\SelfCheckingResourceChecker">
7575
<tag name="config_cache.resource_checker" priority="-990" />
7676
</service>
77+
78+
<service id="Symfony\Bundle\FrameworkBundle\EventListener\ServiceResetSubscriber">
79+
<tag name="kernel.event_subscriber" />
80+
</service>
7781
</services>
7882
</container>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ResettableServicePass;
7+
use Symfony\Bundle\FrameworkBundle\EventListener\ServiceResetSubscriber;
8+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClearableService;
9+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ResettableClearableService;
10+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ResettableService;
11+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
12+
use Symfony\Component\DependencyInjection\ContainerBuilder;
13+
use Symfony\Component\DependencyInjection\ContainerInterface;
14+
use Symfony\Component\DependencyInjection\Reference;
15+
16+
class ResettableServicePassTest extends TestCase
17+
{
18+
public function testCompilerPass()
19+
{
20+
$container = new ContainerBuilder();
21+
$container->register('one', ResettableService::class)
22+
->addTag('kernel.reset', array('method' => 'reset'));
23+
$container->register('two', ClearableService::class)
24+
->addTag('kernel.reset', array('method' => 'clear'));
25+
$container->register('three', ResettableClearableService::class)
26+
->addTag('kernel.reset', array('method' => 'reset'))
27+
->addTag('kernel.reset', array('method' => 'clear'));
28+
29+
$container->register(ServiceResetSubscriber::class);
30+
$container->addCompilerPass(new ResettableServicePass());
31+
32+
$container->compile();
33+
34+
$definition = $container->getDefinition(ServiceResetSubscriber::class);
35+
36+
$this->assertEquals(
37+
array(
38+
new IteratorArgument(array(
39+
'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
40+
'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
41+
'three' => new Reference('three', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
42+
)),
43+
array(
44+
'one' => array('reset'),
45+
'two' => array('clear'),
46+
'three' => array('reset', 'clear'),
47+
),
48+
),
49+
$definition->getArguments()
50+
);
51+
}
52+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\EventListener;
4+
5+
use PHPUnit\Framework\TestCase;
2851
6+
use Symfony\Bundle\FrameworkBundle\EventListener\ServiceResetSubscriber;
7+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ClearableService;
8+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ResettableClearableService;
9+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ResettableService;
10+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
11+
use Symfony\Component\DependencyInjection\ContainerBuilder;
12+
use Symfony\Component\DependencyInjection\ContainerInterface;
13+
use Symfony\Component\DependencyInjection\Reference;
14+
15+
class ServiceResetSubscriberTest extends TestCase
16+
{
17+
/**
18+
* @before
19+
*/
20+
public function resetCounters()
21+
{
22+
ResettableService::$counter = 0;
23+
ClearableService::$counter = 0;
24+
ResettableClearableService::$counter = 0;
25+
}
26+
27+
public function testResetServicesNoOp()
28+
{
29+
$container = $this->buildContainer();
30+
$container->get('reset_subscriber')->onKernelTerminate();
31+
32+
$this->assertEquals(0, ResettableService::$counter);
33+
$this->assertEquals(0, ClearableService::$counter);
34+
$this->assertEquals(0, ResettableClearableService::$counter);
35+
}
36+
37+
public function testResetServicesPartially()
38+
{
39+
$container = $this->buildContainer();
40+
$container->get('one');
41+
$container->get('reset_subscriber')->onKernelTerminate();
42+
43+
$this->assertEquals(1, ResettableService::$counter);
44+
$this->assertEquals(0, ClearableService::$counter);
45+
$this->assertEquals(0, ResettableClearableService::$counter);
46+
}
47+
48+
public function testResetServicesTwice()
49+
{
50+
$container = $this->buildContainer();
51+
$container->get('one');
52+
$container->get('reset_subscriber')->onKernelTerminate();
53+
$container->get('two');
54+
$container->get('reset_subscriber')->onKernelTerminate();
55+
56+
$this->assertEquals(2, ResettableService::$counter);
57+
$this->assertEquals(1, ClearableService::$counter);
58+
$this->assertEquals(0, ResettableClearableService::$counter);
59+
}
60+
61+
public function testMultipleResetMethods()
62+
{
63+
$container = $this->buildContainer();
64+
$container->get('three');
65+
$container->get('reset_subscriber')->onKernelTerminate();
66+
67+
$this->assertEquals(0, ResettableService::$counter);
68+
$this->assertEquals(0, ClearableService::$counter);
69+
$this->assertEquals(2, ResettableClearableService::$counter);
70+
}
71+
72+
/**
73+
* @return ContainerBuilder
74+
*/
75+
private function buildContainer()
76+
{
77+
$container = new ContainerBuilder();
78+
$container->register('one', ResettableService::class);
79+
$container->register('two', ClearableService::class);
80+
$container->register('three', ResettableClearableService::class);
81+
82+
$container->register('reset_subscriber', ServiceResetSubscriber::class)
83+
->addArgument(new IteratorArgument(array(
84+
'one' => new Reference('one', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
85+
'two' => new Reference('two', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
86+
'three' => new Reference('three', ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE),
87+
)))
88+
->addArgument(array(
89+
'one' => array('reset'),
90+
'two' => array('clear'),
91+
'three' => array('clear', 'reset'),
92+
));
93+
94+
$container->compile();
95+
96+
return $container;
97+
}
98+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;
4+
5+
class ClearableService
6+
{
7+
public static $counter = 0;
8+
9+
public function clear()
10+
{
11+
++self::$counter;
12+
}
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;
4+
5+
class ResettableClearableService
6+
{
7+
public static $counter = 0;
8+
9+
public function reset()
10+
{
11+
++self::$counter;
12+
}
13+
14+
public function clear()
15+
{
16+
++self::$counter;
17+
}
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;
4+
5+
class ResettableService
6+
{
7+
public static $counter = 0;
8+
9+
public function reset()
10+
{
11+
++self::$counter;
12+
}
13+
}

0 commit comments

Comments
 (0)
0