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

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