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

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 $tags;
  32.     private $knownTagVersions = [];
  33.     private $knownTagVersionsTtl;
  34.     private static $createCacheItem;
  35.     private static $setCacheItemTags;
  36.     private static $getTagsByKey;
  37.     private static $invalidateTags;
  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.         self::$createCacheItem ?? self::$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.         self::$setCacheItemTags ?? self::$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.         self::$getTagsByKey ?? self::$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.         self::$invalidateTags ?? self::$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.             \assert('' !== 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.             $tagsByKey = (self::$getTagsByKey)($items);
  120.             $this->deferred = [];
  121.         }
  122.         $tagVersions $this->getTagVersions($tagsByKey$invalidatedTags);
  123.         $f self::$createCacheItem;
  124.         foreach ($tagsByKey as $key => $tags) {
  125.             $this->pool->saveDeferred($f(static::TAGS_PREFIX.$keyarray_intersect_key($tagVersions$tags), $items[$key]));
  126.         }
  127.         $ok $this->pool->commit() && $ok;
  128.         if ($invalidatedTags) {
  129.             $ok = (self::$invalidateTags)($this->tags$invalidatedTags) && $ok;
  130.         }
  131.         return $ok;
  132.     }
  133.     /**
  134.      * {@inheritdoc}
  135.      *
  136.      * @return bool
  137.      */
  138.     public function hasItem($key)
  139.     {
  140.         if (\is_string($key) && isset($this->deferred[$key])) {
  141.             $this->commit();
  142.         }
  143.         if (!$this->pool->hasItem($key)) {
  144.             return false;
  145.         }
  146.         $itemTags $this->pool->getItem(static::TAGS_PREFIX.$key);
  147.         if (!$itemTags->isHit()) {
  148.             return false;
  149.         }
  150.         if (!$itemTags $itemTags->get()) {
  151.             return true;
  152.         }
  153.         foreach ($this->getTagVersions([$itemTags]) as $tag => $version) {
  154.             if ($itemTags[$tag] !== $version && !== $itemTags[$tag] - $version) {
  155.                 return false;
  156.             }
  157.         }
  158.         return true;
  159.     }
  160.     /**
  161.      * {@inheritdoc}
  162.      */
  163.     public function getItem($key)
  164.     {
  165.         foreach ($this->getItems([$key]) as $item) {
  166.             return $item;
  167.         }
  168.         return null;
  169.     }
  170.     /**
  171.      * {@inheritdoc}
  172.      */
  173.     public function getItems(array $keys = [])
  174.     {
  175.         $tagKeys = [];
  176.         $commit false;
  177.         foreach ($keys as $key) {
  178.             if ('' !== $key && \is_string($key)) {
  179.                 $commit $commit || isset($this->deferred[$key]);
  180.                 $key = static::TAGS_PREFIX.$key;
  181.                 $tagKeys[$key] = $key;
  182.             }
  183.         }
  184.         if ($commit) {
  185.             $this->commit();
  186.         }
  187.         try {
  188.             $items $this->pool->getItems($tagKeys $keys);
  189.         } catch (InvalidArgumentException $e) {
  190.             $this->pool->getItems($keys); // Should throw an exception
  191.             throw $e;
  192.         }
  193.         return $this->generateItems($items$tagKeys);
  194.     }
  195.     /**
  196.      * {@inheritdoc}
  197.      *
  198.      * @return bool
  199.      */
  200.     public function clear(string $prefix '')
  201.     {
  202.         if ('' !== $prefix) {
  203.             foreach ($this->deferred as $key => $item) {
  204.                 if (str_starts_with($key$prefix)) {
  205.                     unset($this->deferred[$key]);
  206.                 }
  207.             }
  208.         } else {
  209.             $this->deferred = [];
  210.         }
  211.         if ($this->pool instanceof AdapterInterface) {
  212.             return $this->pool->clear($prefix);
  213.         }
  214.         return $this->pool->clear();
  215.     }
  216.     /**
  217.      * {@inheritdoc}
  218.      *
  219.      * @return bool
  220.      */
  221.     public function deleteItem($key)
  222.     {
  223.         return $this->deleteItems([$key]);
  224.     }
  225.     /**
  226.      * {@inheritdoc}
  227.      *
  228.      * @return bool
  229.      */
  230.     public function deleteItems(array $keys)
  231.     {
  232.         foreach ($keys as $key) {
  233.             if ('' !== $key && \is_string($key)) {
  234.                 $keys[] = static::TAGS_PREFIX.$key;
  235.             }
  236.         }
  237.         return $this->pool->deleteItems($keys);
  238.     }
  239.     /**
  240.      * {@inheritdoc}
  241.      *
  242.      * @return bool
  243.      */
  244.     public function save(CacheItemInterface $item)
  245.     {
  246.         if (!$item instanceof CacheItem) {
  247.             return false;
  248.         }
  249.         $this->deferred[$item->getKey()] = $item;
  250.         return $this->commit();
  251.     }
  252.     /**
  253.      * {@inheritdoc}
  254.      *
  255.      * @return bool
  256.      */
  257.     public function saveDeferred(CacheItemInterface $item)
  258.     {
  259.         if (!$item instanceof CacheItem) {
  260.             return false;
  261.         }
  262.         $this->deferred[$item->getKey()] = $item;
  263.         return true;
  264.     }
  265.     /**
  266.      * {@inheritdoc}
  267.      *
  268.      * @return bool
  269.      */
  270.     public function commit()
  271.     {
  272.         return $this->invalidateTags([]);
  273.     }
  274.     /**
  275.      * @return array
  276.      */
  277.     public function __sleep()
  278.     {
  279.         throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  280.     }
  281.     public function __wakeup()
  282.     {
  283.         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  284.     }
  285.     public function __destruct()
  286.     {
  287.         $this->commit();
  288.     }
  289.     private function generateItems(iterable $items, array $tagKeys): \Generator
  290.     {
  291.         $bufferedItems $itemTags = [];
  292.         $f self::$setCacheItemTags;
  293.         foreach ($items as $key => $item) {
  294.             if (!$tagKeys) {
  295.                 yield $key => $f($item, static::TAGS_PREFIX.$key$itemTags);
  296.                 continue;
  297.             }
  298.             if (!isset($tagKeys[$key])) {
  299.                 $bufferedItems[$key] = $item;
  300.                 continue;
  301.             }
  302.             unset($tagKeys[$key]);
  303.             if ($item->isHit()) {
  304.                 $itemTags[$key] = $item->get() ?: [];
  305.             }
  306.             if (!$tagKeys) {
  307.                 $tagVersions $this->getTagVersions($itemTags);
  308.                 foreach ($itemTags as $key => $tags) {
  309.                     foreach ($tags as $tag => $version) {
  310.                         if ($tagVersions[$tag] !== $version && !== $version $tagVersions[$tag]) {
  311.                             unset($itemTags[$key]);
  312.                             continue 2;
  313.                         }
  314.                     }
  315.                 }
  316.                 $tagVersions $tagKeys null;
  317.                 foreach ($bufferedItems as $key => $item) {
  318.                     yield $key => $f($item, static::TAGS_PREFIX.$key$itemTags);
  319.                 }
  320.                 $bufferedItems null;
  321.             }
  322.         }
  323.     }
  324.     private function getTagVersions(array $tagsByKey, array &$invalidatedTags = [])
  325.     {
  326.         $tagVersions $invalidatedTags;
  327.         foreach ($tagsByKey as $tags) {
  328.             $tagVersions += $tags;
  329.         }
  330.         if (!$tagVersions) {
  331.             return [];
  332.         }
  333.         if (!$fetchTagVersions !== \func_num_args()) {
  334.             foreach ($tagsByKey as $tags) {
  335.                 foreach ($tags as $tag => $version) {
  336.                     if ($tagVersions[$tag] > $version) {
  337.                         $tagVersions[$tag] = $version;
  338.                     }
  339.                 }
  340.             }
  341.         }
  342.         $now microtime(true);
  343.         $tags = [];
  344.         foreach ($tagVersions as $tag => $version) {
  345.             $tags[$tag.static::TAGS_PREFIX] = $tag;
  346.             if ($fetchTagVersions || !isset($this->knownTagVersions[$tag])) {
  347.                 $fetchTagVersions true;
  348.                 continue;
  349.             }
  350.             $version -= $this->knownTagVersions[$tag][1];
  351.             if ((!== $version && !== $version) || $now $this->knownTagVersions[$tag][0] >= $this->knownTagVersionsTtl) {
  352.                 // reuse previously fetched tag versions up to the ttl, unless we are storing items or a potential miss arises
  353.                 $fetchTagVersions true;
  354.             } else {
  355.                 $this->knownTagVersions[$tag][1] += $version;
  356.             }
  357.         }
  358.         if (!$fetchTagVersions) {
  359.             return $tagVersions;
  360.         }
  361.         foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
  362.             $tagVersions[$tag $tags[$tag]] = $version->get() ?: 0;
  363.             if (isset($invalidatedTags[$tag])) {
  364.                 $invalidatedTags[$tag] = $version->set(++$tagVersions[$tag]);
  365.             }
  366.             $this->knownTagVersions[$tag] = [$now$tagVersions[$tag]];
  367.         }
  368.         return $tagVersions;
  369.     }
  370. }