vendor/symfony/cache/Adapter/TagAwareAdapter.php line 223

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\Adapter;
  11. use Psr\Cache\CacheItemInterface;
  12. use Psr\Cache\InvalidArgumentException;
  13. use Psr\Log\LoggerAwareInterface;
  14. use Psr\Log\LoggerAwareTrait;
  15. use Symfony\Component\Cache\CacheItem;
  16. use Symfony\Component\Cache\PruneableInterface;
  17. use Symfony\Component\Cache\ResettableInterface;
  18. use Symfony\Component\Cache\Traits\ContractsTrait;
  19. use Symfony\Component\Cache\Traits\ProxyTrait;
  20. use Symfony\Contracts\Cache\TagAwareCacheInterface;
  21. /**
  22.  * @author Nicolas Grekas <p@tchwork.com>
  23.  */
  24. class TagAwareAdapter implements TagAwareAdapterInterfaceTagAwareCacheInterfacePruneableInterfaceResettableInterfaceLoggerAwareInterface
  25. {
  26.     use ContractsTrait;
  27.     use LoggerAwareTrait;
  28.     use ProxyTrait;
  29.     public const TAGS_PREFIX "\0tags\0";
  30.     private $deferred = [];
  31.     private $createCacheItem;
  32.     private $setCacheItemTags;
  33.     private $getTagsByKey;
  34.     private $invalidateTags;
  35.     private $tags;
  36.     private $knownTagVersions = [];
  37.     private $knownTagVersionsTtl;
  38.     public function __construct(AdapterInterface $itemsPoolAdapterInterface $tagsPool nullfloat $knownTagVersionsTtl 0.15)
  39.     {
  40.         $this->pool $itemsPool;
  41.         $this->tags $tagsPool ?: $itemsPool;
  42.         $this->knownTagVersionsTtl $knownTagVersionsTtl;
  43.         $this->createCacheItem = \Closure::bind(
  44.             static function ($key$valueCacheItem $protoItem) {
  45.                 $item = new CacheItem();
  46.                 $item->key $key;
  47.                 $item->value $value;
  48.                 $item->expiry $protoItem->expiry;
  49.                 $item->poolHash $protoItem->poolHash;
  50.                 return $item;
  51.             },
  52.             null,
  53.             CacheItem::class
  54.         );
  55.         $this->setCacheItemTags = \Closure::bind(
  56.             static function (CacheItem $item$key, array &$itemTags) {
  57.                 $item->isTaggable true;
  58.                 if (!$item->isHit) {
  59.                     return $item;
  60.                 }
  61.                 if (isset($itemTags[$key])) {
  62.                     foreach ($itemTags[$key] as $tag => $version) {
  63.                         $item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
  64.                     }
  65.                     unset($itemTags[$key]);
  66.                 } else {
  67.                     $item->value null;
  68.                     $item->isHit false;
  69.                 }
  70.                 return $item;
  71.             },
  72.             null,
  73.             CacheItem::class
  74.         );
  75.         $this->getTagsByKey = \Closure::bind(
  76.             static function ($deferred) {
  77.                 $tagsByKey = [];
  78.                 foreach ($deferred as $key => $item) {
  79.                     $tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
  80.                     $item->metadata $item->newMetadata;
  81.                 }
  82.                 return $tagsByKey;
  83.             },
  84.             null,
  85.             CacheItem::class
  86.         );
  87.         $this->invalidateTags = \Closure::bind(
  88.             static function (AdapterInterface $tagsAdapter, array $tags) {
  89.                 foreach ($tags as $v) {
  90.                     $v->expiry 0;
  91.                     $tagsAdapter->saveDeferred($v);
  92.                 }
  93.                 return $tagsAdapter->commit();
  94.             },
  95.             null,
  96.             CacheItem::class
  97.         );
  98.     }
  99.     /**
  100.      * {@inheritdoc}
  101.      */
  102.     public function invalidateTags(array $tags)
  103.     {
  104.         $ok true;
  105.         $tagsByKey = [];
  106.         $invalidatedTags = [];
  107.         foreach ($tags as $tag) {
  108.             CacheItem::validateKey($tag);
  109.             $invalidatedTags[$tag] = 0;
  110.         }
  111.         if ($this->deferred) {
  112.             $items $this->deferred;
  113.             foreach ($items as $key => $item) {
  114.                 if (!$this->pool->saveDeferred($item)) {
  115.                     unset($this->deferred[$key]);
  116.                     $ok false;
  117.                 }
  118.             }
  119.             $f $this->getTagsByKey;
  120.             $tagsByKey $f($items);
  121.             $this->deferred = [];
  122.         }
  123.         $tagVersions $this->getTagVersions($tagsByKey$invalidatedTags);
  124.         $f $this->createCacheItem;
  125.         foreach ($tagsByKey as $key => $tags) {
  126.             $this->pool->saveDeferred($f(static::TAGS_PREFIX.$keyarray_intersect_key($tagVersions$tags), $items[$key]));
  127.         }
  128.         $ok $this->pool->commit() && $ok;
  129.         if ($invalidatedTags) {
  130.             $f $this->invalidateTags;
  131.             $ok $f($this->tags$invalidatedTags) && $ok;
  132.         }
  133.         return $ok;
  134.     }
  135.     /**
  136.      * {@inheritdoc}
  137.      *
  138.      * @return bool
  139.      */
  140.     public function hasItem($key)
  141.     {
  142.         if (\is_string($key) && isset($this->deferred[$key])) {
  143.             $this->commit();
  144.         }
  145.         if (!$this->pool->hasItem($key)) {
  146.             return false;
  147.         }
  148.         $itemTags $this->pool->getItem(static::TAGS_PREFIX.$key);
  149.         if (!$itemTags->isHit()) {
  150.             return false;
  151.         }
  152.         if (!$itemTags $itemTags->get()) {
  153.             return true;
  154.         }
  155.         foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
  156.             if ($itemTags[$tag] === $version || \is_int($itemTags[$tag]) && \is_int($version) && === $itemTags[$tag] - $version) {
  157.                 continue;
  158.             }
  159.             return false;
  160.         }
  161.         return true;
  162.     }
  163.     /**
  164.      * {@inheritdoc}
  165.      */
  166.     public function getItem($key)
  167.     {
  168.         foreach ($this->getItems([$key]) as $item) {
  169.             return $item;
  170.         }
  171.         return null;
  172.     }
  173.     /**
  174.      * {@inheritdoc}
  175.      */
  176.     public function getItems(array $keys = [])
  177.     {
  178.         $tagKeys = [];
  179.         $commit false;
  180.         foreach ($keys as $key) {
  181.             if ('' !== $key && \is_string($key)) {
  182.                 $commit $commit || isset($this->deferred[$key]);
  183.                 $key = static::TAGS_PREFIX.$key;
  184.                 $tagKeys[$key] = $key;
  185.             }
  186.         }
  187.         if ($commit) {
  188.             $this->commit();
  189.         }
  190.         try {
  191.             $items $this->pool->getItems($tagKeys $keys);
  192.         } catch (InvalidArgumentException $e) {
  193.             $this->pool->getItems($keys); // Should throw an exception
  194.             throw $e;
  195.         }
  196.         return $this->generateItems($items$tagKeys);
  197.     }
  198.     /**
  199.      * {@inheritdoc}
  200.      *
  201.      * @param string $prefix
  202.      *
  203.      * @return bool
  204.      */
  205.     public function clear(/* string $prefix = '' */)
  206.     {
  207.         $prefix < \func_num_args() ? (string) func_get_arg(0) : '';
  208.         if ('' !== $prefix) {
  209.             foreach ($this->deferred as $key => $item) {
  210.                 if (str_starts_with($key$prefix)) {
  211.                     unset($this->deferred[$key]);
  212.                 }
  213.             }
  214.         } else {
  215.             $this->deferred = [];
  216.         }
  217.         if ($this->pool instanceof AdapterInterface) {
  218.             return $this->pool->clear($prefix);
  219.         }
  220.         return $this->pool->clear();
  221.     }
  222.     /**
  223.      * {@inheritdoc}
  224.      *
  225.      * @return bool
  226.      */
  227.     public function deleteItem($key)
  228.     {
  229.         return $this->deleteItems([$key]);
  230.     }
  231.     /**
  232.      * {@inheritdoc}
  233.      *
  234.      * @return bool
  235.      */
  236.     public function deleteItems(array $keys)
  237.     {
  238.         foreach ($keys as $key) {
  239.             if ('' !== $key && \is_string($key)) {
  240.                 $keys[] = static::TAGS_PREFIX.$key;
  241.             }
  242.         }
  243.         return $this->pool->deleteItems($keys);
  244.     }
  245.     /**
  246.      * {@inheritdoc}
  247.      *
  248.      * @return bool
  249.      */
  250.     public function save(CacheItemInterface $item)
  251.     {
  252.         if (!$item instanceof CacheItem) {
  253.             return false;
  254.         }
  255.         $this->deferred[$item->getKey()] = $item;
  256.         return $this->commit();
  257.     }
  258.     /**
  259.      * {@inheritdoc}
  260.      *
  261.      * @return bool
  262.      */
  263.     public function saveDeferred(CacheItemInterface $item)
  264.     {
  265.         if (!$item instanceof CacheItem) {
  266.             return false;
  267.         }
  268.         $this->deferred[$item->getKey()] = $item;
  269.         return true;
  270.     }
  271.     /**
  272.      * {@inheritdoc}
  273.      *
  274.      * @return bool
  275.      */
  276.     public function commit()
  277.     {
  278.         return $this->invalidateTags([]);
  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.         $this->commit();
  294.     }
  295.     private function generateItems(iterable $items, array $tagKeys)
  296.     {
  297.         $bufferedItems $itemTags = [];
  298.         $f $this->setCacheItemTags;
  299.         foreach ($items as $key => $item) {
  300.             if (!$tagKeys) {
  301.                 yield $key => $f($item, static::TAGS_PREFIX.$key$itemTags);
  302.                 continue;
  303.             }
  304.             if (!isset($tagKeys[$key])) {
  305.                 $bufferedItems[$key] = $item;
  306.                 continue;
  307.             }
  308.             unset($tagKeys[$key]);
  309.             if ($item->isHit()) {
  310.                 $itemTags[$key] = $item->get() ?: [];
  311.             }
  312.             if (!$tagKeys) {
  313.                 $tagVersions $this->getTagVersions($itemTags);
  314.                 foreach ($itemTags as $key => $tags) {
  315.                     foreach ($tags as $tag => $version) {
  316.                         if ($tagVersions[$tag] === $version || \is_int($version) && \is_int($tagVersions[$tag]) && === $version $tagVersions[$tag]) {
  317.                             continue;
  318.                         }
  319.                         unset($itemTags[$key]);
  320.                         continue 2;
  321.                     }
  322.                 }
  323.                 $tagVersions $tagKeys null;
  324.                 foreach ($bufferedItems as $key => $item) {
  325.                     yield $key => $f($item, static::TAGS_PREFIX.$key$itemTags);
  326.                 }
  327.                 $bufferedItems null;
  328.             }
  329.         }
  330.     }
  331.     private function getTagVersions(array $tagsByKey, array &$invalidatedTags = [])
  332.     {
  333.         $tagVersions $invalidatedTags;
  334.         foreach ($tagsByKey as $tags) {
  335.             $tagVersions += $tags;
  336.         }
  337.         if (!$tagVersions) {
  338.             return [];
  339.         }
  340.         if (!$fetchTagVersions !== \func_num_args()) {
  341.             foreach ($tagsByKey as $tags) {
  342.                 foreach ($tags as $tag => $version) {
  343.                     if ($tagVersions[$tag] > $version) {
  344.                         $tagVersions[$tag] = $version;
  345.                     }
  346.                 }
  347.             }
  348.         }
  349.         $now microtime(true);
  350.         $tags = [];
  351.         foreach ($tagVersions as $tag => $version) {
  352.             $tags[$tag.static::TAGS_PREFIX] = $tag;
  353.             if ($fetchTagVersions || !isset($this->knownTagVersions[$tag]) || !\is_int($version)) {
  354.                 $fetchTagVersions true;
  355.                 continue;
  356.             }
  357.             $version -= $this->knownTagVersions[$tag][1];
  358.             if ((!== $version && !== $version) || $now $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
  359.                 // reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises
  360.                 $fetchTagVersions true;
  361.             } else {
  362.                 $this->knownTagVersions[$tag][1] += $version;
  363.             }
  364.         }
  365.         if (!$fetchTagVersions) {
  366.             return $tagVersions;
  367.         }
  368.         foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
  369.             $tagVersions[$tag $tags[$tag]] = $version->get() ?: 0;
  370.             if (isset($invalidatedTags[$tag])) {
  371.                 $invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]);
  372.             }
  373.             if (!\is_int($tagVersions[$tag])) {
  374.                 unset($this->knownTagVersions[$tag]);
  375.                 continue;
  376.             }
  377.             $this->knownTagVersions[$tag] = [$now$tagVersions[$tag]];
  378.         }
  379.         return $tagVersions;
  380.     }
  381. }