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

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