8000 add `cache:pool:invalidate-tags` command · symfony/symfony@df36605 · GitHub
[go: up one dir, main page]

Skip to content

Commit df36605

Browse files
committed
add cache:pool:invalidate-tags command
1 parent 53e49a8 commit df36605

File tree

10 files changed

+212
-1
lines changed

10 files changed

+212
-1
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ CHANGELOG
44
6.1
55
---
66

7-
* Environment variable `SYMFONY_IDE` is read by default when `framework.ide` config is not set.
7+
* Environment variable `SYMFONY_IDE` is read by default when `framework.ide` config is not set
8+
* Add `cache:pool:invalidate-tags` command
89

910
6.0
1011
---
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Command;
13+
14+
use Symfony\Component\Console\Attribute\AsCommand;
15+
use Symfony\Component\Console\Command\Command;
16+
use Symfony\Component\Console\Input\InputArgument;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Input\InputOption;
19+
use Symfony\Component\Console\Output\OutputInterface;
20+
use Symfony\Component\Console\Style\SymfonyStyle;
21+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
22+
use Symfony\Contracts\Service\ServiceProviderInterface;
23+
24+
/**
25+
* @author Kevin Bond <kevinbond@gmail.com>
26+
*/
27+
#[AsCommand(name: 'cache:pool:invalidate-tags', description: 'Invalidate cache tags for all or a specific pool')]
28+
final class CachePoolInvalidateTagsCommand extends Command
29+
{
30+
private ServiceProviderInterface $pools;
31+
32+
public function __construct(ServiceProviderInterface $pools)
33+
{
34+
parent::__construct();
35+
36+
$this->pools = $pools;
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
protected function configure(): void
43+
{
44+
$this
45+
->addArgument('tags', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'The tags to invalidate')
46+
->addOption('pool', 'p', InputOption::VALUE_REQUIRED, 'The pool to invalidate on')
47+
->setHelp(<<<'EOF'
48+
The <info>%command.name%</info> command invalidates tags from taggable pools. By default, all pools
49+
have the passed tags invalidated. Pass <info>--pool=my_pool</info> to invalidate tags on a specific pool.
50+
51+
php %command.full_name% tag1 tag2
52+
php %command.full_name% tag1 tag2 --pool=my_pool
53+
EOF)
54+
;
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
protected function execute(InputInterface $input, OutputInterface $output): int
61+
{
62+
$io = new SymfonyStyle($input, $output);
63+
$pools = $input->getOption('pool') ?? array_keys($this->pools->getProvidedServices());
64+
$tags = $input->getArgument('tags');
65+
$tagList = implode(', ', $tags);
66+
67+
foreach ((array) $pools as $name) {
68+
try {
69+
$pool = $this->pools->get($name);
70+
} catch (ServiceNotFoundException $e) {
71+
throw new \InvalidArgumentException(sprintf('Pool "%s" not found or is not taggable.', $name), previous: $e);
72+
}
73+
74+
$io->comment(sprintf('Invalidating tag(s): <info>%s</info> from pool <comment>%s</comment>.', $tagList, $name));
75+
76+
if (!$pool->invalidateTags($tags)) {
77+
throw new \Exception(sprintf('Cache tag(s) "%s" could not be invalidated for pool "%s".', $tagList, $name));
78+
}
79+
}
80+
81+
$io->success('Successfully invalidated cache tags.');
82+
83+
return 0;
84+
}
85+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class UnusedTagsPass implements CompilerPassInterface
2727
'auto_alias',
2828
'cache.pool',
2929
'cache.pool.clearer',
30+
'cache.taggable',
3031
'chatter.transport_factory',
3132
'config_cache.resource_checker',
3233
'console.command',

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
21282128
if ($isRedisTagAware) {
21292129
$tagAwareId = $name;
21302130
$container->setAlias('.'.$name.'.inner', $name);
2131+
$definition->addTag('cache.taggable', ['pool' => $name]);
21312132
} elseif ($pool['tags']) {
21322133
if (true !== $pool['tags'] && ($config['pools'][$pool['tags 10000 ']]['tags'] ?? false)) {
21332134
$pool['tags'] = '.'.$pool['tags'].'.inner';
@@ -2136,6 +2137,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
21362137
->addArgument(new Reference('.'.$name.'.inner'))
21372138
->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null)
21382139
->setPublic($pool['public'])
2140+
->addTag('cache.taggable', ['pool' => $name])
21392141
;
21402142

21412143
if (method_exists(TagAwareAdapter::class, 'setLogger')) {
@@ -2151,6 +2153,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
21512153
$tagAwareId = '.'.$name.'.taggable';
21522154
$container->register($tagAwareId, TagAwareAdapter::class)
21532155
->addArgument(new Reference($name))
2156+
->addTag('cache.taggable', ['pool' => $name])
21542157
;
21552158
}
21562159

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass;
3434
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
3535
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
36+
use Symfony\Component\Cache\DependencyInjection\CachePoolTaggablePass;
3637
use Symfony\Component\Config\Resource\ClassExistenceResource;
3738
use Symfony\Component\Console\ConsoleEvents;
3839
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
@@ -145,6 +146,7 @@ public function build(ContainerBuilder $container)
145146
$container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32);
146147
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);
147148
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
149+
$container->addCompilerPass(new CachePoolTaggablePass());
148150
$this->addCompilerPassIfExists($container, FormPass::class);
149151
$container->addCompilerPass(new WorkflowGuardListenerPass());
150152
$container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);

src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
->set('cache.app.taggable', TagAwareAdapter::class)
4141
->args([service('cache.app')])
42+
->tag('cache.taggable', ['pool' => 'cache.app'])
4243

4344
->set('cache.system')
4445
->parent('cache.adapter.system')

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Bundle\FrameworkBundle\Command\CacheClearCommand;
1717
use Symfony\Bundle\FrameworkBundle\Command\CachePoolClearCommand;
1818
use Symfony\Bundle\FrameworkBundle\Command\CachePoolDeleteCommand;
19+
use Symfony\Bundle\FrameworkBundle\Command\CachePoolInvalidateTagsCommand;
1920
use Symfony\Bundle\FrameworkBundle\Command\CachePoolListCommand;
2021
use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand;
2122
use Symfony\Bundle\FrameworkBundle\Command\CacheWarmupCommand;
@@ -93,6 +94,12 @@
9394
])
9495
->tag('console.command')
9596

