Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #24155 [FrameworkBundle][HttpKernel] Add DI tag for resettabl…
…e services (derrabus) This PR was merged into the 3.4 branch. Discussion ---------- [FrameworkBundle][HttpKernel] Add DI tag for resettable services | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23984 | License | MIT | Doc PR | TODO This PR uses #24033 to introduce a DI tag for resettable services. TODO after merge: * Add an interface, make the "method" attribute optional and enable autoconfiguration. * Consider adding a config option to enable/disable this feature. * Configure leaking services of the core bundles as resettable. Commits ------- d9a6b76 A DI tag for resettable services.
- Loading branch information
Showing
8 changed files
with
314 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
src/Symfony/Component/HttpKernel/EventListener/ServiceResetListener.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), | ||
); | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
src/Symfony/Component/HttpKernel/Tests/EventListener/ServiceResetListenerTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/Symfony/Component/HttpKernel/Tests/Fixtures/ClearableService.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
namespace Symfony\Component\HttpKernel\Tests\Fixtures; | ||
|
||
class ClearableService | ||
{ | ||
public static $counter = 0; | ||
|
||
public function clear() | ||
{ | ||
++self::$counter; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/Symfony/Component/HttpKernel/Tests/Fixtures/ResettableService.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
namespace Symfony\Component\HttpKernel\Tests\Fixtures; | ||
|
||
class ResettableService | ||
{ | ||
public static $counter = 0; | ||
|
||
public function reset() | ||
{ | ||
++self::$counter; | ||
} | ||
} |