Вход Регистрация
Файл: vendor/symfony/cache/Adapter/TagAwareAdapter.php
Строк: 382
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SymfonyComponentCacheAdapter;

use 
PsrCacheCacheItemInterface;
use 
PsrCacheInvalidArgumentException;
use 
PsrLogLoggerAwareInterface;
use 
PsrLogLoggerAwareTrait;
use 
SymfonyComponentCacheCacheItem;
use 
SymfonyComponentCacheExceptionBadMethodCallException;
use 
SymfonyComponentCachePruneableInterface;
use 
SymfonyComponentCacheResettableInterface;
use 
SymfonyComponentCacheTraitsContractsTrait;
use 
SymfonyContractsCacheNamespacedPoolInterface;
use 
SymfonyContractsCacheTagAwareCacheInterface;

/**
 * Implements simple and robust tag-based invalidation suitable for use with volatile caches.
 *
 * This adapter works by storing a version for each tags. When saving an item, it is stored together with its tags and
 * their corresponding versions. When retrieving an item, those tag versions are compared to the current version of
 * each tags. Invalidation is achieved by deleting tags, thereby ensuring that their versions change even when the
 * storage is out of space. When versions of non-existing tags are requested for item commits, this adapter assigns a
 * new random version to them.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Sergey Belyshkin <sbelyshkin@gmail.com>
 */
class TagAwareAdapter implements TagAwareAdapterInterfaceTagAwareCacheInterfaceNamespacedPoolInterfacePruneableInterfaceResettableInterfaceLoggerAwareInterface
{
    use 
ContractsTrait;
    use 
LoggerAwareTrait;

    public const 
TAGS_PREFIX "1tags1";

    private array 
$deferred = [];
    private 
AdapterInterface $pool;
    private 
AdapterInterface $tags;
    private array 
$knownTagVersions = [];

    private static 
Closure $setCacheItemTags;
    private static 
Closure $setTagVersions;
    private static 
Closure $getTagsByKey;
    private static 
Closure $saveTags;

    public function 
__construct(
        
AdapterInterface $itemsPool,
        ?
AdapterInterface $tagsPool null,
        private 
float $knownTagVersionsTtl 0.15,
    ) {
        
$this->pool $itemsPool;
        
$this->tags $tagsPool ?? $itemsPool;
        
self::$setCacheItemTags ??= Closure::bind(
            static function (array 
$items, array $itemTags) {
                foreach (
$items as $key => $item) {
                    
$item->isTaggable true;

                    if (isset(
$itemTags[$key])) {
                        
$tags array_keys($itemTags[$key]);
                        
$item->metadata[CacheItem::METADATA_TAGS] = array_combine($tags$tags);
                    } else {
                        
$item->value null;
                        
$item->isHit false;
                        
$item->metadata = [];
                    }
                }

                return 
$items;
            },
            
null,
            
CacheItem::class
        );
        
self::$setTagVersions ??= Closure::bind(
            static function (array 
$items, array $tagVersions) {
                foreach (
$items as $item) {
                    
$item->newMetadata[CacheItem::METADATA_TAGS] = array_intersect_key($tagVersions$item->newMetadata[CacheItem::METADATA_TAGS] ?? []);
                }
            },
            
null,
            
CacheItem::class
        );
        
self::$getTagsByKey ??= Closure::bind(
            static function (
$deferred) {
                
$tagsByKey = [];
                foreach (
$deferred as $key => $item) {
                    
$tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
                    
$item->metadata $item->newMetadata;
                }

                return 
$tagsByKey;
            },
            
null,
            
CacheItem::class
        );
        
self::$saveTags ??= Closure::bind(
            static function (
AdapterInterface $tagsAdapter, array $tags) {
                
ksort($tags);

                foreach (
$tags as $v) {
                    
$v->expiry 0;
                    
$tagsAdapter->saveDeferred($v);
                }

                return 
$tagsAdapter->commit();
            },
            
null,
            
CacheItem::class
        );
    }

