add (filesystem|phpfiles) cache (adapter|simple) prune method and prune command
- added `Symfony\Component\Cache\PruneableInterface` so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning - added FilesystemTrait::prune() and PhpFilesTrait::prune() implementations - now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, and PhpFilesCache implement PruneableInterface and supports manual stale cache pruning - Added `cache:pool:prune` command via `Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand` to allow manual stale cache item pruning of supported PSR-6 and PSR-16 cache pool implementations - Added `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPrunerPass` compiler pass to fetch all cache pools implementing `PruneableInterface` and pass them to the command as an `IteratorArgument` so these references are lazy loaded by the command - updated changelogs as appropriate
This commit is contained in:
parent
5556a3a1f9
commit
f0d0c5ffef
@ -20,6 +20,8 @@ CHANGELOG
|
|||||||
`Symfony\Component\Translation\DependencyInjection\TranslatorPass` instead
|
`Symfony\Component\Translation\DependencyInjection\TranslatorPass` instead
|
||||||
* Added `command` attribute to the `console.command` tag which takes the command
|
* Added `command` attribute to the `console.command` tag which takes the command
|
||||||
name as value, using it makes the command lazy
|
name as value, using it makes the command lazy
|
||||||
|
* Added `cache:pool:prune` command to allow manual stale cache item pruning of supported PSR-6 and PSR-16 cache pool
|
||||||
|
implementations
|
||||||
|
|
||||||
3.3.0
|
3.3.0
|
||||||
-----
|
-----
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
<?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\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache pool pruner command.
|
||||||
|
*
|
||||||
|
* @author Rob Frawley 2nd <rmf@src.run>
|
||||||
|
*/
|
||||||
|
final class CachePoolPruneCommand extends Command
|
||||||
|
{
|
||||||
|
private $pools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterable|PruneableInterface[] $pools
|
||||||
|
*/
|
||||||
|
public function __construct($pools)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->pools = $pools;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function configure()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('cache:pool:prune')
|
||||||
|
->setDescription('Prune cache pools')
|
||||||
|
->setHelp(<<<'EOF'
|
||||||
|
The <info>%command.name%</info> command deletes all expired items from all pruneable pools.
|
||||||
|
|
||||||
|
%command.full_name%
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
|
||||||
|
foreach ($this->pools as $name => $pool) {
|
||||||
|
$io->comment(sprintf('Pruning cache pool: <info>%s</info>', $name));
|
||||||
|
$pool->prune();
|
||||||
|
}
|
||||||
|
|
||||||
|
$io->success('Successfully pruned cache pool(s).');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
<?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\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Rob Frawley 2nd <rmf@src.run>
|
||||||
|
*/
|
||||||
|
class CachePoolPrunerPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
private $cacheCommandServiceId;
|
||||||
|
private $cachePoolTag;
|
||||||
|
|
||||||
|
public function __construct($cacheCommandServiceId = 'cache.command.pool_pruner', $cachePoolTag = 'cache.pool')
|
||||||
|
{
|
||||||
|
$this->cacheCommandServiceId = $cacheCommandServiceId;
|
||||||
|
$this->cachePoolTag = $cachePoolTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if (!$container->hasDefinition($this->cacheCommandServiceId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$services = array();
|
||||||
|
|
||||||
|
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
|
||||||
|
$class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
|
||||||
|
|
||||||
|
if (!$reflection = $container->getReflectionClass($class)) {
|
||||||
|
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reflection->implementsInterface(PruneableInterface::class)) {
|
||||||
|
$services[$id] = new Reference($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProce
|
|||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CacheCollectorPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CacheCollectorPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPrunerPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\DataCollectorTranslatorPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
|
||||||
@ -108,6 +109,7 @@ class FrameworkBundle extends Bundle
|
|||||||
$container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32);
|
$container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32);
|
||||||
$this->addCompilerPassIfExists($container, ValidateWorkflowsPass::class);
|
$this->addCompilerPassIfExists($container, ValidateWorkflowsPass::class);
|
||||||
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);
|
$container->addCompilerPass(new CachePoolClearerPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||||
|
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||||
$this->addCompilerPassIfExists($container, FormPass::class);
|
$this->addCompilerPassIfExists($container, FormPass::class);
|
||||||
|
|
||||||
if ($container->getParameter('kernel.debug')) {
|
if ($container->getParameter('kernel.debug')) {
|
||||||
|
@ -100,6 +100,11 @@
|
|||||||
</call>
|
</call>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="cache.command.pool_pruner" class="Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand">
|
||||||
|
<argument type="iterator" />
|
||||||
|
<tag name="console.command" command="cache:pool:prune" />
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer" public="true">
|
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer" public="true">
|
||||||
<tag name="kernel.cache_clearer" />
|
<tag name="kernel.cache_clearer" />
|
||||||
</service>
|
</service>
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
<?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\Command;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
|
||||||
|
class CachePruneCommandTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCommandWithPools()
|
||||||
|
{
|
||||||
|
$tester = $this->getCommandTester($this->getKernel(), $this->getRewindableGenerator());
|
||||||
|
$tester->execute(array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCommandWithNoPools()
|
||||||
|
{
|
||||||
|
$tester = $this->getCommandTester($this->getKernel(), $this->getEmptyRewindableGenerator());
|
||||||
|
$tester->execute(array());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RewindableGenerator
|
||||||
|
*/
|
||||||
|
private function getRewindableGenerator()
|
||||||
|
{
|
||||||
|
return new RewindableGenerator(function () {
|
||||||
|
yield 'foo_pool' => $this->getPruneableInterfaceMock();
|
||||||
|
yield 'bar_pool' => $this->getPruneableInterfaceMock();
|
||||||
|
}, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RewindableGenerator
|
||||||
|
*/
|
||||||
|
private function getEmptyRewindableGenerator()
|
||||||
|
{
|
||||||
|
return new RewindableGenerator(function () {
|
||||||
|
return new \ArrayIterator(array());
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \PHPUnit_Framework_MockObject_MockObject|KernelInterface
|
||||||
|
*/
|
||||||
|
private function getKernel()
|
||||||
|
{
|
||||||
|
$container = $this
|
||||||
|
->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$kernel = $this
|
||||||
|
->getMockBuilder(KernelInterface::class)
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$kernel
|
||||||
|
->expects($this->any())
|
||||||
|
->method('getContainer')
|
||||||
|
->willReturn($container);
|
||||||
|
|
||||||
|
$kernel
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getBundles')
|
||||||
|
->willReturn(array());
|
||||||
|
|
||||||
|
return $kernel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \PHPUnit_Framework_MockObject_MockObject|PruneableInterface
|
||||||
|
*/
|
||||||
|
private function getPruneableInterfaceMock()
|
||||||
|
{
|
||||||
|
$pruneable = $this
|
||||||
|
->getMockBuilder(PruneableInterface::class)
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$pruneable
|
||||||
|
->expects($this->atLeastOnce())
|
||||||
|
->method('prune');
|
||||||
|
|
||||||
|
return $pruneable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param KernelInterface $kernel
|
||||||
|
* @param RewindableGenerator $generator
|
||||||
|
*
|
||||||
|
* @return CommandTester
|
||||||
|
*/
|
||||||
|
private function getCommandTester(KernelInterface $kernel, RewindableGenerator $generator)
|
||||||
|
{
|
||||||
|
$application = new Application($kernel);
|
||||||
|
$application->add(new CachePoolPruneCommand($generator));
|
||||||
|
|
||||||
|
return new CommandTester($application->find('cache:pool:prune'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
<?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\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPrunerPass;
|
||||||
|
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||||
|
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
class CachePoolPrunerPassTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCompilerPassReplacesCommandArgument()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('cache.command.pool_pruner')->addArgument(array());
|
||||||
|
$container->register('pool.foo', FilesystemAdapter::class)->addTag('cache.pool');
|
||||||
|
$container->register('pool.bar', PhpFilesAdapter::class)->addTag('cache.pool');
|
||||||
|
|
||||||
|
$pass = new CachePoolPrunerPass();
|
||||||
|
$pass->process($container);
|
||||||
|
|
||||||
|
$expected = array(
|
||||||
|
'pool.foo' => new Reference('pool.foo'),
|
||||||
|
'pool.bar' => new Reference('pool.bar'),
|
||||||
|
);
|
||||||
|
$argument = $container->getDefinition('cache.command.pool_pruner')->getArgument(0);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(IteratorArgument::class, $argument);
|
||||||
|
$this->assertEquals($expected, $argument->getValues());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCompilePassIsIgnoredIfCommandDoesNotExist()
|
||||||
|
{
|
||||||
|
$container = $this
|
||||||
|
->getMockBuilder(ContainerBuilder::class)
|
||||||
|
->setMethods(array('hasDefinition', 'getDefinition', 'findTaggedServiceIds'))
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$container
|
||||||
|
->expects($this->atLeastOnce())
|
||||||
|
->method('hasDefinition')
|
||||||
|
->with('cache.command.pool_pruner')
|
||||||
|
->will($this->returnValue(false));
|
||||||
|
|
||||||
|
$container
|
||||||
|
->expects($this->never())
|
||||||
|
->method('getDefinition');
|
||||||
|
|
||||||
|
$container
|
||||||
|
->expects($this->never())
|
||||||
|
->method('findTaggedServiceIds');
|
||||||
|
|
||||||
|
$pass = new CachePoolPrunerPass();
|
||||||
|
$pass->process($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage Class "Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler\NotFound" used for service "pool.not-found" cannot be found.
|
||||||
|
*/
|
||||||
|
public function testCompilerPassThrowsOnInvalidDefinitionClass()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register('cache.command.pool_pruner')->addArgument(array());
|
||||||
|
$container->register('pool.not-found', NotFound::class)->addTag('cache.pool');
|
||||||
|
|
||||||
|
$pass = new CachePoolPrunerPass();
|
||||||
|
$pass->process($container);
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5.9",
|
"php": ">=5.5.9",
|
||||||
"ext-xml": "*",
|
"ext-xml": "*",
|
||||||
"symfony/cache": "~3.3|~4.0",
|
"symfony/cache": "~3.4|~4.0",
|
||||||
"symfony/class-loader": "~3.2",
|
"symfony/class-loader": "~3.2",
|
||||||
"symfony/dependency-injection": "~3.3|~4.0",
|
"symfony/dependency-injection": "~3.3|~4.0",
|
||||||
"symfony/config": "~3.3|~4.0",
|
"symfony/config": "~3.3|~4.0",
|
||||||
|
@ -11,9 +11,10 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Cache\Adapter;
|
namespace Symfony\Component\Cache\Adapter;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
||||||
|
|
||||||
class FilesystemAdapter extends AbstractAdapter
|
class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
|
||||||
{
|
{
|
||||||
use FilesystemTrait;
|
use FilesystemTrait;
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@
|
|||||||
namespace Symfony\Component\Cache\Adapter;
|
namespace Symfony\Component\Cache\Adapter;
|
||||||
|
|
||||||
use Symfony\Component\Cache\Exception\CacheException;
|
use Symfony\Component\Cache\Exception\CacheException;
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\PhpFilesTrait;
|
use Symfony\Component\Cache\Traits\PhpFilesTrait;
|
||||||
|
|
||||||
class PhpFilesAdapter extends AbstractAdapter
|
class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
|
||||||
{
|
{
|
||||||
use PhpFilesTrait;
|
use PhpFilesTrait;
|
||||||
|
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
3.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
* added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning
|
||||||
|
* added FilesystemTrait::prune() and PhpFilesTrait::prune() implementations
|
||||||
|
* now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, and PhpFilesCache implement PruneableInterface and support
|
||||||
|
manual stale cache pruning
|
||||||
|
|
||||||
3.3.0
|
3.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
23
src/Symfony/Component/Cache/PruneableInterface.php
Normal file
23
src/Symfony/Component/Cache/PruneableInterface.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?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\Cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for adapters and simple cache implementations that allow pruning expired items.
|
||||||
|
*/
|
||||||
|
interface PruneableInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function prune();
|
||||||
|
}
|
@ -11,9 +11,10 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Cache\Simple;
|
namespace Symfony\Component\Cache\Simple;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
||||||
|
|
||||||
class FilesystemCache extends AbstractCache
|
class FilesystemCache extends AbstractCache implements PruneableInterface
|
||||||
{
|
{
|
||||||
use FilesystemTrait;
|
use FilesystemTrait;
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@
|
|||||||
namespace Symfony\Component\Cache\Simple;
|
namespace Symfony\Component\Cache\Simple;
|
||||||
|
|
||||||
use Symfony\Component\Cache\Exception\CacheException;
|
use Symfony\Component\Cache\Exception\CacheException;
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\PhpFilesTrait;
|
use Symfony\Component\Cache\Traits\PhpFilesTrait;
|
||||||
|
|
||||||
class PhpFilesCache extends AbstractCache
|
class PhpFilesCache extends AbstractCache implements PruneableInterface
|
||||||
{
|
{
|
||||||
use PhpFilesTrait;
|
use PhpFilesTrait;
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\Cache\Tests\Adapter;
|
namespace Symfony\Component\Cache\Tests\Adapter;
|
||||||
|
|
||||||
use Cache\IntegrationTests\CachePoolTest;
|
use Cache\IntegrationTests\CachePoolTest;
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
|
|
||||||
abstract class AdapterTestCase extends CachePoolTest
|
abstract class AdapterTestCase extends CachePoolTest
|
||||||
{
|
{
|
||||||
@ -22,6 +23,10 @@ abstract class AdapterTestCase extends CachePoolTest
|
|||||||
if (!array_key_exists('testDeferredSaveWithoutCommit', $this->skippedTests) && defined('HHVM_VERSION')) {
|
if (!array_key_exists('testDeferredSaveWithoutCommit', $this->skippedTests) && defined('HHVM_VERSION')) {
|
||||||
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Destructors are called late on HHVM.';
|
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Destructors are called late on HHVM.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!array_key_exists('testPrune', $this->skippedTests) && !$this->createCachePool() instanceof PruneableInterface) {
|
||||||
|
$this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDefaultLifeTime()
|
public function testDefaultLifeTime()
|
||||||
@ -67,6 +72,59 @@ abstract class AdapterTestCase extends CachePoolTest
|
|||||||
}
|
}
|
||||||
$this->assertFalse($item->isHit());
|
$this->assertFalse($item->isHit());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPrune()
|
||||||
|
{
|
||||||
|
if (isset($this->skippedTests[__FUNCTION__])) {
|
||||||
|
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!method_exists($this, 'isPruned')) {
|
||||||
|
$this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache = $this->createCachePool();
|
||||||
|
|
||||||
|
$doSet = function ($name, $value, \DateInterval $expiresAfter = null) use ($cache) {
|
||||||
|
$item = $cache->getItem($name);
|
||||||
|
$item->set($value);
|
||||||
|
|
||||||
|
if ($expiresAfter) {
|
||||||
|
$item->expiresAfter($expiresAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache->save($item);
|
||||||
|
};
|
||||||
|
|
||||||
|
$doSet('foo', 'foo-val');
|
||||||
|
$doSet('bar', 'bar-val', new \DateInterval('PT20S'));
|
||||||
|
$doSet('baz', 'baz-val', new \DateInterval('PT40S'));
|
||||||
|
$doSet('qux', 'qux-val', new \DateInterval('PT80S'));
|
||||||
|
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'bar'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'baz'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'qux'));
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertTrue($this->isPruned($cache, 'bar'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'baz'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'qux'));
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertTrue($this->isPruned($cache, 'baz'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'qux'));
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertTrue($this->isPruned($cache, 'qux'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotUnserializable implements \Serializable
|
class NotUnserializable implements \Serializable
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Cache\Tests\Adapter;
|
namespace Symfony\Component\Cache\Tests\Adapter;
|
||||||
|
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,4 +50,12 @@ class FilesystemAdapterTest extends AdapterTestCase
|
|||||||
}
|
}
|
||||||
rmdir($dir);
|
rmdir($dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function isPruned(CacheItemPoolInterface $cache, $name)
|
||||||
|
{
|
||||||
|
$getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
|
||||||
|
$getFileMethod->setAccessible(true);
|
||||||
|
|
||||||
|
return !file_exists($getFileMethod->invoke($cache, $name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Cache\Tests\Adapter;
|
namespace Symfony\Component\Cache\Tests\Adapter;
|
||||||
|
|
||||||
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
|
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,4 +36,12 @@ class PhpFilesAdapterTest extends AdapterTestCase
|
|||||||
{
|
{
|
||||||
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
|
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function isPruned(CacheItemPoolInterface $cache, $name)
|
||||||
|
{
|
||||||
|
$getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
|
||||||
|
$getFileMethod->setAccessible(true);
|
||||||
|
|
||||||
|
return !file_exists($getFileMethod->invoke($cache, $name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,19 @@
|
|||||||
namespace Symfony\Component\Cache\Tests\Simple;
|
namespace Symfony\Component\Cache\Tests\Simple;
|
||||||
|
|
||||||
use Cache\IntegrationTests\SimpleCacheTest;
|
use Cache\IntegrationTests\SimpleCacheTest;
|
||||||
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
|
|
||||||
abstract class CacheTestCase extends SimpleCacheTest
|
abstract class CacheTestCase extends SimpleCacheTest
|
||||||
{
|
{
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
if (!array_key_exists('testPrune', $this->skippedTests) && !$this->createSimpleCache() instanceof PruneableInterface) {
|
||||||
|
$this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function validKeys()
|
public static function validKeys()
|
||||||
{
|
{
|
||||||
if (defined('HHVM_VERSION')) {
|
if (defined('HHVM_VERSION')) {
|
||||||
@ -59,6 +69,48 @@ abstract class CacheTestCase extends SimpleCacheTest
|
|||||||
}
|
}
|
||||||
$this->assertNull($value);
|
$this->assertNull($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testPrune()
|
||||||
|
{
|
||||||
|
if (isset($this->skippedTests[__FUNCTION__])) {
|
||||||
|
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!method_exists($this, 'isPruned')) {
|
||||||
|
$this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache = $this->createSimpleCache();
|
||||||
|
|
||||||
|
$cache->set('foo', 'foo-val');
|
||||||
|
$cache->set('bar', 'bar-val', new \DateInterval('PT20S'));
|
||||||
|
$cache->set('baz', 'baz-val', new \DateInterval('PT40S'));
|
||||||
|
$cache->set('qux', 'qux-val', new \DateInterval('PT80S'));
|
||||||
|
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'bar'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'baz'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'qux'));
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertTrue($this->isPruned($cache, 'bar'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'baz'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'qux'));
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertTrue($this->isPruned($cache, 'baz'));
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'qux'));
|
||||||
|
|
||||||
|
sleep(30);
|
||||||
|
$cache->prune();
|
||||||
|
$this->assertFalse($this->isPruned($cache, 'foo'));
|
||||||
|
$this->assertTrue($this->isPruned($cache, 'qux'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotUnserializable implements \Serializable
|
class NotUnserializable implements \Serializable
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Cache\Tests\Simple;
|
namespace Symfony\Component\Cache\Tests\Simple;
|
||||||
|
|
||||||
|
use Psr\SimpleCache\CacheInterface;
|
||||||
use Symfony\Component\Cache\Simple\FilesystemCache;
|
use Symfony\Component\Cache\Simple\FilesystemCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,4 +23,12 @@ class FilesystemCacheTest extends CacheTestCase
|
|||||||
{
|
{
|
||||||
return new FilesystemCache('', $defaultLifetime);
|
return new FilesystemCache('', $defaultLifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function isPruned(CacheInterface $cache, $name)
|
||||||
|
{
|
||||||
|
$getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
|
||||||
|
$getFileMethod->setAccessible(true);
|
||||||
|
|
||||||
|
return !file_exists($getFileMethod->invoke($cache, $name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Cache\Tests\Simple;
|
namespace Symfony\Component\Cache\Tests\Simple;
|
||||||
|
|
||||||
|
use Psr\SimpleCache\CacheInterface;
|
||||||
use Symfony\Component\Cache\Simple\PhpFilesCache;
|
use Symfony\Component\Cache\Simple\PhpFilesCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,4 +31,12 @@ class PhpFilesCacheTest extends CacheTestCase
|
|||||||
|
|
||||||
return new PhpFilesCache('sf-cache');
|
return new PhpFilesCache('sf-cache');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function isPruned(CacheInterface $cache, $name)
|
||||||
|
{
|
||||||
|
$getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
|
||||||
|
$getFileMethod->setAccessible(true);
|
||||||
|
|
||||||
|
return !file_exists($getFileMethod->invoke($cache, $name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ use Symfony\Component\Cache\Exception\CacheException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
* @author Rob Frawley 2nd <rmf@src.run>
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -22,6 +23,30 @@ trait FilesystemTrait
|
|||||||
{
|
{
|
||||||
use FilesystemCommonTrait;
|
use FilesystemCommonTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function prune()
|
||||||
|
{
|
||||||
|
$time = time();
|
||||||
|
$pruned = true;
|
||||||
|
|
||||||
|
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||||
|
if (!$h = @fopen($file, 'rb')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($time >= (int) $expiresAt = fgets($h)) {
|
||||||
|
fclose($h);
|
||||||
|
$pruned = isset($expiresAt[0]) && @unlink($file) && !file_exists($file) && $pruned;
|
||||||
|
} else {
|
||||||
|
fclose($h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pruned;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
@ -17,6 +17,7 @@ use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
|||||||
/**
|
/**
|
||||||
* @author Piotr Stankowski <git@trakos.pl>
|
* @author Piotr Stankowski <git@trakos.pl>
|
||||||
* @author Nicolas Grekas <p@tchwork.com>
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
* @author Rob Frawley 2nd <rmf@src.run>
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -31,6 +32,35 @@ trait PhpFilesTrait
|
|||||||
return function_exists('opcache_invalidate') && ini_get('opcache.enable');
|
return function_exists('opcache_invalidate') && ini_get('opcache.enable');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function prune()
|
||||||
|
{
|
||||||
|
$time = time();
|
||||||
|
$pruned = true;
|
||||||
|
$allowCompile = 'cli' !== PHP_SAPI || ini_get('opcache.enable_cli');
|
||||||
|
|
||||||
|
set_error_handler($this->includeHandler);
|
||||||
|
try {
|
||||||
|
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||||
|
list($expiresAt) = include $file;
|
||||||
|
|
||||||
|
if ($time >= $expiresAt) {
|
||||||
|
$pruned = @unlink($file) && !file_exists($file) && $pruned;
|
||||||
|
|
||||||
|
if ($allowCompile) {
|
||||||
|
@opcache_invalidate($file, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pruned;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user