├── Traits ├── ValueWrapper.php ├── CachedValueInterface.php ├── RedisProxy.php ├── RedisClusterProxy.php ├── Relay │ ├── IsTrackedTrait.php │ ├── SwapdbTrait.php │ ├── GetWithMetaTrait.php │ ├── RelayCluster121Trait.php │ ├── BgsaveTrait.php │ ├── PfcountTrait.php │ ├── CopyTrait.php │ ├── GetrangeTrait.php │ ├── HsetTrait.php │ ├── GeosearchTrait.php │ ├── Relay20Trait.php │ ├── RelayCluster20Trait.php │ ├── MoveTrait.php │ ├── Relay121Trait.php │ ├── RelayCluster11Trait.php │ ├── NullableReturnTrait.php │ ├── Relay12Trait.php │ ├── Relay11Trait.php │ ├── RelayCluster12Trait.php │ └── FtTrait.php ├── ProxyTrait.php ├── RedisProxyTrait.php ├── RedisCluster62ProxyTrait.php ├── RedisClusterNodeProxy.php ├── RedisCluster61ProxyTrait.php ├── Redis62ProxyTrait.php ├── Redis61ProxyTrait.php ├── FilesystemTrait.php ├── ContractsTrait.php ├── RelayProxyTrait.php ├── Redis63ProxyTrait.php ├── RedisCluster63ProxyTrait.php └── FilesystemCommonTrait.php ├── ResettableInterface.php ├── PruneableInterface.php ├── Exception ├── CacheException.php ├── LogicException.php ├── BadMethodCallException.php └── InvalidArgumentException.php ├── Adapter ├── RedisAdapter.php ├── TagAwareAdapterInterface.php ├── AdapterInterface.php ├── FilesystemAdapter.php ├── ParameterNormalizer.php ├── TraceableTagAwareAdapter.php ├── Psr16Adapter.php ├── NullAdapter.php ├── ApcuAdapter.php ├── CouchbaseCollectionAdapter.php ├── ProxyAdapter.php ├── CouchbaseBucketAdapter.php ├── AbstractAdapter.php └── FilesystemTagAwareAdapter.php ├── README.md ├── LICENSE ├── Marshaller ├── MarshallerInterface.php ├── DeflateMarshaller.php ├── SodiumMarshaller.php ├── TagAwareMarshaller.php └── DefaultMarshaller.php ├── DependencyInjection ├── CachePoolClearerPass.php ├── CachePoolPrunerPass.php └── CacheCollectorPass.php ├── composer.json ├── Messenger ├── EarlyExpirationDispatcher.php ├── EarlyExpirationHandler.php └── EarlyExpirationMessage.php ├── CHANGELOG.md ├── DataCollector └── CacheDataCollector.php ├── CacheItem.php ├── LockRegistry.php └── Psr16Cache.php /Traits/ValueWrapper.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symfony/cache/HEAD/Traits/ValueWrapper.php -------------------------------------------------------------------------------- /Traits/CachedValueInterface.php: -------------------------------------------------------------------------------- 1 | 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\Traits; 13 | 14 | /** 15 | * @internal 16 | */ 17 | interface CachedValueInterface 18 | { 19 | public function getValue(): mixed; 20 | } 21 | -------------------------------------------------------------------------------- /ResettableInterface.php: -------------------------------------------------------------------------------- 1 | 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; 13 | 14 | use Symfony\Contracts\Service\ResetInterface; 15 | 16 | /** 17 | * Resets a pool's local state. 18 | */ 19 | interface ResettableInterface extends ResetInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /PruneableInterface.php: -------------------------------------------------------------------------------- 1 | 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; 13 | 14 | /** 15 | * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items. 16 | */ 17 | interface PruneableInterface 18 | { 19 | public function prune(): bool; 20 | } 21 | -------------------------------------------------------------------------------- /Traits/RedisProxy.php: -------------------------------------------------------------------------------- 1 | 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\Traits; 13 | 14 | class_alias(6.0 <= (float) phpversion('redis') ? Redis6Proxy::class : Redis5Proxy::class, RedisProxy::class); 15 | 16 | if (false) { 17 | /** 18 | * @internal 19 | */ 20 | class RedisProxy extends \Redis 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Traits/RedisClusterProxy.php: -------------------------------------------------------------------------------- 1 | 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\Traits; 13 | 14 | class_alias(6.0 <= (float) phpversion('redis') ? RedisCluster6Proxy::class : RedisCluster5Proxy::class, RedisClusterProxy::class); 15 | 16 | if (false) { 17 | /** 18 | * @internal 19 | */ 20 | class RedisClusterProxy extends \RedisCluster 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Exception/CacheException.php: -------------------------------------------------------------------------------- 1 | 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\Exception; 13 | 14 | use Psr\Cache\CacheException as Psr6CacheInterface; 15 | use Psr\SimpleCache\CacheException as SimpleCacheInterface; 16 | 17 | if (interface_exists(SimpleCacheInterface::class)) { 18 | class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface 19 | { 20 | } 21 | } else { 22 | class CacheException extends \Exception implements Psr6CacheInterface 23 | { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Exception/LogicException.php: -------------------------------------------------------------------------------- 1 | 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\Exception; 13 | 14 | use Psr\Cache\CacheException as Psr6CacheInterface; 15 | use Psr\SimpleCache\CacheException as SimpleCacheInterface; 16 | 17 | if (interface_exists(SimpleCacheInterface::class)) { 18 | class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface 19 | { 20 | } 21 | } else { 22 | class LogicException extends \LogicException implements Psr6CacheInterface 23 | { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Traits/Relay/IsTrackedTrait.php: -------------------------------------------------------------------------------- 1 | 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\Traits\Relay; 13 | 14 | if (version_compare(phpversion('relay'), '0.11.1', '>=')) { 15 | /** 16 | * @internal 17 | */ 18 | trait IsTrackedTrait 19 | { 20 | public function isTracked($key): bool 21 | { 22 | return $this->initializeLazyObject()->isTracked(...\func_get_args()); 23 | } 24 | } 25 | } else { 26 | /** 27 | * @internal 28 | */ 29 | trait IsTrackedTrait 30 | { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Traits/Relay/SwapdbTrait.php: -------------------------------------------------------------------------------- 1 | 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\Traits\Relay; 13 | 14 | if (version_compare(phpversion('relay'), '0.9.0', '>=')) { 15 | /** 16 | * @internal 17 | */ 18 | trait SwapdbTrait 19 | { 20 | public function swapdb($index1, $index2): \Relay\Relay|bool 21 | { 22 | return $this->initializeLazyObject()->swapdb(...\func_get_args()); 23 | } 24 | } 25 | } else { 26 | /** 27 | * @internal 28 | */ 29 | trait SwapdbTrait 30 | { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Exception/BadMethodCallException.php: -------------------------------------------------------------------------------- 1 | 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\Exception; 13 | 14 | use Psr\Cache\CacheException as Psr6CacheInterface; 15 | use Psr\SimpleCache\CacheException as SimpleCacheInterface; 16 | 17 | if (interface_exists(SimpleCacheInterface::class)) { 18 | class BadMethodCallException extends \BadMethodCallException implements Psr6CacheInterface, SimpleCacheInterface 19 | { 20 | } 21 | } else { 22 | class BadMethodCallException extends \BadMethodCallException implements Psr6CacheInterface 23 | { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Traits/Relay/GetWithMetaTrait.php: -------------------------------------------------------------------------------- 1 | 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\Traits\Relay; 13 | 14 | if (version_compare(phpversion('relay'), '0.10.1', '>=')) { 15 | /** 16 | * @internal 17 | */ 18 | trait GetWithMetaTrait 19 | { 20 | public function getWithMeta($key): \Relay\Relay|array|false 21 | { 22 | return $this->initializeLazyObject()->getWithMeta(...\func_get_args()); 23 | } 24 | } 25 | } else { 26 | /** 27 | * @internal 28 | */ 29 | trait GetWithMetaTrait 30 | { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 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\Exception; 13 | 14 | use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; 15 | use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; 16 | 17 | if (interface_exists(SimpleCacheInterface::class)) { 18 | class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface 19 | { 20 | } 21 | } else { 22 | class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface 23 | { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Traits/Relay/RelayCluster121Trait.php: -------------------------------------------------------------------------------- 1 | 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\Traits\Relay; 13 | 14 | if (version_compare(phpversion('relay'), '0.12.1', '>=')) { 15 | /** 16 | * @internal 17 | */ 18 | trait RelayCluster121Trait 19 | { 20 | public function hgetWithMeta($hash, $member): \Relay\Cluster|array|false 21 | { 22 | return $this->initializeLazyObject()->getWithMeta(...\func_get_args()); 23 | } 24 | } 25 | } else { 26 | /** 27 | * @internal 28 | */ 29 | trait RelayCluster121Trait 30 | { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Adapter/RedisAdapter.php: -------------------------------------------------------------------------------- 1 | 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\Adapter; 13 | 14 | use Symfony\Component\Cache\Marshaller\MarshallerInterface; 15 | use Symfony\Component\Cache\Traits\RedisTrait; 16 | 17 | class RedisAdapter extends AbstractAdapter 18 | { 19 | use RedisTrait; 20 | 21 | public function __construct(\Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|\Relay\Relay|\Relay\Cluster $redis, string $namespace = '', int $defaultLifetime = 0, ?MarshallerInterface $marshaller = null) 22 | { 23 | $this->init($redis, $namespace, $defaultLifetime, $marshaller); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Adapter/TagAwareAdapterInterface.php: -------------------------------------------------------------------------------- 1 | 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\Adapter; 13 | 14 | use Psr\Cache\InvalidArgumentException; 15 | 16 | /** 17 | * Interface for invalidating cached items using tags. 18 | * 19 | * @author Nicolas Grekas
20 | */ 21 | interface TagAwareAdapterInterface extends AdapterInterface 22 | { 23 | /** 24 | * Invalidates cached items using tags. 25 | * 26 | * @param string[] $tags An array of tags to invalidate 27 | * 28 | * @throws InvalidArgumentException When $tags is not valid 29 | */ 30 | public function invalidateTags(array $tags): bool; 31 | } 32 | -------------------------------------------------------------------------------- /Traits/ProxyTrait.php: -------------------------------------------------------------------------------- 1 | 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\Traits; 13 | 14 | use Symfony\Component\Cache\PruneableInterface; 15 | use Symfony\Contracts\Service\ResetInterface; 16 | 17 | /** 18 | * @author Nicolas Grekas
19 | *
20 | * @internal
21 | */
22 | trait ProxyTrait
23 | {
24 | private object $pool;
25 |
26 | public function prune(): bool
27 | {
28 | return $this->pool instanceof PruneableInterface && $this->pool->prune();
29 | }
30 |
31 | public function reset(): void
32 | {
33 | if ($this->pool instanceof ResetInterface) {
34 | $this->pool->reset();
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Traits/Relay/BgsaveTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.11', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait BgsaveTrait
19 | {
20 | public function bgsave($arg = null): \Relay\Relay|bool
21 | {
22 | return $this->initializeLazyObject()->bgsave(...\func_get_args());
23 | }
24 | }
25 | } else {
26 | /**
27 | * @internal
28 | */
29 | trait BgsaveTrait
30 | {
31 | public function bgsave($schedule = false): \Relay\Relay|bool
32 | {
33 | return $this->initializeLazyObject()->bgsave(...\func_get_args());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Traits/Relay/PfcountTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.9.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait PfcountTrait
19 | {
20 | public function pfcount($key_or_keys): \Relay\Relay|false|int
21 | {
22 | return $this->initializeLazyObject()->pfcount(...\func_get_args());
23 | }
24 | }
25 | } else {
26 | /**
27 | * @internal
28 | */
29 | trait PfcountTrait
30 | {
31 | public function pfcount($key): \Relay\Relay|false|int
32 | {
33 | return $this->initializeLazyObject()->pfcount(...\func_get_args());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Symfony PSR-6 implementation for caching
2 | ========================================
3 |
4 | The Cache component provides extended
5 | [PSR-6](https://www.php-fig.org/psr/psr-6/) implementations for adding cache to
6 | your applications. It is designed to have a low overhead so that caching is
7 | fastest. It ships with adapters for the most widespread caching backends.
8 | It also provides a [PSR-16](https://www.php-fig.org/psr/psr-16/) adapter,
9 | and implementations for [symfony/cache-contracts](https://github.com/symfony/cache-contracts)'
10 | `CacheInterface` and `TagAwareCacheInterface`.
11 |
12 | Resources
13 | ---------
14 |
15 | * [Documentation](https://symfony.com/doc/current/components/cache.html)
16 | * [Contributing](https://symfony.com/doc/current/contributing/index.html)
17 | * [Report issues](https://github.com/symfony/symfony/issues) and
18 | [send Pull Requests](https://github.com/symfony/symfony/pulls)
19 | in the [main Symfony repository](https://github.com/symfony/symfony)
20 |
--------------------------------------------------------------------------------
/Traits/Relay/CopyTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.8.1', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait CopyTrait
19 | {
20 | public function copy($src, $dst, $options = null): \Relay\Relay|bool
21 | {
22 | return $this->initializeLazyObject()->copy(...\func_get_args());
23 | }
24 | }
25 | } else {
26 | /**
27 | * @internal
28 | */
29 | trait CopyTrait
30 | {
31 | public function copy($src, $dst, $options = null): \Relay\Relay|false|int
32 | {
33 | return $this->initializeLazyObject()->copy(...\func_get_args());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Traits/Relay/GetrangeTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.9.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait GetrangeTrait
19 | {
20 | public function getrange($key, $start, $end): mixed
21 | {
22 | return $this->initializeLazyObject()->getrange(...\func_get_args());
23 | }
24 | }
25 | } else {
26 | /**
27 | * @internal
28 | */
29 | trait GetrangeTrait
30 | {
31 | public function getrange($key, $start, $end): \Relay\Relay|false|string
32 | {
33 | return $this->initializeLazyObject()->getrange(...\func_get_args());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Traits/Relay/HsetTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.9.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait HsetTrait
19 | {
20 | public function hset($key, ...$keys_and_vals): \Relay\Relay|false|int
21 | {
22 | return $this->initializeLazyObject()->hset(...\func_get_args());
23 | }
24 | }
25 | } else {
26 | /**
27 | * @internal
28 | */
29 | trait HsetTrait
30 | {
31 | public function hset($key, $mem, $val, ...$kvals): \Relay\Relay|false|int
32 | {
33 | return $this->initializeLazyObject()->hset(...\func_get_args());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Adapter/AdapterInterface.php:
--------------------------------------------------------------------------------
1 |
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\Adapter;
13 |
14 | use Psr\Cache\CacheItemPoolInterface;
15 | use Symfony\Component\Cache\CacheItem;
16 |
17 | // Help opcache.preload discover always-needed symbols
18 | class_exists(CacheItem::class);
19 |
20 | /**
21 | * Interface for adapters managing instances of Symfony's CacheItem.
22 | *
23 | * @author Kévin Dunglas
21 | */
22 | interface MarshallerInterface
23 | {
24 | /**
25 | * Serializes a list of values.
26 | *
27 | * When serialization fails for a specific value, no exception should be
28 | * thrown. Instead, its key should be listed in $failed.
29 | */
30 | public function marshall(array $values, ?array &$failed): array;
31 |
32 | /**
33 | * Unserializes a single value and throws an exception if anything goes wrong.
34 | *
35 | * @throws \Exception Whenever unserialization fails
36 | */
37 | public function unmarshall(string $value): mixed;
38 | }
39 |
--------------------------------------------------------------------------------
/Marshaller/DeflateMarshaller.php:
--------------------------------------------------------------------------------
1 |
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\Marshaller;
13 |
14 | use Symfony\Component\Cache\Exception\CacheException;
15 |
16 | /**
17 | * Compresses values using gzdeflate().
18 | *
19 | * @author Nicolas Grekas
20 | */
21 | class DeflateMarshaller implements MarshallerInterface
22 | {
23 | public function __construct(
24 | private MarshallerInterface $marshaller,
25 | ) {
26 | if (!\function_exists('gzdeflate')) {
27 | throw new CacheException('The "zlib" PHP extension is not loaded.');
28 | }
29 | }
30 |
31 | public function marshall(array $values, ?array &$failed): array
32 | {
33 | return array_map('gzdeflate', $this->marshaller->marshall($values, $failed));
34 | }
35 |
36 | public function unmarshall(string $value): mixed
37 | {
38 | if (false !== $inflatedValue = @gzinflate($value)) {
39 | $value = $inflatedValue;
40 | }
41 |
42 | return $this->marshaller->unmarshall($value);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/DependencyInjection/CachePoolClearerPass.php:
--------------------------------------------------------------------------------
1 |
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\Compiler\CompilerPassInterface;
15 | use Symfony\Component\DependencyInjection\ContainerBuilder;
16 | use Symfony\Component\DependencyInjection\Reference;
17 |
18 | /**
19 | * @author Nicolas Grekas
20 | */
21 | class CachePoolClearerPass implements CompilerPassInterface
22 | {
23 | public function process(ContainerBuilder $container): void
24 | {
25 | $container->getParameterBag()->remove('cache.prefix.seed');
26 |
27 | foreach ($container->findTaggedServiceIds('cache.pool.clearer') as $id => $attr) {
28 | $clearer = $container->getDefinition($id);
29 | $pools = [];
30 | foreach ($clearer->getArgument(0) as $name => $ref) {
31 | if ($container->hasDefinition($ref)) {
32 | $pools[$name] = new Reference($ref);
33 | }
34 | }
35 | $clearer->replaceArgument(0, $pools);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Traits/RedisProxyTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | /**
15 | * @internal
16 | */
17 | trait RedisProxyTrait
18 | {
19 | private \Closure $initializer;
20 | private ?parent $realInstance = null;
21 |
22 | public static function createLazyProxy(\Closure $initializer, ?self $instance = null): static
23 | {
24 | $instance ??= (new \ReflectionClass(static::class))->newInstanceWithoutConstructor();
25 | $instance->realInstance = null;
26 | $instance->initializer = $initializer;
27 |
28 | return $instance;
29 | }
30 |
31 | public function isLazyObjectInitialized(bool $partial = false): bool
32 | {
33 | return isset($this->realInstance);
34 | }
35 |
36 | public function initializeLazyObject(): object
37 | {
38 | return $this->realInstance ??= ($this->initializer)();
39 | }
40 |
41 | public function resetLazyObject(): bool
42 | {
43 | $this->realInstance = null;
44 |
45 | return true;
46 | }
47 |
48 | public function __destruct()
49 | {
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Traits/Relay/Relay20Trait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.20.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait Relay20Trait
19 | {
20 | public function _digest($value): string
21 | {
22 | return $this->initializeLazyObject()->_digest(...\func_get_args());
23 | }
24 |
25 | public function delex($key, $options = null): \Relay\Relay|false|int
26 | {
27 | return $this->initializeLazyObject()->delex(...\func_get_args());
28 | }
29 |
30 | public function digest($key): \Relay\Relay|false|null|string
31 | {
32 | return $this->initializeLazyObject()->digest(...\func_get_args());
33 | }
34 |
35 | public function msetex($kvals, $ttl = null): \Relay\Relay|false|int
36 | {
37 | return $this->initializeLazyObject()->msetx(...\func_get_args());
38 | }
39 | }
40 | } else {
41 | /**
42 | * @internal
43 | */
44 | trait Relay20Trait
45 | {
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Traits/Relay/RelayCluster20Trait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.20.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait RelayCluster20Trait
19 | {
20 | public function _digest($value): string
21 | {
22 | return $this->initializeLazyObject()->_digest(...\func_get_args());
23 | }
24 |
25 | public function delex($key, $options = null): \Relay\Cluster|false|int
26 | {
27 | return $this->initializeLazyObject()->delex(...\func_get_args());
28 | }
29 |
30 | public function digest($key): \Relay\Cluster|false|null|string
31 | {
32 | return $this->initializeLazyObject()->digest(...\func_get_args());
33 | }
34 |
35 | public function wait($key_or_address, $replicas, $timeout): \Relay\Cluster|false|int
36 | {
37 | return $this->initializeLazyObject()->digest(...\func_get_args());
38 | }
39 | }
40 | } else {
41 | /**
42 | * @internal
43 | */
44 | trait RelayCluster20Trait
45 | {
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Traits/RedisCluster62ProxyTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | if (version_compare(phpversion('redis'), '6.2.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait RedisCluster62ProxyTrait
19 | {
20 | public function expiremember($key, $field, $ttl, $unit = null): \Redis|false|int
21 | {
22 | return $this->initializeLazyObject()->expiremember(...\func_get_args());
23 | }
24 |
25 | public function expirememberat($key, $field, $timestamp): \Redis|false|int
26 | {
27 | return $this->initializeLazyObject()->expirememberat(...\func_get_args());
28 | }
29 |
30 | public function getdel($key): mixed
31 | {
32 | return $this->initializeLazyObject()->getdel(...\func_get_args());
33 | }
34 |
35 | public function getWithMeta($key): \RedisCluster|array|false
36 | {
37 | return $this->initializeLazyObject()->getWithMeta(...\func_get_args());
38 | }
39 | }
40 | } else {
41 | /**
42 | * @internal
43 | */
44 | trait RedisCluster62ProxyTrait
45 | {
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Traits/RedisClusterNodeProxy.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | /**
15 | * This file acts as a wrapper to the \RedisCluster implementation so it can accept the same type of calls as
16 | * individual \Redis objects.
17 | *
18 | * Calls are made to individual nodes via: RedisCluster->{method}($host, ...args)'
19 | * according to https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#directed-node-commands
20 | *
21 | * @author Jack Thomas
23 | */
24 | class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
25 | {
26 | use ProxyTrait;
27 |
28 | /**
29 | * @internal
30 | */
31 | protected const NS_SEPARATOR = '_';
32 |
33 | private object $miss;
34 |
35 | public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
36 | {
37 | parent::__construct($namespace, $defaultLifetime);
38 |
39 | $this->pool = $pool;
40 | $this->miss = new \stdClass();
41 | }
42 |
43 | protected function doFetch(array $ids): iterable
44 | {
45 | foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
46 | if ($this->miss !== $value) {
47 | yield $key => $value;
48 | }
49 | }
50 | }
51 |
52 | protected function doHave(string $id): bool
53 | {
54 | return $this->pool->has($id);
55 | }
56 |
57 | protected function doClear(string $namespace): bool
58 | {
59 | return $this->pool->clear();
60 | }
61 |
62 | protected function doDelete(array $ids): bool
63 | {
64 | return $this->pool->deleteMultiple($ids);
65 | }
66 |
67 | protected function doSave(array $values, int $lifetime): array|bool
68 | {
69 | return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "symfony/cache",
3 | "type": "library",
4 | "description": "Provides extended PSR-6, PSR-16 (and tags) implementations",
5 | "keywords": ["caching", "psr6"],
6 | "homepage": "https://symfony.com",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Nicolas Grekas",
11 | "email": "p@tchwork.com"
12 | },
13 | {
14 | "name": "Symfony Community",
15 | "homepage": "https://symfony.com/contributors"
16 | }
17 | ],
18 | "provide": {
19 | "psr/cache-implementation": "2.0|3.0",
20 | "psr/simple-cache-implementation": "1.0|2.0|3.0",
21 | "symfony/cache-implementation": "1.1|2.0|3.0"
22 | },
23 | "require": {
24 | "php": ">=8.2",
25 | "psr/cache": "^2.0|^3.0",
26 | "psr/log": "^1.1|^2|^3",
27 | "symfony/cache-contracts": "^3.6",
28 | "symfony/deprecation-contracts": "^2.5|^3.0",
29 | "symfony/service-contracts": "^2.5|^3",
30 | "symfony/var-exporter": "^6.4|^7.0"
31 | },
32 | "require-dev": {
33 | "cache/integration-tests": "dev-master",
34 | "doctrine/dbal": "^3.6|^4",
35 | "predis/predis": "^1.1|^2.0",
36 | "psr/simple-cache": "^1.0|^2.0|^3.0",
37 | "symfony/clock": "^6.4|^7.0",
38 | "symfony/config": "^6.4|^7.0",
39 | "symfony/dependency-injection": "^6.4|^7.0",
40 | "symfony/filesystem": "^6.4|^7.0",
41 | "symfony/http-kernel": "^6.4|^7.0",
42 | "symfony/messenger": "^6.4|^7.0",
43 | "symfony/var-dumper": "^6.4|^7.0"
44 | },
45 | "conflict": {
46 | "doctrine/dbal": "<3.6",
47 | "symfony/dependency-injection": "<6.4",
48 | "symfony/http-kernel": "<6.4",
49 | "symfony/var-dumper": "<6.4"
50 | },
51 | "autoload": {
52 | "psr-4": { "Symfony\\Component\\Cache\\": "" },
53 | "classmap": [
54 | "Traits/ValueWrapper.php"
55 | ],
56 | "exclude-from-classmap": [
57 | "/Tests/"
58 | ]
59 | },
60 | "minimum-stability": "dev"
61 | }
62 |
--------------------------------------------------------------------------------
/Messenger/EarlyExpirationDispatcher.php:
--------------------------------------------------------------------------------
1 |
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\Messenger;
13 |
14 | use Psr\Log\LoggerInterface;
15 | use Symfony\Component\Cache\Adapter\AdapterInterface;
16 | use Symfony\Component\Cache\CacheItem;
17 | use Symfony\Component\DependencyInjection\ReverseContainer;
18 | use Symfony\Component\Messenger\MessageBusInterface;
19 | use Symfony\Component\Messenger\Stamp\HandledStamp;
20 |
21 | /**
22 | * Sends the computation of cached values to a message bus.
23 | */
24 | class EarlyExpirationDispatcher
25 | {
26 | private ?\Closure $callbackWrapper;
27 |
28 | public function __construct(
29 | private MessageBusInterface $bus,
30 | private ReverseContainer $reverseContainer,
31 | ?callable $callbackWrapper = null,
32 | ) {
33 | $this->callbackWrapper = null === $callbackWrapper ? null : $callbackWrapper(...);
34 | }
35 |
36 | public function __invoke(callable $callback, CacheItem $item, bool &$save, AdapterInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger = null): mixed
37 | {
38 | if (!$item->isHit() || null === $message = EarlyExpirationMessage::create($this->reverseContainer, $callback, $item, $pool)) {
39 | // The item is stale or the callback cannot be reversed: we must compute the value now
40 | $logger?->info('Computing item "{key}" online: '.($item->isHit() ? 'callback cannot be reversed' : 'item is stale'), ['key' => $item->getKey()]);
41 |
42 | return null !== $this->callbackWrapper ? ($this->callbackWrapper)($callback, $item, $save, $pool, $setMetadata, $logger) : $callback($item, $save);
43 | }
44 |
45 | $envelope = $this->bus->dispatch($message);
46 |
47 | if ($logger) {
48 | if ($envelope->last(HandledStamp::class)) {
49 | $logger->info('Item "{key}" was computed online', ['key' => $item->getKey()]);
50 | } else {
51 | $logger->info('Item "{key}" sent for recomputation', ['key' => $item->getKey()]);
52 | }
53 | }
54 |
55 | // The item's value is not stale, no need to write it to the backend
56 | $save = false;
57 |
58 | return $message->getItem()->get() ?? $item->get();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Marshaller/SodiumMarshaller.php:
--------------------------------------------------------------------------------
1 |
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\Marshaller;
13 |
14 | use Symfony\Component\Cache\Exception\CacheException;
15 | use Symfony\Component\Cache\Exception\InvalidArgumentException;
16 |
17 | /**
18 | * Encrypt/decrypt values using Libsodium.
19 | *
20 | * @author Ahmed TAILOULOUTE
18 | */
19 | class TagAwareMarshaller implements MarshallerInterface
20 | {
21 | private MarshallerInterface $marshaller;
22 |
23 | public function __construct(?MarshallerInterface $marshaller = null)
24 | {
25 | $this->marshaller = $marshaller ?? new DefaultMarshaller();
26 | }
27 |
28 | public function marshall(array $values, ?array &$failed): array
29 | {
30 | $failed = $notSerialized = $serialized = [];
31 |
32 | foreach ($values as $id => $value) {
33 | if (\is_array($value) && \is_array($value['tags'] ?? null) && \array_key_exists('value', $value) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) {
34 | // if the value is an array with keys "tags", "value" and "meta", use a compact serialization format
35 | // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall()
36 |
37 | $v = $this->marshaller->marshall($value, $f);
38 |
39 | if ($f) {
40 | $f = [];
41 | $failed[] = $id;
42 | } else {
43 | if ([] === $value['tags']) {
44 | $v['tags'] = '';
45 | }
46 |
47 | $serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($v['tags'])).$v['tags'].$v['value'];
48 | $serialized[$id][9] = "\x5F";
49 | }
50 | } else {
51 | // other arbitrary values are serialized using the decorated marshaller below
52 | $notSerialized[$id] = $value;
53 | }
54 | }
55 |
56 | if ($notSerialized) {
57 | $serialized += $this->marshaller->marshall($notSerialized, $f);
58 | $failed = array_merge($failed, $f);
59 | }
60 |
61 | return $serialized;
62 | }
63 |
64 | public function unmarshall(string $value): mixed
65 | {
66 | // detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
67 | if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) {
68 | return $this->marshaller->unmarshall($value);
69 | }
70 |
71 | // data consists of value, tags and metadata which we need to unpack
72 | $meta = substr($value, 1, 12);
73 | $meta[8] = "\0";
74 | $tagLen = unpack('Nlen', $meta, 8)['len'];
75 | $meta = substr($meta, 0, 8);
76 |
77 | return [
78 | 'value' => $this->marshaller->unmarshall(substr($value, 13 + $tagLen)),
79 | 'tags' => $tagLen ? $this->marshaller->unmarshall(substr($value, 13, $tagLen)) : [],
80 | 'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta,
81 | ];
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Traits/FilesystemTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | use Symfony\Component\Cache\Exception\CacheException;
15 | use Symfony\Component\Cache\Marshaller\MarshallerInterface;
16 |
17 | /**
18 | * @author Nicolas Grekas
19 | * @author Rob Frawley 2nd
20 | */
21 | class ApcuAdapter extends AbstractAdapter
22 | {
23 | /**
24 | * @throws CacheException if APCu is not enabled
25 | */
26 | public function __construct(
27 | string $namespace = '',
28 | int $defaultLifetime = 0,
29 | ?string $version = null,
30 | private ?MarshallerInterface $marshaller = null,
31 | ) {
32 | if (!static::isSupported()) {
33 | throw new CacheException('APCu is not enabled.');
34 | }
35 | if ('cli' === \PHP_SAPI) {
36 | ini_set('apc.use_request_time', 0);
37 | }
38 | parent::__construct($namespace, $defaultLifetime);
39 |
40 | if (null !== $version) {
41 | CacheItem::validateKey($version);
42 |
43 | if (!apcu_exists($version.'@'.$namespace)) {
44 | $this->doClear($namespace);
45 | apcu_add($version.'@'.$namespace, null);
46 | }
47 | }
48 | }
49 |
50 | public static function isSupported(): bool
51 | {
52 | return \function_exists('apcu_fetch') && filter_var(\ini_get('apc.enabled'), \FILTER_VALIDATE_BOOL);
53 | }
54 |
55 | protected function doFetch(array $ids): iterable
56 | {
57 | $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
58 | try {
59 | $values = [];
60 | foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) {
61 | if (null !== $v || $ok) {
62 | $values[$k] = null !== $this->marshaller ? $this->marshaller->unmarshall($v) : $v;
63 | }
64 | }
65 |
66 | return $values;
67 | } catch (\Error $e) {
68 | throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
69 | } finally {
70 | ini_set('unserialize_callback_func', $unserializeCallbackHandler);
71 | }
72 | }
73 |
74 | protected function doHave(string $id): bool
75 | {
76 | return apcu_exists($id);
77 | }
78 |
79 | protected function doClear(string $namespace): bool
80 | {
81 | return isset($namespace[0]) && class_exists(\APCUIterator::class, false) && ('cli' !== \PHP_SAPI || filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL))
82 | ? apcu_delete(new \APCUIterator(\sprintf('/^%s/', preg_quote($namespace, '/')), \APC_ITER_KEY))
83 | : apcu_clear_cache();
84 | }
85 |
86 | protected function doDelete(array $ids): bool
87 | {
88 | foreach ($ids as $id) {
89 | apcu_delete($id);
90 | }
91 |
92 | return true;
93 | }
94 |
95 | protected function doSave(array $values, int $lifetime): array|bool
96 | {
97 | if (null !== $this->marshaller && (!$values = $this->marshaller->marshall($values, $failed))) {
98 | return $failed;
99 | }
100 |
101 | if (false === $failures = apcu_store($values, null, $lifetime)) {
102 | $failures = $values;
103 | }
104 |
105 | return array_keys($failures);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Marshaller/DefaultMarshaller.php:
--------------------------------------------------------------------------------
1 |
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\Marshaller;
13 |
14 | use Symfony\Component\Cache\Exception\CacheException;
15 |
16 | /**
17 | * Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
18 | *
19 | * @author Nicolas Grekas
20 | */
21 | class DefaultMarshaller implements MarshallerInterface
22 | {
23 | private bool $useIgbinarySerialize = false;
24 | private bool $throwOnSerializationFailure = false;
25 |
26 | public function __construct(?bool $useIgbinarySerialize = null, bool $throwOnSerializationFailure = false)
27 | {
28 | if ($useIgbinarySerialize && (!\extension_loaded('igbinary') || version_compare('3.1.6', phpversion('igbinary'), '>'))) {
29 | throw new CacheException(\extension_loaded('igbinary') ? 'Please upgrade the "igbinary" PHP extension to v3.1.6 or higher.' : 'The "igbinary" PHP extension is not loaded.');
30 | }
31 | $this->useIgbinarySerialize = true === $useIgbinarySerialize;
32 | $this->throwOnSerializationFailure = $throwOnSerializationFailure;
33 | }
34 |
35 | public function marshall(array $values, ?array &$failed): array
36 | {
37 | $serialized = $failed = [];
38 |
39 | foreach ($values as $id => $value) {
40 | try {
41 | if ($this->useIgbinarySerialize) {
42 | $serialized[$id] = igbinary_serialize($value);
43 | } else {
44 | $serialized[$id] = serialize($value);
45 | }
46 | } catch (\Exception $e) {
47 | if ($this->throwOnSerializationFailure) {
48 | throw new \ValueError($e->getMessage(), 0, $e);
49 | }
50 | $failed[] = $id;
51 | }
52 | }
53 |
54 | return $serialized;
55 | }
56 |
57 | public function unmarshall(string $value): mixed
58 | {
59 | if ('b:0;' === $value) {
60 | return false;
61 | }
62 | if ('N;' === $value) {
63 | return null;
64 | }
65 | static $igbinaryNull;
66 | if ($value === $igbinaryNull ??= \extension_loaded('igbinary') ? igbinary_serialize(null) : false) {
67 | return null;
68 | }
69 | $unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
70 | try {
71 | if (':' === ($value[1] ?? ':')) {
72 | if (false !== $value = unserialize($value)) {
73 | return $value;
74 | }
75 | } elseif (false === $igbinaryNull) {
76 | throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
77 | } elseif (null !== $value = igbinary_unserialize($value)) {
78 | return $value;
79 | }
80 |
81 | throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
82 | } catch (\Error $e) {
83 | throw new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR, $e->getFile(), $e->getLine());
84 | } finally {
85 | ini_set('unserialize_callback_func', $unserializeCallbackHandler);
86 | }
87 | }
88 |
89 | /**
90 | * @internal
91 | */
92 | public static function handleUnserializeCallback(string $class): never
93 | {
94 | throw new \DomainException('Class not found: '.$class);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Traits/Relay/Relay12Trait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.12.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait Relay12Trait
19 | {
20 | public function delifeq($key, $value): \Relay\Relay|false|int
21 | {
22 | return $this->initializeLazyObject()->delifeq(...\func_get_args());
23 | }
24 |
25 | public function vadd($key, $values, $element, $options = null): \Relay\Relay|false|int
26 | {
27 | return $this->initializeLazyObject()->vadd(...\func_get_args());
28 | }
29 |
30 | public function vcard($key): \Relay\Relay|false|int
31 | {
32 | return $this->initializeLazyObject()->vcard(...\func_get_args());
33 | }
34 |
35 | public function vdim($key): \Relay\Relay|false|int
36 | {
37 | return $this->initializeLazyObject()->vdim(...\func_get_args());
38 | }
39 |
40 | public function vemb($key, $element, $raw = false): \Relay\Relay|array|false
41 | {
42 | return $this->initializeLazyObject()->vemb(...\func_get_args());
43 | }
44 |
45 | public function vgetattr($key, $element, $raw = false): \Relay\Relay|array|false|string
46 | {
47 | return $this->initializeLazyObject()->vgetattr(...\func_get_args());
48 | }
49 |
50 | public function vinfo($key): \Relay\Relay|array|false
51 | {
52 | return $this->initializeLazyObject()->vinfo(...\func_get_args());
53 | }
54 |
55 | public function vismember($key, $element): \Relay\Relay|bool
56 | {
57 | return $this->initializeLazyObject()->vismember(...\func_get_args());
58 | }
59 |
60 | public function vlinks($key, $element, $withscores): \Relay\Relay|array|false
61 | {
62 | return $this->initializeLazyObject()->vlinks(...\func_get_args());
63 | }
64 |
65 | public function vrandmember($key, $count = 0): \Relay\Relay|array|false|string
66 | {
67 | return $this->initializeLazyObject()->vrandmember(...\func_get_args());
68 | }
69 |
70 | public function vrange($key, $min, $max, $count = -1): \Relay\Relay|array|false
71 | {
72 | return $this->initializeLazyObject()->vrange(...\func_get_args());
73 | }
74 |
75 | public function vrem($key, $element): \Relay\Relay|false|int
76 | {
77 | return $this->initializeLazyObject()->vrem(...\func_get_args());
78 | }
79 |
80 | public function vsetattr($key, $element, $attributes): \Relay\Relay|false|int
81 | {
82 | return $this->initializeLazyObject()->vsetattr(...\func_get_args());
83 | }
84 |
85 | public function vsim($key, $member, $options = null): \Relay\Relay|array|false
86 | {
87 | return $this->initializeLazyObject()->vsim(...\func_get_args());
88 | }
89 |
90 | public function xdelex($key, $ids, $mode = null): \Relay\Relay|array|false
91 | {
92 | return $this->initializeLazyObject()->xdelex(...\func_get_args());
93 | }
94 |
95 | public function xackdel($key, $group, $ids, $mode = null): \Relay\Relay|array|false
96 | {
97 | return $this->initializeLazyObject()->xackdel(...\func_get_args());
98 | }
99 | }
100 | } else {
101 | /**
102 | * @internal
103 | */
104 | trait Relay12Trait
105 | {
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Traits/ContractsTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | use Psr\Log\LoggerInterface;
15 | use Symfony\Component\Cache\Adapter\AdapterInterface;
16 | use Symfony\Component\Cache\CacheItem;
17 | use Symfony\Component\Cache\Exception\InvalidArgumentException;
18 | use Symfony\Component\Cache\LockRegistry;
19 | use Symfony\Contracts\Cache\CacheInterface;
20 | use Symfony\Contracts\Cache\CacheTrait;
21 | use Symfony\Contracts\Cache\ItemInterface;
22 |
23 | /**
24 | * @author Nicolas Grekas
25 | *
26 | * @internal
27 | */
28 | trait ContractsTrait
29 | {
30 | use CacheTrait {
31 | doGet as private contractsGet;
32 | }
33 |
34 | private \Closure $callbackWrapper;
35 | private array $computing = [];
36 |
37 | /**
38 | * Wraps the callback passed to ->get() in a callable.
39 | *
40 | * @return callable the previous callback wrapper
41 | */
42 | public function setCallbackWrapper(?callable $callbackWrapper): callable
43 | {
44 | if (!isset($this->callbackWrapper)) {
45 | $this->callbackWrapper = LockRegistry::compute(...);
46 |
47 | if (\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
48 | $this->setCallbackWrapper(null);
49 | }
50 | }
51 |
52 | if (null !== $callbackWrapper && !$callbackWrapper instanceof \Closure) {
53 | $callbackWrapper = $callbackWrapper(...);
54 | }
55 |
56 | $previousWrapper = $this->callbackWrapper;
57 | $this->callbackWrapper = $callbackWrapper ?? static fn (callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata, ?LoggerInterface $logger) => $callback($item, $save);
58 |
59 | return $previousWrapper;
60 | }
61 |
62 | private function doGet(AdapterInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null): mixed
63 | {
64 | if (0 > $beta ??= 1.0) {
65 | throw new InvalidArgumentException(\sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta));
66 | }
67 |
68 | static $setMetadata;
69 |
70 | $setMetadata ??= \Closure::bind(
71 | static function (CacheItem $item, float $startTime, ?array &$metadata) {
72 | if ($item->expiry > $endTime = microtime(true)) {
73 | $item->newMetadata[CacheItem::METADATA_EXPIRY] = $metadata[CacheItem::METADATA_EXPIRY] = $item->expiry;
74 | $item->newMetadata[CacheItem::METADATA_CTIME] = $metadata[CacheItem::METADATA_CTIME] = (int) ceil(1000 * ($endTime - $startTime));
75 | } else {
76 | unset($metadata[CacheItem::METADATA_EXPIRY], $metadata[CacheItem::METADATA_CTIME], $metadata[CacheItem::METADATA_TAGS]);
77 | }
78 | },
79 | null,
80 | CacheItem::class
81 | );
82 |
83 | $this->callbackWrapper ??= LockRegistry::compute(...);
84 |
85 | return $this->contractsGet($pool, $key, function (CacheItem $item, bool &$save) use ($pool, $callback, $setMetadata, &$metadata, $key) {
86 | // don't wrap nor save recursive calls
87 | if (isset($this->computing[$key])) {
88 | $value = $callback($item, $save);
89 | $save = false;
90 |
91 | return $value;
92 | }
93 |
94 | $this->computing[$key] = $key;
95 | $startTime = microtime(true);
96 |
97 | if (!isset($this->callbackWrapper)) {
98 | $this->setCallbackWrapper($this->setCallbackWrapper(null));
99 | }
100 |
101 | try {
102 | $value = ($this->callbackWrapper)($callback, $item, $save, $pool, function (CacheItem $item) use ($setMetadata, $startTime, &$metadata) {
103 | $setMetadata($item, $startTime, $metadata);
104 | }, $this->logger ?? null);
105 | $setMetadata($item, $startTime, $metadata);
106 |
107 | return $value;
108 | } finally {
109 | unset($this->computing[$key]);
110 | }
111 | }, $beta, $metadata, $this->logger ?? null);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Traits/Relay/Relay11Trait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.11.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait Relay11Trait
19 | {
20 | public function cmsIncrBy($key, $field, $value, ...$fields_and_falues): \Relay\Relay|array|false
21 | {
22 | return $this->initializeLazyObject()->cmsIncrBy(...\func_get_args());
23 | }
24 |
25 | public function cmsInfo($key): \Relay\Relay|array|false
26 | {
27 | return $this->initializeLazyObject()->cmsInfo(...\func_get_args());
28 | }
29 |
30 | public function cmsInitByDim($key, $width, $depth): \Relay\Relay|bool
31 | {
32 | return $this->initializeLazyObject()->cmsInitByDim(...\func_get_args());
33 | }
34 |
35 | public function cmsInitByProb($key, $error, $probability): \Relay\Relay|bool
36 | {
37 | return $this->initializeLazyObject()->cmsInitByProb(...\func_get_args());
38 | }
39 |
40 | public function cmsMerge($dstkey, $keys, $weights = []): \Relay\Relay|bool
41 | {
42 | return $this->initializeLazyObject()->cmsMerge(...\func_get_args());
43 | }
44 |
45 | public function cmsQuery($key, ...$fields): \Relay\Relay|array|false
46 | {
47 | return $this->initializeLazyObject()->cmsQuery(...\func_get_args());
48 | }
49 |
50 | public function commandlog($subcmd, ...$args): \Relay\Relay|array|bool|int
51 | {
52 | return $this->initializeLazyObject()->commandlog(...\func_get_args());
53 | }
54 |
55 | public function hexpire($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
56 | {
57 | return $this->initializeLazyObject()->hexpire(...\func_get_args());
58 | }
59 |
60 | public function hexpireat($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
61 | {
62 | return $this->initializeLazyObject()->hexpireat(...\func_get_args());
63 | }
64 |
65 | public function hexpiretime($hash, $fields): \Relay\Relay|array|false
66 | {
67 | return $this->initializeLazyObject()->hexpiretime(...\func_get_args());
68 | }
69 |
70 | public function hgetdel($key, $fields): \Relay\Relay|array|false
71 | {
72 | return $this->initializeLazyObject()->hgetdel(...\func_get_args());
73 | }
74 |
75 | public function hgetex($hash, $fields, $expiry = null): \Relay\Relay|array|false
76 | {
77 | return $this->initializeLazyObject()->hgetex(...\func_get_args());
78 | }
79 |
80 | public function hpersist($hash, $fields): \Relay\Relay|array|false
81 | {
82 | return $this->initializeLazyObject()->hpersist(...\func_get_args());
83 | }
84 |
85 | public function hpexpire($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
86 | {
87 | return $this->initializeLazyObject()->hpexpire(...\func_get_args());
88 | }
89 |
90 | public function hpexpireat($hash, $ttl, $fields, $mode = null): \Relay\Relay|array|false
91 | {
92 | return $this->initializeLazyObject()->hpexpireat(...\func_get_args());
93 | }
94 |
95 | public function hpexpiretime($hash, $fields): \Relay\Relay|array|false
96 | {
97 | return $this->initializeLazyObject()->hpexpiretime(...\func_get_args());
98 | }
99 |
100 | public function hpttl($hash, $fields): \Relay\Relay|array|false
101 | {
102 | return $this->initializeLazyObject()->hpttl(...\func_get_args());
103 | }
104 |
105 | public function hsetex($key, $fields, $expiry = null): \Relay\Relay|false|int
106 | {
107 | return $this->initializeLazyObject()->hsetex(...\func_get_args());
108 | }
109 |
110 | public function httl($hash, $fields): \Relay\Relay|array|false
111 | {
112 | return $this->initializeLazyObject()->httl(...\func_get_args());
113 | }
114 |
115 | public function serverName(): false|string
116 | {
117 | return $this->initializeLazyObject()->serverName(...\func_get_args());
118 | }
119 |
120 | public function serverVersion(): false|string
121 | {
122 | return $this->initializeLazyObject()->serverVersion(...\func_get_args());
123 | }
124 | }
125 | } else {
126 | /**
127 | * @internal
128 | */
129 | trait Relay11Trait
130 | {
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Traits/Relay/RelayCluster12Trait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.12.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait RelayCluster12Trait
19 | {
20 | public function delifeq($key, $value): \Relay\Cluster|false|int
21 | {
22 | return $this->initializeLazyObject()->delifeq(...\func_get_args());
23 | }
24 |
25 | public function hgetdel($key, $fields): \Relay\Cluster|array|false
26 | {
27 | return $this->initializeLazyObject()->hgetdel(...\func_get_args());
28 | }
29 |
30 | public function hgetex($hash, $fields, $expiry = null): \Relay\Cluster|array|false
31 | {
32 | return $this->initializeLazyObject()->hgetex(...\func_get_args());
33 | }
34 |
35 | public function hsetex($key, $fields, $expiry = null): \Relay\Cluster|false|int
36 | {
37 | return $this->initializeLazyObject()->hsetex(...\func_get_args());
38 | }
39 |
40 | public function vadd($key, $values, $element, $options = null): \Relay\Cluster|false|int
41 | {
42 | return $this->initializeLazyObject()->vadd(...\func_get_args());
43 | }
44 |
45 | public function vcard($key): \Relay\Cluster|false|int
46 | {
47 | return $this->initializeLazyObject()->vcard(...\func_get_args());
48 | }
49 |
50 | public function vdim($key): \Relay\Cluster|false|int
51 | {
52 | return $this->initializeLazyObject()->vdim(...\func_get_args());
53 | }
54 |
55 | public function vemb($key, $element, $raw = false): \Relay\Cluster|array|false
56 | {
57 | return $this->initializeLazyObject()->vemb(...\func_get_args());
58 | }
59 |
60 | public function vgetattr($key, $element, $raw = false): \Relay\Cluster|array|false|string
61 | {
62 | return $this->initializeLazyObject()->vgetattr(...\func_get_args());
63 | }
64 |
65 | public function vinfo($key): \Relay\Cluster|array|false
66 | {
67 | return $this->initializeLazyObject()->vinfo(...\func_get_args());
68 | }
69 |
70 | public function vismember($key, $element): \Relay\Cluster|bool
71 | {
72 | return $this->initializeLazyObject()->vismember(...\func_get_args());
73 | }
74 |
75 | public function vlinks($key, $element, $withscores): \Relay\Cluster|array|false
76 | {
77 | return $this->initializeLazyObject()->vlinks(...\func_get_args());
78 | }
79 |
80 | public function vrandmember($key, $count = 0): \Relay\Cluster|array|false|string
81 | {
82 | return $this->initializeLazyObject()->vrandmember(...\func_get_args());
83 | }
84 |
85 | public function vrange($key, $end, $start, $count = -1): \Relay\Cluster|array|bool
86 | {
87 | return $this->initializeLazyObject()->vrange(...\func_get_args());
88 | }
89 |
90 | public function vrem($key, $element): \Relay\Cluster|false|int
91 | {
92 | return $this->initializeLazyObject()->vrem(...\func_get_args());
93 | }
94 |
95 | public function vsetattr($key, $element, $attributes): \Relay\Cluster|false|int
96 | {
97 | return $this->initializeLazyObject()->vsetattr(...\func_get_args());
98 | }
99 |
100 | public function vsim($key, $member, $options = null): \Relay\Cluster|array|false
101 | {
102 | return $this->initializeLazyObject()->vsim(...\func_get_args());
103 | }
104 |
105 | public function waitaof(array|string $key_or_address, int $numlocal, int $numremote, int $timeout): \Relay\Cluster|array|false
106 | {
107 | return $this->initializeLazyObject()->waitaof(...\func_get_args());
108 | }
109 |
110 | public function xackdel($key, $group, $ids, $mode = null): \Relay\Cluster|array|false
111 | {
112 | return $this->initializeLazyObject()->xackdel(...\func_get_args());
113 | }
114 |
115 | public function xdelex($key, $ids, $mode = null): \Relay\Cluster|array|false
116 | {
117 | return $this->initializeLazyObject()->xdelex(...\func_get_args());
118 | }
119 | }
120 | } else {
121 | /**
122 | * @internal
123 | */
124 | trait RelayCluster12Trait
125 | {
126 | public function waitaof(array|string $key_or_address, int $numlocal, int $numremote, int $timeout): \Relay\Relay|array|false
127 | {
128 | return $this->initializeLazyObject()->waitaof(...\func_get_args());
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Traits/Relay/FtTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits\Relay;
13 |
14 | if (version_compare(phpversion('relay'), '0.9.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait FtTrait
19 | {
20 | public function ftAggregate($index, $query, $options = null): \Relay\Relay|array|false
21 | {
22 | return $this->initializeLazyObject()->ftAggregate(...\func_get_args());
23 | }
24 |
25 | public function ftAliasAdd($index, $alias): \Relay\Relay|bool
26 | {
27 | return $this->initializeLazyObject()->ftAliasAdd(...\func_get_args());
28 | }
29 |
30 | public function ftAliasDel($alias): \Relay\Relay|bool
31 | {
32 | return $this->initializeLazyObject()->ftAliasDel(...\func_get_args());
33 | }
34 |
35 | public function ftAliasUpdate($index, $alias): \Relay\Relay|bool
36 | {
37 | return $this->initializeLazyObject()->ftAliasUpdate(...\func_get_args());
38 | }
39 |
40 | public function ftAlter($index, $schema, $skipinitialscan = false): \Relay\Relay|bool
41 | {
42 | return $this->initializeLazyObject()->ftAlter(...\func_get_args());
43 | }
44 |
45 | public function ftConfig($operation, $option, $value = null): \Relay\Relay|array|bool
46 | {
47 | return $this->initializeLazyObject()->ftConfig(...\func_get_args());
48 | }
49 |
50 | public function ftCreate($index, $schema, $options = null): \Relay\Relay|bool
51 | {
52 | return $this->initializeLazyObject()->ftCreate(...\func_get_args());
53 | }
54 |
55 | public function ftCursor($operation, $index, $cursor, $options = null): \Relay\Relay|array|bool
56 | {
57 | return $this->initializeLazyObject()->ftCursor(...\func_get_args());
58 | }
59 |
60 | public function ftDictAdd($dict, $term, ...$other_terms): \Relay\Relay|false|int
61 | {
62 | return $this->initializeLazyObject()->ftDictAdd(...\func_get_args());
63 | }
64 |
65 | public function ftDictDel($dict, $term, ...$other_terms): \Relay\Relay|false|int
66 | {
67 | return $this->initializeLazyObject()->ftDictDel(...\func_get_args());
68 | }
69 |
70 | public function ftDictDump($dict): \Relay\Relay|array|false
71 | {
72 | return $this->initializeLazyObject()->ftDictDump(...\func_get_args());
73 | }
74 |
75 | public function ftDropIndex($index, $dd = false): \Relay\Relay|bool
76 | {
77 | return $this->initializeLazyObject()->ftDropIndex(...\func_get_args());
78 | }
79 |
80 | public function ftExplain($index, $query, $dialect = 0): \Relay\Relay|false|string
81 | {
82 | return $this->initializeLazyObject()->ftExplain(...\func_get_args());
83 | }
84 |
85 | public function ftExplainCli($index, $query, $dialect = 0): \Relay\Relay|array|false
86 | {
87 | return $this->initializeLazyObject()->ftExplainCli(...\func_get_args());
88 | }
89 |
90 | public function ftInfo($index): \Relay\Relay|array|false
91 | {
92 | return $this->initializeLazyObject()->ftInfo(...\func_get_args());
93 | }
94 |
95 | public function ftProfile($index, $command, $query, $limited = false): \Relay\Relay|array|false
96 | {
97 | return $this->initializeLazyObject()->ftProfile(...\func_get_args());
98 | }
99 |
100 | public function ftSearch($index, $query, $options = null): \Relay\Relay|array|false
101 | {
102 | return $this->initializeLazyObject()->ftSearch(...\func_get_args());
103 | }
104 |
105 | public function ftSpellCheck($index, $query, $options = null): \Relay\Relay|array|false
106 | {
107 | return $this->initializeLazyObject()->ftSpellCheck(...\func_get_args());
108 | }
109 |
110 | public function ftSynDump($index): \Relay\Relay|array|false
111 | {
112 | return $this->initializeLazyObject()->ftSynDump(...\func_get_args());
113 | }
114 |
115 | public function ftSynUpdate($index, $synonym, $term_or_terms, $skipinitialscan = false): \Relay\Relay|bool
116 | {
117 | return $this->initializeLazyObject()->ftSynUpdate(...\func_get_args());
118 | }
119 |
120 | public function ftTagVals($index, $tag): \Relay\Relay|array|false
121 | {
122 | return $this->initializeLazyObject()->ftTagVals(...\func_get_args());
123 | }
124 | }
125 | } else {
126 | /**
127 | * @internal
128 | */
129 | trait FtTrait
130 | {
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Traits/RelayProxyTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | if (version_compare(phpversion('relay'), '0.8.1', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait RelayProxyTrait
19 | {
20 | public function jsonArrAppend($key, $value_or_array, $path = null): \Relay\Relay|array|false
21 | {
22 | return $this->initializeLazyObject()->jsonArrAppend(...\func_get_args());
23 | }
24 |
25 | public function jsonArrIndex($key, $path, $value, $start = 0, $stop = -1): \Relay\Relay|array|false
26 | {
27 | return $this->initializeLazyObject()->jsonArrIndex(...\func_get_args());
28 | }
29 |
30 | public function jsonArrInsert($key, $path, $index, $value, ...$other_values): \Relay\Relay|array|false
31 | {
32 | return $this->initializeLazyObject()->jsonArrInsert(...\func_get_args());
33 | }
34 |
35 | public function jsonArrLen($key, $path = null): \Relay\Relay|array|false
36 | {
37 | return $this->initializeLazyObject()->jsonArrLen(...\func_get_args());
38 | }
39 |
40 | public function jsonArrPop($key, $path = null, $index = -1): \Relay\Relay|array|false
41 | {
42 | return $this->initializeLazyObject()->jsonArrPop(...\func_get_args());
43 | }
44 |
45 | public function jsonArrTrim($key, $path, $start, $stop): \Relay\Relay|array|false
46 | {
47 | return $this->initializeLazyObject()->jsonArrTrim(...\func_get_args());
48 | }
49 |
50 | public function jsonClear($key, $path = null): \Relay\Relay|false|int
51 | {
52 | return $this->initializeLazyObject()->jsonClear(...\func_get_args());
53 | }
54 |
55 | public function jsonDebug($command, $key, $path = null): \Relay\Relay|false|int
56 | {
57 | return $this->initializeLazyObject()->jsonDebug(...\func_get_args());
58 | }
59 |
60 | public function jsonDel($key, $path = null): \Relay\Relay|false|int
61 | {
62 | return $this->initializeLazyObject()->jsonDel(...\func_get_args());
63 | }
64 |
65 | public function jsonForget($key, $path = null): \Relay\Relay|false|int
66 | {
67 | return $this->initializeLazyObject()->jsonForget(...\func_get_args());
68 | }
69 |
70 | public function jsonGet($key, $options = [], ...$paths): mixed
71 | {
72 | return $this->initializeLazyObject()->jsonGet(...\func_get_args());
73 | }
74 |
75 | public function jsonMerge($key, $path, $value): \Relay\Relay|bool
76 | {
77 | return $this->initializeLazyObject()->jsonMerge(...\func_get_args());
78 | }
79 |
80 | public function jsonMget($key_or_array, $path): \Relay\Relay|array|false
81 | {
82 | return $this->initializeLazyObject()->jsonMget(...\func_get_args());
83 | }
84 |
85 | public function jsonMset($key, $path, $value, ...$other_triples): \Relay\Relay|bool
86 | {
87 | return $this->initializeLazyObject()->jsonMset(...\func_get_args());
88 | }
89 |
90 | public function jsonNumIncrBy($key, $path, $value): \Relay\Relay|array|false
91 | {
92 | return $this->initializeLazyObject()->jsonNumIncrBy(...\func_get_args());
93 | }
94 |
95 | public function jsonNumMultBy($key, $path, $value): \Relay\Relay|array|false
96 | {
97 | return $this->initializeLazyObject()->jsonNumMultBy(...\func_get_args());
98 | }
99 |
100 | public function jsonObjKeys($key, $path = null): \Relay\Relay|array|false
101 | {
102 | return $this->initializeLazyObject()->jsonObjKeys(...\func_get_args());
103 | }
104 |
105 | public function jsonObjLen($key, $path = null): \Relay\Relay|array|false
106 | {
107 | return $this->initializeLazyObject()->jsonObjLen(...\func_get_args());
108 | }
109 |
110 | public function jsonResp($key, $path = null): \Relay\Relay|array|false|int|string
111 | {
112 | return $this->initializeLazyObject()->jsonResp(...\func_get_args());
113 | }
114 |
115 | public function jsonSet($key, $path, $value, $condition = null): \Relay\Relay|bool
116 | {
117 | return $this->initializeLazyObject()->jsonSet(...\func_get_args());
118 | }
119 |
120 | public function jsonStrAppend($key, $value, $path = null): \Relay\Relay|array|false
121 | {
122 | return $this->initializeLazyObject()->jsonStrAppend(...\func_get_args());
123 | }
124 |
125 | public function jsonStrLen($key, $path = null): \Relay\Relay|array|false
126 | {
127 | return $this->initializeLazyObject()->jsonStrLen(...\func_get_args());
128 | }
129 |
130 | public function jsonToggle($key, $path): \Relay\Relay|array|false
131 | {
132 | return $this->initializeLazyObject()->jsonToggle(...\func_get_args());
133 | }
134 |
135 | public function jsonType($key, $path = null): \Relay\Relay|array|false
136 | {
137 | return $this->initializeLazyObject()->jsonType(...\func_get_args());
138 | }
139 | }
140 | } else {
141 | /**
142 | * @internal
143 | */
144 | trait RelayProxyTrait
145 | {
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/Traits/Redis63ProxyTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | if (version_compare(phpversion('redis'), '6.3.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait Redis63ProxyTrait
19 | {
20 | public function delifeq($key, $value): \Redis|int|false
21 | {
22 | return $this->initializeLazyObject()->delifeq(...\func_get_args());
23 | }
24 |
25 | public function hexpire($key, $ttl, $fields, $mode = null): \Redis|array|false
26 | {
27 | return $this->initializeLazyObject()->hexpire(...\func_get_args());
28 | }
29 |
30 | public function hexpireat($key, $time, $fields, $mode = null): \Redis|array|false
31 | {
32 | return $this->initializeLazyObject()->hexpireat(...\func_get_args());
33 | }
34 |
35 | public function hexpiretime($key, $fields): \Redis|array|false
36 | {
37 | return $this->initializeLazyObject()->hexpiretime(...\func_get_args());
38 | }
39 |
40 | public function hgetdel($key, $fields): \Redis|array|false
41 | {
42 | return $this->initializeLazyObject()->hgetdel(...\func_get_args());
43 | }
44 |
45 | public function hgetex($key, $fields, $expiry = null): \Redis|array|false
46 | {
47 | return $this->initializeLazyObject()->hgetex(...\func_get_args());
48 | }
49 |
50 | public function hGetWithMeta($key, $member): mixed
51 | {
52 | return $this->initializeLazyObject()->hGetWithMeta(...\func_get_args());
53 | }
54 |
55 | public function hpersist($key, $fields): \Redis|array|false
56 | {
57 | return $this->initializeLazyObject()->hpersist(...\func_get_args());
58 | }
59 |
60 | public function hpexpire($key, $ttl, $fields, $mode = null): \Redis|array|false
61 | {
62 | return $this->initializeLazyObject()->hpexpire(...\func_get_args());
63 | }
64 |
65 | public function hpexpireat($key, $mstime, $fields, $mode = null): \Redis|array|false
66 | {
67 | return $this->initializeLazyObject()->hpexpireat(...\func_get_args());
68 | }
69 |
70 | public function hpexpiretime($key, $fields): \Redis|array|false
71 | {
72 | return $this->initializeLazyObject()->hpexpiretime(...\func_get_args());
73 | }
74 |
75 | public function hpttl($key, $fields): \Redis|array|false
76 | {
77 | return $this->initializeLazyObject()->hpttl(...\func_get_args());
78 | }
79 |
80 | public function hsetex($key, $fields, $expiry = null): \Redis|int|false
81 | {
82 | return $this->initializeLazyObject()->hsetex(...\func_get_args());
83 | }
84 |
85 | public function httl($key, $fields): \Redis|array|false
86 | {
87 | return $this->initializeLazyObject()->httl(...\func_get_args());
88 | }
89 |
90 | public function vadd($key, $values, $element, $options = null): \Redis|int|false
91 | {
92 | return $this->initializeLazyObject()->vadd(...\func_get_args());
93 | }
94 |
95 | public function vcard($key): \Redis|int|false
96 | {
97 | return $this->initializeLazyObject()->vcard(...\func_get_args());
98 | }
99 |
100 | public function vdim($key): \Redis|int|false
101 | {
102 | return $this->initializeLazyObject()->vdim(...\func_get_args());
103 | }
104 |
105 | public function vemb($key, $member, $raw = false): \Redis|array|false
106 | {
107 | return $this->initializeLazyObject()->vemb(...\func_get_args());
108 | }
109 |
110 | public function vgetattr($key, $member, $decode = true): \Redis|array|string|false
111 | {
112 | return $this->initializeLazyObject()->vgetattr(...\func_get_args());
113 | }
114 |
115 | public function vinfo($key): \Redis|array|false
116 | {
117 | return $this->initializeLazyObject()->vinfo(...\func_get_args());
118 | }
119 |
120 | public function vismember($key, $member): \Redis|bool
121 | {
122 | return $this->initializeLazyObject()->vismember(...\func_get_args());
123 | }
124 |
125 | public function vlinks($key, $member, $withscores = false): \Redis|array|false
126 | {
127 | return $this->initializeLazyObject()->vlinks(...\func_get_args());
128 | }
129 |
130 | public function vrandmember($key, $count = 0): \Redis|array|string|false
131 | {
132 | return $this->initializeLazyObject()->vrandmember(...\func_get_args());
133 | }
134 |
135 | public function vrange($key, $min, $max, $count = -1): \Redis|array|false
136 | {
137 | return $this->initializeLazyObject()->vrange(...\func_get_args());
138 | }
139 |
140 | public function vrem($key, $member): \Redis|int|false
141 | {
142 | return $this->initializeLazyObject()->vrem(...\func_get_args());
143 | }
144 |
145 | public function vsetattr($key, $member, $attributes): \Redis|int|false
146 | {
147 | return $this->initializeLazyObject()->vsetattr(...\func_get_args());
148 | }
149 |
150 | public function vsim($key, $member, $options = null): \Redis|array|false
151 | {
152 | return $this->initializeLazyObject()->vsim(...\func_get_args());
153 | }
154 | }
155 | } else {
156 | /**
157 | * @internal
158 | */
159 | trait Redis63ProxyTrait
160 | {
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/Traits/RedisCluster63ProxyTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | if (version_compare(phpversion('redis'), '6.3.0', '>=')) {
15 | /**
16 | * @internal
17 | */
18 | trait RedisCluster63ProxyTrait
19 | {
20 | public function delifeq($key, $value): \RedisCluster|int|false
21 | {
22 | return $this->initializeLazyObject()->delifeq(...\func_get_args());
23 | }
24 |
25 | public function hexpire($key, $ttl, $fields, $mode = null): \RedisCluster|array|false
26 | {
27 | return $this->initializeLazyObject()->hexpire(...\func_get_args());
28 | }
29 |
30 | public function hexpireat($key, $time, $fields, $mode = null): \RedisCluster|array|false
31 | {
32 | return $this->initializeLazyObject()->hexpireat(...\func_get_args());
33 | }
34 |
35 | public function hexpiretime($key, $fields): \RedisCluster|array|false
36 | {
37 | return $this->initializeLazyObject()->hexpiretime(...\func_get_args());
38 | }
39 |
40 | public function hgetdel($key, $fields): \RedisCluster|array|false
41 | {
42 | return $this->initializeLazyObject()->hgetdel(...\func_get_args());
43 | }
44 |
45 | public function hgetex($key, $fields, $expiry = null): \RedisCluster|array|false
46 | {
47 | return $this->initializeLazyObject()->hgetex(...\func_get_args());
48 | }
49 |
50 | public function hgetWithMeta($key, $member): mixed
51 | {
52 | return $this->initializeLazyObject()->hgetWithMeta(...\func_get_args());
53 | }
54 |
55 | public function hpersist($key, $fields): \RedisCluster|array|false
56 | {
57 | return $this->initializeLazyObject()->hpersist(...\func_get_args());
58 | }
59 |
60 | public function hpexpire($key, $ttl, $fields, $mode = null): \RedisCluster|array|false
61 | {
62 | return $this->initializeLazyObject()->hpexpire(...\func_get_args());
63 | }
64 |
65 | public function hpexpireat($key, $mstime, $fields, $mode = null): \RedisCluster|array|false
66 | {
67 | return $this->initializeLazyObject()->hpexpireat(...\func_get_args());
68 | }
69 |
70 | public function hpexpiretime($key, $fields): \RedisCluster|array|false
71 | {
72 | return $this->initializeLazyObject()->hpexpiretime(...\func_get_args());
73 | }
74 |
75 | public function hpttl($key, $fields): \RedisCluster|array|false
76 | {
77 | return $this->initializeLazyObject()->hpttl(...\func_get_args());
78 | }
79 |
80 | public function hsetex($key, $fields, $expiry = null): \RedisCluster|int|false
81 | {
82 | return $this->initializeLazyObject()->hsetex(...\func_get_args());
83 | }
84 |
85 | public function httl($key, $fields): \RedisCluster|array|false
86 | {
87 | return $this->initializeLazyObject()->httl(...\func_get_args());
88 | }
89 |
90 | public function vadd($key, $values, $element, $options = null): \RedisCluster|int|false
91 | {
92 | return $this->initializeLazyObject()->vadd(...\func_get_args());
93 | }
94 |
95 | public function vcard($key): \RedisCluster|int|false
96 | {
97 | return $this->initializeLazyObject()->vcard(...\func_get_args());
98 | }
99 |
100 | public function vdim($key): \RedisCluster|int|false
101 | {
102 | return $this->initializeLazyObject()->vdim(...\func_get_args());
103 | }
104 |
105 | public function vemb($key, $member, $raw = false): \RedisCluster|array|false
106 | {
107 | return $this->initializeLazyObject()->vemb(...\func_get_args());
108 | }
109 |
110 | public function vgetattr($key, $member, $decode = true): \RedisCluster|array|string|false
111 | {
112 | return $this->initializeLazyObject()->vgetattr(...\func_get_args());
113 | }
114 |
115 | public function vinfo($key): \RedisCluster|array|false
116 | {
117 | return $this->initializeLazyObject()->vinfo(...\func_get_args());
118 | }
119 |
120 | public function vismember($key, $member): \RedisCluster|bool
121 | {
122 | return $this->initializeLazyObject()->vismember(...\func_get_args());
123 | }
124 |
125 | public function vlinks($key, $member, $withscores = false): \RedisCluster|array|false
126 | {
127 | return $this->initializeLazyObject()->vlinks(...\func_get_args());
128 | }
129 |
130 | public function vrandmember($key, $count = 0): \RedisCluster|array|string|false
131 | {
132 | return $this->initializeLazyObject()->vrandmember(...\func_get_args());
133 | }
134 |
135 | public function vrange($key, $min, $max, $count = -1): \RedisCluster|array|false
136 | {
137 | return $this->initializeLazyObject()->vrange(...\func_get_args());
138 | }
139 |
140 | public function vrem($key, $member): \RedisCluster|int|false
141 | {
142 | return $this->initializeLazyObject()->vrem(...\func_get_args());
143 | }
144 |
145 | public function vsetattr($key, $member, $attributes): \RedisCluster|int|false
146 | {
147 | return $this->initializeLazyObject()->vsetattr(...\func_get_args());
148 | }
149 |
150 | public function vsim($key, $member, $options = null): \RedisCluster|array|false
151 | {
152 | return $this->initializeLazyObject()->vsim(...\func_get_args());
153 | }
154 | }
155 | } else {
156 | /**
157 | * @internal
158 | */
159 | trait RedisCluster63ProxyTrait
160 | {
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/Traits/FilesystemCommonTrait.php:
--------------------------------------------------------------------------------
1 |
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\Traits;
13 |
14 | use Symfony\Component\Cache\Exception\InvalidArgumentException;
15 |
16 | /**
17 | * @author Nicolas Grekas
18 | *
19 | * @internal
20 | */
21 | trait FilesystemCommonTrait
22 | {
23 | private string $directory;
24 | private string $tmpSuffix;
25 |
26 | private function init(string $namespace, ?string $directory): void
27 | {
28 | if (!isset($directory[0])) {
29 | $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache';
30 | } else {
31 | $directory = realpath($directory) ?: $directory;
32 | }
33 | if (isset($namespace[0])) {
34 | if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
35 | throw new InvalidArgumentException(\sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));
36 | }
37 | $directory .= \DIRECTORY_SEPARATOR.$namespace;
38 | } else {
39 | $directory .= \DIRECTORY_SEPARATOR.'@';
40 | }
41 | if (!is_dir($directory)) {
42 | @mkdir($directory, 0777, true);
43 | }
44 | $directory .= \DIRECTORY_SEPARATOR;
45 | // On Windows the whole path is limited to 258 chars
46 | if ('\\' === \DIRECTORY_SEPARATOR && \strlen($directory) > 234) {
47 | throw new InvalidArgumentException(\sprintf('Cache directory too long (%s).', $directory));
48 | }
49 |
50 | $this->directory = $directory;
51 | }
52 |
53 | protected function doClear(string $namespace): bool
54 | {
55 | $ok = true;
56 |
57 | foreach ($this->scanHashDir($this->directory) as $file) {
58 | if ('' !== $namespace && !str_starts_with($this->getFileKey($file), $namespace)) {
59 | continue;
60 | }
61 |
62 | $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok;
63 | }
64 |
65 | return $ok;
66 | }
67 |
68 | protected function doDelete(array $ids): bool
69 | {
70 | $ok = true;
71 |
72 | foreach ($ids as $id) {
73 | $file = $this->getFile($id);
74 | $ok = (!is_file($file) || $this->doUnlink($file) || !file_exists($file)) && $ok;
75 | }
76 |
77 | return $ok;
78 | }
79 |
80 | protected function doUnlink(string $file): bool
81 | {
82 | return @unlink($file);
83 | }
84 |
85 | private function write(string $file, string $data, ?int $expiresAt = null): bool
86 | {
87 | $unlink = false;
88 | set_error_handler(static fn ($type, $message, $file, $line) => throw new \ErrorException($message, 0, $type, $file, $line));
89 | try {
90 | $tmp = $this->directory.$this->tmpSuffix ??= str_replace('/', '-', base64_encode(random_bytes(6)));
91 | try {
92 | $h = fopen($tmp, 'x');
93 | } catch (\ErrorException $e) {
94 | if (!str_contains($e->getMessage(), 'File exists')) {
95 | throw $e;
96 | }
97 |
98 | $tmp = $this->directory.$this->tmpSuffix = str_replace('/', '-', base64_encode(random_bytes(6)));
99 | $h = fopen($tmp, 'x');
100 | }
101 | fwrite($h, $data);
102 | fclose($h);
103 | $unlink = true;
104 |
105 | if (null !== $expiresAt) {
106 | touch($tmp, $expiresAt ?: time() + 31556952); // 1 year in seconds
107 | }
108 |
109 | if ('\\' === \DIRECTORY_SEPARATOR) {
110 | $success = copy($tmp, $file);
111 | } else {
112 | $success = rename($tmp, $file);
113 | $unlink = !$success;
114 | }
115 |
116 | return $success;
117 | } finally {
118 | restore_error_handler();
119 |
120 | if ($unlink) {
121 | @unlink($tmp);
122 | }
123 | }
124 | }
125 |
126 | private function getFile(string $id, bool $mkdir = false, ?string $directory = null): string
127 | {
128 | // Use xxh128 to favor speed over security, which is not an issue here
129 | $hash = str_replace('/', '-', base64_encode(hash('xxh128', static::class.$id, true)));
130 | $dir = ($directory ?? $this->directory).strtoupper($hash[0].\DIRECTORY_SEPARATOR.$hash[1].\DIRECTORY_SEPARATOR);
131 |
132 | if ($mkdir && !is_dir($dir)) {
133 | @mkdir($dir, 0777, true);
134 | }
135 |
136 | return $dir.substr($hash, 2, 20);
137 | }
138 |
139 | private function getFileKey(string $file): string
140 | {
141 | return '';
142 | }
143 |
144 | private function scanHashDir(string $directory): \Generator
145 | {
146 | if (!is_dir($directory)) {
147 | return;
148 | }
149 |
150 | $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
151 |
152 | for ($i = 0; $i < 38; ++$i) {
153 | if (!is_dir($directory.$chars[$i])) {
154 | continue;
155 | }
156 |
157 | for ($j = 0; $j < 38; ++$j) {
158 | if (!is_dir($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) {
159 | continue;
160 | }
161 |
162 | foreach (@scandir($dir, \SCANDIR_SORT_NONE) ?: [] as $file) {
163 | if ('.' !== $file && '..' !== $file) {
164 | yield $dir.\DIRECTORY_SEPARATOR.$file;
165 | }
166 | }
167 | }
168 | }
169 | }
170 |
171 | public function __serialize(): array
172 | {
173 | throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
174 | }
175 |
176 | public function __unserialize(array $data): void
177 | {
178 | throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
179 | }
180 |
181 | public function __destruct()
182 | {
183 | if (method_exists(parent::class, '__destruct')) {
184 | parent::__destruct();
185 | }
186 | if (isset($this->tmpSuffix) && is_file($this->directory.$this->tmpSuffix)) {
187 | unlink($this->directory.$this->tmpSuffix);
188 | }
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 | =========
3 |
4 | 7.3
5 | ---
6 |
7 | * Add support for `\Relay\Cluster` in `RedisAdapter`
8 | * Add support for `valkey:` / `valkeys:` schemes
9 | * Add support for namespace-based invalidation
10 | * Rename options "redis_cluster" and "redis_sentinel" to "cluster" and "sentinel" respectively
11 |
12 | 7.2
13 | ---
14 |
15 | * `igbinary_serialize()` is no longer used instead of `serialize()` by default when the igbinary extension is installed,
16 | due to behavior compatibilities between the two
17 | * Add optional `Psr\Clock\ClockInterface` parameter to `ArrayAdapter`
18 |
19 | 7.1
20 | ---
21 |
22 | * Add option `sentinel_master` as an alias for `redis_sentinel`
23 | * Deprecate `CouchbaseBucketAdapter`, use `CouchbaseCollectionAdapter`
24 | * Add support for URL encoded characters in Couchbase DSN
25 | * Add support for using DSN with PDOAdapter
26 | * The algorithm for the default cache namespace changed from SHA256 to XXH128
27 |
28 | 7.0
29 | ---
30 |
31 | * Add parameter `$isSameDatabase` to `DoctrineDbalAdapter::configureSchema()`
32 | * Drop support for Postgres < 9.5 and SQL Server < 2008 in `DoctrineDbalAdapter`
33 |
34 | 6.4
35 | ---
36 |
37 | * `EarlyExpirationHandler` no longer implements `MessageHandlerInterface`, rely on `AsMessageHandler` instead
38 |
39 | 6.3
40 | ---
41 |
42 | * Add support for Relay PHP extension for Redis
43 | * Updates to allow Redis cluster connections using predis/predis:^2.0
44 | * Add optional parameter `$isSameDatabase` to `DoctrineDbalAdapter::configureSchema()`
45 |
46 | 6.1
47 | ---
48 |
49 | * Add support for ACL auth in RedisAdapter
50 | * Improve reliability and performance of `TagAwareAdapter` by making tag versions an integral part of item value
51 |
52 | 6.0
53 | ---
54 |
55 | * Remove `DoctrineProvider` and `DoctrineAdapter`
56 | * Remove support of Doctrine DBAL in `PdoAdapter`
57 |
58 | 5.4
59 | ---
60 |
61 | * Deprecate `DoctrineProvider` and `DoctrineAdapter` because these classes have been added to the `doctrine/cache` package
62 | * Add `DoctrineDbalAdapter` identical to `PdoAdapter` for `Doctrine\DBAL\Connection` or DBAL URL
63 | * Deprecate usage of `PdoAdapter` with `Doctrine\DBAL\Connection` or DBAL URL
64 |
65 | 5.3
66 | ---
67 |
68 | * added support for connecting to Redis Sentinel clusters when using the Redis PHP extension
69 | * add support for a custom serializer to the `ApcuAdapter` class
70 |
71 | 5.2.0
72 | -----
73 |
74 | * added integration with Messenger to allow computing cached values in a worker
75 | * allow ISO 8601 time intervals to specify default lifetime
76 |
77 | 5.1.0
78 | -----
79 |
80 | * added max-items + LRU + max-lifetime capabilities to `ArrayCache`
81 | * added `CouchbaseBucketAdapter`
82 | * added context `cache-adapter` to log messages
83 |
84 | 5.0.0
85 | -----
86 |
87 | * removed all PSR-16 implementations in the `Simple` namespace
88 | * removed `SimpleCacheAdapter`
89 | * removed `AbstractAdapter::unserialize()`
90 | * removed `CacheItem::getPreviousTags()`
91 |
92 | 4.4.0
93 | -----
94 |
95 | * added support for connecting to Redis Sentinel clusters
96 | * added argument `$prefix` to `AdapterInterface::clear()`
97 | * improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag
98 | * added `TagAwareMarshaller` for optimized data storage when using `AbstractTagAwareAdapter`
99 | * added `DeflateMarshaller` to compress serialized values
100 | * removed support for phpredis 4 `compression`
101 | * [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead
102 | * Marked the `CacheDataCollector` class as `@final`.
103 | * added `SodiumMarshaller` to encrypt/decrypt values using libsodium
104 |
105 | 4.3.0
106 | -----
107 |
108 | * removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
109 | * deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
110 | * deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
111 |
112 | 4.2.0
113 | -----
114 |
115 | * added support for connecting to Redis clusters via DSN
116 | * added support for configuring multiple Memcached servers via DSN
117 | * added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
118 | * implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
119 | * added sub-second expiry accuracy for backends that support it
120 | * added support for phpredis 4 `compression` and `tcp_keepalive` options
121 | * added automatic table creation when using Doctrine DBAL with PDO-based backends
122 | * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
123 | * deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
124 | * deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
125 | * added `CacheCollectorPass` (originally in `FrameworkBundle`)
126 | * added `CachePoolClearerPass` (originally in `FrameworkBundle`)
127 | * added `CachePoolPass` (originally in `FrameworkBundle`)
128 | * added `CachePoolPrunerPass` (originally in `FrameworkBundle`)
129 |
130 | 3.4.0
131 | -----
132 |
133 | * added using options from Memcached DSN
134 | * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning
135 | * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, TagAwareAdapter and ChainTrait
136 | * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and
137 | ChainCache implement PruneableInterface and support manual stale cache pruning
138 |
139 | 3.3.0
140 | -----
141 |
142 | * added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
143 | * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
144 | * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
145 | * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)
146 | * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16)
147 |
148 | 3.2.0
149 | -----
150 |
151 | * added TagAwareAdapter for tags-based invalidation
152 | * added PdoAdapter with PDO and Doctrine DBAL support
153 | * added PhpArrayAdapter and PhpFilesAdapter for OPcache-backed shared memory storage (PHP 7+ only)
154 | * added NullAdapter
155 |
156 | 3.1.0
157 | -----
158 |
159 | * added the component with strict PSR-6 implementations
160 | * added ApcuAdapter, ArrayAdapter, FilesystemAdapter and RedisAdapter
161 | * added AbstractAdapter, ChainAdapter and ProxyAdapter
162 | * added DoctrineAdapter and DoctrineProvider for bidirectional interoperability with Doctrine Cache
163 |
--------------------------------------------------------------------------------
/DataCollector/CacheDataCollector.php:
--------------------------------------------------------------------------------
1 |
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\DataCollector;
13 |
14 | use Symfony\Component\Cache\Adapter\TraceableAdapter;
15 | use Symfony\Component\Cache\Adapter\TraceableAdapterEvent;
16 | use Symfony\Component\HttpFoundation\Request;
17 | use Symfony\Component\HttpFoundation\Response;
18 | use Symfony\Component\HttpKernel\DataCollector\DataCollector;
19 | use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
20 |
21 | /**
22 | * @author Aaron Scherer
22 | */
23 | final class CacheItem implements ItemInterface
24 | {
25 | private const METADATA_EXPIRY_OFFSET = 1527506807;
26 | private const VALUE_WRAPPER = "\xA9";
27 |
28 | protected string $key;
29 | protected mixed $value = null;
30 | protected bool $isHit = false;
31 | protected float|int|null $expiry = null;
32 | protected array $metadata = [];
33 | protected array $newMetadata = [];
34 | protected ?CacheItemInterface $innerItem = null;
35 | protected ?string $poolHash = null;
36 | protected bool $isTaggable = false;
37 |
38 | public function getKey(): string
39 | {
40 | return $this->key;
41 | }
42 |
43 | public function get(): mixed
44 | {
45 | return $this->value;
46 | }
47 |
48 | public function isHit(): bool
49 | {
50 | return $this->isHit;
51 | }
52 |
53 | /**
54 | * @return $this
55 | */
56 | public function set($value): static
57 | {
58 | $this->value = $value;
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * @return $this
65 | */
66 | public function expiresAt(?\DateTimeInterface $expiration): static
67 | {
68 | $this->expiry = null !== $expiration ? (float) $expiration->format('U.u') : null;
69 |
70 | return $this;
71 | }
72 |
73 | /**
74 | * @return $this
75 | */
76 | public function expiresAfter(mixed $time): static
77 | {
78 | if (null === $time) {
79 | $this->expiry = null;
80 | } elseif ($time instanceof \DateInterval) {
81 | $this->expiry = microtime(true) + \DateTimeImmutable::createFromFormat('U', 0)->add($time)->format('U.u');
82 | } elseif (\is_int($time)) {
83 | $this->expiry = $time + microtime(true);
84 | } else {
85 | throw new InvalidArgumentException(\sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', get_debug_type($time)));
86 | }
87 |
88 | return $this;
89 | }
90 |
91 | public function tag(mixed $tags): static
92 | {
93 | if (!$this->isTaggable) {
94 | throw new LogicException(\sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key));
95 | }
96 | if (!\is_array($tags) && !$tags instanceof \Traversable) { // don't use is_iterable(), it's slow
97 | $tags = [$tags];
98 | }
99 | foreach ($tags as $tag) {
100 | if (!\is_string($tag) && !$tag instanceof \Stringable) {
101 | throw new InvalidArgumentException(\sprintf('Cache tag must be string or object that implements __toString(), "%s" given.', get_debug_type($tag)));
102 | }
103 | $tag = (string) $tag;
104 | if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) {
105 | continue;
106 | }
107 | if ('' === $tag) {
108 | throw new InvalidArgumentException('Cache tag length must be greater than zero.');
109 | }
110 | if (false !== strpbrk($tag, self::RESERVED_CHARACTERS)) {
111 | throw new InvalidArgumentException(\sprintf('Cache tag "%s" contains reserved characters "%s".', $tag, self::RESERVED_CHARACTERS));
112 | }
113 | $this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
114 | }
115 |
116 | return $this;
117 | }
118 |
119 | public function getMetadata(): array
120 | {
121 | return $this->metadata;
122 | }
123 |
124 | /**
125 | * Validates a cache key according to PSR-6.
126 | *
127 | * @param mixed $key The key to validate
128 | *
129 | * @throws InvalidArgumentException When $key is not valid
130 | */
131 | public static function validateKey($key): string
132 | {
133 | if (!\is_string($key)) {
134 | throw new InvalidArgumentException(\sprintf('Cache key must be string, "%s" given.', get_debug_type($key)));
135 | }
136 | if ('' === $key) {
137 | throw new InvalidArgumentException('Cache key length must be greater than zero.');
138 | }
139 | if (false !== strpbrk($key, self::RESERVED_CHARACTERS)) {
140 | throw new InvalidArgumentException(\sprintf('Cache key "%s" contains reserved characters "%s".', $key, self::RESERVED_CHARACTERS));
141 | }
142 |
143 | return $key;
144 | }
145 |
146 | /**
147 | * Internal logging helper.
148 | *
149 | * @internal
150 | */
151 | public static function log(?LoggerInterface $logger, string $message, array $context = []): void
152 | {
153 | if ($logger) {
154 | $logger->warning($message, $context);
155 | } else {
156 | $replace = [];
157 | foreach ($context as $k => $v) {
158 | if (\is_scalar($v)) {
159 | $replace['{'.$k.'}'] = $v;
160 | }
161 | }
162 | @trigger_error(strtr($message, $replace), \E_USER_WARNING);
163 | }
164 | }
165 |
166 | private function pack(): mixed
167 | {
168 | if (!$m = $this->newMetadata) {
169 | return $this->value;
170 | }
171 | $valueWrapper = self::VALUE_WRAPPER;
172 |
173 | return new $valueWrapper($this->value, $m + ['expiry' => $this->expiry]);
174 | }
175 |
176 | private function unpack(): bool
177 | {
178 | $v = $this->value;
179 | $valueWrapper = self::VALUE_WRAPPER;
180 |
181 | if ($v instanceof $valueWrapper) {
182 | $this->value = $v->value;
183 | $this->metadata = $v->metadata;
184 |
185 | return true;
186 | }
187 |
188 | if (!\is_array($v) || 1 !== \count($v) || 10 !== \strlen($k = (string) array_key_first($v)) || "\x9D" !== $k[0] || "\0" !== $k[5] || "\x5F" !== $k[9]) {
189 | return false;
190 | }
191 |
192 | // BC with pools populated before v6.1
193 | $this->value = $v[$k];
194 | $this->metadata = unpack('Vexpiry/Nctime', substr($k, 1, -1));
195 | $this->metadata['expiry'] += self::METADATA_EXPIRY_OFFSET;
196 |
197 | return true;
198 | }
199 | }
200 |
201 | // @php-cs-fixer-ignore protected_to_private Friend-level scope access relies on protected properties
202 |
--------------------------------------------------------------------------------
/Adapter/CouchbaseCollectionAdapter.php:
--------------------------------------------------------------------------------
1 |
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\Adapter;
13 |
14 | use Couchbase\Bucket;
15 | use Couchbase\Cluster;
16 | use Couchbase\ClusterOptions;
17 | use Couchbase\Collection;
18 | use Couchbase\DocumentNotFoundException;
19 | use Couchbase\UpsertOptions;
20 | use Symfony\Component\Cache\Exception\CacheException;
21 | use Symfony\Component\Cache\Exception\InvalidArgumentException;
22 | use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
23 | use Symfony\Component\Cache\Marshaller\MarshallerInterface;
24 |
25 | /**
26 | * @author Antonio Jose Cerezo Aranda
26 | */
27 | final class LockRegistry
28 | {
29 | private static array $openedFiles = [];
30 | private static ?array $lockedFiles = null;
31 | private static \Exception $signalingException;
32 | private static \Closure $signalingCallback;
33 |
34 | /**
35 | * The number of items in this list controls the max number of concurrent processes.
36 | */
37 | private static array $files = [
38 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php',
39 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php',
40 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php',
41 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php',
42 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php',
43 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php',
44 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseBucketAdapter.php',
45 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'CouchbaseCollectionAdapter.php',
46 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineDbalAdapter.php',
47 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php',
48 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php',
49 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php',
50 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php',
51 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ParameterNormalizer.php',
52 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php',
53 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php',
54 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php',
55 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php',
56 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php',
57 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php',
58 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php',
59 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php',
60 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php',
61 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php',
62 | __DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php',
63 | ];
64 |
65 | /**
66 | * Defines a set of existing files that will be used as keys to acquire locks.
67 | *
68 | * @return array The previously defined set of files
69 | */
70 | public static function setFiles(array $files): array
71 | {
72 | $previousFiles = self::$files;
73 | self::$files = $files;
74 |
75 | foreach (self::$openedFiles as $file) {
76 | if ($file) {
77 | flock($file, \LOCK_UN);
78 | fclose($file);
79 | }
80 | }
81 | self::$openedFiles = self::$lockedFiles = [];
82 |
83 | return $previousFiles;
84 | }
85 |
86 | public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, ?\Closure $setMetadata = null, ?LoggerInterface $logger = null): mixed
87 | {
88 | if ('\\' === \DIRECTORY_SEPARATOR && null === self::$lockedFiles) {
89 | // disable locking on Windows by default
90 | self::$files = self::$lockedFiles = [];
91 | }
92 |
93 | $key = self::$files ? abs(crc32($item->getKey())) % \count(self::$files) : -1;
94 |
95 | if ($key < 0 || self::$lockedFiles || !$lock = self::open($key)) {
96 | return $callback($item, $save);
97 | }
98 |
99 | self::$signalingException ??= unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
100 | self::$signalingCallback ??= fn () => throw self::$signalingException;
101 |
102 | while (true) {
103 | try {
104 | // race to get the lock in non-blocking mode
105 | $locked = flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock);
106 |
107 | if ($locked || !$wouldBlock) {
108 | $logger?->info(\sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
109 | self::$lockedFiles[$key] = true;
110 |
111 | $value = $callback($item, $save);
112 |
113 | if ($save) {
114 | if ($setMetadata) {
115 | $setMetadata($item);
116 | }
117 |
118 | $pool->save($item->set($value));
119 | $save = false;
120 | }
121 |
122 | return $value;
123 | }
124 | // if we failed the race, retry locking in blocking mode to wait for the winner
125 | $logger?->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
126 | flock($lock, \LOCK_SH);
127 | } finally {
128 | flock($lock, \LOCK_UN);
129 | unset(self::$lockedFiles[$key]);
130 | }
131 |
132 | try {
133 | $value = $pool->get($item->getKey(), self::$signalingCallback, 0);
134 | $logger?->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
135 | $save = false;
136 |
137 | return $value;
138 | } catch (\Exception $e) {
139 | if (self::$signalingException !== $e) {
140 | throw $e;
141 | }
142 | $logger?->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
143 | }
144 | }
145 |
146 | return null;
147 | }
148 |
149 | /**
150 | * @return resource|false
151 | */
152 | private static function open(int $key)
153 | {
154 | if (null !== $h = self::$openedFiles[$key] ?? null) {
155 | return $h;
156 | }
157 | set_error_handler(static fn () => null);
158 | try {
159 | $h = fopen(self::$files[$key], 'r+');
160 | } finally {
161 | restore_error_handler();
162 | }
163 |
164 | return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/Adapter/ProxyAdapter.php:
--------------------------------------------------------------------------------
1 |
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\Adapter;
13 |
14 | use Psr\Cache\CacheItemInterface;
15 | use Psr\Cache\CacheItemPoolInterface;
16 | use Symfony\Component\Cache\CacheItem;
17 | use Symfony\Component\Cache\PruneableInterface;
18 | use Symfony\Component\Cache\ResettableInterface;
19 | use Symfony\Component\Cache\Traits\ContractsTrait;
20 | use Symfony\Component\Cache\Traits\ProxyTrait;
21 | use Symfony\Contracts\Cache\CacheInterface;
22 | use Symfony\Contracts\Cache\NamespacedPoolInterface;
23 |
24 | /**
25 | * @author Nicolas Grekas
26 | */
27 | class ProxyAdapter implements AdapterInterface, NamespacedPoolInterface, CacheInterface, PruneableInterface, ResettableInterface
28 | {
29 | use ContractsTrait;
30 | use ProxyTrait;
31 |
32 | private string $namespace = '';
33 | private int $namespaceLen;
34 | private string $poolHash;
35 | private int $defaultLifetime;
36 |
37 | private static \Closure $createCacheItem;
38 | private static \Closure $setInnerItem;
39 |
40 | public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
41 | {
42 | if ('' !== $namespace) {
43 | if ($pool instanceof NamespacedPoolInterface) {
44 | $pool = $pool->withSubNamespace($namespace);
45 | $this->namespace = $namespace = '';
46 | } else {
47 | \assert('' !== CacheItem::validateKey($namespace));
48 | $this->namespace = $namespace;
49 | }
50 | }
51 | $this->pool = $pool;
52 | $this->poolHash = spl_object_hash($pool);
53 | $this->namespaceLen = \strlen($namespace);
54 | $this->defaultLifetime = $defaultLifetime;
55 | self::$createCacheItem ??= \Closure::bind(
56 | static function ($key, $innerItem, $poolHash) {
57 | $item = new CacheItem();
58 | $item->key = $key;
59 |
60 | if (null === $innerItem) {
61 | return $item;
62 | }
63 |
64 | $item->value = $innerItem->get();
65 | $item->isHit = $innerItem->isHit();
66 | $item->innerItem = $innerItem;
67 | $item->poolHash = $poolHash;
68 |
69 | if (!$item->unpack() && $innerItem instanceof CacheItem) {
70 | $item->metadata = $innerItem->metadata;
71 | }
72 | $innerItem->set(null);
73 |
74 | return $item;
75 | },
76 | null,
77 | CacheItem::class
78 | );
79 | self::$setInnerItem ??= \Closure::bind(
80 | static function (CacheItemInterface $innerItem, CacheItem $item, $expiry = null) {
81 | $innerItem->set($item->pack());
82 | $innerItem->expiresAt(($expiry ?? $item->expiry) ? \DateTimeImmutable::createFromFormat('U.u', \sprintf('%.6F', $expiry ?? $item->expiry)) : null);
83 | },
84 | null,
85 | CacheItem::class
86 | );
87 | }
88 |
89 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed
90 | {
91 | if (!$this->pool instanceof CacheInterface) {
92 | return $this->doGet($this, $key, $callback, $beta, $metadata);
93 | }
94 |
95 | return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
96 | $item = (self::$createCacheItem)($key, $innerItem, $this->poolHash);
97 | $item->set($value = $callback($item, $save));
98 | (self::$setInnerItem)($innerItem, $item);
99 |
100 | return $value;
101 | }, $beta, $metadata);
102 | }
103 |
104 | public function getItem(mixed $key): CacheItem
105 | {
106 | $item = $this->pool->getItem($this->getId($key));
107 |
108 | return (self::$createCacheItem)($key, $item, $this->poolHash);
109 | }
110 |
111 | public function getItems(array $keys = []): iterable
112 | {
113 | if ($this->namespaceLen) {
114 | foreach ($keys as $i => $key) {
115 | $keys[$i] = $this->getId($key);
116 | }
117 | }
118 |
119 | return $this->generateItems($this->pool->getItems($keys));
120 | }
121 |
122 | public function hasItem(mixed $key): bool
123 | {
124 | return $this->pool->hasItem($this->getId($key));
125 | }
126 |
127 | public function clear(string $prefix = ''): bool
128 | {
129 | if ($this->pool instanceof AdapterInterface) {
130 | return $this->pool->clear($this->namespace.$prefix);
131 | }
132 |
133 | return $this->pool->clear();
134 | }
135 |
136 | public function deleteItem(mixed $key): bool
137 | {
138 | return $this->pool->deleteItem($this->getId($key));
139 | }
140 |
141 | public function deleteItems(array $keys): bool
142 | {
143 | if ($this->namespaceLen) {
144 | foreach ($keys as $i => $key) {
145 | $keys[$i] = $this->getId($key);
146 | }
147 | }
148 |
149 | return $this->pool->deleteItems($keys);
150 | }
151 |
152 | public function save(CacheItemInterface $item): bool
153 | {
154 | return $this->doSave($item, __FUNCTION__);
155 | }
156 |
157 | public function saveDeferred(CacheItemInterface $item): bool
158 | {
159 | return $this->doSave($item, __FUNCTION__);
160 | }
161 |
162 | public function commit(): bool
163 | {
164 | return $this->pool->commit();
165 | }
166 |
167 | public function withSubNamespace(string $namespace): static
168 | {
169 | $clone = clone $this;
170 |
171 | if ($clone->pool instanceof NamespacedPoolInterface) {
172 | $clone->pool = $clone->pool->withSubNamespace($namespace);
173 | } else {
174 | $clone->namespace .= CacheItem::validateKey($namespace);
175 | $clone->namespaceLen = \strlen($clone->namespace);
176 | }
177 |
178 | return $clone;
179 | }
180 |
181 | private function doSave(CacheItemInterface $item, string $method): bool
182 | {
183 | if (!$item instanceof CacheItem) {
184 | return false;
185 | }
186 | $castItem = (array) $item;
187 |
188 | if (null === $castItem["\0*\0expiry"] && 0 < $this->defaultLifetime) {
189 | $castItem["\0*\0expiry"] = microtime(true) + $this->defaultLifetime;
190 | }
191 |
192 | if ($castItem["\0*\0poolHash"] === $this->poolHash && $castItem["\0*\0innerItem"]) {
193 | $innerItem = $castItem["\0*\0innerItem"];
194 | } elseif ($this->pool instanceof AdapterInterface) {
195 | // this is an optimization specific for AdapterInterface implementations
196 | // so we can save a round-trip to the backend by just creating a new item
197 | $innerItem = (self::$createCacheItem)($this->namespace.$castItem["\0*\0key"], null, $this->poolHash);
198 | } else {
199 | $innerItem = $this->pool->getItem($this->namespace.$castItem["\0*\0key"]);
200 | }
201 |
202 | (self::$setInnerItem)($innerItem, $item, $castItem["\0*\0expiry"]);
203 |
204 | return $this->pool->$method($innerItem);
205 | }
206 |
207 | private function generateItems(iterable $items): \Generator
208 | {
209 | $f = self::$createCacheItem;
210 |
211 | foreach ($items as $key => $item) {
212 | if ($this->namespaceLen) {
213 | $key = substr($key, $this->namespaceLen);
214 | }
215 |
216 | yield $key => $f($key, $item, $this->poolHash);
217 | }
218 | }
219 |
220 | private function getId(mixed $key): string
221 | {
222 | \assert('' !== CacheItem::validateKey($key));
223 |
224 | return $this->namespace.$key;
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/Adapter/CouchbaseBucketAdapter.php:
--------------------------------------------------------------------------------
1 |
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\Adapter;
13 |
14 | use Symfony\Component\Cache\Exception\CacheException;
15 | use Symfony\Component\Cache\Exception\InvalidArgumentException;
16 | use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
17 | use Symfony\Component\Cache\Marshaller\MarshallerInterface;
18 |
19 | trigger_deprecation('symfony/cache', '7.1', 'The "%s" class is deprecated, use "%s" instead.', CouchbaseBucketAdapter::class, CouchbaseCollectionAdapter::class);
20 |
21 | /**
22 | * @author Antonio Jose Cerezo Aranda
26 | */
27 | class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
28 | {
29 | use ProxyTrait;
30 |
31 | private ?\Closure $createCacheItem = null;
32 | private ?CacheItem $cacheItemPrototype = null;
33 | private static \Closure $packCacheItem;
34 |
35 | public function __construct(CacheItemPoolInterface $pool)
36 | {
37 | $this->pool = $pool;
38 |
39 | if (!$pool instanceof AdapterInterface) {
40 | return;
41 | }
42 | $cacheItemPrototype = &$this->cacheItemPrototype;
43 | $createCacheItem = \Closure::bind(
44 | static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
45 | $item = clone $cacheItemPrototype;
46 | $item->poolHash = $item->innerItem = null;
47 | if ($allowInt && \is_int($key)) {
48 | $item->key = (string) $key;
49 | } else {
50 | \assert('' !== CacheItem::validateKey($key));
51 | $item->key = $key;
52 | }
53 | $item->value = $value;
54 | $item->isHit = false;
55 |
56 | return $item;
57 | },
58 | null,
59 | CacheItem::class
60 | );
61 | $this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
62 | if (null === $this->cacheItemPrototype) {
63 | $this->get($allowInt && \is_int($key) ? (string) $key : $key);
64 | }
65 | $this->createCacheItem = $createCacheItem;
66 |
67 | return $createCacheItem($key, null, $allowInt)->set($value);
68 | };
69 | self::$packCacheItem ??= \Closure::bind(
70 | static function (CacheItem $item) {
71 | $item->newMetadata = $item->metadata;
72 |
73 | return $item->pack();
74 | },
75 | null,
76 | CacheItem::class
77 | );
78 | }
79 |
80 | public function get($key, $default = null): mixed
81 | {
82 | try {
83 | $item = $this->pool->getItem($key);
84 | } catch (SimpleCacheException $e) {
85 | throw $e;
86 | } catch (Psr6CacheException $e) {
87 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
88 | }
89 | if (null === $this->cacheItemPrototype) {
90 | $this->cacheItemPrototype = clone $item;
91 | $this->cacheItemPrototype->set(null);
92 | }
93 |
94 | return $item->isHit() ? $item->get() : $default;
95 | }
96 |
97 | public function set($key, $value, $ttl = null): bool
98 | {
99 | try {
100 | if (null !== $f = $this->createCacheItem) {
101 | $item = $f($key, $value);
102 | } else {
103 | $item = $this->pool->getItem($key)->set($value);
104 | }
105 | } catch (SimpleCacheException $e) {
106 | throw $e;
107 | } catch (Psr6CacheException $e) {
108 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
109 | }
110 | if (null !== $ttl) {
111 | $item->expiresAfter($ttl);
112 | }
113 |
114 | return $this->pool->save($item);
115 | }
116 |
117 | public function delete($key): bool
118 | {
119 | try {
120 | return $this->pool->deleteItem($key);
121 | } catch (SimpleCacheException $e) {
122 | throw $e;
123 | } catch (Psr6CacheException $e) {
124 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
125 | }
126 | }
127 |
128 | public function clear(): bool
129 | {
130 | return $this->pool->clear();
131 | }
132 |
133 | public function getMultiple($keys, $default = null): iterable
134 | {
135 | if ($keys instanceof \Traversable) {
136 | $keys = iterator_to_array($keys, false);
137 | } elseif (!\is_array($keys)) {
138 | throw new InvalidArgumentException(\sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys)));
139 | }
140 |
141 | try {
142 | $items = $this->pool->getItems($keys);
143 | } catch (SimpleCacheException $e) {
144 | throw $e;
145 | } catch (Psr6CacheException $e) {
146 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
147 | }
148 | $values = [];
149 |
150 | if (!$this->pool instanceof AdapterInterface) {
151 | foreach ($items as $key => $item) {
152 | $values[$key] = $item->isHit() ? $item->get() : $default;
153 | }
154 |
155 | return $values;
156 | }
157 |
158 | foreach ($items as $key => $item) {
159 | $values[$key] = $item->isHit() ? (self::$packCacheItem)($item) : $default;
160 | }
161 |
162 | return $values;
163 | }
164 |
165 | public function setMultiple($values, $ttl = null): bool
166 | {
167 | $valuesIsArray = \is_array($values);
168 | if (!$valuesIsArray && !$values instanceof \Traversable) {
169 | throw new InvalidArgumentException(\sprintf('Cache values must be array or Traversable, "%s" given.', get_debug_type($values)));
170 | }
171 | $items = [];
172 |
173 | try {
174 | if (null !== $f = $this->createCacheItem) {
175 | $valuesIsArray = false;
176 | foreach ($values as $key => $value) {
177 | $items[$key] = $f($key, $value, true);
178 | }
179 | } elseif ($valuesIsArray) {
180 | $items = [];
181 | foreach ($values as $key => $value) {
182 | $items[] = (string) $key;
183 | }
184 | $items = $this->pool->getItems($items);
185 | } else {
186 | foreach ($values as $key => $value) {
187 | if (\is_int($key)) {
188 | $key = (string) $key;
189 | }
190 | $items[$key] = $this->pool->getItem($key)->set($value);
191 | }
192 | }
193 | } catch (SimpleCacheException $e) {
194 | throw $e;
195 | } catch (Psr6CacheException $e) {
196 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
197 | }
198 | $ok = true;
199 |
200 | foreach ($items as $key => $item) {
201 | if ($valuesIsArray) {
202 | $item->set($values[$key]);
203 | }
204 | if (null !== $ttl) {
205 | $item->expiresAfter($ttl);
206 | }
207 | $ok = $this->pool->saveDeferred($item) && $ok;
208 | }
209 |
210 | return $this->pool->commit() && $ok;
211 | }
212 |
213 | public function deleteMultiple($keys): bool
214 | {
215 | if ($keys instanceof \Traversable) {
216 | $keys = iterator_to_array($keys, false);
217 | } elseif (!\is_array($keys)) {
218 | throw new InvalidArgumentException(\sprintf('Cache keys must be array or Traversable, "%s" given.', get_debug_type($keys)));
219 | }
220 |
221 | try {
222 | return $this->pool->deleteItems($keys);
223 | } catch (SimpleCacheException $e) {
224 | throw $e;
225 | } catch (Psr6CacheException $e) {
226 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
227 | }
228 | }
229 |
230 | public function has($key): bool
231 | {
232 | try {
233 | return $this->pool->hasItem($key);
234 | } catch (SimpleCacheException $e) {
235 | throw $e;
236 | } catch (Psr6CacheException $e) {
237 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
238 | }
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/Adapter/AbstractAdapter.php:
--------------------------------------------------------------------------------
1 |
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\Adapter;
13 |
14 | use Psr\Log\LoggerAwareInterface;
15 | use Psr\Log\LoggerInterface;
16 | use Symfony\Component\Cache\CacheItem;
17 | use Symfony\Component\Cache\Exception\InvalidArgumentException;
18 | use Symfony\Component\Cache\ResettableInterface;
19 | use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
20 | use Symfony\Component\Cache\Traits\ContractsTrait;
21 | use Symfony\Contracts\Cache\CacheInterface;
22 | use Symfony\Contracts\Cache\NamespacedPoolInterface;
23 |
24 | /**
25 | * @author Nicolas Grekas
26 | */
27 | abstract class AbstractAdapter implements AdapterInterface, CacheInterface, NamespacedPoolInterface, LoggerAwareInterface, ResettableInterface
28 | {
29 | use AbstractAdapterTrait;
30 | use ContractsTrait;
31 |
32 | /**
33 | * @internal
34 | */
35 | protected const NS_SEPARATOR = ':';
36 |
37 | private static bool $apcuSupported;
38 |
39 | protected function __construct(string $namespace = '', int $defaultLifetime = 0)
40 | {
41 | if ('' !== $namespace) {
42 | if (str_contains($namespace, static::NS_SEPARATOR)) {
43 | if (str_contains($namespace, static::NS_SEPARATOR.static::NS_SEPARATOR)) {
44 | throw new InvalidArgumentException(\sprintf('Cache namespace "%s" contains empty sub-namespace.', $namespace));
45 | }
46 | CacheItem::validateKey(str_replace(static::NS_SEPARATOR, '', $namespace));
47 | } else {
48 | CacheItem::validateKey($namespace);
49 | }
50 | $this->namespace = $namespace.static::NS_SEPARATOR;
51 | }
52 | $this->rootNamespace = $this->namespace;
53 |
54 | $this->defaultLifetime = $defaultLifetime;
55 | if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
56 | throw new InvalidArgumentException(\sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
57 | }
58 | self::$createCacheItem ??= \Closure::bind(
59 | static function ($key, $value, $isHit) {
60 | $item = new CacheItem();
61 | $item->key = $key;
62 | $item->value = $value;
63 | $item->isHit = $isHit;
64 | $item->unpack();
65 |
66 | return $item;
67 | },
68 | null,
69 | CacheItem::class
70 | );
71 | self::$mergeByLifetime ??= \Closure::bind(
72 | static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) {
73 | $byLifetime = [];
74 | $now = microtime(true);
75 | $expiredIds = [];
76 |
77 | foreach ($deferred as $key => $item) {
78 | $key = (string) $key;
79 | if (null === $item->expiry) {
80 | $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
81 | } elseif (!$item->expiry) {
82 | $ttl = 0;
83 | } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
84 | $expiredIds[] = $getId($key);
85 | continue;
86 | }
87 | $byLifetime[$ttl][$getId($key)] = $item->pack();
88 | }
89 |
90 | return $byLifetime;
91 | },
92 | null,
93 | CacheItem::class
94 | );
95 | }
96 |
97 | /**
98 | * Returns the best possible adapter that your runtime supports.
99 | *
100 | * Using ApcuAdapter makes system caches compatible with read-only filesystems.
101 | */
102 | public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, ?LoggerInterface $logger = null): AdapterInterface
103 | {
104 | $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
105 | if (null !== $logger) {
106 | $opcache->setLogger($logger);
107 | }
108 |
109 | if (!self::$apcuSupported ??= ApcuAdapter::isSupported()) {
110 | return $opcache;
111 | }
112 |
113 | if ('cli' === \PHP_SAPI && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOL)) {
114 | return $opcache;
115 | }
116 |
117 | $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version);
118 | if (null !== $logger) {
119 | $apcu->setLogger($logger);
120 | }
121 |
122 | return new ChainAdapter([$apcu, $opcache]);
123 | }
124 |
125 | public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): mixed
126 | {
127 | if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:') || str_starts_with($dsn, 'valkey:') || str_starts_with($dsn, 'valkeys:')) {
128 | return RedisAdapter::createConnection($dsn, $options);
129 | }
130 | if (str_starts_with($dsn, 'memcached:')) {
131 | return MemcachedAdapter::createConnection($dsn, $options);
132 | }
133 | if (str_starts_with($dsn, 'couchbase:')) {
134 | if (class_exists(\CouchbaseBucket::class) && CouchbaseBucketAdapter::isSupported()) {
135 | return CouchbaseBucketAdapter::createConnection($dsn, $options);
136 | }
137 |
138 | return CouchbaseCollectionAdapter::createConnection($dsn, $options);
139 | }
140 | if (preg_match('/^(mysql|oci|pgsql|sqlsrv|sqlite):/', $dsn)) {
141 | return PdoAdapter::createConnection($dsn, $options);
142 | }
143 |
144 | throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "valkey[s]:", "memcached:", "couchbase:", "mysql:", "oci:", "pgsql:", "sqlsrv:" nor "sqlite:".');
145 | }
146 |
147 | public function commit(): bool
148 | {
149 | $ok = true;
150 | $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, $this->getId(...), $this->defaultLifetime);
151 | $retry = $this->deferred = [];
152 |
153 | if ($expiredIds) {
154 | try {
155 | $this->doDelete($expiredIds);
156 | } catch (\Exception $e) {
157 | $ok = false;
158 | CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
159 | }
160 | }
161 | foreach ($byLifetime as $lifetime => $values) {
162 | try {
163 | $e = $this->doSave($values, $lifetime);
164 | } catch (\Exception $e) {
165 | }
166 | if (true === $e || [] === $e) {
167 | continue;
168 | }
169 | if (\is_array($e) || 1 === \count($values)) {
170 | foreach (\is_array($e) ? $e : array_keys($values) as $id) {
171 | $ok = false;
172 | $v = $values[$id];
173 | $type = get_debug_type($v);
174 | $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
175 | CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->rootNamespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
176 | }
177 | } else {
178 | foreach ($values as $id => $v) {
179 | $retry[$lifetime][] = $id;
180 | }
181 | }
182 | }
183 |
184 | // When bulk-save failed, retry each item individually
185 | foreach ($retry as $lifetime => $ids) {
186 | foreach ($ids as $id) {
187 | try {
188 | $v = $byLifetime[$lifetime][$id];
189 | $e = $this->doSave([$id => $v], $lifetime);
190 | } catch (\Exception $e) {
191 | }
192 | if (true === $e || [] === $e) {
193 | continue;
194 | }
195 | $ok = false;
196 | $type = get_debug_type($v);
197 | $message = \sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
198 | CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->rootNamespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
199 | }
200 | }
201 |
202 | return $ok;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/Adapter/FilesystemTagAwareAdapter.php:
--------------------------------------------------------------------------------
1 |
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\Adapter;
13 |
14 | use Symfony\Component\Cache\Marshaller\MarshallerInterface;
15 | use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
16 | use Symfony\Component\Cache\PruneableInterface;
17 | use Symfony\Component\Cache\Traits\FilesystemTrait;
18 |
19 | /**
20 | * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
21 | *
22 | * @author Nicolas Grekas
23 | * @author André Rømcke