    public function 
invalidateTags(array $tags): bool
    
{
        
$ids = [];
        foreach (
$tags as $tag) {
            
assert('' !== CacheItem::validateKey($tag));
            unset(
$this->knownTagVersions[$tag]);
            
$ids[] = $tag.static::TAGS_PREFIX;
        }

        return !
$tags || $this->tags->deleteItems($ids);
    }

    public function 
hasItem(mixed $key): bool
    
{
        return 
$this->getItem($key)->isHit();
    }

    public function 
getItem(mixed $key): CacheItem
    
{
        foreach (
$this->getItems([$key]) as $item) {
            return 
$item;
        }
    }

    public function 
getItems(array $keys = []): iterable
    
{
        
$tagKeys = [];
        
$commit false;

        foreach (
$keys as $key) {
            if (
'' !== $key && is_string($key)) {
                
$commit $commit || isset($this->deferred[$key]);
            }
        }

        if (
$commit) {
            
$this->commit();
        }

        try {
            
$items $this->pool->getItems($keys);
        } catch (
InvalidArgumentException $e) {
            
$this->pool->getItems($keys); // Should throw an exception

            
throw $e;
        }

        
$bufferedItems $itemTags = [];

        foreach (
$items as $key => $item) {
            if (
null !== $tags $item->getMetadata()[CacheItem::METADATA_TAGS] ?? null) {
                
$itemTags[$key] = $tags;
            }

            
$bufferedItems[$key] = $item;

            if (
null === $tags) {
                
$key "tags".$key;
                
$tagKeys[$key] = $key// BC with pools populated before v6.1
            
}
        }

        if (
$tagKeys) {
            foreach (
$this->pool->getItems($tagKeys) as $key => $item) {
                if (
$item->isHit()) {
                    
$itemTags[substr($keystrlen("tags"))] = $item->get() ?: [];
                }
            }
        }

        
$tagVersions $this->getTagVersions($itemTagsfalse);
        foreach (
$itemTags as $key => $tags) {
            foreach (
$tags as $tag => $version) {
                if (
$tagVersions[$tag] !== $version) {
                    unset(
$itemTags[$key]);
                    continue 
2;
                }
            }
        }
        
$tagVersions null;

        return (
self::$setCacheItemTags)($bufferedItems$itemTags);
    }

    public function 
clear(string $prefix ''): bool
    
{
        if (
'' !== $prefix) {
            foreach (
$this->deferred as $key => $item) {
                if (
str_starts_with($key$prefix)) {
                    unset(
$this->deferred[$key]);
                }
            }

            return 
$this->pool->clear($prefix);
        }
        
$this->deferred = [];

        return 
$this->pool->clear();
    }

    public function 
deleteItem(mixed $key): bool
    
{
        return 
$this->deleteItems([$key]);
    }

    public function 
deleteItems(array $keys): bool
    
{
        foreach (
$keys as $key) {
            if (
'' !== $key && is_string($key)) {
                
$keys[] = "tags".$key// BC with pools populated before v6.1
            
}
        }

        return 
$this->pool->deleteItems($keys);
    }

    public function 
save(CacheItemInterface $item): bool
    
{
        if (!
$item instanceof CacheItem) {
            return 
false;
        }
        
$this->deferred[$item->getKey()] = $item;

        return 
$this->commit();
    }

    public function 
saveDeferred(CacheItemInterface $item): bool
    
{
        if (!
$item instanceof CacheItem) {
            return 
false;
        }
        
$this->deferred[$item->getKey()] = $item;

        return 
true;
    }

