mirror of
https://github.com/symfony/cache.git
synced 2026-03-23 23:22:07 +01:00
Merge branch '8.0' into 8.1
* 8.0: [Config][FrameworkBundle] Allow using ParamConfigurator with every configurable value [HttpFoundation] Improve doc blocks in `ParameterBag` [HttpClient] Fix ever growing $maxHostConnections Fix typo [DependencyInjection] Fix referencing build-time array parameters cs fix [FrameworkBundle] Fix cache:pool:prune exit code on failure [Form] Add type hint for FormTypeInterface in FormBuilderInterface [Form] Always normalize CRLF and CR to LF in `TextareaType` [Cache] Fix stampede protection when forcing item recomputation [DoctrineBridge] Fix checking for the session table when using PDO fix(messenger): allow signing message without routing definition [Console] Fix EofShortcut instruction when using a modern terminal on Windows [Console] Do not call non-static method via class-name [Console] Fix choice autocomplete issue when string has spaces Update SameOriginCsrfTokenManager.php [Serializer] Fix inconsistent field naming from accessors when using groups [Finder] Fix converting unanchored glob patterns to regex
This commit is contained in:
@@ -18,6 +18,7 @@ use Symfony\Component\Cache\Adapter\NullAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ParameterNormalizer;
|
||||
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
|
||||
use Symfony\Component\Cache\Messenger\EarlyExpirationDispatcher;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@@ -49,6 +50,7 @@ class CachePoolPass implements CompilerPassInterface
|
||||
'default_lifetime',
|
||||
'early_expiration_message_bus',
|
||||
'reset',
|
||||
'pruneable',
|
||||
];
|
||||
foreach ($container->findTaggedServiceIds('cache.pool') as $id => $tags) {
|
||||
$adapter = $pool = $container->getDefinition($id);
|
||||
@@ -88,6 +90,8 @@ class CachePoolPass implements CompilerPassInterface
|
||||
$tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
|
||||
}
|
||||
|
||||
$pruneable = $tags[0]['pruneable'] ?? $container->getReflectionClass($class, false)?->implementsInterface(PruneableInterface::class) ?? false;
|
||||
|
||||
if (ChainAdapter::class === $class) {
|
||||
$adapters = [];
|
||||
foreach ($providers['index_0'] ?? $providers[0] as $provider => $adapter) {
|
||||
@@ -154,6 +158,8 @@ class CachePoolPass implements CompilerPassInterface
|
||||
),
|
||||
]);
|
||||
$pool->addTag('container.reversible');
|
||||
} elseif ('pruneable' === $attr) {
|
||||
// no-op
|
||||
} elseif ('namespace' !== $attr || !\in_array($class, [ArrayAdapter::class, NullAdapter::class, TagAwareAdapter::class], true)) {
|
||||
$argument = $tags[0][$attr];
|
||||
|
||||
@@ -167,13 +173,17 @@ class CachePoolPass implements CompilerPassInterface
|
||||
unset($tags[0][$attr]);
|
||||
}
|
||||
if (!empty($tags[0])) {
|
||||
throw new InvalidArgumentException(\sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus" and "reset", found "%s".', $id, implode('", "', array_keys($tags[0]))));
|
||||
throw new InvalidArgumentException(\sprintf('Invalid "cache.pool" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime", "early_expiration_message_bus", "reset" and "pruneable", found "%s".', $id, implode('", "', array_keys($tags[0]))));
|
||||
}
|
||||
|
||||
if (null !== $clearer) {
|
||||
$clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
|
||||
}
|
||||
|
||||
$poolTags = $pool->getTags();
|
||||
$poolTags['cache.pool'][0]['pruneable'] ??= $pruneable;
|
||||
$pool->setTags($poolTags);
|
||||
|
||||
$allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
@@ -32,14 +31,8 @@ class CachePoolPrunerPass implements CompilerPassInterface
|
||||
$services = [];
|
||||
|
||||
foreach ($container->findTaggedServiceIds('cache.pool') 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);
|
||||
if ($tags[0]['pruneable'] ?? $container->getReflectionClass($container->getDefinition($id)->getClass(), false)?->implementsInterface(PruneableInterface::class) ?? false) {
|
||||
$services[$tags[0]['name'] ?? $id] = new Reference($id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ final class LockRegistry
|
||||
return $previousFiles;
|
||||
}
|
||||
|
||||
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, ?\Closure $setMetadata = null, ?LoggerInterface $logger = null): mixed
|
||||
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, ?\Closure $setMetadata = null, ?LoggerInterface $logger = null, ?float $beta = null): mixed
|
||||
{
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) {
|
||||
// disable locking on Windows by default
|
||||
@@ -123,6 +123,11 @@ final class LockRegistry
|
||||
// if we failed the race, retry locking in blocking mode to wait for the winner
|
||||
$logger?->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
|
||||
flock($lock, \LOCK_SH);
|
||||
|
||||
if (\INF === $beta) {
|
||||
$logger?->info('Force-recomputing item "{key}"', ['key' => $item->getKey()]);
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
flock($lock, \LOCK_UN);
|
||||
unset(self::$lockedFiles[$key]);
|
||||
|
||||
@@ -33,13 +33,13 @@ class EarlyExpirationDispatcher
|
||||
$this->callbackWrapper = null === $callbackWrapper ? null : $callbackWrapper(...);
|
||||
}
|
||||
|
||||
public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger = null): mixed
|
||||
public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger = null, ?float $beta = null): mixed
|
||||
{
|
||||
if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) {
|
||||
// The item is stale or the callback cannot be reversed: we must compute the value now
|
||||
$logger?->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]);
|
||||
|
||||
return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save);
|
||||
return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger, $beta) : $callback($item, $save);
|
||||
}
|
||||
|
||||
$envelope = $this->bus->dispatch($message);
|
||||
|
||||
@@ -12,12 +12,14 @@
|
||||
namespace Symfony\Component\Cache\Tests\DependencyInjection;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bundle\FrameworkBundle\Command\CachePoolPruneCommand;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
|
||||
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
|
||||
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class CachePoolPrunerPassTest extends TestCase
|
||||
@@ -57,17 +59,66 @@ class CachePoolPrunerPassTest extends TestCase
|
||||
$this->assertCount($aliasesBefore, $container->getAliases());
|
||||
}
|
||||
|
||||
public function testCompilerPassThrowsOnInvalidDefinitionClass()
|
||||
public function testNonPruneablePoolsAreNotAdded()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('console.command.cache_pool_prune')->addArgument([]);
|
||||
$container->register('pool.not-found', NotFound::class)->addTag('cache.pool');
|
||||
$container->setParameter('kernel.debug', false);
|
||||
$container->setParameter('kernel.project_dir', __DIR__);
|
||||
$container->setParameter('kernel.container_class', 'TestContainer');
|
||||
|
||||
$pass = new CachePoolPrunerPass();
|
||||
$container->register('console.command.cache_pool_prune', CachePoolPruneCommand::class)
|
||||
->setArguments([new IteratorArgument([])]);
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Class "Symfony\Component\Cache\Tests\DependencyInjection\NotFound" used for service "pool.not-found" cannot be found.');
|
||||
$container->register('cache.null', NonPruneableAdapter::class)
|
||||
->setArguments([null])
|
||||
->addTag('cache.pool');
|
||||
|
||||
$pass->process($container);
|
||||
$container->register('cache.fs', PruneableAdapter::class)
|
||||
->setArguments([null])
|
||||
->addTag('cache.pool');
|
||||
|
||||
(new CachePoolPass())->process($container);
|
||||
(new CachePoolPrunerPass())->process($container);
|
||||
|
||||
$arg = $container->getDefinition('console.command.cache_pool_prune')->getArgument(0);
|
||||
$values = $arg->getValues();
|
||||
|
||||
$this->assertArrayNotHasKey('cache.null', $values);
|
||||
$this->assertArrayHasKey('cache.fs', $values);
|
||||
}
|
||||
|
||||
public function testPruneableAttributeOverridesInterfaceCheck()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->setParameter('kernel.debug', false);
|
||||
$container->setParameter('kernel.project_dir', __DIR__);
|
||||
$container->setParameter('kernel.container_class', 'TestContainer');
|
||||
|
||||
$container->register('console.command.cache_pool_prune', 'stdClass')
|
||||
->setArguments([new IteratorArgument([])]);
|
||||
|
||||
$container->register('manual.pool', NonPruneableAdapter::class)
|
||||
->setArguments([null])
|
||||
->addTag('cache.pool', ['pruneable' => true]);
|
||||
|
||||
(new CachePoolPass())->process($container);
|
||||
(new CachePoolPrunerPass())->process($container);
|
||||
|
||||
$arg = $container->getDefinition('console.command.cache_pool_prune')->getArgument(0);
|
||||
$values = $arg->getValues();
|
||||
|
||||
$this->assertArrayHasKey('manual.pool', $values);
|
||||
}
|
||||
}
|
||||
|
||||
class PruneableAdapter implements PruneableInterface
|
||||
{
|
||||
public function prune(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class NonPruneableAdapter
|
||||
{
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ trait ContractsTrait
|
||||
}
|
||||
|
||||
$previousWrapper = $this->callbackWrapper;
|
||||
$this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) => $callback($item, $save);
|
||||
$this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger, ?float $beta = null) => $callback($item, $save);
|
||||
|
||||
return $previousWrapper;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ trait ContractsTrait
|
||||
|
||||
$this->callbackWrapper ??= LockRegistry::compute(...);
|
||||
|
||||
return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) {
|
||||
return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key, $beta) {
|
||||
// don't wrap nor save recursive calls
|
||||
if (isset($this->computing[$key])) {
|
||||
$value = $callback($item, $save);
|
||||
@@ -101,7 +101,7 @@ trait ContractsTrait
|
||||
try {
|
||||
$value = ($this->callbackWrapper)($callback, $item, $save, $pool, static function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) {
|
||||
$setMetadata($item, $startTime, $metadata);
|
||||
}, $this->logger ?? null);
|
||||
}, $this->logger ?? null, $beta);
|
||||
$setMetadata($item, $startTime, $metadata);
|
||||
|
||||
return $value;
|
||||
|
||||
Reference in New Issue
Block a user