vendor/symfony/cache/Adapter/AbstractAdapter.php line 165

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\Log\LoggerAwareInterface;
  13. use Psr\Log\LoggerInterface;
  14. use Psr\Log\NullLogger;
  15. use Symfony\Component\Cache\CacheItem;
  16. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  17. use Symfony\Component\Cache\ResettableInterface;
  18. use Symfony\Component\Cache\Traits\AbstractTrait;
  19. use Symfony\Component\Cache\Traits\ContractsTrait;
  20. use Symfony\Contracts\Cache\CacheInterface;
  21. /**
  22.  * @author Nicolas Grekas <p@tchwork.com>
  23.  */
  24. abstract class AbstractAdapter implements AdapterInterfaceCacheInterfaceLoggerAwareInterfaceResettableInterface
  25. {
  26.     /**
  27.      * @internal
  28.      */
  29.     protected const NS_SEPARATOR ':';
  30.     use AbstractTrait;
  31.     use ContractsTrait;
  32.     private static $apcuSupported;
  33.     private static $phpFilesSupported;
  34.     private $createCacheItem;
  35.     private $mergeByLifetime;
  36.     protected function __construct(string $namespace ''int $defaultLifetime 0)
  37.     {
  38.         $this->namespace '' === $namespace '' CacheItem::validateKey($namespace).static::NS_SEPARATOR;
  39.         if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength 24) {
  40.             throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")'$this->maxIdLength 24, \strlen($namespace), $namespace));
  41.         }
  42.         $this->createCacheItem = \Closure::bind(
  43.             function ($key$value$isHit) use ($defaultLifetime) {
  44.                 $item = new CacheItem();
  45.                 $item->key $key;
  46.                 $item->value $v $value;
  47.                 $item->isHit $isHit;
  48.                 $item->defaultLifetime $defaultLifetime;
  49.                 // Detect wrapped values that encode for their expiry and creation duration
  50.                 // For compactness, these values are packed in the key of an array using
  51.                 // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
  52.                 if (\is_array($v) && === \count($v) && 10 === \strlen($k key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
  53.                     $item->value $v[$k];
  54.                     $v unpack('Ve/Nc'substr($k1, -1));
  55.                     $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
  56.                     $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
  57.                 }
  58.                 return $item;
  59.             },
  60.             null,
  61.             CacheItem::class
  62.         );
  63.         $getId = \Closure::fromCallable([$this'getId']);
  64.         $this->mergeByLifetime = \Closure::bind(
  65.             function ($deferred$namespace, &$expiredIds) use ($getId) {
  66.                 $byLifetime = [];
  67.                 $now microtime(true);
  68.                 $expiredIds = [];
  69.                 foreach ($deferred as $key => $item) {
  70.                     $key = (string) $key;
  71.                     if (null === $item->expiry) {
  72.                         $ttl $item->defaultLifetime $item->defaultLifetime 0;
  73.                     } elseif (>= $ttl = (int) ($item->expiry $now)) {
  74.                         $expiredIds[] = $getId($key);
  75.                         continue;
  76.                     }
  77.                     if (isset(($metadata $item->newMetadata)[CacheItem::METADATA_TAGS])) {
  78.                         unset($metadata[CacheItem::METADATA_TAGS]);
  79.                     }
  80.                     // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
  81.                     $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET$metadata[CacheItem::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
  82.                 }
  83.                 return $byLifetime;
  84.             },
  85.             null,
  86.             CacheItem::class
  87.         );
  88.     }
  89.     /**
  90.      * Returns the best possible adapter that your runtime supports.
  91.      *
  92.      * Using ApcuAdapter makes system caches compatible with read-only filesystems.
  93.      *
  94.      * @param string               $namespace
  95.      * @param int                  $defaultLifetime
  96.      * @param string               $version
  97.      * @param string               $directory
  98.      * @param LoggerInterface|null $logger
  99.      *
  100.      * @return AdapterInterface
  101.      */
  102.     public static function createSystemCache($namespace$defaultLifetime$version$directoryLoggerInterface $logger null)
  103.     {
  104.         $opcache = new PhpFilesAdapter($namespace$defaultLifetime$directorytrue);
  105.         if (null !== $logger) {
  106.             $opcache->setLogger($logger);
  107.         }
  108.         if (!self::$apcuSupported self::$apcuSupported ?? ApcuAdapter::isSupported()) {
  109.             return $opcache;
  110.         }
  111.         $apcu = new ApcuAdapter($namespace, (int) $defaultLifetime 5$version);
  112.         if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
  113.             $apcu->setLogger(new NullLogger());
  114.         } elseif (null !== $logger) {
  115.             $apcu->setLogger($logger);
  116.         }
  117.         return new ChainAdapter([$apcu$opcache]);
  118.     }
  119.     public static function createConnection($dsn, array $options = [])
  120.     {
  121.         if (!\is_string($dsn)) {
  122.             throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.'__METHOD__, \gettype($dsn)));
  123.         }
  124.         if (=== strpos($dsn'redis:')) {
  125.             return RedisAdapter::createConnection($dsn$options);
  126.         }
  127.         if (=== strpos($dsn'memcached:')) {
  128.             return MemcachedAdapter::createConnection($dsn$options);
  129.         }
  130.         throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.'$dsn));
  131.     }
  132.     /**
  133.      * {@inheritdoc}
  134.      */
  135.     public function getItem($key)
  136.     {
  137.         if ($this->deferred) {
  138.             $this->commit();
  139.         }
  140.         $id $this->getId($key);
  141.         $f $this->createCacheItem;
  142.         $isHit false;
  143.         $value null;
  144.         try {
  145.             foreach ($this->doFetch([$id]) as $value) {
  146.                 $isHit true;
  147.             }
  148.         } catch (\Exception $e) {
  149.             CacheItem::log($this->logger'Failed to fetch key "{key}"', ['key' => $key'exception' => $e]);
  150.         }
  151.         return $f($key$value$isHit);
  152.     }
  153.     /**
  154.      * {@inheritdoc}
  155.      */
  156.     public function getItems(array $keys = [])
  157.     {
  158.         if ($this->deferred) {
  159.             $this->commit();
  160.         }
  161.         $ids = [];
  162.         foreach ($keys as $key) {
  163.             $ids[] = $this->getId($key);
  164.         }
  165.         try {
  166.             $items $this->doFetch($ids);
  167.         } catch (\Exception $e) {
  168.             CacheItem::log($this->logger'Failed to fetch requested items', ['keys' => $keys'exception' => $e]);
  169.             $items = [];
  170.         }
  171.         $ids array_combine($ids$keys);
  172.         return $this->generateItems($items$ids);
  173.     }
  174.     /**
  175.      * {@inheritdoc}
  176.      */
  177.     public function save(CacheItemInterface $item)
  178.     {
  179.         if (!$item instanceof CacheItem) {
  180.             return false;
  181.         }
  182.         $this->deferred[$item->getKey()] = $item;
  183.         return $this->commit();
  184.     }
  185.     /**
  186.      * {@inheritdoc}
  187.      */
  188.     public function saveDeferred(CacheItemInterface $item)
  189.     {
  190.         if (!$item instanceof CacheItem) {
  191.             return false;
  192.         }
  193.         $this->deferred[$item->getKey()] = $item;
  194.         return true;
  195.     }
  196.     /**
  197.      * {@inheritdoc}
  198.      */
  199.     public function commit()
  200.     {
  201.         $ok true;
  202.         $byLifetime $this->mergeByLifetime;
  203.         $byLifetime $byLifetime($this->deferred$this->namespace$expiredIds);
  204.         $retry $this->deferred = [];
  205.         if ($expiredIds) {
  206.             $this->doDelete($expiredIds);
  207.         }
  208.         foreach ($byLifetime as $lifetime => $values) {
  209.             try {
  210.                 $e $this->doSave($values$lifetime);
  211.             } catch (\Exception $e) {
  212.             }
  213.             if (true === $e || [] === $e) {
  214.                 continue;
  215.             }
  216.             if (\is_array($e) || === \count($values)) {
  217.                 foreach (\is_array($e) ? $e array_keys($values) as $id) {
  218.                     $ok false;
  219.                     $v $values[$id];
  220.                     $type = \is_object($v) ? \get_class($v) : \gettype($v);
  221.                     CacheItem::log($this->logger'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type'exception' => $e instanceof \Exception $e null]);
  222.                 }
  223.             } else {
  224.                 foreach ($values as $id => $v) {
  225.                     $retry[$lifetime][] = $id;
  226.                 }
  227.             }
  228.         }
  229.         // When bulk-save failed, retry each item individually
  230.         foreach ($retry as $lifetime => $ids) {
  231.             foreach ($ids as $id) {
  232.                 try {
  233.                     $v $byLifetime[$lifetime][$id];
  234.                     $e $this->doSave([$id => $v], $lifetime);
  235.                 } catch (\Exception $e) {
  236.                 }
  237.                 if (true === $e || [] === $e) {
  238.                     continue;
  239.                 }
  240.                 $ok false;
  241.                 $type = \is_object($v) ? \get_class($v) : \gettype($v);
  242.                 CacheItem::log($this->logger'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type'exception' => $e instanceof \Exception $e null]);
  243.             }
  244.         }
  245.         return $ok;
  246.     }
  247.     public function __sleep()
  248.     {
  249.         throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  250.     }
  251.     public function __wakeup()
  252.     {
  253.         throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  254.     }
  255.     public function __destruct()
  256.     {
  257.         if ($this->deferred) {
  258.             $this->commit();
  259.         }
  260.     }
  261.     private function generateItems($items, &$keys)
  262.     {
  263.         $f $this->createCacheItem;
  264.         try {
  265.             foreach ($items as $id => $value) {
  266.                 if (!isset($keys[$id])) {
  267.                     $id key($keys);
  268.                 }
  269.                 $key $keys[$id];
  270.                 unset($keys[$id]);
  271.                 yield $key => $f($key$valuetrue);
  272.             }
  273.         } catch (\Exception $e) {
  274.             CacheItem::log($this->logger'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]);
  275.         }
  276.         foreach ($keys as $key) {
  277.             yield $key => $f($keynullfalse);
  278.         }
  279.     }
  280. }