    public function 
commit(): bool
    
{
        if (!
$items $this->deferred) {
            return 
true;
        }

        
$tagVersions $this->getTagVersions((self::$getTagsByKey)($items), true);
        (
self::$setTagVersions)($items$tagVersions);

        
$ok true;
        foreach (
$items as $key => $item) {
            if (
$this->pool->saveDeferred($item)) {
                unset(
$this->deferred[$key]);
            } else {
                
$ok false;
            }
        }
        
$ok $this->pool->commit() && $ok;

        
$tagVersions array_keys($tagVersions);
        (
self::$setTagVersions)($itemsarray_combine($tagVersions$tagVersions));

        return 
$ok;
    }

    
/**
     * @throws BadMethodCallException When the item pool is not a NamespacedPoolInterface
     */
    
public function withSubNamespace(string $namespace): static
    {
        if (!
$this->pool instanceof NamespacedPoolInterface) {
            throw new 
BadMethodCallException(sprintf('Cannot call "%s::withSubNamespace()": this class doesn't implement "%s".', get_debug_type($this->pool), NamespacedPoolInterface::class));
        }

        $knownTagVersions = &$this->knownTagVersions; // ensures clones share the same array
        $clone = clone $this;
        $clone->deferred = [];
        $clone->pool = $this->pool->withSubNamespace($namespace);

        return $clone;
    }

    public function prune(): bool
    {
        return $this->pool instanceof PruneableInterface && $this->pool->prune();
    }

    public function reset(): void
    {
        try {
            $this->commit();
        } finally {
            $this->knownTagVersions = [];
            $this->deferred = [];
            $this->pool instanceof ResettableInterface && $this->pool->reset();
            $this->tags instanceof ResettableInterface && $this->tags->reset();
        }
    }

    public function __serialize(): array
    {
        throw new BadMethodCallException('
Cannot serialize '.__CLASS__);
    }

    public function __unserialize(array $data): void
    {
        throw new BadMethodCallException('
Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        $this->commit();
    }

    private function getTagVersions(array $tagsByKey, bool $persistTags): array
    {
        $tagVersions = [];
        $fetchTagVersions = $persistTags;

        foreach ($tagsByKey as $tags) {
            $tagVersions += $tags;
            if ($fetchTagVersions) {
                continue;
            }
            foreach ($tags as $tag => $version) {
                if ($tagVersions[$tag] !== $version) {
                    $fetchTagVersions = true;
                }
            }
        }

        if (!$tagVersions) {
            return [];
        }

        $now = microtime(true);
        $tags = [];
        foreach ($tagVersions as $tag => $version) {
            $tags[$tag.static::TAGS_PREFIX] = $tag;
            $knownTagVersion = $this->knownTagVersions[$tag] ?? [0, null];
            if ($fetchTagVersions || $now > $knownTagVersion[0] || $knownTagVersion[1] !== $version) {
                // reuse previously fetched tag versions until the expiration
                $fetchTagVersions = true;
            }
        }

        if (!$fetchTagVersions) {
            return $tagVersions;
        }

        $newTags = [];
        $newVersion = null;
        $expiration = $now + $this->knownTagVersionsTtl;
        foreach ($this->tags->getItems(array_keys($tags)) as $tag => $version) {
            unset($this->knownTagVersions[$tag = $tags[$tag]]); // update FIFO
            if (null !== $tagVersions[$tag] = $version->get()) {
                $this->knownTagVersions[$tag] = [$expiration, $tagVersions[$tag]];
            } elseif ($persistTags) {
                $newTags[$tag] = $version->set($newVersion ??= random_bytes(6));
                $tagVersions[$tag] = $newVersion;
                $this->knownTagVersions[$tag] = [$expiration, $newVersion];
            }
        }

        if ($newTags) {
            (self::$saveTags)($this->tags, $newTags);
        }

        while ($now > ($this->knownTagVersions[$tag = array_key_first($this->knownTagVersions) ?? ''][0] ?? INF)) {
            unset($this->knownTagVersions[$tag]);
        }

        return $tagVersions;
    }
}
Онлайн: 1
Реклама