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

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\Log\LoggerAwareInterface;
  12. use Psr\Log\LoggerInterface;
  13. use Symfony\Component\Cache\CacheItem;
  14. use Symfony\Component\Cache\Exception\InvalidArgumentException;
  15. use Symfony\Component\Cache\ResettableInterface;
  16. use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
  17. use Symfony\Component\Cache\Traits\ContractsTrait;
  18. use Symfony\Contracts\Cache\CacheInterface;
  19. /**
  20.  * @author Nicolas Grekas <p@tchwork.com>
  21.  */
  22. abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
  23. {
  24.     use AbstractAdapterTrait;
  25.     use ContractsTrait;
  26.     /**
  27.      * @internal
  28.      */
  29.     protected const NS_SEPARATOR = ':';
  30.     private static $apcuSupported;
  31.     private static $phpFilesSupported;
  32.     protected function __construct(string $namespace = '', int $defaultLifetime = 0)
  33.     {
  34.         $this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
  35.         $this->defaultLifetime = $defaultLifetime;
  36.         if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
  37.             throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
  38.         }
  39.         self::$createCacheItem ?? self::$createCacheItem = \Closure::bind(
  40.             static function ($key, $value, $isHit) {
  41.                 $item = new CacheItem();
  42.                 $item->key = $key;
  43.                 $item->value = $v = $value;
  44.                 $item->isHit = $isHit;
  45.                 // Detect wrapped values that encode for their expiry and creation duration
  46.                 // For compactness, these values are packed in the key of an array using
  47.                 // magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
  48.                 if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = (string) array_key_first($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
  49.                     $item->value = $v[$k];
  50.                     $v = unpack('Ve/Nc', substr($k, 1, -1));
  51.                     $item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
  52.                     $item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
  53.                 }
  54.                 return $item;
  55.             },
  56.             null,
  57.             CacheItem::class
  58.         );
  59.         self::$mergeByLifetime ?? self::$mergeByLifetime = \Closure::bind(
  60.             static function ($deferred, $namespace, &$expiredIds, $getId, $defaultLifetime) {
  61.                 $byLifetime = [];
  62.                 $now = microtime(true);
  63.                 $expiredIds = [];
  64.                 foreach ($deferred as $key => $item) {
  65.                     $key = (string) $key;
  66.                     if (null === $item->expiry) {
  67.                         $ttl = 0 < $defaultLifetime ? $defaultLifetime : 0;
  68.                     } elseif (!$item->expiry) {
  69.                         $ttl = 0;
  70.                     } elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
  71.                         $expiredIds[] = $getId($key);
  72.                         continue;
  73.                     }
  74.                     if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
  75.                         unset($metadata[CacheItem::METADATA_TAGS]);
  76.                     }
  77.                     // For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
  78.                     $byLifetime[$ttl][$getId($key)] = $metadata ? ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item->value] : $item->value;
  79.                 }
  80.                 return $byLifetime;
  81.             },
  82.             null,
  83.             CacheItem::class
  84.         );
  85.     }
  86.     /**
  87.      * Returns the best possible adapter that your runtime supports.
  88.      *
  89.      * Using ApcuAdapter makes system caches compatible with read-only filesystems.
  90.      *
  91.      * @return AdapterInterface
  92.      */
  93.     public static function createSystemCache(string $namespace, int $defaultLifetime, string $version, string $directory, ?LoggerInterface $logger = null)
  94.     {
  95.         $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
  96.         if (null !== $logger) {
  97.             $opcache->setLogger($logger);
  98.         }
  99.         if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
  100.             return $opcache;
  101.         }
  102.         if (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(\ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
  103.             return $opcache;
  104.         }
  105.         $apcu = new ApcuAdapter($namespace, intdiv($defaultLifetime, 5), $version);
  106.         if (null !== $logger) {
  107.             $apcu->setLogger($logger);
  108.         }
  109.         return new ChainAdapter([$apcu, $opcache]);
  110.     }
  111.     public static function createConnection(string $dsn, array $options = [])
  112.     {
  113.         if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) {
  114.             return RedisAdapter::createConnection($dsn, $options);
  115.         }
  116.         if (str_starts_with($dsn, 'memcached:')) {
  117.             return MemcachedAdapter::createConnection($dsn, $options);
  118.         }
  119.         if (0 === strpos($dsn, 'couchbase:')) {
  120.             if (CouchbaseBucketAdapter::isSupported()) {
  121.                 return CouchbaseBucketAdapter::createConnection($dsn, $options);
  122.             }
  123.             return CouchbaseCollectionAdapter::createConnection($dsn, $options);
  124.         }
  125.         throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:" nor "couchbase:".');
  126.     }
  127.     /**
  128.      * {@inheritdoc}
  129.      *
  130.      * @return bool
  131.      */
  132.     public function commit()
  133.     {
  134.         $ok = true;
  135.         $byLifetime = (self::$mergeByLifetime)($this->deferred, $this->namespace, $expiredIds, \Closure::fromCallable([$this, 'getId']), $this->defaultLifetime);
  136.         $retry = $this->deferred = [];
  137.         if ($expiredIds) {
  138.             try {
  139.                 $this->doDelete($expiredIds);
  140.             } catch (\Exception $e) {
  141.                 $ok = false;
  142.                 CacheItem::log($this->logger, 'Failed to delete expired items: '.$e->getMessage(), ['exception' => $e, 'cache-adapter' => get_debug_type($this)]);
  143.             }
  144.         }
  145.         foreach ($byLifetime as $lifetime => $values) {
  146.             try {
  147.                 $e = $this->doSave($values, $lifetime);
  148.             } catch (\Exception $e) {
  149.             }
  150.             if (true === $e || [] === $e) {
  151.                 continue;
  152.             }
  153.             if (\is_array($e) || 1 === \count($values)) {
  154.                 foreach (\is_array($e) ? $e : array_keys($values) as $id) {
  155.                     $ok = false;
  156.                     $v = $values[$id];
  157.                     $type = get_debug_type($v);
  158.                     $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
  159.                     CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
  160.                 }
  161.             } else {
  162.                 foreach ($values as $id => $v) {
  163.                     $retry[$lifetime][] = $id;
  164.                 }
  165.             }
  166.         }
  167.         // When bulk-save failed, retry each item individually
  168.         foreach ($retry as $lifetime => $ids) {
  169.             foreach ($ids as $id) {
  170.                 try {
  171.                     $v = $byLifetime[$lifetime][$id];
  172.                     $e = $this->doSave([$id => $v], $lifetime);
  173.                 } catch (\Exception $e) {
  174.                 }
  175.                 if (true === $e || [] === $e) {
  176.                     continue;
  177.                 }
  178.                 $ok = false;
  179.                 $type = get_debug_type($v);
  180.                 $message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
  181.                 CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null, 'cache-adapter' => get_debug_type($this)]);
  182.             }
  183.         }
  184.         return $ok;
  185.     }
  186. }