97+
->set('console.command.cache_pool_invalidate_tags', CachePoolInvalidateTagsCommand::class)
98+
->args([
99+
service_locator([]),
100+
])
101+
->tag('console.command')
102+
96103
->set('console.command.cache_pool_delete', CachePoolDeleteCommand::class)
97104
->args([
98105
service('cache.global_clearer'),
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\Command;
13+
14+
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
15+
16+
final class CachePoolInvalidateTagsCommandTest extends TestCase
17+
{
18+
public function testCommandWithPools()
19+
{
20+
$this->markTestIncomplete('TODO');
21+
}
22+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
15+
use Symfony\Component\DependencyInjection\ChildDefinition;
16+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Exception\LogicException;
19+
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Contracts\Cache\TagAwareCacheInterface;
21+
22+
/**
23+
* @author Kevin Bond <kevinbond@gmail.com>
24+
*/
25+
final class CachePoolTaggablePass implements CompilerPassInterface
26+
{
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
public function process(ContainerBuilder $container): void
31+
{
32+
if (!$container->hasDefinition('console.command.cache_pool_invalidate_tags')) {
33+
return;
34+
}
35+
36+
$services = [];
37+
38+
foreach ($container->findTaggedServiceIds('cache.taggable') as $id => $tags) {
39+
foreach ($tags as $tag) {
40+
$definition = $container->getDefinition($id);
41+
42+
if ($definition->isAbstract()) {
43+
continue;
44+
}
45+
46+
if (!$pool = $tag['pool'] ?? null) {
47+
throw new LogicException(sprintf('Tag "cache.taggable" on service "%s" must have a "pool" attribute.', $id));
48+
}
49+
50+
while ($definition instanceof ChildDefinition) {
51+
$definition = $container->getDefinition($definition->getParent());
52+
}
53+
54+
if (!\is_a($definition->getClass(), TagAwareCacheInterface::class, true)) {
55+
throw new LogicException(sprintf('Service "%s" must implement "%s" to be tagged with "cache.taggable".', $id, TagAwareCacheInterface::class));
56+
}
57+
58+
$services[$pool] = new Reference($id);
59+
}
60+
}
61+
62+
$container->getDefinition('console.command.cache_pool_invalidate_tags')->replaceArgument(0, new ServiceLocatorArgument($services));
63+
}
64+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
& BA62 lt;?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
16+
/**
17+
* @author Kevin Bond <kevinbond@gmail.com>
18+
*/
19+
final class CachePoolTaggablePassTest extends TestCase
20+
{
21+
public function testCompilerPassReplacesCommandArgument()
22+
{
23+
$this->markTestIncomplete('TODO');
24+
}
25+
}

0 commit comments

Comments
 (0)
0