vendor/symfony/cache/Traits/AbstractAdapterTrait.php line 181

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Cache\Traits;
  11. use Psr\Cache\CacheItemInterface;
  12. use Psr\Log\LoggerAwareTrait;
  13. use Symfony\Component\Cache\CacheItem;
  14. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  15. /**
  16.  * @author Nicolas Grekas <p@tchwork.com>
  17.  *
  18.  * @internal
  19.  */
  20. trait AbstractAdapterTrait
  21. {
  22.     use LoggerAwareTrait;
  23.     /**
  24.      * needs to be set by class, signature is function(string <key>, mixed <value>, bool <isHit>).
  25.      */
  26.     private static \Closure $createCacheItem;
  27.     /**
  28.      * needs to be set by class, signature is function(array <deferred>, string <namespace>, array <&expiredIds>).
  29.      */
  30.     private static \Closure $mergeByLifetime;
  31.     private string $namespace '';
  32.     private int $defaultLifetime;
  33.     private string $namespaceVersion '';
  34.     private bool $versioningIsEnabled false;
  35.     private array $deferred = [];
  36.     private array $ids = [];
  37.     /**
  38.      * @var int|null The maximum length to enforce for identifiers or null when no limit applies
  39.      */
  40.     protected $maxIdLength;
  41.     /**
  42.      * Fetches several cache items.
  43.      *
  44.      * @param array $ids The cache identifiers to fetch
  45.      */
  46.     abstract protected function doFetch(array $ids): iterable;
  47.     /**
  48.      * Confirms if the cache contains specified cache item.
  49.      *
  50.      * @param string $id The identifier for which to check existence
  51.      */
  52.     abstract protected function doHave(string $id): bool;
  53.     /**
  54.      * Deletes all items in the pool.
  55.      *
  56.      * @param string $namespace The prefix used for all identifiers managed by this pool
  57.      */
  58.     abstract protected function doClear(string $namespace): bool;
  59.     /**
  60.      * Removes multiple items from the pool.
  61.      *
  62.      * @param array $ids An array of identifiers that should be removed from the pool
  63.      */
  64.     abstract protected function doDelete(array $ids): bool;
  65.     /**
  66.      * Persists several cache items immediately.
  67.      *
  68.      * @param array $values   The values to cache, indexed by their cache identifier
  69.      * @param int   $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
  70.      *
  71.      * @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
  72.      */
  73.     abstract protected function doSave(array $valuesint $lifetime): array|bool;
  74.     public function hasItem(mixed $key): bool
  75.     {
  76.         $id $this->getId($key);
  77.         if (isset($this->deferred[$key])) {
  78.             $this->commit();
  79.         }
  80.         try {
  81.             return $this->doHave($id);
  82.         } catch (\Exception $e) {
  83.             CacheItem::log($this->logger'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key'exception' => $e'cache-adapter' => get_debug_type($this)]);
  84.             return false;
  85.         }
  86.     }
  87.     public function clear(string $prefix ''): bool
  88.     {
  89.         $this->deferred = [];
  90.         if ($cleared $this->versioningIsEnabled) {
  91.             if ('' === $namespaceVersionToClear $this->namespaceVersion) {
  92.                 foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
  93.                     $namespaceVersionToClear $v;
  94.                 }
  95.             }
  96.             $namespaceToClear $this->namespace.$namespaceVersionToClear;
  97.             $namespaceVersion self::formatNamespaceVersion(mt_rand());
  98.             try {
  99.                 $e $this->doSave([static::NS_SEPARATOR.$this->namespace => $namespaceVersion], 0);
  100.             } catch (\Exception $e) {
  101.             }
  102.             if (true !== $e && [] !== $e) {
  103.                 $cleared false;
  104.                 $message 'Failed to save the new namespace'.($e instanceof \Exception ': '.$e->getMessage() : '.');
  105.                 CacheItem::log($this->logger$message, ['exception' => $e instanceof \Exception $e null'cache-adapter' => get_debug_type($this)]);
  106.             } else {
  107.                 $this->namespaceVersion $namespaceVersion;
  108.                 $this->ids = [];
  109.             }
  110.         } else {
  111.             $namespaceToClear $this->namespace.$prefix;
  112.         }
  113.         try {
  114.             return $this->doClear($namespaceToClear) || $cleared;
  115.         } catch (\Exception $e) {
  116.             CacheItem::log($this->logger'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e'cache-adapter' => get_debug_type($this)]);
  117.             return false;
  118.         }
  119.     }
  120.     public function deleteItem(mixed $key): bool
  121.     {
  122.         return $this->deleteItems([$key]);
  123.     }
  124.     public function deleteItems(array $keys): bool
  125.     {
  126.         $ids = [];
  127.         foreach ($keys as $key) {
  128.             $ids[$key] = $this->getId($key);
  129.             unset($this->deferred[$key]);
  130.         }
  131.         try {
  132.             if ($this->doDelete($ids)) {
  133.                 return true;
  134.             }
  135.         } catch (\Exception) {
  136.         }
  137.         $ok true;
  138.         // When bulk-delete failed, retry each item individually
  139.         foreach ($ids as $key => $id) {
  140.             try {
  141.                 $e null;
  142.                 if ($this->doDelete([$id])) {
  143.                     continue;
  144.                 }
  145.             } catch (\Exception $e) {
  146.             }
  147.             $message 'Failed to delete key "{key}"'.($e instanceof \Exception ': '.$e->getMessage() : '.');
  148.             CacheItem::log($this->logger$message, ['key' => $key'exception' => $e'cache-adapter' => get_debug_type($this)]);
  149.             $ok false;
  150.         }
  151.         return $ok;
  152.     }
  153.     public function getItem(mixed $key): CacheItem
  154.     {
  155.         $id $this->getId($key);
  156.         if (isset($this->deferred[$key])) {
  157.             $this->commit();
  158.         }
  159.         $isHit false;
  160.         $value null;
  161.         try {
  162.             foreach ($this->doFetch([$id]) as $value) {
  163.                 $isHit true;
  164.             }
  165.             return (self::$createCacheItem)($key$value$isHit);
  166.         } catch (\Exception $e) {
  167.             CacheItem::log($this->logger'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key'exception' => $e'cache-adapter' => get_debug_type($this)]);
  168.         }
  169.         return (self::$createCacheItem)($keynullfalse);
  170.     }
  171.     public function getItems(array $keys = []): iterable
  172.     {
  173.         $ids = [];
  174.         $commit false;
  175.         foreach ($keys as $key) {
  176.             $ids[] = $this->getId($key);
  177.             $commit $commit || isset($this->deferred[$key]);
  178.         }
  179.         if ($commit) {
  180.             $this->commit();
  181.         }
  182.         try {
  183.             $items $this->doFetch($ids);
  184.         } catch (\Exception $e) {
  185.             CacheItem::log($this->logger'Failed to fetch items: '.$e->getMessage(), ['keys' => $keys'exception' => $e'cache-adapter' => get_debug_type($this)]);
  186.             $items = [];
  187.         }
  188.         $ids array_combine($ids$keys);
  189.         return $this->generateItems($items$ids);
  190.     }
  191.     public function save(CacheItemInterface $item): bool
  192.     {
  193.         if (!$item instanceof CacheItem) {
  194.             return false;
  195.         }
  196.         $this->deferred[$item->getKey()] = $item;
  197.         return $this->commit();
  198.     }
  199.     public function saveDeferred(CacheItemInterface $item): bool
  200.     {
  201.         if (!$item instanceof CacheItem) {
  202.             return false;
  203.         }
  204.         $this->deferred[$item->getKey()] = $item;
  205.         return true;
  206.     }
  207.     /**
  208.      * Enables/disables versioning of items.
  209.      *
  210.      * When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed,
  211.      * but old keys may need garbage collection and extra round-trips to the back-end are required.
  212.      *
  213.      * Calling this method also clears the memoized namespace version and thus forces a resynchronization of it.
  214.      *
  215.      * @return bool the previous state of versioning
  216.      */
  217.     public function enableVersioning(bool $enable true): bool
  218.     {
  219.         $wasEnabled $this->versioningIsEnabled;
  220.         $this->versioningIsEnabled $enable;
  221.         $this->namespaceVersion '';
  222.         $this->ids = [];
  223.         return $wasEnabled;
  224.     }
  225.     public function reset(): void
  226.     {
  227.         if ($this->deferred) {
  228.             $this->commit();
  229.         }
  230.         $this->namespaceVersion '';
  231.         $this->ids = [];
  232.     }
  233.     public function __sleep(): array
  234.     {
  235.         throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  236.     }
  237.     /**
  238.      * @return void
  239.      */
  240.     public function __wakeup()
  241.     {
  242.         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  243.     }
  244.     public function __destruct()
  245.     {
  246.         if ($this->deferred) {
  247.             $this->commit();
  248.         }
  249.     }
  250.     private function generateItems(iterable $items, array &$keys): \Generator
  251.     {
  252.         $f self::$createCacheItem;
  253.         try {
  254.             foreach ($items as $id => $value) {
  255.                 if (!isset($keys[$id])) {
  256.                     throw new InvalidArgumentException(\sprintf('Could not match value id "%s" to keys "%s".'$idimplode('", "'$keys)));
  257.                 }
  258.                 $key $keys[$id];
  259.                 unset($keys[$id]);
  260.                 yield $key => $f($key$valuetrue);
  261.             }
  262.         } catch (\Exception $e) {
  263.             CacheItem::log($this->logger'Failed to fetch items: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e'cache-adapter' => get_debug_type($this)]);
  264.         }
  265.         foreach ($keys as $key) {
  266.             yield $key => $f($keynullfalse);
  267.         }
  268.     }
  269.     /**
  270.      * @internal
  271.      */
  272.     protected function getId(mixed $key): string
  273.     {
  274.         if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
  275.             $this->ids = [];
  276.             $this->namespaceVersion '1'.static::NS_SEPARATOR;
  277.             try {
  278.                 foreach ($this->doFetch([static::NS_SEPARATOR.$this->namespace]) as $v) {
  279.                     $this->namespaceVersion $v;
  280.                 }
  281.                 $e true;
  282.                 if ('1'.static::NS_SEPARATOR === $this->namespaceVersion) {
  283.                     $this->namespaceVersion self::formatNamespaceVersion(time());
  284.                     $e $this->doSave([static::NS_SEPARATOR.$this->namespace => $this->namespaceVersion], 0);
  285.                 }
  286.             } catch (\Exception $e) {
  287.             }
  288.             if (true !== $e && [] !== $e) {
  289.                 $message 'Failed to save the new namespace'.($e instanceof \Exception ': '.$e->getMessage() : '.');
  290.                 CacheItem::log($this->logger$message, ['exception' => $e instanceof \Exception $e null'cache-adapter' => get_debug_type($this)]);
  291.             }
  292.         }
  293.         if (\is_string($key) && isset($this->ids[$key])) {
  294.             return $this->namespace.$this->namespaceVersion.$this->ids[$key];
  295.         }
  296.         \assert('' !== CacheItem::validateKey($key));
  297.         $this->ids[$key] = $key;
  298.         if (\count($this->ids) > 1000) {
  299.             $this->ids \array_slice($this->ids500nulltrue); // stop memory leak if there are many keys
  300.         }
  301.         if (null === $this->maxIdLength) {
  302.             return $this->namespace.$this->namespaceVersion.$key;
  303.         }
  304.         if (\strlen($id $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
  305.             // Use xxh128 to favor speed over security, which is not an issue here
  306.             $this->ids[$key] = $id substr_replace(base64_encode(hash('xxh128'$keytrue)), static::NS_SEPARATOR, -(\strlen($this->namespaceVersion) + 2));
  307.             $id $this->namespace.$this->namespaceVersion.$id;
  308.         }
  309.         return $id;
  310.     }
  311.     /**
  312.      * @internal
  313.      */
  314.     public static function handleUnserializeCallback(string $class): never
  315.     {
  316.         throw new \DomainException('Class not found: '.$class);
  317.     }
  318.     private static function formatNamespaceVersion(int $value): string
  319.     {
  320.         return strtr(substr_replace(base64_encode(pack('V'$value)), static::NS_SEPARATOR5), '/''_');
  321.     }
  322. }