diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 31ca2075e4a98..da0e5b55edaf8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -75,10 +75,6 @@ - - - - - + diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php index 56cd059284afe..52cc3a4a8383f 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ResettableServicePass.php @@ -17,7 +17,6 @@ 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 @@ -26,9 +25,6 @@ class ResettableServicePass implements CompilerPassInterface { private $tagName; - /** - * @param string $tagName - */ public function __construct($tagName = 'kernel.reset') { $this->tagName = $tagName; @@ -39,7 +35,7 @@ public function __construct($tagName = 'kernel.reset') */ public function process(ContainerBuilder $container) { - if (!$container->has(ServiceResetListener::class)) { + if (!$container->has('services_resetter')) { return; } @@ -57,13 +53,13 @@ public function process(ContainerBuilder $container) } if (empty($services)) { - $container->removeDefinition(ServiceResetListener::class); + $container->removeDefinition('services_resetter'); return; } - $container->findDefinition(ServiceResetListener::class) - ->replaceArgument(0, new IteratorArgument($services)) - ->replaceArgument(1, $methods); + $container->findDefinition('services_resetter') + ->setArgument(0, new IteratorArgument($services)) + ->setArgument(1, $methods); } } diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php b/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php new file mode 100644 index 0000000000000..b82d2fef3c056 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/ServicesResetter.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +/** + * Resets provided services. + * + * @author Alexander M. Turek + * @author Nicolas Grekas + * + * @internal + */ +class ServicesResetter +{ + private $resettableServices; + private $resetMethods; + + public function __construct(\Traversable $resettableServices, array $resetMethods) + { + $this->resettableServices = $resettableServices; + $this->resetMethods = $resetMethods; + } + + public function reset() + { + foreach ($this->resettableServices as $id => $service) { + $service->{$this->resetMethods[$id]}(); + } + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/ServiceResetListener.php b/src/Symfony/Component/HttpKernel/EventListener/ServiceResetListener.php deleted file mode 100644 index cf6d15930315f..0000000000000 --- a/src/Symfony/Component/HttpKernel/EventListener/ServiceResetListener.php +++ /dev/null @@ -1,50 +0,0 @@ - - * - * 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 - */ -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), - ); - } -} diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index cb143d327cb07..f4e4fb7a5171b 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -64,6 +64,8 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $projectDir; private $warmupDir; + private $requestStackSize = 0; + private $resetServices = false; const VERSION = '3.4.0-DEV'; const VERSION_ID = 30400; @@ -99,6 +101,8 @@ public function __clone() $this->booted = false; $this->container = null; + $this->requestStackSize = 0; + $this->resetServices = false; } /** @@ -107,8 +111,20 @@ public function __clone() public function boot() { if (true === $this->booted) { + if (!$this->requestStackSize && $this->resetServices) { + if ($this->container->has('services_resetter')) { + $this->container->get('services_resetter')->reset(); + } + $this->resetServices = false; + } + return; } + if ($this->debug && !isset($_SERVER['SHELL_VERBOSITY'])) { + putenv('SHELL_VERBOSITY=3'); + $_ENV['SHELL_VERBOSITY'] = 3; + $_SERVER['SHELL_VERBOSITY'] = 3; + } if ($this->loadClassCache) { $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); @@ -169,6 +185,8 @@ public function shutdown() } $this->container = null; + $this->requestStackSize = 0; + $this->resetServices = false; } /** @@ -176,17 +194,15 @@ public function shutdown() */ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) { - if (false === $this->booted) { - if ($this->debug && !isset($_SERVER['SHELL_VERBOSITY'])) { - putenv('SHELL_VERBOSITY=3'); - $_ENV['SHELL_VERBOSITY'] = 3; - $_SERVER['SHELL_VERBOSITY'] = 3; - } + $this->boot(); + ++$this->requestStackSize; + $this->resetServices = true; - $this->boot(); + try { + return $this->getHttpKernel()->handle($request, $type, $catch); + } finally { + --$this->requestStackSize; } - - return $this->getHttpKernel()->handle($request, $type, $catch); } /** diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php index c998ef2eaf086..f7ea16dbfb036 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ResettableServicePassTest.php @@ -8,7 +8,7 @@ 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\DependencyInjection\ServicesResetter; use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService; use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService; @@ -24,14 +24,14 @@ public function testCompilerPass() ->setPublic(true) ->addTag('kernel.reset', array('method' => 'clear')); - $container->register(ServiceResetListener::class) + $container->register('services_resetter', ServicesResetter::class) ->setPublic(true) ->setArguments(array(null, array())); - $container->addCompilerPass(new ResettableServicePass('kernel.reset')); + $container->addCompilerPass(new ResettableServicePass()); $container->compile(); - $definition = $container->getDefinition(ServiceResetListener::class); + $definition = $container->getDefinition('services_resetter'); $this->assertEquals( array( @@ -57,9 +57,9 @@ public function testMissingMethod() $container = new ContainerBuilder(); $container->register(ResettableService::class) ->addTag('kernel.reset'); - $container->register(ServiceResetListener::class) + $container->register('services_resetter', ServicesResetter::class) ->setArguments(array(null, array())); - $container->addCompilerPass(new ResettableServicePass('kernel.reset')); + $container->addCompilerPass(new ResettableServicePass()); $container->compile(); } @@ -67,22 +67,12 @@ public function testMissingMethod() public function testCompilerPassWithoutResetters() { $container = new ContainerBuilder(); - $container->register(ServiceResetListener::class) + $container->register('services_resetter', ServicesResetter::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)); + $this->assertFalse($container->has('services_resetter')); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ServicesResetterTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ServicesResetterTest.php new file mode 100644 index 0000000000000..47c62abd0d998 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ServicesResetterTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; +use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService; +use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService; + +class ServicesResetterTest extends TestCase +{ + protected function setUp() + { + ResettableService::$counter = 0; + ClearableService::$counter = 0; + } + + public function testResetServices() + { + $resetter = new ServicesResetter(new \ArrayIterator(array( + 'id1' => new ResettableService(), + 'id2' => new ClearableService(), + )), array( + 'id1' => 'reset', + 'id2' => 'clear', + )); + + $resetter->reset(); + + $this->assertEquals(1, ResettableService::$counter); + $this->assertEquals(1, ClearableService::$counter); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ServiceResetListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ServiceResetListenerTest.php deleted file mode 100644 index 603d11b2bf412..0000000000000 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ServiceResetListenerTest.php +++ /dev/null @@ -1,77 +0,0 @@ -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)->setPublic(true); - $container->register('two', ClearableService::class)->setPublic(true); - - $container->register('reset_subscriber', ServiceResetListener::class) - ->setPublic(true) - ->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; - } -} diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index fd7f6f09da13d..21342f026e4d7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -18,6 +18,8 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass; +use Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpFoundation\Request; @@ -25,6 +27,7 @@ use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest; use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForOverrideName; use Symfony\Component\HttpKernel\Tests\Fixtures\KernelWithoutBundles; +use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService; class KernelTest extends TestCase { @@ -840,6 +843,38 @@ public function testKernelPass() $this->assertTrue($kernel->getContainer()->getParameter('test.processed')); } + public function testServicesResetter() + { + $httpKernelMock = $this->getMockBuilder(HttpKernelInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $httpKernelMock + ->expects($this->exactly(2)) + ->method('handle'); + + $kernel = new CustomProjectDirKernel(function ($container) { + $container->addCompilerPass(new ResettableServicePass()); + $container->register('one', ResettableService::class) + ->setPublic(true) + ->addTag('kernel.reset', array('method' => 'reset')); + $container->register('services_resetter', ServicesResetter::class)->setPublic(true); + }, $httpKernelMock, 'resetting'); + + ResettableService::$counter = 0; + + $request = new Request(); + + $kernel->handle($request); + $kernel->getContainer()->get('one'); + + $this->assertEquals(0, ResettableService::$counter); + $this->assertFalse($kernel->getContainer()->initialized('services_resetter')); + + $kernel->handle($request); + + $this->assertEquals(1, ResettableService::$counter); + } + /** * Returns a mock for the BundleInterface. * @@ -941,13 +976,15 @@ class CustomProjectDirKernel extends Kernel { private $baseDir; private $buildContainer; + private $httpKernel; - public function __construct(\Closure $buildContainer = null) + public function __construct(\Closure $buildContainer = null, HttpKernelInterface $httpKernel = null, $name = 'custom') { - parent::__construct('custom', true); + parent::__construct($name, true); $this->baseDir = 'foo'; $this->buildContainer = $buildContainer; + $this->httpKernel = $httpKernel; } public function registerBundles() @@ -975,6 +1012,11 @@ protected function build(ContainerBuilder $container) $build($container); } } + + protected function getHttpKernel() + { + return $this->httpKernel; + } } class PassKernel extends CustomProjectDirKernel implements CompilerPassInterface