From fad4d9e2efa4c1340005c48daa7f6929058f9bd1 Mon Sep 17 00:00:00 2001 From: Maxime Steinhausser Date: Thu, 23 Feb 2017 00:13:41 +0100 Subject: [PATCH] [DI][Router][DX] Invalidate routing cache when container parameters changed --- .../Resources/config/services.xml | 5 ++ .../Bundle/FrameworkBundle/Routing/Router.php | 5 ++ .../Tests/Routing/RouterTest.php | 15 ++++ .../Config/ContainerParametersResource.php | 64 +++++++++++++++ .../ContainerParametersResourceChecker.php | 52 +++++++++++++ ...ContainerParametersResourceCheckerTest.php | 77 +++++++++++++++++++ .../ContainerParametersResourceTest.php | 43 +++++++++++ 7 files changed, 261 insertions(+) create mode 100644 src/Symfony/Component/DependencyInjection/Config/ContainerParametersResource.php create mode 100644 src/Symfony/Component/DependencyInjection/Config/ContainerParametersResourceChecker.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php create mode 100644 src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index c8123c4619..93e9c9f659 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -64,6 +64,11 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php index 35bd5ef1ef..c437cb373c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php +++ b/src/Symfony/Bundle/FrameworkBundle/Routing/Router.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle\Routing; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; use Symfony\Component\Routing\Router as BaseRouter; use Symfony\Component\Routing\RequestContext; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -27,6 +28,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException; class Router extends BaseRouter implements WarmableInterface { private $container; + private $collectedParameters = array(); /** * Constructor. @@ -53,6 +55,7 @@ class Router extends BaseRouter implements WarmableInterface if (null === $this->collection) { $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']); $this->resolveParameters($this->collection); + $this->collection->addResource(new ContainerParametersResource($this->collectedParameters)); } return $this->collection; @@ -153,6 +156,8 @@ class Router extends BaseRouter implements WarmableInterface $resolved = $container->getParameter($match[1]); if (is_string($resolved) || is_numeric($resolved)) { + $this->collectedParameters[$match[1]] = $resolved; + return (string) $resolved; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php index 677fc4843c..2876d5d735 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Routing/RouterTest.php @@ -13,6 +13,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Routing; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\Routing\Router; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -217,6 +218,20 @@ class RouterTest extends TestCase $this->assertSame($value, $route->getDefault('foo')); } + public function testGetRouteCollectionAddsContainerParametersResource() + { + $routeCollection = $this->getMockBuilder(RouteCollection::class)->getMock(); + $routeCollection->method('getIterator')->willReturn(new \ArrayIterator(array(new Route('/%locale%')))); + $routeCollection->expects($this->once())->method('addResource')->with(new ContainerParametersResource(array('locale' => 'en'))); + + $sc = $this->getServiceContainer($routeCollection); + $sc->setParameter('locale', 'en'); + + $router = new Router($sc, 'foo'); + + $router->getRouteCollection(); + } + public function getNonStringValues() { return array(array(null), array(false), array(true), array(new \stdClass()), array(array('foo', 'bar')), array(array(array()))); diff --git a/src/Symfony/Component/DependencyInjection/Config/ContainerParametersResource.php b/src/Symfony/Component/DependencyInjection/Config/ContainerParametersResource.php new file mode 100644 index 0000000000..072f0580aa --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Config/ContainerParametersResource.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Tracks container parameters. + * + * @author Maxime Steinhausser + */ +class ContainerParametersResource implements ResourceInterface, \Serializable +{ + private $parameters; + + /** + * @param array $parameters The container parameters to track + */ + public function __construct(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return 'container_parameters_'.md5(serialize($this->parameters)); + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize($this->parameters); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $this->parameters = unserialize($serialized); + } + + /** + * @return array Tracked parameters + */ + public function getParameters() + { + return $this->parameters; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Config/ContainerParametersResourceChecker.php b/src/Symfony/Component/DependencyInjection/Config/ContainerParametersResourceChecker.php new file mode 100644 index 0000000000..6ed77e3fd2 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Config/ContainerParametersResourceChecker.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\ResourceCheckerInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * @author Maxime Steinhausser + */ +class ContainerParametersResourceChecker implements ResourceCheckerInterface +{ + /** @var ContainerInterface */ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function supports(ResourceInterface $metadata) + { + return $metadata instanceof ContainerParametersResource; + } + + /** + * {@inheritdoc} + */ + public function isFresh(ResourceInterface $resource, $timestamp) + { + foreach ($resource->getParameters() as $key => $value) { + if (!$this->container->hasParameter($key) || $this->container->getParameter($key) !== $value) { + return false; + } + } + + return true; + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php new file mode 100644 index 0000000000..a91934b3ef --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceCheckerTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\ResourceCheckerInterface; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResourceChecker; +use Symfony\Component\DependencyInjection\ContainerInterface; + +class ContainerParametersResourceCheckerTest extends TestCase +{ + /** @var ContainerParametersResource */ + private $resource; + + /** @var ResourceCheckerInterface */ + private $resourceChecker; + + /** @var ContainerInterface */ + private $container; + + protected function setUp() + { + $this->resource = new ContainerParametersResource(array('locales' => array('fr', 'en'), 'default_locale' => 'fr')); + $this->container = $this->getMockBuilder(ContainerInterface::class)->getMock(); + $this->resourceChecker = new ContainerParametersResourceChecker($this->container); + } + + public function testSupports() + { + $this->assertTrue($this->resourceChecker->supports($this->resource)); + } + + /** + * @dataProvider isFreshProvider + */ + public function testIsFresh(callable $mockContainer, $expected) + { + $mockContainer($this->container); + + $this->assertSame($expected, $this->resourceChecker->isFresh($this->resource, time())); + } + + public function isFreshProvider() + { + yield 'not fresh on missing parameter' => array(function (\PHPUnit_Framework_MockObject_MockObject $container) { + $container->method('hasParameter')->with('locales')->willReturn(false); + }, false); + + yield 'not fresh on different value' => array(function (\PHPUnit_Framework_MockObject_MockObject $container) { + $container->method('getParameter')->with('locales')->willReturn(array('nl', 'es')); + }, false); + + yield 'fresh on every identical parameters' => array(function (\PHPUnit_Framework_MockObject_MockObject $container) { + $container->expects($this->exactly(2))->method('hasParameter')->willReturn(true); + $container->expects($this->exactly(2))->method('getParameter') + ->withConsecutive( + array($this->equalTo('locales')), + array($this->equalTo('default_locale')) + ) + ->will($this->returnValueMap(array( + array('locales', array('fr', 'en')), + array('default_locale', 'fr'), + ))) + ; + }, true); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceTest.php b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceTest.php new file mode 100644 index 0000000000..4da4766f27 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Config/ContainerParametersResourceTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Config; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Config\ContainerParametersResource; + +class ContainerParametersResourceTest extends TestCase +{ + /** @var ContainerParametersResource */ + private $resource; + + protected function setUp() + { + $this->resource = new ContainerParametersResource(array('locales' => array('fr', 'en'), 'default_locale' => 'fr')); + } + + public function testToString() + { + $this->assertSame('container_parameters_9893d3133814ab03cac3490f36dece77', (string) $this->resource); + } + + public function testSerializeUnserialize() + { + $unserialized = unserialize(serialize($this->resource)); + + $this->assertEquals($this->resource, $unserialized); + } + + public function testGetParameters() + { + $this->assertSame(array('locales' => array('fr', 'en'), 'default_locale' => 'fr'), $this->resource->getParameters()); + } +}