| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 | <?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 Symfony\Component\Cache\Adapter;use Psr\Cache\CacheItemInterface;use Psr\Cache\CacheItemPoolInterface;use Symfony\Component\Cache\CacheItem;use Symfony\Component\Cache\Exception\InvalidArgumentException;use Symfony\Component\Cache\PruneableInterface;use Symfony\Component\Cache\ResettableInterface;use Symfony\Component\Cache\Traits\ContractsTrait;use Symfony\Contracts\Cache\CacheInterface;use Symfony\Contracts\Service\ResetInterface;/** * Chains several adapters together. * * Cached items are fetched from the first adapter having them in its data store. * They are saved and deleted in all adapters at once. * * @author Kévin Dunglas <dunglas@gmail.com> */class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface{    use ContractsTrait;    private $adapters = [];    private $adapterCount;    private $defaultLifetime;    private static $syncItem;    /**     * @param CacheItemPoolInterface[] $adapters        The ordered list of adapters used to fetch cached items     * @param int                      $defaultLifetime The default lifetime of items propagated from lower adapters to upper ones     */    public function __construct(array $adapters, int $defaultLifetime = 0)    {        if (!$adapters) {            throw new InvalidArgumentException('At least one adapter must be specified.');        }        foreach ($adapters as $adapter) {            if (!$adapter instanceof CacheItemPoolInterface) {                throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_debug_type($adapter), CacheItemPoolInterface::class));            }            if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && $adapter instanceof ApcuAdapter && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {                continue; // skip putting APCu in the chain when the backend is disabled            }            if ($adapter instanceof AdapterInterface) {                $this->adapters[] = $adapter;            } else {                $this->adapters[] = new ProxyAdapter($adapter);            }        }        $this->adapterCount = \count($this->adapters);        $this->defaultLifetime = $defaultLifetime;        self::$syncItem ?? self::$syncItem = \Closure::bind(            static function ($sourceItem, $item, $defaultLifetime, $sourceMetadata = null) {                $sourceItem->isTaggable = false;                $sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;                unset($sourceMetadata[CacheItem::METADATA_TAGS]);                $item->value = $sourceItem->value;                $item->isHit = $sourceItem->isHit;                $item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;                if (isset($item->metadata[CacheItem::METADATA_EXPIRY])) {                    $item->expiresAt(\DateTime::createFromFormat('U.u', sprintf('%.6F', $item->metadata[CacheItem::METADATA_EXPIRY])));                } elseif (0 < $defaultLifetime) {                    $item->expiresAfter($defaultLifetime);                }                return $item;            },            null,            CacheItem::class        );    }    /**     * {@inheritdoc}     */    public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)    {        $lastItem = null;        $i = 0;        $wrap = function (CacheItem $item = null) use ($key, $callback, $beta, &$wrap, &$i, &$lastItem, &$metadata) {            $adapter = $this->adapters[$i];            if (isset($this->adapters[++$i])) {                $callback = $wrap;                $beta = \INF === $beta ? \INF : 0;            }            if ($adapter instanceof CacheInterface) {                $value = $adapter->get($key, $callback, $beta, $metadata);            } else {                $value = $this->doGet($adapter, $key, $callback, $beta, $metadata);            }            if (null !== $item) {                (self::$syncItem)($lastItem = $lastItem ?? $item, $item, $this->defaultLifetime, $metadata);            }            return $value;        };        return $wrap();    }    /**     * {@inheritdoc}     */    public function getItem($key)    {        $syncItem = self::$syncItem;        $misses = [];        foreach ($this->adapters as $i => $adapter) {            $item = $adapter->getItem($key);            if ($item->isHit()) {                while (0 <= --$i) {                    $this->adapters[$i]->save($syncItem($item, $misses[$i], $this->defaultLifetime));                }                return $item;            }            $misses[$i] = $item;        }        return $item;    }    /**     * {@inheritdoc}     */    public function getItems(array $keys = [])    {        return $this->generateItems($this->adapters[0]->getItems($keys), 0);    }    private function generateItems(iterable $items, int $adapterIndex): \Generator    {        $missing = [];        $misses = [];        $nextAdapterIndex = $adapterIndex + 1;        $nextAdapter = $this->adapters[$nextAdapterIndex] ?? null;        foreach ($items as $k => $item) {            if (!$nextAdapter || $item->isHit()) {                yield $k => $item;            } else {                $missing[] = $k;                $misses[$k] = $item;            }        }        if ($missing) {            $syncItem = self::$syncItem;            $adapter = $this->adapters[$adapterIndex];            $items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);            foreach ($items as $k => $item) {                if ($item->isHit()) {                    $adapter->save($syncItem($item, $misses[$k], $this->defaultLifetime));                }                yield $k => $item;            }        }    }    /**     * {@inheritdoc}     *     * @return bool     */    public function hasItem($key)    {        foreach ($this->adapters as $adapter) {            if ($adapter->hasItem($key)) {                return true;            }        }        return false;    }    /**     * {@inheritdoc}     *     * @return bool     */    public function clear(string $prefix = '')    {        $cleared = true;        $i = $this->adapterCount;        while ($i--) {            if ($this->adapters[$i] instanceof AdapterInterface) {                $cleared = $this->adapters[$i]->clear($prefix) && $cleared;            } else {                $cleared = $this->adapters[$i]->clear() && $cleared;            }        }        return $cleared;    }    /**     * {@inheritdoc}     *     * @return bool     */    public function deleteItem($key)    {        $deleted = true;        $i = $this->adapterCount;        while ($i--) {            $deleted = $this->adapters[$i]->deleteItem($key) && $deleted;        }        return $deleted;    }    /**     * {@inheritdoc}     *     * @return bool     */    public function deleteItems(array $keys)    {        $deleted = true;        $i = $this->adapterCount;        while ($i--) {            $deleted = $this->adapters[$i]->deleteItems($keys) && $deleted;        }        return $deleted;    }    /**     * {@inheritdoc}     *     * @return bool     */    public function save(CacheItemInterface $item)    {        $saved = true;        $i = $this->adapterCount;        while ($i--) {            $saved = $this->adapters[$i]->save($item) && $saved;        }        return $saved;    }    /**     * {@inheritdoc}     *     * @return bool     */    public function saveDeferred(CacheItemInterface $item)    {        $saved = true;        $i = $this->adapterCount;        while ($i--) {            $saved = $this->adapters[$i]->saveDeferred($item) && $saved;        }        return $saved;    }    /**     * {@inheritdoc}     *     * @return bool     */    public function commit()    {        $committed = true;        $i = $this->adapterCount;        while ($i--) {            $committed = $this->adapters[$i]->commit() && $committed;        }        return $committed;    }    /**     * {@inheritdoc}     */    public function prune()    {        $pruned = true;        foreach ($this->adapters as $adapter) {            if ($adapter instanceof PruneableInterface) {                $pruned = $adapter->prune() && $pruned;            }        }        return $pruned;    }    /**     * {@inheritdoc}     */    public function reset()    {        foreach ($this->adapters as $adapter) {            if ($adapter instanceof ResetInterface) {                $adapter->reset();            }        }    }}
 |