summaryrefslogtreecommitdiff
path: root/vendor/symfony/cache/Adapter
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/cache/Adapter')
-rw-r--r--vendor/symfony/cache/Adapter/AbstractAdapter.php120
-rw-r--r--vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php305
-rw-r--r--vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php149
-rw-r--r--vendor/symfony/cache/Adapter/Psr16Adapter.php81
-rw-r--r--vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php209
-rw-r--r--vendor/symfony/cache/Adapter/SimpleCacheAdapter.php63
6 files changed, 754 insertions, 173 deletions
diff --git a/vendor/symfony/cache/Adapter/AbstractAdapter.php b/vendor/symfony/cache/Adapter/AbstractAdapter.php
index 6897fbceee..883a94ba9b 100644
--- a/vendor/symfony/cache/Adapter/AbstractAdapter.php
+++ b/vendor/symfony/cache/Adapter/AbstractAdapter.php
@@ -11,14 +11,13 @@
namespace Symfony\Component\Cache\Adapter;
-use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
-use Symfony\Component\Cache\Traits\AbstractTrait;
+use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\CacheInterface;
@@ -27,15 +26,12 @@ use Symfony\Contracts\Cache\CacheInterface;
*/
abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
- use AbstractTrait;
+ use AbstractAdapterTrait;
use ContractsTrait;
private static $apcuSupported;
private static $phpFilesSupported;
- private $createCacheItem;
- private $mergeByLifetime;
-
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
@@ -132,7 +128,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
if (!\is_string($dsn)) {
throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, \gettype($dsn)));
}
- if (0 === strpos($dsn, 'redis:')) {
+ if (0 === strpos($dsn, 'redis:') || 0 === strpos($dsn, 'rediss:')) {
return RedisAdapter::createConnection($dsn, $options);
}
if (0 === strpos($dsn, 'memcached:')) {
@@ -145,81 +141,6 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
/**
* {@inheritdoc}
*/
- public function getItem($key)
- {
- if ($this->deferred) {
- $this->commit();
- }
- $id = $this->getId($key);
-
- $f = $this->createCacheItem;
- $isHit = false;
- $value = null;
-
- try {
- foreach ($this->doFetch([$id]) as $value) {
- $isHit = true;
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
- }
-
- return $f($key, $value, $isHit);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getItems(array $keys = [])
- {
- if ($this->deferred) {
- $this->commit();
- }
- $ids = [];
-
- foreach ($keys as $key) {
- $ids[] = $this->getId($key);
- }
- try {
- $items = $this->doFetch($ids);
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]);
- $items = [];
- }
- $ids = array_combine($ids, $keys);
-
- return $this->generateItems($items, $ids);
- }
-
- /**
- * {@inheritdoc}
- */
- public function save(CacheItemInterface $item)
- {
- if (!$item instanceof CacheItem) {
- return false;
- }
- $this->deferred[$item->getKey()] = $item;
-
- return $this->commit();
- }
-
- /**
- * {@inheritdoc}
- */
- public function saveDeferred(CacheItemInterface $item)
- {
- if (!$item instanceof CacheItem) {
- return false;
- }
- $this->deferred[$item->getKey()] = $item;
-
- return true;
- }
-
- /**
- * {@inheritdoc}
- */
public function commit()
{
$ok = true;
@@ -243,7 +164,8 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
} else {
foreach ($values as $id => $v) {
@@ -265,39 +187,11 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
- CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
}
return $ok;
}
-
- public function __destruct()
- {
- if ($this->deferred) {
- $this->commit();
- }
- }
-
- private function generateItems($items, &$keys)
- {
- $f = $this->createCacheItem;
-
- try {
- foreach ($items as $id => $value) {
- if (!isset($keys[$id])) {
- $id = key($keys);
- }
- $key = $keys[$id];
- unset($keys[$id]);
- yield $key => $f($key, $value, true);
- }
- } catch (\Exception $e) {
- CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]);
- }
-
- foreach ($keys as $key) {
- yield $key => $f($key, null, false);
- }
- }
}
diff --git a/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
new file mode 100644
index 0000000000..2eb5ce8c6e
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/AbstractTagAwareAdapter.php
@@ -0,0 +1,305 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\Log\LoggerAwareInterface;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\InvalidArgumentException;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
+use Symfony\Component\Cache\Traits\ContractsTrait;
+use Symfony\Contracts\Cache\TagAwareCacheInterface;
+
+/**
+ * Abstract for native TagAware adapters.
+ *
+ * To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids
+ * to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ *
+ * @internal
+ * @experimental in 4.3
+ */
+abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
+{
+ use AbstractAdapterTrait;
+ use ContractsTrait;
+
+ private const TAGS_PREFIX = "\0tags\0";
+
+ protected function __construct(string $namespace = '', int $defaultLifetime = 0)
+ {
+ $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
+ if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
+ throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
+ }
+ $this->createCacheItem = \Closure::bind(
+ static function ($key, $value, $isHit) use ($defaultLifetime) {
+ $item = new CacheItem();
+ $item->key = $key;
+ $item->defaultLifetime = $defaultLifetime;
+ $item->isTaggable = true;
+ // If structure does not match what we expect return item as is (no value and not a hit)
+ if (!\is_array($value) || !\array_key_exists('value', $value)) {
+ return $item;
+ }
+ $item->isHit = $isHit;
+ // Extract value, tags and meta data from the cache value
+ $item->value = $value['value'];
+ $item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
+ if (isset($value['meta'])) {
+ // For compactness these values are packed, & expiry is offset to reduce size
+ $v = \unpack('Ve/Nc', $value['meta']);
+ $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
+ $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
+ }
+
+ return $item;
+ },
+ null,
+ CacheItem::class
+ );
+ $getId = \Closure::fromCallable([$this, 'getId']);
+ $tagPrefix = self::TAGS_PREFIX;
+ $this->mergeByLifetime = \Closure::bind(
+ static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
+ $byLifetime = [];
+ $now = microtime(true);
+ $expiredIds = [];
+
+ foreach ($deferred as $key => $item) {
+ $key = (string) $key;
+ if (null === $item->expiry) {
+ $ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
+ } elseif (0 >= $ttl = (int) ($item->expiry - $now)) {
+ $expiredIds[] = $getId($key);
+ continue;
+ }
+ // Store Value and Tags on the cache value
+ if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
+ $value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
+ unset($metadata[CacheItem::METADATA_TAGS]);
+ } else {
+ $value = ['value' => $item->value, 'tags' => []];
+ }
+
+ if ($metadata) {
+ // For compactness, expiry and creation duration are packed, using magic numbers as separators
+ $value['meta'] = \pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME]);
+ }
+
+ // Extract tag changes, these should be removed from values in doSave()
+ $value['tag-operations'] = ['add' => [], 'remove' => []];
+ $oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
+ foreach (\array_diff($value['tags'], $oldTags) as $addedTag) {
+ $value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
+ }
+ foreach (\array_diff($oldTags, $value['tags']) as $removedTag) {
+ $value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
+ }
+
+ $byLifetime[$ttl][$getId($key)] = $value;
+ }
+
+ return $byLifetime;
+ },
+ null,
+ CacheItem::class
+ );
+ }
+
+ /**
+ * Persists several cache items immediately.
+ *
+ * @param array $values The values to cache, indexed by their cache identifier
+ * @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
+ * @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag
+ * @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
+ *
+ * @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
+ */
+ abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array;
+
+ /**
+ * Removes multiple items from the pool and their corresponding tags.
+ *
+ * @param array $ids An array of identifiers that should be removed from the pool
+ * @param array $tagData Optional array of tag identifiers => key identifiers that should be removed from the pool
+ *
+ * @return bool True if the items were successfully removed, false otherwise
+ */
+ abstract protected function doDelete(array $ids, array $tagData = []): bool;
+
+ /**
+ * Invalidates cached items using tags.
+ *
+ * @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
+ *
+ * @return bool True on success
+ */
+ abstract protected function doInvalidate(array $tagIds): bool;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commit()
+ {
+ $ok = true;
+ $byLifetime = $this->mergeByLifetime;
+ $byLifetime = $byLifetime($this->deferred, $expiredIds);
+ $retry = $this->deferred = [];
+
+ if ($expiredIds) {
+ // Tags are not cleaned up in this case, however that is done on invalidateTags().
+ $this->doDelete($expiredIds);
+ }
+ foreach ($byLifetime as $lifetime => $values) {
+ try {
+ $values = $this->extractTagData($values, $addTagData, $removeTagData);
+ $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ if (\is_array($e) || 1 === \count($values)) {
+ foreach (\is_array($e) ? $e : array_keys($values) as $id) {
+ $ok = false;
+ $v = $values[$id];
+ $type = \is_object($v) ? \get_class($v) : \gettype($v);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+ }
+ } else {
+ foreach ($values as $id => $v) {
+ $retry[$lifetime][] = $id;
+ }
+ }
+ }
+
+ // When bulk-save failed, retry each item individually
+ foreach ($retry as $lifetime => $ids) {
+ foreach ($ids as $id) {
+ try {
+ $v = $byLifetime[$lifetime][$id];
+ $values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
+ $e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
+ } catch (\Exception $e) {
+ }
+ if (true === $e || [] === $e) {
+ continue;
+ }
+ $ok = false;
+ $type = \is_object($v) ? \get_class($v) : \gettype($v);
+ $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Overloaded in order to deal with tags for adjusted doDelete() signature.
+ */
+ public function deleteItems(array $keys)
+ {
+ if (!$keys) {
+ return true;
+ }
+
+ $ids = [];
+ $tagData = [];
+
+ foreach ($keys as $key) {
+ $ids[$key] = $this->getId($key);
+ unset($this->deferred[$key]);
+ }
+
+ foreach ($this->doFetch($ids) as $id => $value) {
+ foreach ($value['tags'] ?? [] as $tag) {
+ $tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
+ }
+ }
+
+ try {
+ if ($this->doDelete(\array_values($ids), $tagData)) {
+ return true;
+ }
+ } catch (\Exception $e) {
+ }
+
+ $ok = true;
+
+ // When bulk-delete failed, retry each item individually
+ foreach ($ids as $key => $id) {
+ try {
+ $e = null;
+ if ($this->doDelete([$id])) {
+ continue;
+ }
+ } catch (\Exception $e) {
+ }
+ $message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
+ CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
+ $ok = false;
+ }
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function invalidateTags(array $tags)
+ {
+ if (empty($tags)) {
+ return false;
+ }
+
+ $tagIds = [];
+ foreach (\array_unique($tags) as $tag) {
+ $tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
+ }
+
+ if ($this->doInvalidate($tagIds)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
+ */
+ private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
+ {
+ $addTagData = $removeTagData = [];
+ foreach ($values as $id => $value) {
+ foreach ($value['tag-operations']['add'] as $tag => $tagId) {
+ $addTagData[$tagId][] = $id;
+ }
+
+ foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
+ $removeTagData[$tagId][] = $id;
+ }
+
+ unset($values[$id]['tag-operations']);
+ }
+
+ return $values;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
new file mode 100644
index 0000000000..f96c670ae9
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/FilesystemTagAwareAdapter.php
@@ -0,0 +1,149 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\Traits\FilesystemTrait;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ *
+ * @experimental in 4.3
+ */
+class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
+{
+ use FilesystemTrait {
+ doSave as doSaveCache;
+ doDelete as doDeleteCache;
+ }
+
+ /**
+ * Folder used for tag symlinks.
+ */
+ private const TAG_FOLDER = 'tags';
+
+ /**
+ * @var Filesystem|null
+ */
+ private $fs;
+
+ public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
+ {
+ $this->marshaller = $marshaller ?? new DefaultMarshaller();
+ parent::__construct('', $defaultLifetime);
+ $this->init($namespace, $directory);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array
+ {
+ $failed = $this->doSaveCache($values, $lifetime);
+
+ $fs = $this->getFilesystem();
+ // Add Tags as symlinks
+ foreach ($addTagData as $tagId => $ids) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($ids as $id) {
+ if ($failed && \in_array($id, $failed, true)) {
+ continue;
+ }
+
+ $file = $this->getFile($id);
+ $fs->symlink($file, $this->getFile($id, true, $tagFolder));
+ }
+ }
+
+ // Unlink removed Tags
+ $files = [];
+ foreach ($removeTagData as $tagId => $ids) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($ids as $id) {
+ if ($failed && \in_array($id, $failed, true)) {
+ continue;
+ }
+
+ $files[] = $this->getFile($id, false, $tagFolder);
+ }
+ }
+ $fs->remove($files);
+
+ return $failed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids, array $tagData = []): bool
+ {
+ $ok = $this->doDeleteCache($ids);
+
+ // Remove tags
+ $files = [];
+ $fs = $this->getFilesystem();
+ foreach ($tagData as $tagId => $idMap) {
+ $tagFolder = $this->getTagFolder($tagId);
+ foreach ($idMap as $id) {
+ $files[] = $this->getFile($id, false, $tagFolder);
+ }
+ }
+ $fs->remove($files);
+
+ return $ok;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doInvalidate(array $tagIds): bool
+ {
+ foreach ($tagIds as $tagId) {
+ $tagsFolder = $this->getTagFolder($tagId);
+ if (!file_exists($tagsFolder)) {
+ continue;
+ }
+
+ foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tagsFolder, \FilesystemIterator::SKIP_DOTS)) as $itemLink) {
+ if (!$itemLink->isLink()) {
+ throw new LogicException('Expected a (sym)link when iterating over tag folder, non link found: '.$itemLink);
+ }
+
+ $valueFile = $itemLink->getRealPath();
+ if ($valueFile && \file_exists($valueFile)) {
+ @unlink($valueFile);
+ }
+
+ @unlink((string) $itemLink);
+ }
+ }
+
+ return true;
+ }
+
+ private function getFilesystem(): Filesystem
+ {
+ return $this->fs ?? $this->fs = new Filesystem();
+ }
+
+ private function getTagFolder(string $tagId): string
+ {
+ return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/Psr16Adapter.php b/vendor/symfony/cache/Adapter/Psr16Adapter.php
new file mode 100644
index 0000000000..a14ee082be
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/Psr16Adapter.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Psr\SimpleCache\CacheInterface;
+use Symfony\Component\Cache\PruneableInterface;
+use Symfony\Component\Cache\ResettableInterface;
+use Symfony\Component\Cache\Traits\ProxyTrait;
+
+/**
+ * Turns a PSR-16 cache into a PSR-6 one.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
+{
+ use ProxyTrait;
+
+ private $miss;
+
+ public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
+ {
+ parent::__construct($namespace, $defaultLifetime);
+
+ $this->pool = $pool;
+ $this->miss = new \stdClass();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doFetch(array $ids)
+ {
+ foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
+ if ($this->miss !== $value) {
+ yield $key => $value;
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doHave($id)
+ {
+ return $this->pool->has($id);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doClear($namespace)
+ {
+ return $this->pool->clear();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids)
+ {
+ return $this->pool->deleteMultiple($ids);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, $lifetime)
+ {
+ return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
new file mode 100644
index 0000000000..4e09387213
--- /dev/null
+++ b/vendor/symfony/cache/Adapter/RedisTagAwareAdapter.php
@@ -0,0 +1,209 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Cache\Adapter;
+
+use Predis;
+use Predis\Connection\Aggregate\ClusterInterface;
+use Predis\Response\Status;
+use Symfony\Component\Cache\CacheItem;
+use Symfony\Component\Cache\Exception\LogicException;
+use Symfony\Component\Cache\Marshaller\MarshallerInterface;
+use Symfony\Component\Cache\Traits\RedisTrait;
+
+/**
+ * Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using sPOP.
+ *
+ * Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
+ * if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
+ * relationship survives eviction (cache cleanup when Redis runs out of memory).
+ *
+ * Requirements:
+ * - Server: Redis 3.2+
+ * - Client: PHP Redis 3.1.3+ OR Predis
+ * - Redis Server(s) configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
+ *
+ * Design limitations:
+ * - Max 2 billion cache keys per cache tag
+ * E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 2 billion cache items as well
+ *
+ * @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
+ * @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
+ * @see https://redis.io/commands/spop Documentation for sPOP operation, capable of retriving AND emptying a Set at once.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ * @author André Rømcke <andre.romcke+symfony@gmail.com>
+ *
+ * @experimental in 4.3
+ */
+class RedisTagAwareAdapter extends AbstractTagAwareAdapter
+{
+ use RedisTrait;
+
+ /**
+ * Redis "Set" can hold more than 4 billion members, here we limit ourselves to PHP's > 2 billion max int (32Bit).
+ */
+ private const POP_MAX_LIMIT = 2147483647 - 1;
+
+ /**
+ * Limits for how many keys are deleted in batch.
+ */
+ private const BULK_DELETE_LIMIT = 10000;
+
+ /**
+ * On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
+ * preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
+ */
+ private const DEFAULT_CACHE_TTL = 8640000;
+
+ /**
+ * @var bool|null
+ */
+ private $redisServerSupportSPOP = null;
+
+ /**
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient The redis client
+ * @param string $namespace The default namespace
+ * @param int $defaultLifetime The default lifetime
+ * @param MarshallerInterface|null $marshaller
+ *
+ * @throws \Symfony\Component\Cache\Exception\LogicException If phpredis with version lower than 3.1.3.
+ */
+ public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
+ {
+ $this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
+
+ // Make sure php-redis is 3.1.3 or higher configured for Redis classes
+ if (!$this->redis instanceof Predis\Client && \version_compare(\phpversion('redis'), '3.1.3', '<')) {
+ throw new LogicException('RedisTagAwareAdapter requires php-redis 3.1.3 or higher, alternatively use predis/predis');
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $delTagData = []): array
+ {
+ // serialize values
+ if (!$serialized = $this->marshaller->marshall($values, $failed)) {
+ return $failed;
+ }
+
+ // While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
+ $results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData) {
+ // Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
+ foreach ($serialized as $id => $value) {
+ yield 'setEx' => [
+ $id,
+ 0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime,
+ $value,
+ ];
+ }
+
+ // Add and Remove Tags
+ foreach ($addTagData as $tagId => $ids) {
+ yield 'sAdd' => array_merge([$tagId], $ids);
+ }
+
+ foreach ($delTagData as $tagId => $ids) {
+ yield 'sRem' => array_merge([$tagId], $ids);
+ }
+ });
+
+ foreach ($results as $id => $result) {
+ // Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not
+ if (\is_numeric($result)) {
+ continue;
+ }
+ // setEx results
+ if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
+ $failed[] = $id;
+ }
+ }
+
+ return $failed;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doDelete(array $ids, array $tagData = []): bool
+ {
+ if (!$ids) {
+ return true;
+ }
+
+ $predisCluster = $this->redis instanceof \Predis\Client && $this->redis->getConnection() instanceof ClusterInterface;
+ $this->pipeline(static function () use ($ids, $tagData, $predisCluster) {
+ if ($predisCluster) {
+ foreach ($ids as $id) {
+ yield 'del' => [$id];
+ }
+ } else {
+ yield 'del' => $ids;
+ }
+
+ foreach ($tagData as $tagId => $idList) {
+ yield 'sRem' => \array_merge([$tagId], $idList);
+ }
+ })->rewind();
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doInvalidate(array $tagIds): bool
+ {
+ if (!$this->redisServerSupportSPOP()) {
+ return false;
+ }
+
+ // Pop all tag info at once to avoid race conditions
+ $tagIdSets = $this->pipeline(static function () use ($tagIds) {
+ foreach ($tagIds as $tagId) {
+ // Client: Predis or PHP Redis 3.1.3+ (https://github.com/phpredis/phpredis/commit/d2e203a6)
+ // Server: Redis 3.2 or higher (https://redis.io/commands/spop)
+ yield 'sPop' => [$tagId, self::POP_MAX_LIMIT];
+ }
+ });
+
+ // Flatten generator result from pipeline, ignore keys (tag ids)
+ $ids = \array_unique(\array_merge(...\iterator_to_array($tagIdSets, false)));
+
+ // Delete cache in chunks to avoid overloading the connection
+ foreach (\array_chunk($ids, self::BULK_DELETE_LIMIT) as $chunkIds) {
+ $this->doDelete($chunkIds);
+ }
+
+ return true;
+ }
+
+ private function redisServerSupportSPOP(): bool
+ {
+ if (null !== $this->redisServerSupportSPOP) {
+ return $this->redisServerSupportSPOP;
+ }
+
+ foreach ($this->getHosts() as $host) {
+ $info = $host->info('Server');
+ $info = isset($info['Server']) ? $info['Server'] : $info;
+ if (version_compare($info['redis_version'], '3.2', '<')) {
+ CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as '.$info['redis_version']);
+
+ return $this->redisServerSupportSPOP = false;
+ }
+ }
+
+ return $this->redisServerSupportSPOP = true;
+ }
+}
diff --git a/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php b/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php
index 5a836a3dbf..d0d42e57f0 100644
--- a/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php
+++ b/vendor/symfony/cache/Adapter/SimpleCacheAdapter.php
@@ -11,68 +11,11 @@
namespace Symfony\Component\Cache\Adapter;
-use Psr\SimpleCache\CacheInterface;
-use Symfony\Component\Cache\PruneableInterface;
-use Symfony\Component\Cache\Traits\ProxyTrait;
+@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), E_USER_DEPRECATED);
/**
- * @author Nicolas Grekas <p@tchwork.com>
+ * @deprecated since Symfony 4.3, use Psr16Adapter instead.
*/
-class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
+class SimpleCacheAdapter extends Psr16Adapter
{
- use ProxyTrait;
-
- private $miss;
-
- public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
- {
- parent::__construct($namespace, $defaultLifetime);
-
- $this->pool = $pool;
- $this->miss = new \stdClass();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doFetch(array $ids)
- {
- foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
- if ($this->miss !== $value) {
- yield $key => $value;
- }
- }
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doHave($id)
- {
- return $this->pool->has($id);
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doClear($namespace)
- {
- return $this->pool->clear();
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doDelete(array $ids)
- {
- return $this->pool->deleteMultiple($ids);
- }
-
- /**
- * {@inheritdoc}
- */
- protected function doSave(array $values, $lifetime)
- {
- return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
- }
}