From b71df3f2958f91518363e9e9fd9fa39ca272bf27 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 7 Dec 2016 20:11:50 +0100 Subject: [PATCH] [FrameworkBundle] Allow clearing private cache pools --- UPGRADE-3.3.md | 6 ++ UPGRADE-4.0.md | 3 + .../Command/CachePoolClearCommand.php | 26 ++++-- .../Compiler/CachePoolClearerPass.php | 18 +++- .../Compiler/CachePoolPass.php | 12 ++- .../Resources/config/cache.xml | 1 + .../Compiler/CachePoolClearerPassTest.php | 7 +- .../Functional/CachePoolClearCommandTest.php | 86 +++++++++++++++++++ .../Functional/app/CachePoolClear/bundles.php | 18 ++++ .../Functional/app/CachePoolClear/config.yml | 21 +++++ .../Bundle/FrameworkBundle/composer.json | 2 +- .../CacheClearer/Psr6CacheClearer.php | 21 +++++ .../CacheClearer/Psr6CacheClearerTest.php | 68 +++++++++++++++ 13 files changed, 275 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/bundles.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/config.yml create mode 100644 src/Symfony/Component/HttpKernel/Tests/CacheClearer/Psr6CacheClearerTest.php diff --git a/UPGRADE-3.3.md b/UPGRADE-3.3.md index 2989eaa550..2f0d91aade 100644 --- a/UPGRADE-3.3.md +++ b/UPGRADE-3.3.md @@ -18,3 +18,9 @@ SecurityBundle * The `FirewallContext::getContext()` method has been deprecated and will be removed in 4.0. Use the `getListeners()` method instead. + +HttpKernel +----------- + + * The `Psr6CacheClearer::addPool()` method has been deprecated. Pass an array of pools indexed + by name to the constructor instead. diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index b1c788de84..4a743b2da3 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -175,6 +175,9 @@ HttpKernel * The `DataCollector::varToString()` method has been removed in favor of `cloneVar()`. + * The `Psr6CacheClearer::addPool()` method has been removed. Pass an array of pools indexed + by name to the constructor instead. + Security -------- diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php index 82934e1845..9c6ab53a82 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/CachePoolClearCommand.php @@ -55,16 +55,21 @@ EOF $clearers = array(); $container = $this->getContainer(); $cacheDir = $container->getParameter('kernel.cache_dir'); + $globalClearer = $container->get('cache.global_clearer'); foreach ($input->getArgument('pools') as $id) { - $pool = $container->get($id); - - if ($pool instanceof CacheItemPoolInterface) { - $pools[$id] = $pool; - } elseif ($pool instanceof Psr6CacheClearer) { - $clearers[$id] = $pool; + if ($globalClearer->hasPool($id)) { + $pools[$id] = $id; } else { - throw new \InvalidArgumentException(sprintf('"%s" is not a cache pool nor a cache clearer.', $id)); + $pool = $container->get($id); + + if ($pool instanceof CacheItemPoolInterface) { + $pools[$id] = $pool; + } elseif ($pool instanceof Psr6CacheClearer) { + $clearers[$id] = $pool; + } else { + throw new \InvalidArgumentException(sprintf('"%s" is not a cache pool nor a cache clearer.', $id)); + } } } @@ -75,7 +80,12 @@ EOF foreach ($pools as $id => $pool) { $io->comment(sprintf('Clearing cache pool: %s', $id)); - $pool->clear(); + + if ($pool instanceof CacheItemPoolInterface) { + $pool->clear(); + } else { + $globalClearer->clearPool($id); + } } $io->success('Cache was successfully cleared.'); diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php index c859a6ba90..591d03c58a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolClearerPass.php @@ -27,19 +27,31 @@ final class CachePoolClearerPass implements CompilerPassInterface public function process(ContainerBuilder $container) { $container->getParameterBag()->remove('cache.prefix.seed'); + $poolsByClearer = array(); + $pools = array(); foreach ($container->findTaggedServiceIds('cache.pool') as $id => $attributes) { + $pools[$id] = new Reference($id); foreach (array_reverse($attributes) as $attr) { if (isset($attr['clearer'])) { - $clearer = $container->getDefinition($attr['clearer']); - $clearer->addMethodCall('addPool', array(new Reference($id))); + $poolsByClearer[$attr['clearer']][$id] = $pools[$id]; } - if (array_key_exists('clearer', $attr)) { + if (!empty($attr['unlazy'])) { + $container->getDefinition($id)->setLazy(false); + } + if (array_key_exists('clearer', $attr) || array_key_exists('unlazy', $attr)) { break; } } } + $container->getDefinition('cache.global_clearer')->addArgument($pools); + + foreach ($poolsByClearer as $clearer => $pools) { + $clearer = $container->getDefinition($clearer); + $clearer->addArgument($pools); + } + if (!$container->has('cache.annotations')) { return; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php index 1e09b88214..e4e3bc3506 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/CachePoolPass.php @@ -47,8 +47,10 @@ class CachePoolPass implements CompilerPassInterface if ($pool->isAbstract()) { continue; } + $isLazy = $pool->isLazy(); while ($adapter instanceof DefinitionDecorator) { $adapter = $container->findDefinition($adapter->getParent()); + $isLazy = $isLazy || $adapter->isLazy(); if ($t = $adapter->getTag('cache.pool')) { $tags[0] += $t[0]; } @@ -80,8 +82,16 @@ class CachePoolPass implements CompilerPassInterface throw new InvalidArgumentException(sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "namespace" and "default_lifetime", found "%s".', $id, implode('", "', array_keys($tags[0])))); } + $attr = array(); if (null !== $clearer) { - $pool->addTag('cache.pool', array('clearer' => $clearer)); + $attr['clearer'] = $clearer; + } + if (!$isLazy) { + $pool->setLazy(true); + $attr['unlazy'] = true; + } + if ($attr) { + $pool->addTag('cache.pool', $attr); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index 80cb00ada9..c790ce5a6e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -97,6 +97,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php index 38a2d38761..98caf89ba0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/CachePoolClearerPassTest.php @@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\Compiler\RepeatedPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; class CachePoolClearerPassTest extends \PHPUnit_Framework_TestCase { @@ -29,6 +30,9 @@ class CachePoolClearerPassTest extends \PHPUnit_Framework_TestCase $container->setParameter('kernel.environment', 'prod'); $container->setParameter('kernel.root_dir', 'foo'); + $globalClearer = new Definition(Psr6CacheClearer::class); + $container->setDefinition('cache.global_clearer', $globalClearer); + $publicPool = new Definition(); $publicPool->addArgument('namespace'); $publicPool->addTag('cache.pool', array('clearer' => 'clearer_alias')); @@ -50,6 +54,7 @@ class CachePoolClearerPassTest extends \PHPUnit_Framework_TestCase $pass->process($container); } - $this->assertEquals(array(array('addPool', array(new Reference('public.pool')))), $clearer->getMethodCalls()); + $this->assertEquals(array(array('public.pool' => new Reference('public.pool'))), $clearer->getArguments()); + $this->assertEquals(array(array('public.pool' => new Reference('public.pool'))), $globalClearer->getArguments()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php new file mode 100644 index 0000000000..59949dfdfd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/CachePoolClearCommandTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; + +use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand; +use Symfony\Component\Console\Tester\CommandTester; + +/** + * @group functional + */ +class CachePoolClearCommandTest extends WebTestCase +{ + private $application; + + protected function setUp() + { + static::bootKernel(array('test_case' => 'CachePoolClear', 'root_config' => 'config.yml')); + } + + public function testClearPrivatePool() + { + $tester = $this->createCommandTester(); + $tester->execute(array('pools' => array('cache.private_pool')), array('decorated' => false)); + + $this->assertSame(0, $tester->getStatusCode(), 'cache:pool:clear exits with 0 in case of success'); + $this->assertContains('Clearing cache pool: cache.private_pool', $tester->getDisplay()); + $this->assertContains('[OK] Cache was successfully cleared.', $tester->getDisplay()); + } + + public function testClearPublicPool() + { + $tester = $this->createCommandTester(); + $tester->execute(array('pools' => array('cache.public_pool')), array('decorated' => false)); + + $this->assertSame(0, $tester->getStatusCode(), 'cache:pool:clear exits with 0 in case of success'); + $this->assertContains('Clearing cache pool: cache.public_pool', $tester->getDisplay()); + $this->assertContains('[OK] Cache was successfully cleared.', $tester->getDisplay()); + } + + public function testClearPoolWithCustomClearer() + { + $tester = $this->createCommandTester(); + $tester->execute(array('pools' => array('cache.pool_with_clearer')), array('decorated' => false)); + + $this->assertSame(0, $tester->getStatusCode(), 'cache:pool:clear exits with 0 in case of success'); + $this->assertContains('Clearing cache pool: cache.pool_with_clearer', $tester->getDisplay()); + $this->assertContains('[OK] Cache was successfully cleared.', $tester->getDisplay()); + } + + public function testCallClearer() + { + $tester = $this->createCommandTester(); + $tester->execute(array('pools' => array('cache.default_clearer')), array('decorated' => false)); + + $this->assertSame(0, $tester->getStatusCode(), 'cache:pool:clear exits with 0 in case of success'); + $this->assertContains('Calling cache clearer: cache.default_clearer', $tester->getDisplay()); + $this->assertContains('[OK] Cache was successfully cleared.', $tester->getDisplay()); + } + + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException + * @expectedExceptionMessage You have requested a non-existent service "unknown_pool" + */ + public function testClearUnexistingPool() + { + $this->createCommandTester() + ->execute(array('pools' => array('unknown_pool')), array('decorated' => false)); + } + + private function createCommandTester() + { + $command = new CachePoolClearCommand(); + $command->setContainer(static::$kernel->getContainer()); + + return new CommandTester($command); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/bundles.php new file mode 100644 index 0000000000..a73987bcc9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/bundles.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; + +return array( + new FrameworkBundle(), + new TestBundle(), +); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/config.yml new file mode 100644 index 0000000000..75107485ee --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/CachePoolClear/config.yml @@ -0,0 +1,21 @@ +imports: + - { resource: ../config/default.yml } + +services: + dummy: + class: Symfony\Bundle\FrameworkBundle\Tests\Fixtures\DeclaredClass + arguments: ['@cache.private_pool'] + custom_clearer: + parent: cache.default_clearer + tags: + - name: kernel.cache_clearer + +framework: + cache: + pools: + cache.private_pool: ~ + cache.public_pool: + public: true + cache.pool_with_clearer: + public: true + clearer: custom_clearer diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 6a45eb0177..0e43050e03 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -23,7 +23,7 @@ "symfony/config": "~2.8|~3.0", "symfony/event-dispatcher": "~2.8|~3.0", "symfony/http-foundation": "~3.1", - "symfony/http-kernel": "~3.2", + "symfony/http-kernel": "~3.3", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "~2.8|~3.0", "symfony/finder": "~2.8|~3.0", diff --git a/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php b/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php index 30261b3f7c..2336b18a29 100644 --- a/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php +++ b/src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php @@ -20,11 +20,32 @@ class Psr6CacheClearer implements CacheClearerInterface { private $pools = array(); + public function __construct(array $pools = array()) + { + $this->pools = $pools; + } + public function addPool(CacheItemPoolInterface $pool) { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead.', __METHOD__), E_USER_DEPRECATED); + $this->pools[] = $pool; } + public function hasPool($name) + { + return isset($this->pools[$name]); + } + + public function clearPool($name) + { + if (!isset($this->pools[$name])) { + throw new \InvalidArgumentException(sprintf('Cache pool not found: %s.', $name)); + } + + return $this->pools[$name]->clear(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/HttpKernel/Tests/CacheClearer/Psr6CacheClearerTest.php b/src/Symfony/Component/HttpKernel/Tests/CacheClearer/Psr6CacheClearerTest.php new file mode 100644 index 0000000000..dd47971120 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/CacheClearer/Psr6CacheClearerTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer; +use Psr\Cache\CacheItemPoolInterface; + +class Psr6CacheClearerTest extends \PHPUnit_Framework_TestCase +{ + public function testClearPoolsInjectedInConstructor() + { + $pool = $this->getMock(CacheItemPoolInterface::class); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clear(''); + } + + public function testClearPool() + { + $pool = $this->getMock(CacheItemPoolInterface::class); + $pool + ->expects($this->once()) + ->method('clear'); + + (new Psr6CacheClearer(array('pool' => $pool)))->clearPool('pool'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Cache pool not found: unknown + */ + public function testClearPoolThrowsExceptionOnUnreferencedPool() + { + (new Psr6CacheClearer())->clearPool('unknown'); + } + + /** + * @group legacy + * @expectedDeprecation The Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer::addPool() method is deprecated since version 3.3 and will be removed in 4.0. Pass an array of pools indexed by name to the constructor instead. + */ + public function testClearPoolsInjectedByAdder() + { + $pool1 = $this->getMock(CacheItemPoolInterface::class); + $pool1 + ->expects($this->once()) + ->method('clear'); + + $pool2 = $this->getMock(CacheItemPoolInterface::class); + $pool2 + ->expects($this->once()) + ->method('clear'); + + $clearer = new Psr6CacheClearer(array('pool1' => $pool1)); + $clearer->addPool($pool2); + $clearer->clear(''); + } +}