Skip to content

[FrameworkBundle] Allow clearing private cache pools in cache:pool:clear #20810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions UPGRADE-3.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions UPGRADE-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,21 @@ protected function execute(InputInterface $input, OutputInterface $output)
$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));
}
}
}

Expand All @@ -75,7 +80,12 @@ protected function execute(InputInterface $input, OutputInterface $output)

foreach ($pools as $id => $pool) {
$io->comment(sprintf('Clearing cache pool: <info>%s</info>', $id));
$pool->clear();

if ($pool instanceof CacheItemPoolInterface) {
$pool->clear();
} else {
$globalClearer->clearPool($id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I understand why you needed hasPool :)
This can throw in the middle of a clean because of a missing pool.
Then I propose to replace clearPool($pool) by PsrCacheClearer::getPools() and handle validation with the resulting array.
WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either we keep hasPool()/clearPool() or we get all the pools and let the caller doing the filtering logic as you propose, it's debatable but I would prefer keep the clearer being a clearer rather than an accessor. As you prefer :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's go for hasPool :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

}
}

$io->success('Cache was successfully cleared.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ public function process(ContainerBuilder $container)
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];
}
Expand Down Expand Up @@ -80,8 +82,16 @@ public function process(ContainerBuilder $container)
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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
<tag name="kernel.cache_clearer" />
</service>

<service id="cache.global_clearer" parent="cache.default_clearer" />
<service id="cache.app_clearer" alias="cache.default_clearer" />

</services>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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
{
Expand All @@ -29,6 +30,9 @@ public function testPoolRefsAreWeak()
$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'));
Expand All @@ -50,6 +54,7 @@ public function testPoolRefsAreWeak()
$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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?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\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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?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.
*/

use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;

return array(
new FrameworkBundle(),
new TestBundle(),
);
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 21 additions & 0 deletions src/Symfony/Component/HttpKernel/CacheClearer/Psr6CacheClearer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

@nicolas-grekas nicolas-grekas Dec 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is useless to me: let clearPool throw, that's way enough.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's convenient to use it in the command since we want to try fetching the pool from the container if the clearer can't find it. I'll first add the global clearer and come back for this point

{
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}
*/
Expand Down
Loading