[HttpKernel] Move services reset to Kernel

This commit is contained in:
Nicolas Grekas 2017-10-27 17:36:31 +02:00
parent fdac9e3911
commit 4501a3688b
9 changed files with 164 additions and 170 deletions

View File

@ -75,10 +75,6 @@
<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>
<service id="services_resetter" class="Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter" public="true" />
</services>
</container>

View File

@ -17,7 +17,6 @@ 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>
@ -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 @@ class ResettableServicePass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container)
{
if (!$container->has(ServiceResetListener::class)) {
if (!$container->has('services_resetter')) {
return;
}
@ -57,13 +53,13 @@ class ResettableServicePass implements CompilerPassInterface
}
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);
}
}

View File

@ -0,0 +1,39 @@
<?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;
/**
* Resets provided services.
*
* @author Alexander M. Turek <me@derrabus.de>
* @author Nicolas Grekas <p@tchwork.com>
*
* @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]}();
}
}
}

View File

@ -1,50 +0,0 @@
<?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),
);
}
}

View File

@ -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 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
$this->booted = false;
$this->container = null;
$this->requestStackSize = 0;
$this->resetServices = false;
}
/**
@ -107,8 +111,20 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
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 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
}
$this->container = null;
$this->requestStackSize = 0;
$this->resetServices = false;
}
/**
@ -176,17 +194,15 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
*/
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);
}
/**

View File

@ -8,7 +8,7 @@ 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\DependencyInjection\ServicesResetter;
use Symfony\Component\HttpKernel\Tests\Fixtures\ClearableService;
use Symfony\Component\HttpKernel\Tests\Fixtures\ResettableService;
@ -24,14 +24,14 @@ class ResettableServicePassTest extends TestCase
->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 @@ class ResettableServicePassTest extends TestCase
$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 @@ class ResettableServicePassTest extends TestCase
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'));
}
}

View File

@ -0,0 +1,42 @@
<?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\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);
}
}

View File

@ -1,77 +0,0 @@
<?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)->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;
}
}

View File

@ -18,6 +18,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
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\HttpFoundation\Response;
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 @@ EOF;
$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 @@ class CustomProjectDirKernel extends Kernel
$build($container);
}
}
protected function getHttpKernel()
{
return $this->httpKernel;
}
}
class PassKernel extends CustomProjectDirKernel implements CompilerPassInterface