Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
+167 -54
View File
@@ -11,80 +11,71 @@
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Component\Cache\Traits\AbstractTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = ':';
const NS_SEPARATOR = ':';
use AbstractAdapterTrait;
use ContractsTrait;
use AbstractTrait;
private static $apcuSupported;
private static $phpFilesSupported;
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
private $createCacheItem;
private $mergeByLifetime;
/**
* @param string $namespace
* @param int $defaultLifetime
*/
protected function __construct($namespace = '', $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).static::NS_SEPARATOR;
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
$item->value = $v = $value;
$item->value = $value;
$item->isHit = $isHit;
$item->defaultLifetime = $defaultLifetime;
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
}
return $item;
},
null,
CacheItem::class
);
$getId = \Closure::fromCallable([$this, 'getId']);
$getId = function ($key) { return $this->getId((string) $key); };
$this->mergeByLifetime = \Closure::bind(
static function ($deferred, $namespace, &$expiredIds) use ($getId) {
static function ($deferred, $namespace, &$expiredIds) use ($getId, $defaultLifetime) {
$byLifetime = [];
$now = microtime(true);
$now = time();
$expiredIds = [];
foreach ($deferred as $key => $item) {
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$byLifetime[0 < $defaultLifetime ? $defaultLifetime : 0][$getId($key)] = $item->value;
} elseif (0 === $item->expiry) {
$byLifetime[0][$getId($key)] = $item->value;
} elseif ($item->expiry > $now) {
$byLifetime[$item->expiry - $now][$getId($key)] = $item->value;
} else {
$expiredIds[] = $getId($key);
continue;
}
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
unset($metadata[CacheItem::METADATA_TAGS]);
}
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
$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;
}
return $byLifetime;
@@ -95,10 +86,6 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
}
/**
* Returns the best possible adapter that your runtime supports.
*
* Using ApcuAdapter makes system caches compatible with read-only filesystems.
*
* @param string $namespace
* @param int $defaultLifetime
* @param string $version
@@ -108,38 +95,127 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
*/
public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null)
{
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory, true);
if (null !== $logger) {
$opcache->setLogger($logger);
if (null === self::$apcuSupported) {
self::$apcuSupported = ApcuAdapter::isSupported();
}
if (!self::$apcuSupported = self::$apcuSupported ?? ApcuAdapter::isSupported()) {
if (!self::$apcuSupported && null === self::$phpFilesSupported) {
self::$phpFilesSupported = PhpFilesAdapter::isSupported();
}
if (self::$phpFilesSupported) {
$opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory);
if (null !== $logger) {
$opcache->setLogger($logger);
}
return $opcache;
}
$fs = new FilesystemAdapter($namespace, $defaultLifetime, $directory);
if (null !== $logger) {
$fs->setLogger($logger);
}
if (!self::$apcuSupported || (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
return $fs;
}
$apcu = new ApcuAdapter($namespace, (int) $defaultLifetime / 5, $version);
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
$apcu->setLogger(new NullLogger());
} elseif (null !== $logger) {
if (null !== $logger) {
$apcu->setLogger($logger);
}
return new ChainAdapter([$apcu, $opcache]);
return new ChainAdapter([$apcu, $fs]);
}
public static function createConnection($dsn, array $options = [])
{
if (!\is_string($dsn)) {
throw new InvalidArgumentException(sprintf('The %s() method expect argument #1 to be string, %s given.', __METHOD__, \gettype($dsn)));
throw new InvalidArgumentException(sprintf('The "%s()" method expect argument #1 to be string, "%s" given.', __METHOD__, \gettype($dsn)));
}
if (0 === strpos($dsn, 'redis:') || 0 === strpos($dsn, 'rediss:')) {
if (0 === strpos($dsn, 'redis://')) {
return RedisAdapter::createConnection($dsn, $options);
}
if (0 === strpos($dsn, 'memcached:')) {
if (0 === strpos($dsn, 'memcached://')) {
return MemcachedAdapter::createConnection($dsn, $options);
}
throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn));
throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn));
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
if ($this->deferred) {
$this->commit();
}
$id = $this->getId($key);
$f = $this->createCacheItem;
$isHit = false;
$value = null;
try {
foreach ($this->doFetch([$id]) as $value) {
$isHit = true;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
}
return $f($key, $value, $isHit);
}
/**
* {@inheritdoc}
*/
public function getItems(array $keys = [])
{
if ($this->deferred) {
$this->commit();
}
$ids = [];
foreach ($keys as $key) {
$ids[] = $this->getId($key);
}
try {
$items = $this->doFetch($ids);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => $keys, 'exception' => $e]);
$items = [];
}
$ids = array_combine($ids, $keys);
return $this->generateItems($items, $ids);
}
/**
* {@inheritdoc}
*/
public function save(CacheItemInterface $item)
{
if (!$item instanceof CacheItem) {
return false;
}
$this->deferred[$item->getKey()] = $item;
return $this->commit();
}
/**
* {@inheritdoc}
*/
public function saveDeferred(CacheItemInterface $item)
{
if (!$item instanceof CacheItem) {
return false;
}
$this->deferred[$item->getKey()] = $item;
return true;
}
/**
@@ -168,8 +244,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
}
} else {
foreach ($values as $id => $v) {
@@ -191,11 +266,49 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => substr($id, \strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null]);
}
}
return $ok;
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
if ($this->deferred) {
$this->commit();
}
}
private function generateItems($items, &$keys)
{
$f = $this->createCacheItem;
try {
foreach ($items as $id => $value) {
if (!isset($keys[$id])) {
$id = key($keys);
}
$key = $keys[$id];
unset($keys[$id]);
yield $key => $f($key, $value, true);
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch requested items', ['keys' => array_values($keys), 'exception' => $e]);
}
foreach ($keys as $key) {
yield $key => $f($key, null, false);
}
}
}
-309
View File
@@ -1,309 +0,0 @@
<?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\Log\LoggerAwareInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* Abstract for native TagAware adapters.
*
* To keep info on tags, the tags are both serialized as part of cache value and provided as tag ids
* to Adapters on operations when needed for storage to doSave(), doDelete() & doInvalidate().
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*
* @internal
* @experimental in 4.3
*/
abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, LoggerAwareInterface, ResettableInterface
{
use AbstractAdapterTrait;
use ContractsTrait;
private const TAGS_PREFIX = "\0tags\0";
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
{
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
$item = new CacheItem();
$item->key = $key;
$item->defaultLifetime = $defaultLifetime;
$item->isTaggable = true;
// If structure does not match what we expect return item as is (no value and not a hit)
if (!\is_array($value) || !\array_key_exists('value', $value)) {
return $item;
}
$item->isHit = $isHit;
// Extract value, tags and meta data from the cache value
$item->value = $value['value'];
$item->metadata[CacheItem::METADATA_TAGS] = $value['tags'] ?? [];
if (isset($value['meta'])) {
// For compactness these values are packed, & expiry is offset to reduce size
$v = unpack('Ve/Nc', $value['meta']);
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
}
return $item;
},
null,
CacheItem::class
);
$getId = \Closure::fromCallable([$this, 'getId']);
$tagPrefix = self::TAGS_PREFIX;
$this->mergeByLifetime = \Closure::bind(
static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
$byLifetime = [];
$now = microtime(true);
$expiredIds = [];
foreach ($deferred as $key => $item) {
$key = (string) $key;
if (null === $item->expiry) {
$ttl = 0 < $item->defaultLifetime ? $item->defaultLifetime : 0;
} elseif (0 >= $ttl = (int) (0.1 + $item->expiry - $now)) {
$expiredIds[] = $getId($key);
continue;
}
// Store Value and Tags on the cache value
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
$value = ['value' => $item->value, 'tags' => $metadata[CacheItem::METADATA_TAGS]];
unset($metadata[CacheItem::METADATA_TAGS]);
} else {
$value = ['value' => $item->value, 'tags' => []];
}
if ($metadata) {
// For compactness, expiry and creation duration are packed, using magic numbers as separators
$value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
}
// Extract tag changes, these should be removed from values in doSave()
$value['tag-operations'] = ['add' => [], 'remove' => []];
$oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
$value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
}
foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
$value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
}
$byLifetime[$ttl][$getId($key)] = $value;
}
return $byLifetime;
},
null,
CacheItem::class
);
}
/**
* Persists several cache items immediately.
*
* @param array $values The values to cache, indexed by their cache identifier
* @param int $lifetime The lifetime of the cached values, 0 for persisting until manual cleaning
* @param array[] $addTagData Hash where key is tag id, and array value is list of cache id's to add to tag
* @param array[] $removeTagData Hash where key is tag id, and array value is list of cache id's to remove to tag
*
* @return array The identifiers that failed to be cached or a boolean stating if caching succeeded or not
*/
abstract protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array;
/**
* Removes multiple items from the pool and their corresponding tags.
*
* @param array $ids An array of identifiers that should be removed from the pool
* @param array $tagData Optional array of tag identifiers => key identifiers that should be removed from the pool
*
* @return bool True if the items were successfully removed, false otherwise
*/
abstract protected function doDelete(array $ids, array $tagData = []): bool;
/**
* Invalidates cached items using tags.
*
* @param string[] $tagIds An array of tags to invalidate, key is tag and value is tag id
*
* @return bool True on success
*/
abstract protected function doInvalidate(array $tagIds): bool;
/**
* {@inheritdoc}
*/
public function commit()
{
$ok = true;
$byLifetime = $this->mergeByLifetime;
$byLifetime = $byLifetime($this->deferred, $expiredIds);
$retry = $this->deferred = [];
if ($expiredIds) {
// Tags are not cleaned up in this case, however that is done on invalidateTags().
$this->doDelete($expiredIds);
}
foreach ($byLifetime as $lifetime => $values) {
try {
$values = $this->extractTagData($values, $addTagData, $removeTagData);
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
if (\is_array($e) || 1 === \count($values)) {
foreach (\is_array($e) ? $e : array_keys($values) as $id) {
$ok = false;
$v = $values[$id];
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
} else {
foreach ($values as $id => $v) {
$retry[$lifetime][] = $id;
}
}
}
// When bulk-save failed, retry each item individually
foreach ($retry as $lifetime => $ids) {
foreach ($ids as $id) {
try {
$v = $byLifetime[$lifetime][$id];
$values = $this->extractTagData([$id => $v], $addTagData, $removeTagData);
$e = $this->doSave($values, $lifetime, $addTagData, $removeTagData);
} catch (\Exception $e) {
}
if (true === $e || [] === $e) {
continue;
}
$ok = false;
$type = \is_object($v) ? \get_class($v) : \gettype($v);
$message = sprintf('Failed to save key "{key}" of type %s%s', $type, $e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e instanceof \Exception ? $e : null]);
}
}
return $ok;
}
/**
* {@inheritdoc}
*
* Overloaded in order to deal with tags for adjusted doDelete() signature.
*/
public function deleteItems(array $keys)
{
if (!$keys) {
return true;
}
$ids = [];
$tagData = [];
foreach ($keys as $key) {
$ids[$key] = $this->getId($key);
unset($this->deferred[$key]);
}
try {
foreach ($this->doFetch($ids) as $id => $value) {
foreach ($value['tags'] ?? [] as $tag) {
$tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
}
}
} catch (\Exception $e) {
// ignore unserialization failures
}
try {
if ($this->doDelete(array_values($ids), $tagData)) {
return true;
}
} catch (\Exception $e) {
}
$ok = true;
// When bulk-delete failed, retry each item individually
foreach ($ids as $key => $id) {
try {
$e = null;
if ($this->doDelete([$id])) {
continue;
}
} catch (\Exception $e) {
}
$message = 'Failed to delete key "{key}"'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['key' => $key, 'exception' => $e]);
$ok = false;
}
return $ok;
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags)
{
if (empty($tags)) {
return false;
}
$tagIds = [];
foreach (array_unique($tags) as $tag) {
$tagIds[] = $this->getId(self::TAGS_PREFIX.$tag);
}
if ($this->doInvalidate($tagIds)) {
return true;
}
return false;
}
/**
* Extracts tags operation data from $values set in mergeByLifetime, and returns values without it.
*/
private function extractTagData(array $values, ?array &$addTagData, ?array &$removeTagData): array
{
$addTagData = $removeTagData = [];
foreach ($values as $id => $value) {
foreach ($value['tag-operations']['add'] as $tag => $tagId) {
$addTagData[$tagId][] = $id;
}
foreach ($value['tag-operations']['remove'] as $tag => $tagId) {
$removeTagData[$tagId][] = $id;
}
unset($values[$id]['tag-operations']);
}
return $values;
}
}
+5 -1
View File
@@ -18,9 +18,13 @@ class ApcuAdapter extends AbstractAdapter
use ApcuTrait;
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $version
*
* @throws CacheException if APCu is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
{
$this->init($namespace, $defaultLifetime, $version);
}
+40 -43
View File
@@ -16,30 +16,31 @@ use Psr\Log\LoggerAwareInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait;
private $createCacheItem;
private $defaultLifetime;
/**
* @param int $defaultLifetime
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
public function __construct($defaultLifetime = 0, $storeSerialized = true)
{
$this->defaultLifetime = $defaultLifetime;
$this->storeSerialized = $storeSerialized;
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) use ($defaultLifetime) {
static function ($key, $value, $isHit) {
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->isHit = $isHit;
$item->defaultLifetime = $defaultLifetime;
return $item;
},
@@ -48,32 +49,27 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$item = $this->getItem($key);
$metadata = $item->getMetadata();
// ArrayAdapter works in memory, we don't care about stampede protection
if (INF === $beta || !$item->isHit()) {
$save = true;
$this->save($item->set($callback($item, $save)));
}
return $item->get();
}
/**
* {@inheritdoc}
*/
public function getItem($key)
{
if (!$isHit = $this->hasItem($key)) {
$isHit = $this->hasItem($key);
try {
if (!$isHit) {
$this->values[$key] = $value = null;
} elseif (!$this->storeSerialized) {
$value = $this->values[$key];
} elseif ('b:0;' === $value = $this->values[$key]) {
$value = false;
} elseif (false === $value = unserialize($value)) {
$this->values[$key] = $value = null;
$isHit = false;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', ['key' => $key, 'exception' => $e]);
$this->values[$key] = $value = null;
} else {
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
$isHit = false;
}
$f = $this->createCacheItem;
@@ -86,12 +82,10 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
public function getItems(array $keys = [])
{
foreach ($keys as $key) {
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
}
CacheItem::validateKey($key);
}
return $this->generateItems($keys, microtime(true), $this->createCacheItem);
return $this->generateItems($keys, time(), $this->createCacheItem);
}
/**
@@ -119,20 +113,31 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
$value = $item["\0*\0value"];
$expiry = $item["\0*\0expiry"];
if (null !== $expiry && $expiry <= microtime(true)) {
if (0 === $expiry) {
$expiry = \PHP_INT_MAX;
}
if (null !== $expiry && $expiry <= time()) {
$this->deleteItem($key);
return true;
}
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
if ($this->storeSerialized) {
try {
$value = serialize($value);
} catch (\Exception $e) {
$type = \is_object($value) ? \get_class($value) : \gettype($value);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
return false;
}
}
if (null === $expiry && 0 < $item["\0*\0defaultLifetime"]) {
$expiry = microtime(true) + $item["\0*\0defaultLifetime"];
if (null === $expiry && 0 < $this->defaultLifetime) {
$expiry = time() + $this->defaultLifetime;
}
$this->values[$key] = $value;
$this->expiries[$key] = null !== $expiry ? $expiry : PHP_INT_MAX;
$this->expiries[$key] = null !== $expiry ? $expiry : \PHP_INT_MAX;
return true;
}
@@ -152,12 +157,4 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
{
return true;
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
}
+9 -48
View File
@@ -17,9 +17,6 @@ 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.
@@ -29,10 +26,8 @@ use Symfony\Contracts\Service\ResetInterface;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
use ContractsTrait;
private $adapters = [];
private $adapterCount;
private $syncItem;
@@ -41,7 +36,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
* @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)
public function __construct(array $adapters, $defaultLifetime = 0)
{
if (!$adapters) {
throw new InvalidArgumentException('At least one adapter must be specified.');
@@ -51,6 +46,9 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
if (!$adapter instanceof CacheItemPoolInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($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;
@@ -61,21 +59,12 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
$this->adapterCount = \count($this->adapters);
$this->syncItem = \Closure::bind(
static function ($sourceItem, $item, $sourceMetadata = null) use ($defaultLifetime) {
$sourceItem->isTaggable = false;
$sourceMetadata = $sourceMetadata ?? $sourceItem->metadata;
unset($sourceMetadata[CacheItem::METADATA_TAGS]);
static function ($sourceItem, $item) use ($defaultLifetime) {
$item->value = $sourceItem->value;
$item->expiry = $sourceMetadata[CacheItem::METADATA_EXPIRY] ?? $sourceItem->expiry;
$item->isHit = $sourceItem->isHit;
$item->metadata = $item->newMetadata = $sourceItem->metadata = $sourceMetadata;
if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) {
$defaultLifetime = $sourceItem->defaultLifetime;
}
if (0 < $defaultLifetime && ($item->defaultLifetime <= 0 || $defaultLifetime < $item->defaultLifetime)) {
$item->defaultLifetime = $defaultLifetime;
if (0 < $defaultLifetime) {
$item->expiresAfter($defaultLifetime);
}
return $item;
@@ -85,34 +74,6 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
);
}
/**
* {@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) {
($this->syncItem)($lastItem = $lastItem ?? $item, $item, $metadata);
}
return $value;
};
return $wrap();
}
/**
* {@inheritdoc}
*/
@@ -303,7 +264,7 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
public function reset()
{
foreach ($this->adapters as $adapter) {
if ($adapter instanceof ResetInterface) {
if ($adapter instanceof ResettableInterface) {
$adapter->reset();
}
}
+5 -1
View File
@@ -18,7 +18,11 @@ class DoctrineAdapter extends AbstractAdapter
{
use DoctrineTrait;
public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
{
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
+6 -4
View File
@@ -11,8 +11,6 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
@@ -20,9 +18,13 @@ class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
{
use FilesystemTrait;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*/
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
@@ -1,152 +0,0 @@
<?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 Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
/**
* Stores tag id <> cache id relationship as a symlink, and lookup on invalidation calls.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*
* @experimental in 4.3
*/
class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface
{
use FilesystemTrait {
doSave as private doSaveCache;
doDelete as private doDeleteCache;
}
/**
* Folder used for tag symlinks.
*/
private const TAG_FOLDER = 'tags';
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $removeTagData = []): array
{
$failed = $this->doSaveCache($values, $lifetime);
// Add Tags as symlinks
foreach ($addTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($ids as $id) {
if ($failed && \in_array($id, $failed, true)) {
continue;
}
$file = $this->getFile($id);
if (!@symlink($file, $this->getFile($id, true, $tagFolder))) {
@unlink($file);
$failed[] = $id;
}
}
}
// Unlink removed Tags
foreach ($removeTagData as $tagId => $ids) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($ids as $id) {
if ($failed && \in_array($id, $failed, true)) {
continue;
}
@unlink($this->getFile($id, false, $tagFolder));
}
}
return $failed;
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids, array $tagData = []): bool
{
$ok = $this->doDeleteCache($ids);
// Remove tags
foreach ($tagData as $tagId => $idMap) {
$tagFolder = $this->getTagFolder($tagId);
foreach ($idMap as $id) {
@unlink($this->getFile($id, false, $tagFolder));
}
}
return $ok;
}
/**
* {@inheritdoc}
*/
protected function doInvalidate(array $tagIds): bool
{
foreach ($tagIds as $tagId) {
if (!file_exists($tagFolder = $this->getTagFolder($tagId))) {
continue;
}
set_error_handler(static function () {});
try {
if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -1))) {
$tagFolder = $renamed.\DIRECTORY_SEPARATOR;
} else {
$renamed = null;
}
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tagFolder, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $itemLink) {
unlink(realpath($itemLink) ?: $itemLink);
unlink($itemLink);
}
if (null === $renamed) {
continue;
}
$chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for ($i = 0; $i < 38; ++$i) {
for ($j = 0; $j < 38; ++$j) {
rmdir($tagFolder.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j]);
}
rmdir($tagFolder.$chars[$i]);
}
rmdir($renamed);
} finally {
restore_error_handler();
}
}
return true;
}
private function getTagFolder(string $tagId): string
{
return $this->getFile($tagId, false, $this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR).\DIRECTORY_SEPARATOR;
}
}
+2 -3
View File
@@ -11,7 +11,6 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
class MemcachedAdapter extends AbstractAdapter
@@ -30,8 +29,8 @@ class MemcachedAdapter extends AbstractAdapter
*
* Using a MemcachedAdapter as a pure items store is fine.
*/
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
{
$this->init($client, $namespace, $defaultLifetime, $marshaller);
$this->init($client, $namespace, $defaultLifetime);
}
}
+1 -20
View File
@@ -13,12 +13,11 @@ namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class NullAdapter implements AdapterInterface, CacheInterface
class NullAdapter implements AdapterInterface
{
private $createCacheItem;
@@ -37,16 +36,6 @@ class NullAdapter implements AdapterInterface, CacheInterface
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
$save = true;
return $callback(($this->createCacheItem)($key), $save);
}
/**
* {@inheritdoc}
*/
@@ -121,14 +110,6 @@ class NullAdapter implements AdapterInterface, CacheInterface
return false;
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
return $this->deleteItem($key);
}
private function generateItems(array $keys)
{
$f = $this->createCacheItem;
+6 -7
View File
@@ -13,7 +13,6 @@ namespace Symfony\Component\Cache\Adapter;
use Doctrine\DBAL\Connection;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PdoTrait;
@@ -28,9 +27,6 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
* a Doctrine DBAL Connection or a DSN string that will be used to
* lazy-connect to the database when the cache is actually used.
*
* When a Doctrine DBAL Connection is passed, the cache table is created
* automatically when possible. Otherwise, use the createTable() method.
*
* List of available options:
* * db_table: The name of the table [default: cache_items]
* * db_id_col: The column where to store the cache id [default: item_id]
@@ -41,14 +37,17 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
*
* @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
* @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null
* @param string $namespace
* @param int $defaultLifetime
* @param array $options An associative array of options
*
* @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
* @throws InvalidArgumentException When namespace contains invalid characters
*/
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = [])
{
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
}
}
+49 -60
View File
@@ -17,9 +17,7 @@ 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\Component\Cache\Traits\PhpArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
@@ -28,10 +26,9 @@ use Symfony\Contracts\Cache\CacheInterface;
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
use ContractsTrait;
private $createCacheItem;
@@ -39,10 +36,11 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
* @param string $file The PHP file were values are cached
* @param AdapterInterface $fallbackPool A pool to fallback on when an item is not hit
*/
public function __construct(string $file, AdapterInterface $fallbackPool)
public function __construct($file, AdapterInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
$this->createCacheItem = \Closure::bind(
static function ($key, $value, $isHit) {
$item = new CacheItem();
@@ -58,7 +56,9 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
}
/**
* This adapter takes advantage of how PHP stores arrays in its latest versions.
* This adapter should only be used on PHP 7.0+ to take advantage of how PHP
* stores arrays in its latest versions. This factory method decorates the given
* fallback pool with this adapter only if the current PHP version is supported.
*
* @param string $file The PHP file were values are cached
* @param CacheItemPoolInterface $fallbackPool A pool to fallback on when an item is not hit
@@ -67,44 +67,15 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
*/
public static function create($file, CacheItemPoolInterface $fallbackPool)
{
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return new static($file, $fallbackPool);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
get_from_pool:
if ($this->pool instanceof CacheInterface) {
return $this->pool->get($key, $callback, $beta, $metadata);
if (\PHP_VERSION_ID >= 70000) {
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
return $this->doGet($this->pool, $key, $callback, $beta, $metadata);
}
$value = $this->values[$this->keys[$key]];
if ('N;' === $value) {
return null;
}
try {
if ($value instanceof \Closure) {
return $value();
}
} catch (\Throwable $e) {
unset($this->keys[$key]);
goto get_from_pool;
return new static($file, $fallbackPool);
}
return $value;
return $fallbackPool;
}
/**
@@ -118,19 +89,23 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
if (!isset($this->values[$key])) {
return $this->pool->getItem($key);
}
$value = $this->values[$this->keys[$key]];
$value = $this->values[$key];
$isHit = true;
if ('N;' === $value) {
$value = null;
} elseif ($value instanceof \Closure) {
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
$value = $value();
} catch (\Throwable $e) {
$e = null;
$value = unserialize($value);
} catch (\Error $e) {
} catch (\Exception $e) {
}
if (null !== $e) {
$value = null;
$isHit = false;
}
@@ -170,7 +145,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return isset($this->keys[$key]) || $this->pool->hasItem($key);
return isset($this->values[$key]) || $this->pool->hasItem($key);
}
/**
@@ -185,7 +160,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->keys[$key]) && $this->pool->deleteItem($key);
return !isset($this->values[$key]) && $this->pool->deleteItem($key);
}
/**
@@ -201,7 +176,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (isset($this->keys[$key])) {
if (isset($this->values[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
@@ -227,7 +202,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->keys[$item->getKey()]) && $this->pool->save($item);
return !isset($this->values[$item->getKey()]) && $this->pool->save($item);
}
/**
@@ -239,7 +214,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$this->initialize();
}
return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item);
return !isset($this->values[$item->getKey()]) && $this->pool->saveDeferred($item);
}
/**
@@ -250,21 +225,26 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
return $this->pool->commit();
}
private function generateItems(array $keys): \Generator
/**
* @return \Generator
*/
private function generateItems(array $keys)
{
$f = $this->createCacheItem;
$fallbackKeys = [];
foreach ($keys as $key) {
if (isset($this->keys[$key])) {
$value = $this->values[$this->keys[$key]];
if (isset($this->values[$key])) {
$value = $this->values[$key];
if ('N;' === $value) {
yield $key => $f($key, null, true);
} elseif ($value instanceof \Closure) {
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => $f($key, $value(), true);
} catch (\Throwable $e) {
yield $key => $f($key, unserialize($value), true);
} catch (\Error $e) {
yield $key => $f($key, null, false);
} catch (\Exception $e) {
yield $key => $f($key, null, false);
}
} else {
@@ -276,7 +256,9 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
}
if ($fallbackKeys) {
yield from $this->pool->getItems($fallbackKeys);
foreach ($this->pool->getItems($fallbackKeys) as $key => $item) {
yield $key => $item;
}
}
}
@@ -293,10 +275,17 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
'function' => 'spl_autoload_call',
'args' => [$class],
];
$i = 1 + array_search($autoloadFrame, $trace, true);
if (isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
switch ($trace[$i]['function']) {
if (\PHP_VERSION_ID >= 80000 && isset($trace[1])) {
$callerFrame = $trace[1];
} elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
$callerFrame = $trace[++$i];
} else {
throw $e;
}
if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
switch ($callerFrame['function']) {
case 'get_class_methods':
case 'get_class_vars':
case 'get_parent_class':
+11 -8
View File
@@ -20,19 +20,22 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
use PhpFilesTrait;
/**
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
* Doing so is encouraged because it fits perfectly OPcache's memory model.
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*
* @throws CacheException if OPcache is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->appendOnly = $appendOnly;
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
if (!static::isSupported()) {
throw new CacheException('OPcache is not enabled.');
}
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
$this->includeHandler = static function ($type, $msg, $file, $line) {
throw new \ErrorException($msg, 0, $type, $file, $line);
};
$e = new \Exception();
$this->includeHandler = function () use ($e) { throw $e; };
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
}
}
+19 -67
View File
@@ -16,100 +16,50 @@ use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\ProxyTrait;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
use ContractsTrait;
private $namespace;
private $namespaceLen;
private $createCacheItem;
private $setInnerItem;
private $poolHash;
private $defaultLifetime;
public function __construct(CacheItemPoolInterface $pool, string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(CacheItemPoolInterface $pool, $namespace = '', $defaultLifetime = 0)
{
$this->pool = $pool;
$this->poolHash = $poolHash = spl_object_hash($pool);
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace);
$this->namespaceLen = \strlen($namespace);
$this->defaultLifetime = $defaultLifetime;
$this->createCacheItem = \Closure::bind(
static function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
static function ($key, $innerItem) use ($poolHash) {
$item = new CacheItem();
$item->key = $key;
if (null === $innerItem) {
return $item;
}
$item->value = $v = $innerItem->get();
$item->isHit = $innerItem->isHit();
$item->innerItem = $innerItem;
$item->defaultLifetime = $defaultLifetime;
$item->poolHash = $poolHash;
// Detect wrapped values that encode for their expiry and creation duration
// For compactness, these values are packed in the key of an array using
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
if (\is_array($v) && 1 === \count($v) && 10 === \strlen($k = key($v)) && "\x9D" === $k[0] && "\0" === $k[5] && "\x5F" === $k[9]) {
$item->value = $v[$k];
$v = unpack('Ve/Nc', substr($k, 1, -1));
$item->metadata[CacheItem::METADATA_EXPIRY] = $v['e'] + CacheItem::METADATA_EXPIRY_OFFSET;
$item->metadata[CacheItem::METADATA_CTIME] = $v['c'];
} elseif ($innerItem instanceof CacheItem) {
$item->metadata = $innerItem->metadata;
if (null !== $innerItem) {
$item->value = $innerItem->get();
$item->isHit = $innerItem->isHit();
$item->innerItem = $innerItem;
$innerItem->set(null);
}
$innerItem->set(null);
return $item;
},
null,
CacheItem::class
);
$this->setInnerItem = \Closure::bind(
/**
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
*/
static function (CacheItemInterface $innerItem, array $item) {
// Tags are stored separately, no need to account for them when considering this item's newly set metadata
if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
unset($metadata[CacheItem::METADATA_TAGS]);
}
if ($metadata) {
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
$item["\0*\0value"] = ["\x9D".pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME])."\x5F" => $item["\0*\0value"]];
}
$innerItem->set($item["\0*\0value"]);
$innerItem->expiresAt(null !== $item["\0*\0expiry"] ? \DateTime::createFromFormat('U.u', sprintf('%.6f', $item["\0*\0expiry"])) : null);
},
null,
CacheItem::class
);
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (!$this->pool instanceof CacheInterface) {
return $this->doGet($this, $key, $callback, $beta, $metadata);
}
return $this->pool->get($this->getId($key), function ($innerItem, bool &$save) use ($key, $callback) {
$item = ($this->createCacheItem)($key, $innerItem);
$item->set($value = $callback($item, $save));
($this->setInnerItem)($innerItem, (array) $item);
return $value;
}, $beta, $metadata);
}
/**
@@ -205,8 +155,9 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
return false;
}
$item = (array) $item;
if (null === $item["\0*\0expiry"] && 0 < $item["\0*\0defaultLifetime"]) {
$item["\0*\0expiry"] = microtime(true) + $item["\0*\0defaultLifetime"];
$expiry = $item["\0*\0expiry"];
if (null === $expiry && 0 < $this->defaultLifetime) {
$expiry = time() + $this->defaultLifetime;
}
if ($item["\0*\0poolHash"] === $this->poolHash && $item["\0*\0innerItem"]) {
@@ -220,7 +171,8 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
$innerItem = $this->pool->getItem($this->namespace.$item["\0*\0key"]);
}
($this->setInnerItem)($innerItem, $item);
$innerItem->set($item["\0*\0value"]);
$innerItem->expiresAt(null !== $expiry ? \DateTime::createFromFormat('U', $expiry) : null);
return $this->pool->$method($innerItem);
}
-86
View File
@@ -1,86 +0,0 @@
<?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\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* Turns a PSR-16 cache into a PSR-6 one.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr16Adapter extends AbstractAdapter implements PruneableInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = '_';
use ProxyTrait;
private $miss;
public function __construct(CacheInterface $pool, string $namespace = '', int $defaultLifetime = 0)
{
parent::__construct($namespace, $defaultLifetime);
$this->pool = $pool;
$this->miss = new \stdClass();
}
/**
* {@inheritdoc}
*/
protected function doFetch(array $ids)
{
foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
if ($this->miss !== $value) {
yield $key => $value;
}
}
}
/**
* {@inheritdoc}
*/
protected function doHave($id)
{
return $this->pool->has($id);
}
/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids)
{
return $this->pool->deleteMultiple($ids);
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
{
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
}
}
+5 -6
View File
@@ -11,7 +11,6 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
class RedisAdapter extends AbstractAdapter
@@ -19,12 +18,12 @@ class RedisAdapter extends AbstractAdapter
use RedisTrait;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
$this->init($redisClient, $namespace, $defaultLifetime);
}
}
-212
View File
@@ -1,212 +0,0 @@
<?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 Predis;
use Predis\Connection\Aggregate\ClusterInterface;
use Predis\Response\Status;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\LogicException;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
/**
* Stores tag id <> cache id relationship as a Redis Set, lookup on invalidation using sPOP.
*
* Set (tag relation info) is stored without expiry (non-volatile), while cache always gets an expiry (volatile) even
* if not set by caller. Thus if you configure redis with the right eviction policy you can be safe this tag <> cache
* relationship survives eviction (cache cleanup when Redis runs out of memory).
*
* Requirements:
* - Server: Redis 3.2+
* - Client: PHP Redis 3.1.3+ OR Predis
* - Redis Server(s) configured with any `volatile-*` eviction policy, OR `noeviction` if it will NEVER fill up memory
*
* Design limitations:
* - Max 2 billion cache keys per cache tag
* E.g. If you use a "all" items tag for expiry instead of clear(), that limits you to 2 billion cache items as well
*
* @see https://redis.io/topics/lru-cache#eviction-policies Documentation for Redis eviction policies.
* @see https://redis.io/topics/data-types#sets Documentation for Redis Set datatype.
* @see https://redis.io/commands/spop Documentation for sPOP operation, capable of retriving AND emptying a Set at once.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author André Rømcke <andre.romcke+symfony@gmail.com>
*
* @experimental in 4.3
*/
class RedisTagAwareAdapter extends AbstractTagAwareAdapter
{
use RedisTrait;
/**
* Redis "Set" can hold more than 4 billion members, here we limit ourselves to PHP's > 2 billion max int (32Bit).
*/
private const POP_MAX_LIMIT = 2147483647 - 1;
/**
* Limits for how many keys are deleted in batch.
*/
private const BULK_DELETE_LIMIT = 10000;
/**
* On cache items without a lifetime set, we set it to 100 days. This is to make sure cache items are
* preferred to be evicted over tag Sets, if eviction policy is configured according to requirements.
*/
private const DEFAULT_CACHE_TTL = 8640000;
/**
* @var bool|null
*/
private $redisServerSupportSPOP = null;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* @param string $namespace The default namespace
* @param int $defaultLifetime The default lifetime
*
* @throws \Symfony\Component\Cache\Exception\LogicException If phpredis with version lower than 3.1.3.
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
// Make sure php-redis is 3.1.3 or higher configured for Redis classes
if (!$this->redis instanceof \Predis\ClientInterface && version_compare(phpversion('redis'), '3.1.3', '<')) {
throw new LogicException('RedisTagAwareAdapter requires php-redis 3.1.3 or higher, alternatively use predis/predis');
}
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, ?int $lifetime, array $addTagData = [], array $delTagData = []): array
{
// serialize values
if (!$serialized = $this->marshaller->marshall($values, $failed)) {
return $failed;
}
// While pipeline isn't supported on RedisCluster, other setups will at least benefit from doing this in one op
$results = $this->pipeline(static function () use ($serialized, $lifetime, $addTagData, $delTagData, $failed) {
// Store cache items, force a ttl if none is set, as there is no MSETEX we need to set each one
foreach ($serialized as $id => $value) {
yield 'setEx' => [
$id,
0 >= $lifetime ? self::DEFAULT_CACHE_TTL : $lifetime,
$value,
];
}
// Add and Remove Tags
foreach ($addTagData as $tagId => $ids) {
if (!$failed || $ids = array_diff($ids, $failed)) {
yield 'sAdd' => array_merge([$tagId], $ids);
}
}
foreach ($delTagData as $tagId => $ids) {
if (!$failed || $ids = array_diff($ids, $failed)) {
yield 'sRem' => array_merge([$tagId], $ids);
}
}
});
foreach ($results as $id => $result) {
// Skip results of SADD/SREM operations, they'll be 1 or 0 depending on if set value already existed or not
if (is_numeric($result)) {
continue;
}
// setEx results
if (true !== $result && (!$result instanceof Status || $result !== Status::get('OK'))) {
$failed[] = $id;
}
}
return $failed;
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids, array $tagData = []): bool
{
if (!$ids) {
return true;
}
$predisCluster = $this->redis instanceof \Predis\ClientInterface && $this->redis->getConnection() instanceof ClusterInterface;
$this->pipeline(static function () use ($ids, $tagData, $predisCluster) {
if ($predisCluster) {
foreach ($ids as $id) {
yield 'del' => [$id];
}
} else {
yield 'del' => $ids;
}
foreach ($tagData as $tagId => $idList) {
yield 'sRem' => array_merge([$tagId], $idList);
}
})->rewind();
return true;
}
/**
* {@inheritdoc}
*/
protected function doInvalidate(array $tagIds): bool
{
if (!$this->redisServerSupportSPOP()) {
return false;
}
// Pop all tag info at once to avoid race conditions
$tagIdSets = $this->pipeline(static function () use ($tagIds) {
foreach ($tagIds as $tagId) {
// Client: Predis or PHP Redis 3.1.3+ (https://github.com/phpredis/phpredis/commit/d2e203a6)
// Server: Redis 3.2 or higher (https://redis.io/commands/spop)
yield 'sPop' => [$tagId, self::POP_MAX_LIMIT];
}
});
// Flatten generator result from pipeline, ignore keys (tag ids)
$ids = array_unique(array_merge(...iterator_to_array($tagIdSets, false)));
// Delete cache in chunks to avoid overloading the connection
foreach (array_chunk($ids, self::BULK_DELETE_LIMIT) as $chunkIds) {
$this->doDelete($chunkIds);
}
return true;
}
private function redisServerSupportSPOP(): bool
{
if (null !== $this->redisServerSupportSPOP) {
return $this->redisServerSupportSPOP;
}
foreach ($this->getHosts() as $host) {
$info = $host->info('Server');
$info = isset($info['Server']) ? $info['Server'] : $info;
if (version_compare($info['redis_version'], '3.2', '<')) {
CacheItem::log($this->logger, 'Redis server needs to be version 3.2 or higher, your Redis server was detected as '.$info['redis_version']);
return $this->redisServerSupportSPOP = false;
}
}
return $this->redisServerSupportSPOP = true;
}
}
+65 -3
View File
@@ -11,11 +11,73 @@
namespace Symfony\Component\Cache\Adapter;
@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), E_USER_DEPRECATED);
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* @deprecated since Symfony 4.3, use Psr16Adapter instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class SimpleCacheAdapter extends Psr16Adapter
class SimpleCacheAdapter extends AbstractAdapter implements PruneableInterface
{
/**
* @internal
*/
const NS_SEPARATOR = '_';
use ProxyTrait;
private $miss;
public function __construct(CacheInterface $pool, $namespace = '', $defaultLifetime = 0)
{
parent::__construct($namespace, $defaultLifetime);
$this->pool = $pool;
$this->miss = new \stdClass();
}
/**
* {@inheritdoc}
*/
protected function doFetch(array $ids)
{
foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) {
if ($this->miss !== $value) {
yield $key => $value;
}
}
}
/**
* {@inheritdoc}
*/
protected function doHave($id)
{
return $this->pool->has($id);
}
/**
* {@inheritdoc}
*/
protected function doClear($namespace)
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
protected function doDelete(array $ids)
{
return $this->pool->deleteMultiple($ids);
}
/**
* {@inheritdoc}
*/
protected function doSave(array $values, $lifetime)
{
return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime);
}
}
+5 -11
View File
@@ -16,19 +16,16 @@ use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ContractsTrait;
use Symfony\Component\Cache\Traits\ProxyTrait;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface
class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface
{
const TAGS_PREFIX = "\0tags\0";
use ProxyTrait;
use ContractsTrait;
private $deferred = [];
private $createCacheItem;
@@ -39,7 +36,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
private $knownTagVersions = [];
private $knownTagVersionsTtl;
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, float $knownTagVersionsTtl = 0.15)
public function __construct(AdapterInterface $itemsPool, AdapterInterface $tagsPool = null, $knownTagVersionsTtl = 0.15)
{
$this->pool = $itemsPool;
$this->tags = $tagsPool ?: $itemsPool;
@@ -49,7 +46,6 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
$item = new CacheItem();
$item->key = $key;
$item->value = $value;
$item->defaultLifetime = $protoItem->defaultLifetime;
$item->expiry = $protoItem->expiry;
$item->poolHash = $protoItem->poolHash;
@@ -60,13 +56,12 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
);
$this->setCacheItemTags = \Closure::bind(
static function (CacheItem $item, $key, array &$itemTags) {
$item->isTaggable = true;
if (!$item->isHit) {
return $item;
}
if (isset($itemTags[$key])) {
foreach ($itemTags[$key] as $tag => $version) {
$item->metadata[CacheItem::METADATA_TAGS][$tag] = $tag;
$item->prevTags[$tag] = $tag;
}
unset($itemTags[$key]);
} else {
@@ -83,7 +78,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
static function ($deferred) {
$tagsByKey = [];
foreach ($deferred as $key => $item) {
$tagsByKey[$key] = $item->newMetadata[CacheItem::METADATA_TAGS] ?? [];
$tagsByKey[$key] = $item->tags;
}
return $tagsByKey;
@@ -94,8 +89,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
$this->invalidateTags = \Closure::bind(
static function (AdapterInterface $tagsAdapter, array $tags) {
foreach ($tags as $v) {
$v->defaultLifetime = 0;
$v->expiry = null;
$v->expiry = 0;
$tagsAdapter->saveDeferred($v);
}
+3 -55
View File
@@ -12,11 +12,8 @@
namespace Symfony\Component\Cache\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* An adapter that collects data about all cache calls.
@@ -25,7 +22,7 @@ use Symfony\Contracts\Service\ResetInterface;
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
{
protected $pool;
private $calls = [];
@@ -35,38 +32,6 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt
$this->pool = $pool;
}
/**
* {@inheritdoc}
*/
public function get(string $key, callable $callback, float $beta = null, array &$metadata = null)
{
if (!$this->pool instanceof CacheInterface) {
throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', \get_class($this->pool), CacheInterface::class));
}
$isHit = true;
$callback = function (CacheItem $item, bool &$save) use ($callback, &$isHit) {
$isHit = $item->isHit();
return $callback($item, $save);
};
$event = $this->start(__FUNCTION__);
try {
$value = $this->pool->get($key, $callback, $beta, $metadata);
$event->result[$key] = \is_object($value) ? \get_class($value) : \gettype($value);
} finally {
$event->end = microtime(true);
}
if ($isHit) {
++$event->hits;
} else {
++$event->misses;
}
return $value;
}
/**
* {@inheritdoc}
*/
@@ -226,28 +191,11 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt
*/
public function reset()
{
if (!$this->pool instanceof ResetInterface) {
return;
}
$event = $this->start(__FUNCTION__);
try {
if ($this->pool instanceof ResettableInterface) {
$this->pool->reset();
} finally {
$event->end = microtime(true);
}
}
/**
* {@inheritdoc}
*/
public function delete(string $key): bool
{
$event = $this->start(__FUNCTION__);
try {
return $event->result[$key] = $this->pool->deleteItem($key);
} finally {
$event->end = microtime(true);
}
$this->clearCalls();
}
public function getCalls()
+1 -3
View File
@@ -11,12 +11,10 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface
class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface
{
public function __construct(TagAwareAdapterInterface $pool)
{
+1 -26
View File
@@ -1,31 +1,6 @@
CHANGELOG
=========
4.3.0
-----
* removed `psr/simple-cache` dependency, run `composer require psr/simple-cache` if you need it
* deprecated all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead
* deprecated `SimpleCacheAdapter`, use `Psr16Adapter` instead
4.2.0
-----
* added support for connecting to Redis clusters via DSN
* added support for configuring multiple Memcached servers via DSN
* added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
* implemented `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
* added sub-second expiry accuracy for backends that support it
* added support for phpredis 4 `compression` and `tcp_keepalive` options
* added automatic table creation when using Doctrine DBAL with PDO-based backends
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
* deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
* added `CacheCollectorPass` (originally in `FrameworkBundle`)
* added `CachePoolClearerPass` (originally in `FrameworkBundle`)
* added `CachePoolPass` (originally in `FrameworkBundle`)
* added `CachePoolPrunerPass` (originally in `FrameworkBundle`)
3.4.0
-----
@@ -38,7 +13,7 @@ CHANGELOG
3.3.0
-----
* added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
* [EXPERIMENTAL] added CacheItem::getPreviousTags() to get bound tags coming from the pool storage if any
* added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters
* added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16
* added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16)
+30 -44
View File
@@ -11,28 +11,23 @@
namespace Symfony\Component\Cache;
use Psr\Cache\CacheItemInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Exception\LogicException;
use Symfony\Contracts\Cache\ItemInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
final class CacheItem implements ItemInterface
final class CacheItem implements CacheItemInterface
{
private const METADATA_EXPIRY_OFFSET = 1527506807;
protected $key;
protected $value;
protected $isHit = false;
protected $expiry;
protected $defaultLifetime;
protected $metadata = [];
protected $newMetadata = [];
protected $tags = [];
protected $prevTags = [];
protected $innerItem;
protected $poolHash;
protected $isTaggable = false;
/**
* {@inheritdoc}
@@ -78,11 +73,11 @@ final class CacheItem implements ItemInterface
public function expiresAt($expiration)
{
if (null === $expiration) {
$this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
$this->expiry = null;
} elseif ($expiration instanceof \DateTimeInterface) {
$this->expiry = (float) $expiration->format('U.u');
$this->expiry = (int) $expiration->format('U');
} else {
throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given.', \is_object($expiration) ? \get_class($expiration) : \gettype($expiration)));
}
return $this;
@@ -96,68 +91,59 @@ final class CacheItem implements ItemInterface
public function expiresAfter($time)
{
if (null === $time) {
$this->expiry = $this->defaultLifetime > 0 ? microtime(true) + $this->defaultLifetime : null;
$this->expiry = null;
} elseif ($time instanceof \DateInterval) {
$this->expiry = microtime(true) + \DateTime::createFromFormat('U', 0)->add($time)->format('U.u');
$this->expiry = (int) \DateTime::createFromFormat('U', time())->add($time)->format('U');
} elseif (\is_int($time)) {
$this->expiry = $time + microtime(true);
$this->expiry = $time + time();
} else {
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($time) ? \get_class($time) : \gettype($time)));
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($time) ? \get_class($time) : \gettype($time)));
}
return $this;
}
/**
* {@inheritdoc}
* Adds a tag to a cache item.
*
* @param string|string[] $tags A tag or array of tags
*
* @return $this
*
* @throws InvalidArgumentException When $tag is not valid
*/
public function tag($tags): ItemInterface
public function tag($tags)
{
if (!$this->isTaggable) {
throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key));
}
if (!is_iterable($tags)) {
if (!\is_array($tags)) {
$tags = [$tags];
}
foreach ($tags as $tag) {
if (!\is_string($tag)) {
throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given.', \is_object($tag) ? \get_class($tag) : \gettype($tag)));
}
if (isset($this->newMetadata[self::METADATA_TAGS][$tag])) {
if (isset($this->tags[$tag])) {
continue;
}
if ('' === $tag) {
throw new InvalidArgumentException('Cache tag length must be greater than zero');
throw new InvalidArgumentException('Cache tag length must be greater than zero.');
}
if (false !== strpbrk($tag, '{}()/\@:')) {
throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag));
throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:.', $tag));
}
$this->newMetadata[self::METADATA_TAGS][$tag] = $tag;
$this->tags[$tag] = $tag;
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getMetadata(): array
{
return $this->metadata;
}
/**
* Returns the list of tags bound to the value coming from the pool storage if any.
*
* @return array
*
* @deprecated since Symfony 4.2, use the "getMetadata()" method instead.
*/
public function getPreviousTags()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "getMetadata()" method instead.', __METHOD__), E_USER_DEPRECATED);
return $this->metadata[self::METADATA_TAGS] ?? [];
return $this->prevTags;
}
/**
@@ -172,13 +158,13 @@ final class CacheItem implements ItemInterface
public static function validateKey($key)
{
if (!\is_string($key)) {
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', \is_object($key) ? \get_class($key) : \gettype($key)));
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if ('' === $key) {
throw new InvalidArgumentException('Cache key length must be greater than zero');
throw new InvalidArgumentException('Cache key length must be greater than zero.');
}
if (false !== strpbrk($key, '{}()/\@:')) {
throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key));
throw new InvalidArgumentException(sprintf('Cache key "%s" contains reserved characters {}()/\@:.', $key));
}
return $key;
@@ -200,7 +186,7 @@ final class CacheItem implements ItemInterface
$replace['{'.$k.'}'] = $v;
}
}
@trigger_error(strtr($message, $replace), E_USER_WARNING);
@trigger_error(strtr($message, $replace), \E_USER_WARNING);
}
}
}
+9 -11
View File
@@ -103,7 +103,10 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
return $this->data['instances']['calls'];
}
private function calculateStatistics(): array
/**
* @return array
*/
private function calculateStatistics()
{
$statistics = [];
foreach ($this->data['instances']['calls'] as $name => $calls) {
@@ -120,15 +123,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
foreach ($calls as $call) {
++$statistics[$name]['calls'];
$statistics[$name]['time'] += $call->end - $call->start;
if ('get' === $call->name) {
++$statistics[$name]['reads'];
if ($call->hits) {
++$statistics[$name]['hits'];
} else {
++$statistics[$name]['misses'];
++$statistics[$name]['writes'];
}
} elseif ('getItem' === $call->name) {
if ('getItem' === $call->name) {
++$statistics[$name]['reads'];
if ($call->hits) {
++$statistics[$name]['hits'];
@@ -162,7 +157,10 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter
return $statistics;
}
private function calculateTotalStatistics(): array
/**
* @return array
*/
private function calculateTotalStatistics()
{
$statistics = $this->getStatistics();
$totals = [
@@ -1,72 +0,0 @@
<?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\DependencyInjection;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\Cache\Adapter\TraceableAdapter;
use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Inject a data collector to all the cache services to be able to get detailed statistics.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class CacheCollectorPass implements CompilerPassInterface
{
private $dataCollectorCacheId;
private $cachePoolTag;
private $cachePoolRecorderInnerSuffix;
public function __construct(string $dataCollectorCacheId = 'data_collector.cache', string $cachePoolTag = 'cache.pool', string $cachePoolRecorderInnerSuffix = '.recorder_inner')
{
$this->dataCollectorCacheId = $dataCollectorCacheId;
$this->cachePoolTag = $cachePoolTag;
$this->cachePoolRecorderInnerSuffix = $cachePoolRecorderInnerSuffix;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dataCollectorCacheId)) {
return;
}
$collectorDefinition = $container->getDefinition($this->dataCollectorCacheId);
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $attributes) {
$definition = $container->getDefinition($id);
if ($definition->isAbstract()) {
continue;
}
$recorder = new Definition(is_subclass_of($definition->getClass(), TagAwareAdapterInterface::class) ? TraceableTagAwareAdapter::class : TraceableAdapter::class);
$recorder->setTags($definition->getTags());
$recorder->setPublic($definition->isPublic());
$recorder->setArguments([new Reference($innerId = $id.$this->cachePoolRecorderInnerSuffix)]);
$definition->setTags([]);
$definition->setPublic(false);
$container->setDefinition($innerId, $definition);
$container->setDefinition($id, $recorder);
// Tell the collector to add the new instance
$collectorDefinition->addMethodCall('addInstance', [$id, new Reference($id)]);
$collectorDefinition->setPublic(false);
}
}
}
@@ -1,48 +0,0 @@
<?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\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CachePoolClearerPass implements CompilerPassInterface
{
private $cachePoolClearerTag;
public function __construct(string $cachePoolClearerTag = 'cache.pool.clearer')
{
$this->cachePoolClearerTag = $cachePoolClearerTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$container->getParameterBag()->remove('cache.prefix.seed');
foreach ($container->findTaggedServiceIds($this->cachePoolClearerTag) as $id => $attr) {
$clearer = $container->getDefinition($id);
$pools = [];
foreach ($clearer->getArgument(0) as $name => $ref) {
if ($container->hasDefinition($ref)) {
$pools[$name] = new Reference($ref);
}
}
$clearer->replaceArgument(0, $pools);
}
}
}
@@ -1,178 +0,0 @@
<?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\DependencyInjection;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CachePoolPass implements CompilerPassInterface
{
private $cachePoolTag;
private $kernelResetTag;
private $cacheClearerId;
private $cachePoolClearerTag;
private $cacheSystemClearerId;
private $cacheSystemClearerTag;
public function __construct(string $cachePoolTag = 'cache.pool', string $kernelResetTag = 'kernel.reset', string $cacheClearerId = 'cache.global_clearer', string $cachePoolClearerTag = 'cache.pool.clearer', string $cacheSystemClearerId = 'cache.system_clearer', string $cacheSystemClearerTag = 'kernel.cache_clearer')
{
$this->cachePoolTag = $cachePoolTag;
$this->kernelResetTag = $kernelResetTag;
$this->cacheClearerId = $cacheClearerId;
$this->cachePoolClearerTag = $cachePoolClearerTag;
$this->cacheSystemClearerId = $cacheSystemClearerId;
$this->cacheSystemClearerTag = $cacheSystemClearerTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if ($container->hasParameter('cache.prefix.seed')) {
$seed = '.'.$container->getParameterBag()->resolveValue($container->getParameter('cache.prefix.seed'));
} else {
$seed = '_'.$container->getParameter('kernel.project_dir');
}
$seed .= '.'.$container->getParameter('kernel.container_class');
$allPools = [];
$clearers = [];
$attributes = [
'provider',
'name',
'namespace',
'default_lifetime',
'reset',
];
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
$adapter = $pool = $container->getDefinition($id);
if ($pool->isAbstract()) {
continue;
}
$class = $adapter->getClass();
while ($adapter instanceof ChildDefinition) {
$adapter = $container->findDefinition($adapter->getParent());
$class = $class ?: $adapter->getClass();
if ($t = $adapter->getTag($this->cachePoolTag)) {
$tags[0] += $t[0];
}
}
$name = $tags[0]['name'] ?? $id;
if (!isset($tags[0]['namespace'])) {
$namespaceSeed = $seed;
if (null !== $class) {
$namespaceSeed .= '.'.$class;
}
$tags[0]['namespace'] = $this->getNamespace($namespaceSeed, $name);
}
if (isset($tags[0]['clearer'])) {
$clearer = $tags[0]['clearer'];
while ($container->hasAlias($clearer)) {
$clearer = (string) $container->getAlias($clearer);
}
} else {
$clearer = null;
}
unset($tags[0]['clearer'], $tags[0]['name']);
if (isset($tags[0]['provider'])) {
$tags[0]['provider'] = new Reference(static::getServiceProvider($container, $tags[0]['provider']));
}
$i = 0;
foreach ($attributes as $attr) {
if (!isset($tags[0][$attr])) {
// no-op
} elseif ('reset' === $attr) {
if ($tags[0][$attr]) {
$pool->addTag($this->kernelResetTag, ['method' => $tags[0][$attr]]);
}
} elseif ('namespace' !== $attr || ArrayAdapter::class !== $adapter->getClass()) {
$pool->replaceArgument($i++, $tags[0][$attr]);
}
unset($tags[0][$attr]);
}
if (!empty($tags[0])) {
throw new InvalidArgumentException(sprintf('Invalid "%s" tag for service "%s": accepted attributes are "clearer", "provider", "name", "namespace", "default_lifetime" and "reset", found "%s".', $this->cachePoolTag, $id, implode('", "', array_keys($tags[0]))));
}
if (null !== $clearer) {
$clearers[$clearer][$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
$allPools[$name] = new Reference($id, $container::IGNORE_ON_UNINITIALIZED_REFERENCE);
}
$notAliasedCacheClearerId = $this->cacheClearerId;
while ($container->hasAlias($this->cacheClearerId)) {
$this->cacheClearerId = (string) $container->getAlias($this->cacheClearerId);
}
if ($container->hasDefinition($this->cacheClearerId)) {
$clearers[$notAliasedCacheClearerId] = $allPools;
}
foreach ($clearers as $id => $pools) {
$clearer = $container->getDefinition($id);
if ($clearer instanceof ChildDefinition) {
$clearer->replaceArgument(0, $pools);
} else {
$clearer->setArgument(0, $pools);
}
$clearer->addTag($this->cachePoolClearerTag);
if ($this->cacheSystemClearerId === $id) {
$clearer->addTag($this->cacheSystemClearerTag);
}
}
if ($container->hasDefinition('console.command.cache_pool_list')) {
$container->getDefinition('console.command.cache_pool_list')->replaceArgument(0, array_keys($allPools));
}
}
private function getNamespace($seed, $id)
{
return substr(str_replace('/', '-', base64_encode(hash('sha256', $id.$seed, true))), 0, 10);
}
/**
* @internal
*/
public static function getServiceProvider(ContainerBuilder $container, $name)
{
$container->resolveEnvPlaceholders($name, null, $usedEnvs);
if ($usedEnvs || preg_match('#^[a-z]++:#', $name)) {
$dsn = $name;
if (!$container->hasDefinition($name = '.cache_connection.'.ContainerBuilder::hash($dsn))) {
$definition = new Definition(AbstractAdapter::class);
$definition->setPublic(false);
$definition->setFactory([AbstractAdapter::class, 'createConnection']);
$definition->setArguments([$dsn, ['lazy' => true]]);
$container->setDefinition($name, $definition);
}
}
return $name;
}
}
@@ -1,60 +0,0 @@
<?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\DependencyInjection;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Rob Frawley 2nd <rmf@src.run>
*/
class CachePoolPrunerPass implements CompilerPassInterface
{
private $cacheCommandServiceId;
private $cachePoolTag;
public function __construct(string $cacheCommandServiceId = 'console.command.cache_pool_prune', string $cachePoolTag = 'cache.pool')
{
$this->cacheCommandServiceId = $cacheCommandServiceId;
$this->cachePoolTag = $cachePoolTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->cacheCommandServiceId)) {
return;
}
$services = [];
foreach ($container->findTaggedServiceIds($this->cachePoolTag) as $id => $tags) {
$class = $container->getParameterBag()->resolveValue($container->getDefinition($id)->getClass());
if (!$reflection = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if ($reflection->implementsInterface(PruneableInterface::class)) {
$services[$id] = new Reference($id);
}
}
$container->getDefinition($this->cacheCommandServiceId)->replaceArgument(0, new IteratorArgument($services));
}
}
+1 -2
View File
@@ -13,7 +13,6 @@ namespace Symfony\Component\Cache;
use Doctrine\Common\Cache\CacheProvider;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
@@ -40,7 +39,7 @@ class DoctrineProvider extends CacheProvider implements PruneableInterface, Rese
*/
public function reset()
{
if ($this->pool instanceof ResetInterface) {
if ($this->pool instanceof ResettableInterface) {
$this->pool->reset();
}
$this->setNamespace($this->getNamespace());
+2 -8
View File
@@ -14,12 +14,6 @@ namespace Symfony\Component\Cache\Exception;
use Psr\Cache\CacheException as Psr6CacheInterface;
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
if (interface_exists(SimpleCacheInterface::class)) {
class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
{
}
} else {
class CacheException extends \Exception implements Psr6CacheInterface
{
}
class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface
{
}
@@ -14,12 +14,6 @@ namespace Symfony\Component\Cache\Exception;
use Psr\Cache\InvalidArgumentException as Psr6CacheInterface;
use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface;
if (interface_exists(SimpleCacheInterface::class)) {
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
{
}
} else {
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface
{
}
class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface
{
}
-25
View File
@@ -1,25 +0,0 @@
<?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\Exception;
use Psr\Cache\CacheException as Psr6CacheInterface;
use Psr\SimpleCache\CacheException as SimpleCacheInterface;
if (interface_exists(SimpleCacheInterface::class)) {
class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface
{
}
} else {
class LogicException extends \LogicException implements Psr6CacheInterface
{
}
}
-152
View File
@@ -1,152 +0,0 @@
<?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;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
/**
* LockRegistry is used internally by existing adapters to protect against cache stampede.
*
* It does so by wrapping the computation of items in a pool of locks.
* Foreach each apps, there can be at most 20 concurrent processes that
* compute items at the same time and only one per cache-key.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class LockRegistry
{
private static $openedFiles = [];
private static $lockedFiles = [];
/**
* The number of items in this list controls the max number of concurrent processes.
*/
private static $files = [
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AbstractTagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'AdapterInterface.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ApcuAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ArrayAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ChainAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'DoctrineAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'FilesystemTagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'MemcachedAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'NullAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PdoAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpArrayAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'PhpFilesAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'ProxyAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'Psr16Adapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'RedisTagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'SimpleCacheAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TagAwareAdapterInterface.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableAdapter.php',
__DIR__.\DIRECTORY_SEPARATOR.'Adapter'.\DIRECTORY_SEPARATOR.'TraceableTagAwareAdapter.php',
];
/**
* Defines a set of existing files that will be used as keys to acquire locks.
*
* @return array The previously defined set of files
*/
public static function setFiles(array $files): array
{
$previousFiles = self::$files;
self::$files = $files;
foreach (self::$openedFiles as $file) {
if ($file) {
flock($file, LOCK_UN);
fclose($file);
}
}
self::$openedFiles = self::$lockedFiles = [];
return $previousFiles;
}
public static function compute(callable $callback, ItemInterface $item, bool &$save, CacheInterface $pool, \Closure $setMetadata = null, LoggerInterface $logger = null)
{
$key = self::$files ? crc32($item->getKey()) % \count(self::$files) : -1;
if ($key < 0 || (self::$lockedFiles[$key] ?? false) || !$lock = self::open($key)) {
return $callback($item, $save);
}
while (true) {
try {
// race to get the lock in non-blocking mode
$locked = flock($lock, LOCK_EX | LOCK_NB, $wouldBlock);
if ($locked || !$wouldBlock) {
$logger && $logger->info(sprintf('Lock %s, now computing item "{key}"', $locked ? 'acquired' : 'not supported'), ['key' => $item->getKey()]);
self::$lockedFiles[$key] = true;
$value = $callback($item, $save);
if ($save) {
if ($setMetadata) {
$setMetadata($item);
}
$pool->save($item->set($value));
$save = false;
}
return $value;
}
// if we failed the race, retry locking in blocking mode to wait for the winner
$logger && $logger->info('Item "{key}" is locked, waiting for it to be released', ['key' => $item->getKey()]);
flock($lock, LOCK_SH);
} finally {
flock($lock, LOCK_UN);
unset(self::$lockedFiles[$key]);
}
static $signalingException, $signalingCallback;
$signalingException = $signalingException ?? unserialize("O:9:\"Exception\":1:{s:16:\"\0Exception\0trace\";a:0:{}}");
$signalingCallback = $signalingCallback ?? function () use ($signalingException) { throw $signalingException; };
try {
$value = $pool->get($item->getKey(), $signalingCallback, 0);
$logger && $logger->info('Item "{key}" retrieved after lock was released', ['key' => $item->getKey()]);
$save = false;
return $value;
} catch (\Exception $e) {
if ($signalingException !== $e) {
throw $e;
}
$logger && $logger->info('Item "{key}" not found while lock was released, now retrying', ['key' => $item->getKey()]);
}
}
}
private static function open(int $key)
{
if (null !== $h = self::$openedFiles[$key] ?? null) {
return $h;
}
set_error_handler(function () {});
try {
$h = fopen(self::$files[$key], 'r+');
} finally {
restore_error_handler();
}
return self::$openedFiles[$key] = $h ?: @fopen(self::$files[$key], 'r');
}
}
-99
View File
@@ -1,99 +0,0 @@
<?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\Marshaller;
use Symfony\Component\Cache\Exception\CacheException;
/**
* Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class DefaultMarshaller implements MarshallerInterface
{
private $useIgbinarySerialize = true;
public function __construct(bool $useIgbinarySerialize = null)
{
if (null === $useIgbinarySerialize) {
$useIgbinarySerialize = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<='));
} elseif ($useIgbinarySerialize && (!\extension_loaded('igbinary') || (\PHP_VERSION_ID >= 70400 && version_compare('3.1.0', phpversion('igbinary'), '>')))) {
throw new CacheException(\extension_loaded('igbinary') && \PHP_VERSION_ID >= 70400 ? 'Please upgrade the "igbinary" PHP extension to v3.1 or higher.' : 'The "igbinary" PHP extension is not loaded.');
}
$this->useIgbinarySerialize = $useIgbinarySerialize;
}
/**
* {@inheritdoc}
*/
public function marshall(array $values, ?array &$failed): array
{
$serialized = $failed = [];
foreach ($values as $id => $value) {
try {
if ($this->useIgbinarySerialize) {
$serialized[$id] = igbinary_serialize($value);
} else {
$serialized[$id] = serialize($value);
}
} catch (\Exception $e) {
$failed[] = $id;
}
}
return $serialized;
}
/**
* {@inheritdoc}
*/
public function unmarshall(string $value)
{
if ('b:0;' === $value) {
return false;
}
if ('N;' === $value) {
return null;
}
static $igbinaryNull;
if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<=')) ? igbinary_serialize(null) : false)) {
return null;
}
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
try {
if (':' === ($value[1] ?? ':')) {
if (false !== $value = unserialize($value)) {
return $value;
}
} elseif (false === $igbinaryNull) {
throw new \RuntimeException('Failed to unserialize values, did you forget to install the "igbinary" extension?');
} elseif (null !== $value = igbinary_unserialize($value)) {
return $value;
}
throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize values.');
} catch (\Error $e) {
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
} finally {
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
}
}
/**
* @internal
*/
public static function handleUnserializeCallback($class)
{
throw new \DomainException('Class not found: '.$class);
}
}
-40
View File
@@ -1,40 +0,0 @@
<?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\Marshaller;
/**
* Serializes/unserializes PHP values.
*
* Implementations of this interface MUST deal with errors carefully. They MUST
* also deal with forward and backward compatibility at the storage format level.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface MarshallerInterface
{
/**
* Serializes a list of values.
*
* When serialization fails for a specific value, no exception should be
* thrown. Instead, its key should be listed in $failed.
*/
public function marshall(array $values, ?array &$failed): array;
/**
* Unserializes a single value and throws an exception if anything goes wrong.
*
* @return mixed
*
* @throws \Exception Whenever unserialization fails
*/
public function unmarshall(string $value);
}
-263
View File
@@ -1,263 +0,0 @@
<?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;
use Psr\Cache\CacheException as Psr6CacheException;
use Psr\Cache\CacheItemPoolInterface;
use Psr\SimpleCache\CacheException as SimpleCacheException;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Traits\ProxyTrait;
/**
* Turns a PSR-6 cache into a PSR-16 one.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr16Cache implements CacheInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
private const METADATA_EXPIRY_OFFSET = 1527506807;
private $createCacheItem;
private $cacheItemPrototype;
public function __construct(CacheItemPoolInterface $pool)
{
$this->pool = $pool;
if (!$pool instanceof AdapterInterface) {
return;
}
$cacheItemPrototype = &$this->cacheItemPrototype;
$createCacheItem = \Closure::bind(
static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
$item = clone $cacheItemPrototype;
$item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key);
$item->value = $value;
$item->isHit = false;
return $item;
},
null,
CacheItem::class
);
$this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
if (null === $this->cacheItemPrototype) {
$this->get($allowInt && \is_int($key) ? (string) $key : $key);
}
$this->createCacheItem = $createCacheItem;
return $createCacheItem($key, null, $allowInt)->set($value);
};
}
/**
* {@inheritdoc}
*/
public function get($key, $default = null)
{
try {
$item = $this->pool->getItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null === $this->cacheItemPrototype) {
$this->cacheItemPrototype = clone $item;
$this->cacheItemPrototype->set(null);
}
return $item->isHit() ? $item->get() : $default;
}
/**
* {@inheritdoc}
*/
public function set($key, $value, $ttl = null)
{
try {
if (null !== $f = $this->createCacheItem) {
$item = $f($key, $value);
} else {
$item = $this->pool->getItem($key)->set($value);
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
return $this->pool->save($item);
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
return $this->pool->deleteItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
public function getMultiple($keys, $default = null)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
$items = $this->pool->getItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$values = [];
if (!$this->pool instanceof AdapterInterface) {
foreach ($items as $key => $item) {
$values[$key] = $item->isHit() ? $item->get() : $default;
}
return $values;
}
foreach ($items as $key => $item) {
if (!$item->isHit()) {
$values[$key] = $default;
continue;
}
$values[$key] = $item->get();
if (!$metadata = $item->getMetadata()) {
continue;
}
unset($metadata[CacheItem::METADATA_TAGS]);
if ($metadata) {
$values[$key] = ["\x9D".pack('VN', (int) (0.1 + $metadata[CacheItem::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[CacheItem::METADATA_CTIME])."\x5F" => $values[$key]];
}
}
return $values;
}
/**
* {@inheritdoc}
*/
public function setMultiple($values, $ttl = null)
{
$valuesIsArray = \is_array($values);
if (!$valuesIsArray && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$items = [];
try {
if (null !== $f = $this->createCacheItem) {
$valuesIsArray = false;
foreach ($values as $key => $value) {
$items[$key] = $f($key, $value, true);
}
} elseif ($valuesIsArray) {
$items = [];
foreach ($values as $key => $value) {
$items[] = (string) $key;
}
$items = $this->pool->getItems($items);
} else {
foreach ($values as $key => $value) {
if (\is_int($key)) {
$key = (string) $key;
}
$items[$key] = $this->pool->getItem($key)->set($value);
}
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$ok = true;
foreach ($items as $key => $item) {
if ($valuesIsArray) {
$item->set($values[$key]);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
$ok = $this->pool->saveDeferred($item) && $ok;
}
return $this->pool->commit() && $ok;
}
/**
* {@inheritdoc}
*/
public function deleteMultiple($keys)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
return $this->pool->deleteItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function has($key)
{
try {
return $this->pool->hasItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
}
+2 -3
View File
@@ -11,11 +11,10 @@
namespace Symfony\Component\Cache;
use Symfony\Contracts\Service\ResetInterface;
/**
* Resets a pool's local state.
*/
interface ResettableInterface extends ResetInterface
interface ResettableInterface
{
public function reset();
}
+19 -20
View File
@@ -12,25 +12,21 @@
namespace Symfony\Component\Cache\Simple;
use Psr\Log\LoggerAwareInterface;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\AbstractTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', AbstractCache::class, AbstractAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use AbstractAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
abstract class AbstractCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
{
/**
* @internal
*/
protected const NS_SEPARATOR = ':';
const NS_SEPARATOR = ':';
use AbstractTrait {
deleteItems as private;
@@ -40,12 +36,16 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
private $defaultLifetime;
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
protected function __construct($namespace = '', $defaultLifetime = 0)
{
$this->defaultLifetime = max(0, $defaultLifetime);
$this->defaultLifetime = max(0, (int) $defaultLifetime);
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s").', $this->maxIdLength - 24, \strlen($namespace), $namespace));
}
}
@@ -61,7 +61,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
return $value;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
CacheItem::log($this->logger, 'Failed to fetch key "{key}"', ['key' => $key, 'exception' => $e]);
}
return $default;
@@ -85,7 +85,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
$ids = [];
@@ -95,7 +95,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
try {
$values = $this->doFetch($ids);
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => $keys, 'exception' => $e]);
$values = [];
}
$ids = array_combine($ids, $keys);
@@ -109,7 +109,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
public function setMultiple($values, $ttl = null)
{
if (!\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$valuesById = [];
@@ -134,8 +134,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
foreach (\is_array($e) ? $e : array_keys($valuesById) as $id) {
$keys[] = substr($id, \strlen($this->namespace));
}
$message = 'Failed to save values'.($e instanceof \Exception ? ': '.$e->getMessage() : '.');
CacheItem::log($this->logger, $message, ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
CacheItem::log($this->logger, 'Failed to save values', ['keys' => $keys, 'exception' => $e instanceof \Exception ? $e : null]);
return false;
}
@@ -148,7 +147,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
return $this->deleteItems($keys);
@@ -166,7 +165,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
return 0 < $ttl ? $ttl : false;
}
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
}
private function generateValues($values, &$keys, $default)
@@ -181,7 +180,7 @@ abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterfac
yield $key => $value;
}
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
CacheItem::log($this->logger, 'Failed to fetch requested values', ['keys' => array_values($keys), 'exception' => $e]);
}
foreach ($keys as $key) {
+6 -8
View File
@@ -11,20 +11,18 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Traits\ApcuTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ApcuCache::class, ApcuAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use ApcuAdapter and type-hint for CacheInterface instead.
*/
class ApcuCache extends AbstractCache
{
use ApcuTrait;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $version
*/
public function __construct($namespace = '', $defaultLifetime = 0, $version = null)
{
$this->init($namespace, $defaultLifetime, $version);
}
+29 -40
View File
@@ -12,20 +12,16 @@
namespace Symfony\Component\Cache\Simple;
use Psr\Log\LoggerAwareInterface;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\ArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ArrayCache::class, ArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use ArrayAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
class ArrayCache implements CacheInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait {
ArrayTrait::deleteItem as delete;
@@ -35,11 +31,12 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
private $defaultLifetime;
/**
* @param int $defaultLifetime
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
*/
public function __construct(int $defaultLifetime = 0, bool $storeSerialized = true)
public function __construct($defaultLifetime = 0, $storeSerialized = true)
{
$this->defaultLifetime = $defaultLifetime;
$this->defaultLifetime = (int) $defaultLifetime;
$this->storeSerialized = $storeSerialized;
}
@@ -48,20 +45,9 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
*/
public function get($key, $default = null)
{
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
foreach ($this->getMultiple([$key], $default) as $v) {
return $v;
}
if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > microtime(true) || !$this->delete($key))) {
$this->values[$key] = null;
return $default;
}
if (!$this->storeSerialized) {
return $this->values[$key];
}
$value = $this->unfreeze($key, $isHit);
return $isHit ? $value : $default;
}
/**
@@ -72,15 +58,13 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
foreach ($keys as $key) {
if (!\is_string($key) || !isset($this->expiries[$key])) {
CacheItem::validateKey($key);
}
CacheItem::validateKey($key);
}
return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
return $this->generateItems($keys, time(), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
}
/**
@@ -89,7 +73,7 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
public function deleteMultiple($keys)
{
if (!\is_array($keys) && !$keys instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
foreach ($keys as $key) {
$this->delete($key);
@@ -103,9 +87,7 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
*/
public function set($key, $value, $ttl = null)
{
if (!\is_string($key)) {
CacheItem::validateKey($key);
}
CacheItem::validateKey($key);
return $this->setMultiple([$key => $value], $ttl);
}
@@ -116,25 +98,32 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
public function setMultiple($values, $ttl = null)
{
if (!\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$valuesArray = [];
foreach ($values as $key => $value) {
if (!\is_int($key) && !(\is_string($key) && isset($this->expiries[$key]))) {
CacheItem::validateKey($key);
}
\is_int($key) || CacheItem::validateKey($key);
$valuesArray[$key] = $value;
}
if (false === $ttl = $this->normalizeTtl($ttl)) {
return $this->deleteMultiple(array_keys($valuesArray));
}
$expiry = 0 < $ttl ? microtime(true) + $ttl : PHP_INT_MAX;
if ($this->storeSerialized) {
foreach ($valuesArray as $key => $value) {
try {
$valuesArray[$key] = serialize($value);
} catch (\Exception $e) {
$type = \is_object($value) ? \get_class($value) : \gettype($value);
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', ['key' => $key, 'type' => $type, 'exception' => $e]);
return false;
}
}
}
$expiry = 0 < $ttl ? time() + $ttl : \PHP_INT_MAX;
foreach ($valuesArray as $key => $value) {
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
return false;
}
$this->values[$key] = $value;
$this->expiries[$key] = $expiry;
}
@@ -154,6 +143,6 @@ class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, Resettabl
return 0 < $ttl ? $ttl : false;
}
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given.', \is_object($ttl) ? \get_class($ttl) : \gettype($ttl)));
}
}
+10 -15
View File
@@ -11,15 +11,10 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', ChainCache::class, ChainAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* Chains several caches together.
@@ -27,9 +22,9 @@ use Symfony\Contracts\Service\ResetInterface;
* Cached items are fetched from the first cache having them in its data store.
* They are saved and deleted in all caches at once.
*
* @deprecated since Symfony 4.3, use ChainAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
class ChainCache implements CacheInterface, PruneableInterface, ResettableInterface
{
private $miss;
private $caches = [];
@@ -37,25 +32,25 @@ class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableI
private $cacheCount;
/**
* @param Psr16CacheInterface[] $caches The ordered list of caches used to fetch cached items
* @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
* @param CacheInterface[] $caches The ordered list of caches used to fetch cached items
* @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones
*/
public function __construct(array $caches, int $defaultLifetime = 0)
public function __construct(array $caches, $defaultLifetime = 0)
{
if (!$caches) {
throw new InvalidArgumentException('At least one cache must be specified.');
}
foreach ($caches as $cache) {
if (!$cache instanceof Psr16CacheInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), Psr16CacheInterface::class));
if (!$cache instanceof CacheInterface) {
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', \get_class($cache), CacheInterface::class));
}
}
$this->miss = new \stdClass();
$this->caches = array_values($caches);
$this->cacheCount = \count($this->caches);
$this->defaultLifetime = 0 < $defaultLifetime ? $defaultLifetime : null;
$this->defaultLifetime = 0 < $defaultLifetime ? (int) $defaultLifetime : null;
}
/**
@@ -249,7 +244,7 @@ class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableI
public function reset()
{
foreach ($this->caches as $cache) {
if ($cache instanceof ResetInterface) {
if ($cache instanceof ResettableInterface) {
$cache->reset();
}
}
+5 -8
View File
@@ -12,20 +12,17 @@
namespace Symfony\Component\Cache\Simple;
use Doctrine\Common\Cache\CacheProvider;
use Symfony\Component\Cache\Adapter\DoctrineAdapter;
use Symfony\Component\Cache\Traits\DoctrineTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', DoctrineCache::class, DoctrineAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use DoctrineAdapter and type-hint for CacheInterface instead.
*/
class DoctrineCache extends AbstractCache
{
use DoctrineTrait;
public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0)
{
parent::__construct('', $defaultLifetime);
$this->provider = $provider;
+6 -11
View File
@@ -11,25 +11,20 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\FilesystemTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', FilesystemCache::class, FilesystemAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use FilesystemAdapter and type-hint for CacheInterface instead.
*/
class FilesystemCache extends AbstractCache implements PruneableInterface
{
use FilesystemTrait;
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
/**
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*/
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->marshaller = $marshaller ?? new DefaultMarshaller();
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
}
+6 -10
View File
@@ -11,24 +11,20 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\MemcachedTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', MemcachedCache::class, MemcachedAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use MemcachedAdapter and type-hint for CacheInterface instead.
*/
class MemcachedCache extends AbstractCache
{
use MemcachedTrait;
protected $maxIdLength = 250;
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
/**
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)
{
$this->init($client, $namespace, $defaultLifetime, $marshaller);
$this->init($client, $namespace, $defaultLifetime);
}
}
+3 -7
View File
@@ -11,16 +11,12 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', NullCache::class, NullAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
use Psr\SimpleCache\CacheInterface;
/**
* @deprecated since Symfony 4.3, use NullAdapter and type-hint for CacheInterface instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class NullCache implements Psr16CacheInterface
class NullCache implements CacheInterface
{
/**
* {@inheritdoc}
+6 -14
View File
@@ -11,17 +11,9 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\PdoAdapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PdoTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PdoCache::class, PdoAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use PdoAdapter and type-hint for CacheInterface instead.
*/
class PdoCache extends AbstractCache implements PruneableInterface
{
use PdoTrait;
@@ -33,9 +25,6 @@ class PdoCache extends AbstractCache implements PruneableInterface
* a Doctrine DBAL Connection or a DSN string that will be used to
* lazy-connect to the database when the cache is actually used.
*
* When a Doctrine DBAL Connection is passed, the cache table is created
* automatically when possible. Otherwise, use the createTable() method.
*
* List of available options:
* * db_table: The name of the table [default: cache_items]
* * db_id_col: The column where to store the cache id [default: item_id]
@@ -46,14 +35,17 @@ class PdoCache extends AbstractCache implements PruneableInterface
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
*
* @param \PDO|Connection|string $connOrDsn a \PDO or Connection instance or DSN string or null
* @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null
* @param string $namespace
* @param int $defaultLifetime
* @param array $options An associative array of options
*
* @throws InvalidArgumentException When first argument is not PDO nor Connection nor string
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
* @throws InvalidArgumentException When namespace contains invalid characters
*/
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = [])
{
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
}
}
+50 -35
View File
@@ -11,44 +11,51 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpArrayCache::class, PhpArrayAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use PhpArrayAdapter and type-hint for CacheInterface instead.
* Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0.
* Warmed up items are read-only and run-time discovered items are cached using a fallback adapter.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
/**
* @param string $file The PHP file were values are cached
* @param Psr16CacheInterface $fallbackPool A pool to fallback on when an item is not hit
* @param string $file The PHP file were values are cached
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
*/
public function __construct(string $file, Psr16CacheInterface $fallbackPool)
public function __construct($file, CacheInterface $fallbackPool)
{
$this->file = $file;
$this->pool = $fallbackPool;
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
}
/**
* This adapter takes advantage of how PHP stores arrays in its latest versions.
* This adapter should only be used on PHP 7.0+ to take advantage of how PHP
* stores arrays in its latest versions. This factory method decorates the given
* fallback pool with this adapter only if the current PHP version is supported.
*
* @param string $file The PHP file were values are cached
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
*
* @return Psr16CacheInterface
* @return CacheInterface
*/
public static function create($file, Psr16CacheInterface $fallbackPool)
public static function create($file, CacheInterface $fallbackPool)
{
return new static($file, $fallbackPool);
if (\PHP_VERSION_ID >= 70000) {
return new static($file, $fallbackPool);
}
return $fallbackPool;
}
/**
@@ -62,18 +69,22 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
if (null === $this->values) {
$this->initialize();
}
if (!isset($this->keys[$key])) {
if (!isset($this->values[$key])) {
return $this->pool->get($key, $default);
}
$value = $this->values[$this->keys[$key]];
$value = $this->values[$key];
if ('N;' === $value) {
return null;
}
if ($value instanceof \Closure) {
$value = null;
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
return $value();
} catch (\Throwable $e) {
$e = null;
$value = unserialize($value);
} catch (\Error $e) {
} catch (\Exception $e) {
}
if (null !== $e) {
return $default;
}
}
@@ -89,7 +100,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
foreach ($keys as $key) {
if (!\is_string($key)) {
@@ -115,7 +126,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$this->initialize();
}
return isset($this->keys[$key]) || $this->pool->has($key);
return isset($this->values[$key]) || $this->pool->has($key);
}
/**
@@ -130,7 +141,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$this->initialize();
}
return !isset($this->keys[$key]) && $this->pool->delete($key);
return !isset($this->values[$key]) && $this->pool->delete($key);
}
/**
@@ -139,7 +150,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
public function deleteMultiple($keys)
{
if (!\is_array($keys) && !$keys instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
$deleted = true;
@@ -150,7 +161,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (isset($this->keys[$key])) {
if (isset($this->values[$key])) {
$deleted = false;
} else {
$fallbackKeys[] = $key;
@@ -179,7 +190,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$this->initialize();
}
return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl);
return !isset($this->values[$key]) && $this->pool->set($key, $value, $ttl);
}
/**
@@ -188,7 +199,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
public function setMultiple($values, $ttl = null)
{
if (!\is_array($values) && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', \is_object($values) ? \get_class($values) : \gettype($values)));
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$saved = true;
@@ -199,7 +210,7 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
}
if (isset($this->keys[$key])) {
if (isset($this->values[$key])) {
$saved = false;
} else {
$fallbackValues[$key] = $value;
@@ -218,15 +229,17 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
$fallbackKeys = [];
foreach ($keys as $key) {
if (isset($this->keys[$key])) {
$value = $this->values[$this->keys[$key]];
if (isset($this->values[$key])) {
$value = $this->values[$key];
if ('N;' === $value) {
yield $key => null;
} elseif ($value instanceof \Closure) {
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => $value();
} catch (\Throwable $e) {
yield $key => unserialize($value);
} catch (\Error $e) {
yield $key => $default;
} catch (\Exception $e) {
yield $key => $default;
}
} else {
@@ -238,7 +251,9 @@ class PhpArrayCache implements Psr16CacheInterface, PruneableInterface, Resettab
}
if ($fallbackKeys) {
yield from $this->pool->getMultiple($fallbackKeys, $default);
foreach ($this->pool->getMultiple($fallbackKeys, $default) as $key => $item) {
yield $key => $item;
}
}
}
}
+11 -15
View File
@@ -11,35 +11,31 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Traits\PhpFilesTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', PhpFilesCache::class, PhpFilesAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use PhpFilesAdapter and type-hint for CacheInterface instead.
*/
class PhpFilesCache extends AbstractCache implements PruneableInterface
{
use PhpFilesTrait;
/**
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
* Doing so is encouraged because it fits perfectly OPcache's memory model.
* @param string $namespace
* @param int $defaultLifetime
* @param string|null $directory
*
* @throws CacheException if OPcache is not enabled
*/
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
public function __construct($namespace = '', $defaultLifetime = 0, $directory = null)
{
$this->appendOnly = $appendOnly;
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
if (!static::isSupported()) {
throw new CacheException('OPcache is not enabled.');
}
parent::__construct('', $defaultLifetime);
$this->init($namespace, $directory);
$this->includeHandler = static function ($type, $msg, $file, $line) {
throw new \ErrorException($msg, 0, $type, $file, $line);
};
$e = new \Exception();
$this->includeHandler = function () use ($e) { throw $e; };
$this->zendDetectUnicode = filter_var(ini_get('zend.detect_unicode'), \FILTER_VALIDATE_BOOLEAN);
}
}
+223 -5
View File
@@ -11,13 +11,231 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Psr16Cache;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" instead.', Psr6Cache::class, Psr16Cache::class), E_USER_DEPRECATED);
use Psr\Cache\CacheException as Psr6CacheException;
use Psr\Cache\CacheItemPoolInterface;
use Psr\SimpleCache\CacheException as SimpleCacheException;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
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\ProxyTrait;
/**
* @deprecated since Symfony 4.3, use Psr16Cache instead.
* @author Nicolas Grekas <p@tchwork.com>
*/
class Psr6Cache extends Psr16Cache
class Psr6Cache implements CacheInterface, PruneableInterface, ResettableInterface
{
use ProxyTrait;
private $createCacheItem;
private $cacheItemPrototype;
public function __construct(CacheItemPoolInterface $pool)
{
$this->pool = $pool;
if (!$pool instanceof AdapterInterface) {
return;
}
$cacheItemPrototype = &$this->cacheItemPrototype;
$createCacheItem = \Closure::bind(
static function ($key, $value, $allowInt = false) use (&$cacheItemPrototype) {
$item = clone $cacheItemPrototype;
$item->key = $allowInt && \is_int($key) ? (string) $key : CacheItem::validateKey($key);
$item->value = $value;
$item->isHit = false;
return $item;
},
null,
CacheItem::class
);
$this->createCacheItem = function ($key, $value, $allowInt = false) use ($createCacheItem) {
if (null === $this->cacheItemPrototype) {
$this->get($allowInt && \is_int($key) ? (string) $key : $key);
}
$this->createCacheItem = $createCacheItem;
return $createCacheItem($key, $value, $allowInt);
};
}
/**
* {@inheritdoc}
*/
public function get($key, $default = null)
{
try {
$item = $this->pool->getItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null === $this->cacheItemPrototype) {
$this->cacheItemPrototype = clone $item;
$this->cacheItemPrototype->set(null);
}
return $item->isHit() ? $item->get() : $default;
}
/**
* {@inheritdoc}
*/
public function set($key, $value, $ttl = null)
{
try {
if (null !== $f = $this->createCacheItem) {
$item = $f($key, $value);
} else {
$item = $this->pool->getItem($key)->set($value);
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
return $this->pool->save($item);
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
return $this->pool->deleteItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function clear()
{
return $this->pool->clear();
}
/**
* {@inheritdoc}
*/
public function getMultiple($keys, $default = null)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
$items = $this->pool->getItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$values = [];
foreach ($items as $key => $item) {
$values[$key] = $item->isHit() ? $item->get() : $default;
}
return $values;
}
/**
* {@inheritdoc}
*/
public function setMultiple($values, $ttl = null)
{
$valuesIsArray = \is_array($values);
if (!$valuesIsArray && !$values instanceof \Traversable) {
throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given.', \is_object($values) ? \get_class($values) : \gettype($values)));
}
$items = [];
try {
if (null !== $f = $this->createCacheItem) {
$valuesIsArray = false;
foreach ($values as $key => $value) {
$items[$key] = $f($key, $value, true);
}
} elseif ($valuesIsArray) {
$items = [];
foreach ($values as $key => $value) {
$items[] = (string) $key;
}
$items = $this->pool->getItems($items);
} else {
foreach ($values as $key => $value) {
if (\is_int($key)) {
$key = (string) $key;
}
$items[$key] = $this->pool->getItem($key)->set($value);
}
}
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
$ok = true;
foreach ($items as $key => $item) {
if ($valuesIsArray) {
$item->set($values[$key]);
}
if (null !== $ttl) {
$item->expiresAfter($ttl);
}
$ok = $this->pool->saveDeferred($item) && $ok;
}
return $this->pool->commit() && $ok;
}
/**
* {@inheritdoc}
*/
public function deleteMultiple($keys)
{
if ($keys instanceof \Traversable) {
$keys = iterator_to_array($keys, false);
} elseif (!\is_array($keys)) {
throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given.', \is_object($keys) ? \get_class($keys) : \gettype($keys)));
}
try {
return $this->pool->deleteItems($keys);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function has($key)
{
try {
return $this->pool->hasItem($key);
} catch (SimpleCacheException $e) {
throw $e;
} catch (Psr6CacheException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
}
}
+5 -11
View File
@@ -11,25 +11,19 @@
namespace Symfony\Component\Cache\Simple;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
use Symfony\Component\Cache\Traits\RedisTrait;
use Symfony\Contracts\Cache\CacheInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', RedisCache::class, RedisAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use RedisAdapter and type-hint for CacheInterface instead.
*/
class RedisCache extends AbstractCache
{
use RedisTrait;
/**
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
* @param string $namespace
* @param int $defaultLifetime
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
$this->init($redisClient, $namespace, $defaultLifetime);
}
}
+7 -9
View File
@@ -11,24 +11,22 @@
namespace Symfony\Component\Cache\Simple;
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Service\ResetInterface;
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3, use "%s" and type-hint for "%s" instead.', TraceableCache::class, TraceableAdapter::class, CacheInterface::class), E_USER_DEPRECATED);
/**
* @deprecated since Symfony 4.3, use TraceableAdapter and type-hint for CacheInterface instead.
* An adapter that collects data about all cache calls.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class TraceableCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
class TraceableCache implements CacheInterface, PruneableInterface, ResettableInterface
{
private $pool;
private $miss;
private $calls = [];
public function __construct(Psr16CacheInterface $pool)
public function __construct(CacheInterface $pool)
{
$this->pool = $pool;
$this->miss = new \stdClass();
@@ -202,7 +200,7 @@ class TraceableCache implements Psr16CacheInterface, PruneableInterface, Resetta
*/
public function reset()
{
if (!$this->pool instanceof ResetInterface) {
if (!$this->pool instanceof ResettableInterface) {
return;
}
$event = $this->start(__FUNCTION__);
@@ -28,18 +28,19 @@ abstract class AbstractRedisAdapterTest extends AdapterTestCase
return new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
}
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!\extension_loaded('redis')) {
self::markTestSkipped('Extension redis required.');
}
if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
$e = error_get_last();
self::markTestSkipped($e['message']);
try {
(new \Redis())->connect(getenv('REDIS_HOST'));
} catch (\Exception $e) {
self::markTestSkipped($e->getMessage());
}
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
self::$redis = null;
}
+12 -100
View File
@@ -12,117 +12,24 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Cache\IntegrationTests\CachePoolTest;
use PHPUnit\Framework\Assert;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Contracts\Cache\CallbackInterface;
abstract class AdapterTestCase extends CachePoolTest
{
protected function setUp(): void
protected function setUp()
{
parent::setUp();
if (!\array_key_exists('testDeferredSaveWithoutCommit', $this->skippedTests) && \defined('HHVM_VERSION')) {
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Destructors are called late on HHVM.';
}
if (!\array_key_exists('testPrune', $this->skippedTests) && !$this->createCachePool() instanceof PruneableInterface) {
$this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
}
}
public function testGet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createCachePool();
$cache->clear();
$value = mt_rand();
$this->assertSame($value, $cache->get('foo', function (CacheItem $item) use ($value) {
$this->assertSame('foo', $item->getKey());
return $value;
}));
$item = $cache->getItem('foo');
$this->assertSame($value, $item->get());
$isHit = true;
$this->assertSame($value, $cache->get('foo', function (CacheItem $item) use (&$isHit) { $isHit = false; }, 0));
$this->assertTrue($isHit);
$this->assertNull($cache->get('foo', function (CacheItem $item) use (&$isHit, $value) {
$isHit = false;
$this->assertTrue($item->isHit());
$this->assertSame($value, $item->get());
}, INF));
$this->assertFalse($isHit);
$this->assertSame($value, $cache->get('bar', new class($value) implements CallbackInterface {
private $value;
public function __construct(int $value)
{
$this->value = $value;
}
public function __invoke(CacheItemInterface $item, bool &$save)
{
Assert::assertSame('bar', $item->getKey());
return $this->value;
}
}));
}
public function testRecursiveGet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createCachePool(0, __FUNCTION__);
$v = $cache->get('k1', function () use (&$counter, $cache) {
$cache->get('k2', function () use (&$counter) { return ++$counter; });
$v = $cache->get('k2', function () use (&$counter) { return ++$counter; }); // ensure the callback is called once
return $v;
});
$this->assertSame(1, $counter);
$this->assertSame(1, $v);
$this->assertSame(1, $cache->get('k2', function () { return 2; }));
}
public function testGetMetadata()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createCachePool(0, __FUNCTION__);
$cache->deleteItem('foo');
$cache->get('foo', function ($item) {
$item->expiresAfter(10);
usleep(999000);
return 'bar';
});
$item = $cache->getItem('foo');
$expected = [
CacheItem::METADATA_EXPIRY => 9.5 + time(),
CacheItem::METADATA_CTIME => 1000,
];
$this->assertEqualsWithDelta($expected, $item->getMetadata(), .6, 'Item metadata should embed expiry and ctime.');
}
public function testDefaultLifeTime()
{
if (isset($this->skippedTests[__FUNCTION__])) {
@@ -254,9 +161,14 @@ abstract class AdapterTestCase extends CachePoolTest
}
}
class NotUnserializable
class NotUnserializable implements \Serializable
{
public function __wakeup()
public function serialize()
{
return serialize(123);
}
public function unserialize($ser)
{
throw new \Exception(__CLASS__);
}
+4 -4
View File
@@ -24,10 +24,10 @@ class ApcuAdapterTest extends AdapterTestCase
public function createCachePool($defaultLifetime = 0)
{
if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN)) {
$this->markTestSkipped('APCu extension is required.');
}
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
if ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN)) {
if ('testWithCliSapi' !== $this->getName()) {
$this->markTestSkipped('apc.enable_cli=1 is required.');
}
@@ -54,7 +54,7 @@ class ApcuAdapterTest extends AdapterTestCase
public function testVersion()
{
$namespace = str_replace('\\', '.', \get_class($this));
$namespace = str_replace('\\', '.', static::class);
$pool1 = new ApcuAdapter($namespace, 0, 'p1');
@@ -79,7 +79,7 @@ class ApcuAdapterTest extends AdapterTestCase
public function testNamespace()
{
$namespace = str_replace('\\', '.', \get_class($this));
$namespace = str_replace('\\', '.', static::class);
$pool1 = new ApcuAdapter($namespace.'_1', 0, 'p1');
+3 -4
View File
@@ -19,7 +19,6 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter;
class ArrayAdapterTest extends AdapterTestCase
{
protected $skippedTests = [
'testGetMetadata' => 'ArrayAdapter does not keep metadata.',
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
];
@@ -36,12 +35,12 @@ class ArrayAdapterTest extends AdapterTestCase
// Hit
$item = $cache->getItem('foo');
$item->set('::4711');
$item->set('4711');
$cache->save($item);
$fooItem = $cache->getItem('foo');
$this->assertTrue($fooItem->isHit());
$this->assertEquals('::4711', $fooItem->get());
$this->assertEquals('4711', $fooItem->get());
// Miss (should be present as NULL in $values)
$cache->getItem('bar');
@@ -50,7 +49,7 @@ class ArrayAdapterTest extends AdapterTestCase
$this->assertCount(2, $values);
$this->assertArrayHasKey('foo', $values);
$this->assertSame(serialize('::4711'), $values['foo']);
$this->assertSame(serialize('4711'), $values['foo']);
$this->assertArrayHasKey('bar', $values);
$this->assertNull($values['bar']);
}
+120 -6
View File
@@ -25,13 +25,9 @@ use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
*/
class ChainAdapterTest extends AdapterTestCase
{
public function createCachePool($defaultLifetime = 0, $testMethod = null)
public function createCachePool($defaultLifetime = 0)
{
if ('testGetMetadata' === $testMethod) {
return new ChainAdapter([new FilesystemAdapter('a', $defaultLifetime), new FilesystemAdapter('b', $defaultLifetime)], $defaultLifetime);
}
return new ChainAdapter([new ArrayAdapter($defaultLifetime), new ExternalAdapter(), new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime);
return new ChainAdapter([new ArrayAdapter($defaultLifetime), new ExternalAdapter($defaultLifetime), new FilesystemAdapter('', $defaultLifetime)], $defaultLifetime);
}
public function testEmptyAdaptersException()
@@ -69,6 +65,124 @@ class ChainAdapterTest extends AdapterTestCase
$this->assertFalse($cache->prune());
}
public function testMultipleCachesExpirationWhenCommonTtlIsNotSet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$adapter1 = new ArrayAdapter(4);
$adapter2 = new ArrayAdapter(2);
$cache = new ChainAdapter([$adapter1, $adapter2]);
$cache->save($cache->getItem('key')->set('value'));
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertFalse($item->isHit());
$adapter2->save($adapter2->getItem('key1')->set('value1'));
$item = $cache->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
$item = $adapter2->getItem('key1');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertFalse($item->isHit());
}
public function testMultipleCachesExpirationWhenCommonTtlIsSet()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$adapter1 = new ArrayAdapter(4);
$adapter2 = new ArrayAdapter(2);
$cache = new ChainAdapter([$adapter1, $adapter2], 6);
$cache->save($cache->getItem('key')->set('value'));
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertTrue($item->isHit());
$this->assertEquals('value', $item->get());
$item = $adapter2->getItem('key');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key');
$this->assertFalse($item->isHit());
$adapter2->save($adapter2->getItem('key1')->set('value1'));
$item = $cache->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
$item = $adapter2->getItem('key1');
$this->assertFalse($item->isHit());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertTrue($item->isHit());
$this->assertEquals('value1', $item->get());
sleep(2);
$item = $adapter1->getItem('key1');
$this->assertFalse($item->isHit());
}
/**
* @return MockObject|PruneableCacheInterface
*/
@@ -24,7 +24,7 @@ class FilesystemAdapterTest extends AdapterTestCase
return new FilesystemAdapter('', $defaultLifetime);
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
self::rmdir(sys_get_temp_dir().'/symfony-cache');
}
@@ -1,28 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\FilesystemTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
/**
* @group time-sensitive
*/
class FilesystemTagAwareAdapterTest extends FilesystemAdapterTest
{
use TagAwareTestTrait;
public function createCachePool($defaultLifetime = 0)
{
return new FilesystemTagAwareAdapter('', $defaultLifetime);
}
}
@@ -26,7 +26,7 @@ class MaxIdLengthAdapterTest extends TestCase
$cache->expects($this->exactly(2))
->method('doHave')
->withConsecutive(
[$this->equalTo('----------:nWfzGiCgLczv3SSUzXL3kg:')],
[$this->equalTo('----------:0GTYWa9n4ed8vqNlOT2iEr:')],
[$this->equalTo('----------:---------------------------------------')]
);
+11 -47
View File
@@ -23,7 +23,7 @@ class MemcachedAdapterTest extends AdapterTestCase
protected static $client;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!MemcachedAdapter::isSupported()) {
self::markTestSkipped('Extension memcached >=2.2.0 required.');
@@ -66,8 +66,14 @@ class MemcachedAdapterTest extends AdapterTestCase
*/
public function testBadOptions($name, $value)
{
$this->expectException('ErrorException');
$this->expectExceptionMessage('constant(): Couldn\'t find constant Memcached::');
if (\PHP_VERSION_ID < 80000) {
$this->expectException('ErrorException');
$this->expectExceptionMessage('constant(): Couldn\'t find constant Memcached::');
} else {
$this->expectException('Error');
$this->expectExceptionMessage('Undefined constant Memcached::');
}
MemcachedAdapter::createConnection([], [$name => $value]);
}
@@ -135,7 +141,7 @@ class MemcachedAdapterTest extends AdapterTestCase
'localhost',
11222,
];
if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
if (filter_var(ini_get('memcached.use_sasl'), \FILTER_VALIDATE_BOOLEAN)) {
yield [
'memcached://user:password@127.0.0.1?weight=50',
'127.0.0.1',
@@ -152,7 +158,7 @@ class MemcachedAdapterTest extends AdapterTestCase
'/var/local/run/memcached.socket',
0,
];
if (filter_var(ini_get('memcached.use_sasl'), FILTER_VALIDATE_BOOLEAN)) {
if (filter_var(ini_get('memcached.use_sasl'), \FILTER_VALIDATE_BOOLEAN)) {
yield [
'memcached://user:password@/var/local/run/memcached.socket?weight=25',
'/var/local/run/memcached.socket',
@@ -195,46 +201,4 @@ class MemcachedAdapterTest extends AdapterTestCase
{
$this->assertTrue($this->createCachePool()->clear());
}
public function testMultiServerDsn()
{
$dsn = 'memcached:?host[localhost]&host[localhost:12345]&host[/some/memcached.sock:]=3';
$client = MemcachedAdapter::createConnection($dsn);
$expected = [
0 => [
'host' => 'localhost',
'port' => 11211,
'type' => 'TCP',
],
1 => [
'host' => 'localhost',
'port' => 12345,
'type' => 'TCP',
],
2 => [
'host' => '/some/memcached.sock',
'port' => 0,
'type' => 'SOCKET',
],
];
$this->assertSame($expected, $client->getServerList());
$dsn = 'memcached://localhost?host[foo.bar]=3';
$client = MemcachedAdapter::createConnection($dsn);
$expected = [
0 => [
'host' => 'localhost',
'port' => 11211,
'type' => 'TCP',
],
1 => [
'host' => 'foo.bar',
'port' => 11211,
'type' => 'TCP',
],
];
$this->assertSame($expected, $client->getServerList());
}
}
@@ -12,7 +12,6 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
/**
@@ -20,12 +19,8 @@ use Symfony\Component\Cache\Adapter\ProxyAdapter;
*/
class NamespacedProxyAdapterTest extends ProxyAdapterTest
{
public function createCachePool($defaultLifetime = 0, $testMethod = null)
public function createCachePool($defaultLifetime = 0)
{
if ('testGetMetadata' === $testMethod) {
return new ProxyAdapter(new FilesystemAdapter(), 'foo', $defaultLifetime);
}
return new ProxyAdapter(new ArrayAdapter($defaultLifetime), 'foo', $defaultLifetime);
}
}
-13
View File
@@ -34,19 +34,6 @@ class NullAdapterTest extends TestCase
$this->assertNull($item->get(), "Item's value must be null when isHit is false.");
}
public function testGet()
{
$adapter = $this->createCachePool();
$fetched = [];
$adapter->get('myKey', function ($item) use (&$fetched) { $fetched[] = $item; });
$this->assertCount(1, $fetched);
$item = $fetched[0];
$this->assertFalse($item->isHit());
$this->assertNull($item->get(), "Item's value must be null when isHit is false.");
$this->assertSame('myKey', $item->getKey());
}
public function testHasItem()
{
$this->assertFalse($this->createCachePool()->hasItem('key'));
+2 -2
View File
@@ -23,7 +23,7 @@ class PdoAdapterTest extends AdapterTestCase
protected static $dbFile;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!\extension_loaded('pdo_sqlite')) {
self::markTestSkipped('Extension pdo_sqlite required.');
@@ -35,7 +35,7 @@ class PdoAdapterTest extends AdapterTestCase
$pool->createTable();
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
@unlink(self::$dbFile);
}
+5 -2
View File
@@ -24,16 +24,19 @@ class PdoDbalAdapterTest extends AdapterTestCase
protected static $dbFile;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!\extension_loaded('pdo_sqlite')) {
self::markTestSkipped('Extension pdo_sqlite required.');
}
self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache');
$pool = new PdoAdapter(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]));
$pool->createTable();
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
@unlink(self::$dbFile);
}
+10 -37
View File
@@ -12,7 +12,6 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\NullAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
@@ -22,8 +21,6 @@ use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
class PhpArrayAdapterTest extends AdapterTestCase
{
protected $skippedTests = [
'testGet' => 'PhpArrayAdapter is read-only.',
'testRecursiveGet' => 'PhpArrayAdapter is read-only.',
'testBasicUsage' => 'PhpArrayAdapter is read-only.',
'testBasicUsageWithLongKey' => 'PhpArrayAdapter is read-only.',
'testClear' => 'PhpArrayAdapter is read-only.',
@@ -58,12 +55,12 @@ class PhpArrayAdapterTest extends AdapterTestCase
protected static $file;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
}
protected function tearDown(): void
protected function tearDown()
{
$this->createCachePool()->clear();
@@ -72,12 +69,8 @@ class PhpArrayAdapterTest extends AdapterTestCase
}
}
public function createCachePool($defaultLifetime = 0, $testMethod = null)
public function createCachePool()
{
if ('testGetMetadata' === $testMethod) {
return new PhpArrayAdapter(self::$file, new FilesystemAdapter());
}
return new PhpArrayAdapterWrapper(self::$file, new NullAdapter());
}
@@ -110,32 +103,16 @@ class PhpArrayAdapterTest extends AdapterTestCase
public function testStoredFile()
{
$data = [
$expected = [
'integer' => 42,
'float' => 42.42,
'boolean' => true,
'array_simple' => ['foo', 'bar'],
'array_associative' => ['foo' => 'bar', 'foo2' => 'bar2'],
];
$expected = [
[
'integer' => 0,
'float' => 1,
'boolean' => 2,
'array_simple' => 3,
'array_associative' => 4,
],
[
0 => 42,
1 => 42.42,
2 => true,
3 => ['foo', 'bar'],
4 => ['foo' => 'bar', 'foo2' => 'bar2'],
],
];
$adapter = $this->createCachePool();
$adapter->warmUp($data);
$adapter->warmUp($expected);
$values = eval(substr(file_get_contents(self::$file), 6));
@@ -145,17 +122,13 @@ class PhpArrayAdapterTest extends AdapterTestCase
class PhpArrayAdapterWrapper extends PhpArrayAdapter
{
protected $data = [];
public function save(CacheItemInterface $item)
{
(\Closure::bind(function () use ($item) {
$key = $item->getKey();
$this->keys[$key] = $id = \count($this->values);
$this->data[$key] = $this->values[$id] = $item->get();
$this->warmUp($this->data);
list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
}, $this, PhpArrayAdapter::class))();
\call_user_func(\Closure::bind(function () use ($item) {
$this->values[$item->getKey()] = $item->get();
$this->warmUp($this->values);
$this->values = eval(substr(file_get_contents($this->file), 6));
}, $this, PhpArrayAdapter::class));
return true;
}
@@ -30,12 +30,12 @@ class PhpArrayAdapterWithFallbackTest extends AdapterTestCase
protected static $file;
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
}
protected function tearDown(): void
protected function tearDown()
{
$this->createCachePool()->clear();
@@ -1,31 +0,0 @@
<?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\Tests\Adapter;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
/**
* @group time-sensitive
*/
class PhpFilesAdapterAppendOnlyTest extends PhpFilesAdapterTest
{
protected $skippedTests = [
'testDefaultLifeTime' => 'PhpFilesAdapter does not allow configuring a default lifetime.',
'testExpiration' => 'PhpFilesAdapter in append-only mode does not expiration.',
];
public function createCachePool(): CacheItemPoolInterface
{
return new PhpFilesAdapter('sf-cache', 0, null, true);
}
}
+5 -1
View File
@@ -25,10 +25,14 @@ class PhpFilesAdapterTest extends AdapterTestCase
public function createCachePool()
{
if (!PhpFilesAdapter::isSupported()) {
$this->markTestSkipped('OPcache extension is not enabled.');
}
return new PhpFilesAdapter('sf-cache');
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
}
+10 -4
View File
@@ -16,7 +16,7 @@ use Symfony\Component\Cache\Adapter\RedisAdapter;
class PredisAdapterTest extends AbstractRedisAdapterTest
{
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
self::$redis = new \Predis\Client(['host' => getenv('REDIS_HOST')]);
@@ -35,12 +35,18 @@ class PredisAdapterTest extends AbstractRedisAdapterTest
$params = [
'scheme' => 'tcp',
'host' => $redisHost,
'path' => '',
'dbindex' => '1',
'port' => 6379,
'persistent' => 0,
'class' => 'Predis\Client',
'timeout' => 3,
'read_write_timeout' => 0,
'tcp_nodelay' => true,
'persistent' => 0,
'persistent_id' => null,
'read_timeout' => 0,
'retry_interval' => 0,
'lazy' => false,
'database' => '1',
'password' => null,
];
$this->assertSame($params, $connection->getParameters()->toArray());
}
@@ -13,13 +13,13 @@ namespace Symfony\Component\Cache\Tests\Adapter;
class PredisClusterAdapterTest extends AbstractRedisAdapterTest
{
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
self::$redis = new \Predis\Client([['host' => getenv('REDIS_HOST')]]);
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
self::$redis = null;
}
@@ -11,20 +11,17 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
class PredisRedisClusterAdapterTest extends AbstractRedisAdapterTest
{
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
}
self::$redis = RedisAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['class' => \Predis\Client::class, 'redis_cluster' => true]);
self::$redis = new \Predis\Client(explode(' ', $hosts), ['cluster' => 'redis']);
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
self::$redis = null;
}
@@ -1,34 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
class PredisTagAwareAdapterTest extends PredisAdapterTest
{
use TagAwareTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
}
public function createCachePool($defaultLifetime = 0)
{
$this->assertInstanceOf(\Predis\Client::class, self::$redis);
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
}
@@ -1,34 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
class PredisTagAwareClusterAdapterTest extends PredisClusterAdapterTest
{
use TagAwareTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
}
public function createCachePool($defaultLifetime = 0)
{
$this->assertInstanceOf(\Predis\Client::class, self::$redis);
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
}
@@ -1,34 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
class PredisTagAwareRedisClusterAdapterTest extends PredisRedisClusterAdapterTest
{
use TagAwareTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
}
public function createCachePool($defaultLifetime = 0)
{
$this->assertInstanceOf(\Predis\Client::class, self::$redis);
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
}
+1 -6
View File
@@ -13,7 +13,6 @@ namespace Symfony\Component\Cache\Tests\Adapter;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\Cache\CacheItem;
@@ -28,12 +27,8 @@ class ProxyAdapterTest extends AdapterTestCase
'testPrune' => 'ProxyAdapter just proxies',
];
public function createCachePool($defaultLifetime = 0, $testMethod = null)
public function createCachePool($defaultLifetime = 0)
{
if ('testGetMetadata' === $testMethod) {
return new ProxyAdapter(new FilesystemAdapter(), '', $defaultLifetime);
}
return new ProxyAdapter(new ArrayAdapter(), '', $defaultLifetime);
}
-42
View File
@@ -1,42 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\Psr16Adapter;
use Symfony\Component\Cache\Psr16Cache;
/**
* @group time-sensitive
*/
class Psr16AdapterTest extends AdapterTestCase
{
protected $skippedTests = [
'testPrune' => 'Psr16adapter just proxies',
];
public function createCachePool($defaultLifetime = 0)
{
return new Psr16Adapter(new Psr16Cache(new FilesystemAdapter()), '', $defaultLifetime);
}
public function testValidCacheKeyWithNamespace()
{
$cache = new Psr16Adapter(new Psr16Cache(new ArrayAdapter()), 'some_namespace', 0);
$item = $cache->getItem('my_key');
$item->set('someValue');
$cache->save($item);
$this->assertTrue($cache->getItem('my_key')->isHit(), 'Stored item is successfully retrieved.');
}
}
+8 -24
View File
@@ -17,7 +17,7 @@ use Symfony\Component\Cache\Traits\RedisProxy;
class RedisAdapterTest extends AbstractRedisAdapterTest
{
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
parent::setUpBeforeClass();
self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'), ['lazy' => true]);
@@ -31,33 +31,25 @@ class RedisAdapterTest extends AbstractRedisAdapterTest
return $adapter;
}
/**
* @dataProvider provideValidSchemes
*/
public function testCreateConnection($dsnScheme)
public function testCreateConnection()
{
$redis = RedisAdapter::createConnection($dsnScheme.':?host[h1]&host[h2]&host[/foo:]');
$this->assertInstanceOf(\RedisArray::class, $redis);
$this->assertSame(['h1:6379', 'h2:6379', '/foo'], $redis->_hosts());
@$redis = null; // some versions of phpredis connect on destruct, let's silence the warning
$redisHost = getenv('REDIS_HOST');
$redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost);
$redis = RedisAdapter::createConnection('redis://'.$redisHost);
$this->assertInstanceOf(\Redis::class, $redis);
$this->assertTrue($redis->isConnected());
$this->assertSame(0, $redis->getDbNum());
$redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost.'/2');
$redis = RedisAdapter::createConnection('redis://'.$redisHost.'/2');
$this->assertSame(2, $redis->getDbNum());
$redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost, ['timeout' => 3]);
$redis = RedisAdapter::createConnection('redis://'.$redisHost, ['timeout' => 3]);
$this->assertEquals(3, $redis->getTimeout());
$redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost.'?timeout=4');
$redis = RedisAdapter::createConnection('redis://'.$redisHost.'?timeout=4');
$this->assertEquals(4, $redis->getTimeout());
$redis = RedisAdapter::createConnection($dsnScheme.'://'.$redisHost, ['read_timeout' => 5]);
$redis = RedisAdapter::createConnection('redis://'.$redisHost, ['read_timeout' => 5]);
$this->assertEquals(5, $redis->getReadTimeout());
}
@@ -67,7 +59,7 @@ class RedisAdapterTest extends AbstractRedisAdapterTest
public function testFailedCreateConnection($dsn)
{
$this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Redis connection failed');
$this->expectExceptionMessage('Redis connection ');
RedisAdapter::createConnection($dsn);
}
@@ -90,14 +82,6 @@ class RedisAdapterTest extends AbstractRedisAdapterTest
RedisAdapter::createConnection($dsn);
}
public function provideValidSchemes()
{
return [
['redis'],
['rediss'],
];
}
public function provideInvalidCreateConnection()
{
return [
@@ -13,7 +13,7 @@ namespace Symfony\Component\Cache\Tests\Adapter;
class RedisArrayAdapterTest extends AbstractRedisAdapterTest
{
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
parent::setupBeforeClass();
if (!class_exists('RedisArray')) {
@@ -11,13 +11,9 @@
namespace Symfony\Component\Cache\Tests\Adapter;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Traits\RedisClusterProxy;
class RedisClusterAdapterTest extends AbstractRedisAdapterTest
{
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!class_exists('RedisCluster')) {
self::markTestSkipped('The RedisCluster class is required.');
@@ -26,33 +22,6 @@ class RedisClusterAdapterTest extends AbstractRedisAdapterTest
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
}
self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['lazy' => true, 'redis_cluster' => true]);
}
public function createCachePool($defaultLifetime = 0)
{
$this->assertInstanceOf(RedisClusterProxy::class, self::$redis);
$adapter = new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
/**
* @dataProvider provideFailedCreateConnection
*/
public function testFailedCreateConnection($dsn)
{
$this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Redis connection failed');
RedisAdapter::createConnection($dsn);
}
public function provideFailedCreateConnection()
{
return [
['redis://localhost:1234?redis_cluster=1'],
['redis://foo@localhost?redis_cluster=1'],
['redis://localhost/123?redis_cluster=1'],
];
self::$redis = new \RedisCluster(null, explode(' ', $hosts));
}
}
@@ -1,35 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
use Symfony\Component\Cache\Traits\RedisProxy;
class RedisTagAwareAdapterTest extends RedisAdapterTest
{
use TagAwareTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
}
public function createCachePool($defaultLifetime = 0)
{
$this->assertInstanceOf(RedisProxy::class, self::$redis);
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
}
@@ -1,34 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
class RedisTagAwareArrayAdapterTest extends RedisArrayAdapterTest
{
use TagAwareTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
}
public function createCachePool($defaultLifetime = 0)
{
$this->assertInstanceOf(\RedisArray::class, self::$redis);
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
}
@@ -1,35 +0,0 @@
<?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\Tests\Adapter;
use Symfony\Component\Cache\Adapter\RedisTagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
use Symfony\Component\Cache\Traits\RedisClusterProxy;
class RedisTagAwareClusterAdapterTest extends RedisClusterAdapterTest
{
use TagAwareTestTrait;
protected function setUp(): void
{
parent::setUp();
$this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite';
}
public function createCachePool($defaultLifetime = 0)
{
$this->assertInstanceOf(RedisClusterProxy::class, self::$redis);
$adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
return $adapter;
}
}
@@ -17,7 +17,6 @@ use Symfony\Component\Cache\Simple\FilesystemCache;
/**
* @group time-sensitive
* @group legacy
*/
class SimpleCacheAdapterTest extends AdapterTestCase
{
+123 -8
View File
@@ -14,31 +14,71 @@ namespace Symfony\Component\Cache\Tests\Adapter;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\Tests\Traits\TagAwareTestTrait;
/**
* @group time-sensitive
*/
class TagAwareAdapterTest extends AdapterTestCase
{
use TagAwareTestTrait;
public function createCachePool($defaultLifetime = 0)
{
return new TagAwareAdapter(new FilesystemAdapter('', $defaultLifetime));
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
}
/**
* Test feature specific to TagAwareAdapter as it implicit needs to save deferred when also saving expiry info.
*/
public function testInvalidateCommitsSeperatePools()
public function testInvalidTag()
{
$this->expectException('Psr\Cache\InvalidArgumentException');
$pool = $this->createCachePool();
$item = $pool->getItem('foo');
$item->tag(':');
}
public function testInvalidateTags()
{
$pool = $this->createCachePool();
$i0 = $pool->getItem('i0');
$i1 = $pool->getItem('i1');
$i2 = $pool->getItem('i2');
$i3 = $pool->getItem('i3');
$foo = $pool->getItem('foo');
$pool->save($i0->tag('bar'));
$pool->save($i1->tag('foo'));
$pool->save($i2->tag('foo')->tag('bar'));
$pool->save($i3->tag('foo')->tag('baz'));
$pool->save($foo);
$pool->invalidateTags(['bar']);
$this->assertFalse($pool->getItem('i0')->isHit());
$this->assertTrue($pool->getItem('i1')->isHit());
$this->assertFalse($pool->getItem('i2')->isHit());
$this->assertTrue($pool->getItem('i3')->isHit());
$this->assertTrue($pool->getItem('foo')->isHit());
$pool->invalidateTags(['foo']);
$this->assertFalse($pool->getItem('i1')->isHit());
$this->assertFalse($pool->getItem('i3')->isHit());
$this->assertTrue($pool->getItem('foo')->isHit());
$anotherPoolInstance = $this->createCachePool();
$this->assertFalse($anotherPoolInstance->getItem('i1')->isHit());
$this->assertFalse($anotherPoolInstance->getItem('i3')->isHit());
$this->assertTrue($anotherPoolInstance->getItem('foo')->isHit());
}
public function testInvalidateCommits()
{
$pool1 = $this->createCachePool();
@@ -54,6 +94,62 @@ class TagAwareAdapterTest extends AdapterTestCase
$this->assertTrue($foo->isHit());
}
public function testTagsAreCleanedOnSave()
{
$pool = $this->createCachePool();
$i = $pool->getItem('k');
$pool->save($i->tag('foo'));
$i = $pool->getItem('k');
$pool->save($i->tag('bar'));
$pool->invalidateTags(['foo']);
$this->assertTrue($pool->getItem('k')->isHit());
}
public function testTagsAreCleanedOnDelete()
{
$pool = $this->createCachePool();
$i = $pool->getItem('k');
$pool->save($i->tag('foo'));
$pool->deleteItem('k');
$pool->save($pool->getItem('k'));
$pool->invalidateTags(['foo']);
$this->assertTrue($pool->getItem('k')->isHit());
}
public function testTagItemExpiry()
{
$pool = $this->createCachePool(10);
$item = $pool->getItem('foo');
$item->tag(['baz']);
$item->expiresAfter(100);
$pool->save($item);
$pool->invalidateTags(['baz']);
$this->assertFalse($pool->getItem('foo')->isHit());
sleep(20);
$this->assertFalse($pool->getItem('foo')->isHit());
}
public function testGetPreviousTags()
{
$pool = $this->createCachePool();
$i = $pool->getItem('k');
$pool->save($i->tag('foo'));
$i = $pool->getItem('k');
$this->assertSame(['foo' => 'foo'], $i->getPreviousTags());
}
public function testPrune()
{
$cache = new TagAwareAdapter($this->getPruneableMock());
@@ -160,6 +256,25 @@ class TagAwareAdapterTest extends AdapterTestCase
$this->assertFalse($anotherPool->hasItem($itemKey));
}
public function testInvalidateTagsWithArrayAdapter()
{
$adapter = new TagAwareAdapter(new ArrayAdapter());
$item = $adapter->getItem('foo');
$this->assertFalse($item->isHit());
$item->tag('bar');
$item->expiresAfter(100);
$adapter->save($item);
$this->assertTrue($adapter->getItem('foo')->isHit());
$adapter->invalidateTags(['bar']);
$this->assertFalse($adapter->getItem('foo')->isHit());
}
public function testGetItemReturnsCacheMissWhenPoolDoesNotHaveItemAndOnlyHasTags()
{
$pool = $this->createCachePool();
+3 -22
View File
@@ -55,16 +55,13 @@ class CacheItemTest extends TestCase
public function testTag()
{
$item = new CacheItem();
$r = new \ReflectionProperty($item, 'isTaggable');
$r->setAccessible(true);
$r->setValue($item, true);
$this->assertSame($item, $item->tag('foo'));
$this->assertSame($item, $item->tag(['bar', 'baz']));
(\Closure::bind(function () use ($item) {
$this->assertSame(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], $item->newMetadata[CacheItem::METADATA_TAGS]);
}, $this, CacheItem::class))();
\call_user_func(\Closure::bind(function () use ($item) {
$this->assertSame(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz'], $item->tags);
}, $this, CacheItem::class));
}
/**
@@ -75,22 +72,6 @@ class CacheItemTest extends TestCase
$this->expectException('Symfony\Component\Cache\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Cache tag');
$item = new CacheItem();
$r = new \ReflectionProperty($item, 'isTaggable');
$r->setAccessible(true);
$r->setValue($item, true);
$item->tag($tag);
}
public function testNonTaggableItem()
{
$this->expectException('Symfony\Component\Cache\Exception\LogicException');
$this->expectExceptionMessage('Cache item "foo" comes from a non tag-aware pool: you cannot tag it.');
$item = new CacheItem();
$r = new \ReflectionProperty($item, 'key');
$r->setAccessible(true);
$r->setValue($item, 'foo');
$item->tag([]);
}
}
@@ -1,49 +0,0 @@
<?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\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\Adapter\TraceableAdapter;
use Symfony\Component\Cache\Adapter\TraceableTagAwareAdapter;
use Symfony\Component\Cache\DataCollector\CacheDataCollector;
use Symfony\Component\Cache\DependencyInjection\CacheCollectorPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class CacheCollectorPassTest extends TestCase
{
public function testProcess()
{
$container = new ContainerBuilder();
$container
->register('fs', FilesystemAdapter::class)
->addTag('cache.pool');
$container
->register('tagged_fs', TagAwareAdapter::class)
->addArgument(new Reference('fs'))
->addTag('cache.pool');
$collector = $container->register('data_collector.cache', CacheDataCollector::class);
(new CacheCollectorPass())->process($container);
$this->assertEquals([
['addInstance', ['fs', new Reference('fs')]],
['addInstance', ['tagged_fs', new Reference('tagged_fs')]],
], $collector->getMethodCalls());
$this->assertSame(TraceableAdapter::class, $container->findDefinition('fs')->getClass());
$this->assertSame(TraceableTagAwareAdapter::class, $container->getDefinition('tagged_fs')->getClass());
$this->assertFalse($collector->isPublic(), 'The "data_collector.cache" should be private after processing');
}
}
@@ -1,73 +0,0 @@
<?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\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass;
use Symfony\Component\DependencyInjection\Compiler\RepeatedPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer;
class CachePoolClearerPassTest extends TestCase
{
public function testPoolRefsAreWeak()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('kernel.project_dir', 'foo');
$globalClearer = new Definition(Psr6CacheClearer::class);
$container->setDefinition('cache.global_clearer', $globalClearer);
$publicPool = new Definition();
$publicPool->addArgument('namespace');
$publicPool->addTag('cache.pool', ['clearer' => 'clearer_alias']);
$container->setDefinition('public.pool', $publicPool);
$publicPool = new Definition();
$publicPool->addArgument('namespace');
$publicPool->addTag('cache.pool', ['clearer' => 'clearer_alias', 'name' => 'pool2']);
$container->setDefinition('public.pool2', $publicPool);
$privatePool = new Definition();
$privatePool->setPublic(false);
$privatePool->addArgument('namespace');
$privatePool->addTag('cache.pool', ['clearer' => 'clearer_alias']);
$container->setDefinition('private.pool', $privatePool);
$clearer = new Definition();
$container->setDefinition('clearer', $clearer);
$container->setAlias('clearer_alias', 'clearer');
$pass = new RemoveUnusedDefinitionsPass();
foreach ($container->getCompiler()->getPassConfig()->getRemovingPasses() as $removingPass) {
if ($removingPass instanceof RepeatedPass) {
$pass->setRepeatedPass(new RepeatedPass([$pass]));
break;
}
}
foreach ([new CachePoolPass(), $pass, new CachePoolClearerPass()] as $pass) {
$pass->process($container);
}
$expected = [[
'public.pool' => new Reference('public.pool'),
'pool2' => new Reference('public.pool2'),
]];
$this->assertEquals($expected, $clearer->getArguments());
$this->assertEquals($expected, $globalClearer->getArguments());
}
}
@@ -1,177 +0,0 @@
<?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\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class CachePoolPassTest extends TestCase
{
private $cachePoolPass;
protected function setUp(): void
{
$this->cachePoolPass = new CachePoolPass();
}
public function testNamespaceArgumentIsReplaced()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('kernel.project_dir', 'foo');
$adapter = new Definition();
$adapter->setAbstract(true);
$adapter->addTag('cache.pool');
$container->setDefinition('app.cache_adapter', $adapter);
$container->setAlias('app.cache_adapter_alias', 'app.cache_adapter');
$cachePool = new ChildDefinition('app.cache_adapter_alias');
$cachePool->addArgument(null);
$cachePool->addTag('cache.pool');
$container->setDefinition('app.cache_pool', $cachePool);
$this->cachePoolPass->process($container);
$this->assertSame('z3X945Jbf5', $cachePool->getArgument(0));
}
public function testNamespaceArgumentIsSeededWithAdapterClassName()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('kernel.project_dir', 'foo');
$adapter = new Definition();
$adapter->setAbstract(true);
$adapter->addTag('cache.pool');
$adapter->setClass(RedisAdapter::class);
$container->setDefinition('app.cache_adapter', $adapter);
$container->setAlias('app.cache_adapter_alias', 'app.cache_adapter');
$cachePool = new ChildDefinition('app.cache_adapter_alias');
$cachePool->addArgument(null);
$cachePool->addTag('cache.pool');
$container->setDefinition('app.cache_pool', $cachePool);
$this->cachePoolPass->process($container);
$this->assertSame('xmOJ8gqF-Y', $cachePool->getArgument(0));
}
public function testNamespaceArgumentIsSeededWithAdapterClassNameWithoutAffectingOtherCachePools()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('kernel.project_dir', 'foo');
$adapter = new Definition();
$adapter->setAbstract(true);
$adapter->addTag('cache.pool');
$adapter->setClass(RedisAdapter::class);
$container->setDefinition('app.cache_adapter', $adapter);
$container->setAlias('app.cache_adapter_alias', 'app.cache_adapter');
$otherCachePool = new ChildDefinition('app.cache_adapter_alias');
$otherCachePool->addArgument(null);
$otherCachePool->addTag('cache.pool');
$container->setDefinition('app.other_cache_pool', $otherCachePool);
$cachePool = new ChildDefinition('app.cache_adapter_alias');
$cachePool->addArgument(null);
$cachePool->addTag('cache.pool');
$container->setDefinition('app.cache_pool', $cachePool);
$this->cachePoolPass->process($container);
$this->assertSame('xmOJ8gqF-Y', $cachePool->getArgument(0));
}
public function testNamespaceArgumentIsNotReplacedIfArrayAdapterIsUsed()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('kernel.project_dir', 'foo');
$container->register('cache.adapter.array', ArrayAdapter::class)->addArgument(0);
$cachePool = new ChildDefinition('cache.adapter.array');
$cachePool->addTag('cache.pool');
$container->setDefinition('app.cache_pool', $cachePool);
$this->cachePoolPass->process($container);
$this->assertCount(0, $container->getDefinition('app.cache_pool')->getArguments());
}
public function testArgsAreReplaced()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('cache.prefix.seed', 'foo');
$cachePool = new Definition();
$cachePool->addTag('cache.pool', [
'provider' => 'foobar',
'default_lifetime' => 3,
]);
$cachePool->addArgument(null);
$cachePool->addArgument(null);
$cachePool->addArgument(null);
$container->setDefinition('app.cache_pool', $cachePool);
$this->cachePoolPass->process($container);
$this->assertInstanceOf(Reference::class, $cachePool->getArgument(0));
$this->assertSame('foobar', (string) $cachePool->getArgument(0));
$this->assertSame('tQNhcV-8xa', $cachePool->getArgument(1));
$this->assertSame(3, $cachePool->getArgument(2));
}
public function testWithNameAttribute()
{
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('cache.prefix.seed', 'foo');
$cachePool = new Definition();
$cachePool->addTag('cache.pool', [
'name' => 'foobar',
'provider' => 'foobar',
]);
$cachePool->addArgument(null);
$cachePool->addArgument(null);
$cachePool->addArgument(null);
$container->setDefinition('app.cache_pool', $cachePool);
$this->cachePoolPass->process($container);
$this->assertSame('+naTpPa4Sm', $cachePool->getArgument(1));
}
public function testThrowsExceptionWhenCachePoolTagHasUnknownAttributes()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Invalid "cache.pool" tag for service "app.cache_pool": accepted attributes are');
$container = new ContainerBuilder();
$container->setParameter('kernel.container_class', 'app');
$container->setParameter('kernel.project_dir', 'foo');
$adapter = new Definition();
$adapter->setAbstract(true);
$adapter->addTag('cache.pool');
$container->setDefinition('app.cache_adapter', $adapter);
$cachePool = new ChildDefinition('app.cache_adapter');
$cachePool->addTag('cache.pool', ['foobar' => 123]);
$container->setDefinition('app.cache_pool', $cachePool);
$this->cachePoolPass->process($container);
}
}
@@ -1,70 +0,0 @@
<?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\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Cache\DependencyInjection\CachePoolPrunerPass;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class CachePoolPrunerPassTest extends TestCase
{
public function testCompilerPassReplacesCommandArgument()
{
$container = new ContainerBuilder();
$container->register('console.command.cache_pool_prune')->addArgument([]);
$container->register('pool.foo', FilesystemAdapter::class)->addTag('cache.pool');
$container->register('pool.bar', PhpFilesAdapter::class)->addTag('cache.pool');
$pass = new CachePoolPrunerPass();
$pass->process($container);
$expected = [
'pool.foo' => new Reference('pool.foo'),
'pool.bar' => new Reference('pool.bar'),
];
$argument = $container->getDefinition('console.command.cache_pool_prune')->getArgument(0);
$this->assertInstanceOf(IteratorArgument::class, $argument);
$this->assertEquals($expected, $argument->getValues());
}
public function testCompilePassIsIgnoredIfCommandDoesNotExist()
{
$container = new ContainerBuilder();
$definitionsBefore = \count($container->getDefinitions());
$aliasesBefore = \count($container->getAliases());
$pass = new CachePoolPrunerPass();
$pass->process($container);
// the container is untouched (i.e. no new definitions or aliases)
$this->assertCount($definitionsBefore, $container->getDefinitions());
$this->assertCount($aliasesBefore, $container->getAliases());
}
public function testCompilerPassThrowsOnInvalidDefinitionClass()
{
$this->expectException('Symfony\Component\DependencyInjection\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Class "Symfony\Component\Cache\Tests\DependencyInjection\NotFound" used for service "pool.not-found" cannot be found.');
$container = new ContainerBuilder();
$container->register('console.command.cache_pool_prune')->addArgument([]);
$container->register('pool.not-found', NotFound::class)->addTag('cache.pool');
$pass = new CachePoolPrunerPass();
$pass->process($container);
}
}
+2 -2
View File
@@ -21,12 +21,12 @@ class ArrayCache extends CacheProvider
$expiry = $this->data[$id][1];
return !$expiry || microtime(true) < $expiry || !$this->doDelete($id);
return !$expiry || time() < $expiry || !$this->doDelete($id);
}
protected function doSave($id, $data, $lifeTime = 0)
{
$this->data[$id] = [$data, $lifeTime ? microtime(true) + $lifeTime : false];
$this->data[$id] = [$data, $lifeTime ? time() + $lifeTime : false];
return true;
}
+1 -1
View File
@@ -24,7 +24,7 @@ class ExternalAdapter implements CacheItemPoolInterface
{
private $cache;
public function __construct(int $defaultLifetime = 0)
public function __construct($defaultLifetime = 0)
{
$this->cache = new ArrayAdapter($defaultLifetime);
}
-26
View File
@@ -1,26 +0,0 @@
<?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\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\LockRegistry;
class LockRegistryTest extends TestCase
{
public function testFiles()
{
$lockFiles = LockRegistry::setFiles([]);
LockRegistry::setFiles($lockFiles);
$expected = array_map('realpath', glob(__DIR__.'/../Adapter/*'));
$this->assertSame($expected, $lockFiles);
}
}
@@ -1,112 +0,0 @@
<?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\Tests\Marshaller;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
class DefaultMarshallerTest extends TestCase
{
public function testSerialize()
{
$marshaller = new DefaultMarshaller();
$values = [
'a' => 123,
'b' => function () {},
];
$expected = ['a' => \extension_loaded('igbinary') && (\PHP_VERSION_ID < 70400 || version_compare('3.1.0', phpversion('igbinary'), '<=')) ? igbinary_serialize(123) : serialize(123)];
$this->assertSame($expected, $marshaller->marshall($values, $failed));
$this->assertSame(['b'], $failed);
}
public function testNativeUnserialize()
{
$marshaller = new DefaultMarshaller();
$this->assertNull($marshaller->unmarshall(serialize(null)));
$this->assertFalse($marshaller->unmarshall(serialize(false)));
$this->assertSame('', $marshaller->unmarshall(serialize('')));
$this->assertSame(0, $marshaller->unmarshall(serialize(0)));
}
/**
* @requires extension igbinary
*/
public function testIgbinaryUnserialize()
{
if (\PHP_VERSION_ID >= 70400 && version_compare('3.1.0', phpversion('igbinary'), '>')) {
$this->markTestSkipped('igbinary is not compatible with PHP 7.4.');
}
$marshaller = new DefaultMarshaller();
$this->assertNull($marshaller->unmarshall(igbinary_serialize(null)));
$this->assertFalse($marshaller->unmarshall(igbinary_serialize(false)));
$this->assertSame('', $marshaller->unmarshall(igbinary_serialize('')));
$this->assertSame(0, $marshaller->unmarshall(igbinary_serialize(0)));
}
public function testNativeUnserializeNotFoundClass()
{
$this->expectException('DomainException');
$this->expectExceptionMessage('Class not found: NotExistingClass');
$marshaller = new DefaultMarshaller();
$marshaller->unmarshall('O:16:"NotExistingClass":0:{}');
}
/**
* @requires extension igbinary
*/
public function testIgbinaryUnserializeNotFoundClass()
{
if (\PHP_VERSION_ID >= 70400 && version_compare('3.1.0', phpversion('igbinary'), '>')) {
$this->markTestSkipped('igbinary is not compatible with PHP 7.4.');
}
$this->expectException('DomainException');
$this->expectExceptionMessage('Class not found: NotExistingClass');
$marshaller = new DefaultMarshaller();
$marshaller->unmarshall(rawurldecode('%00%00%00%02%17%10NotExistingClass%14%00'));
}
public function testNativeUnserializeInvalid()
{
$this->expectException('DomainException');
$this->expectExceptionMessage('unserialize(): Error at offset 0 of 3 bytes');
$marshaller = new DefaultMarshaller();
set_error_handler(function () { return false; });
try {
@$marshaller->unmarshall(':::');
} finally {
restore_error_handler();
}
}
/**
* @requires extension igbinary
*/
public function testIgbinaryUnserializeInvalid()
{
if (\PHP_VERSION_ID >= 70400 && version_compare('3.1.0', phpversion('igbinary'), '>')) {
$this->markTestSkipped('igbinary is not compatible with PHP 7.4.');
}
$this->expectException('DomainException');
$this->expectExceptionMessage('igbinary_unserialize_zval: unknown type \'61\', position 5');
$marshaller = new DefaultMarshaller();
set_error_handler(function () { return false; });
try {
@$marshaller->unmarshall(rawurldecode('%00%00%00%02abc'));
} finally {
restore_error_handler();
}
}
}
-168
View File
@@ -1,168 +0,0 @@
<?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\Tests;
use Cache\IntegrationTests\SimpleCacheTest;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\Psr16Cache;
/**
* @group time-sensitive
*/
class Psr16CacheTest extends SimpleCacheTest
{
protected function setUp(): void
{
parent::setUp();
if (\array_key_exists('testPrune', $this->skippedTests)) {
return;
}
$pool = $this->createSimpleCache();
if ($pool instanceof Psr16Cache) {
$pool = ((array) $pool)[sprintf("\0%s\0pool", Psr16Cache::class)];
}
if (!$pool instanceof PruneableInterface) {
$this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
}
}
public function createSimpleCache($defaultLifetime = 0)
{
return new Psr16Cache(new FilesystemAdapter('', $defaultLifetime));
}
public static function validKeys()
{
return array_merge(parent::validKeys(), [["a\0b"]]);
}
public function testDefaultLifeTime()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createSimpleCache(2);
$cache->clear();
$cache->set('key.dlt', 'value');
sleep(1);
$this->assertSame('value', $cache->get('key.dlt'));
sleep(2);
$this->assertNull($cache->get('key.dlt'));
$cache->clear();
}
public function testNotUnserializable()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createSimpleCache();
$cache->clear();
$cache->set('foo', new NotUnserializable());
$this->assertNull($cache->get('foo'));
$cache->setMultiple(['foo' => new NotUnserializable()]);
foreach ($cache->getMultiple(['foo']) as $value) {
}
$this->assertNull($value);
$cache->clear();
}
public function testPrune()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
/** @var PruneableInterface|CacheInterface $cache */
$cache = $this->createSimpleCache();
$cache->clear();
$cache->set('foo', 'foo-val', new \DateInterval('PT05S'));
$cache->set('bar', 'bar-val', new \DateInterval('PT10S'));
$cache->set('baz', 'baz-val', new \DateInterval('PT15S'));
$cache->set('qux', 'qux-val', new \DateInterval('PT20S'));
sleep(30);
$cache->prune();
$this->assertTrue($this->isPruned($cache, 'foo'));
$this->assertTrue($this->isPruned($cache, 'bar'));
$this->assertTrue($this->isPruned($cache, 'baz'));
$this->assertTrue($this->isPruned($cache, 'qux'));
$cache->set('foo', 'foo-val');
$cache->set('bar', 'bar-val', new \DateInterval('PT20S'));
$cache->set('baz', 'baz-val', new \DateInterval('PT40S'));
$cache->set('qux', 'qux-val', new \DateInterval('PT80S'));
$cache->prune();
$this->assertFalse($this->isPruned($cache, 'foo'));
$this->assertFalse($this->isPruned($cache, 'bar'));
$this->assertFalse($this->isPruned($cache, 'baz'));
$this->assertFalse($this->isPruned($cache, 'qux'));
sleep(30);
$cache->prune();
$this->assertFalse($this->isPruned($cache, 'foo'));
$this->assertTrue($this->isPruned($cache, 'bar'));
$this->assertFalse($this->isPruned($cache, 'baz'));
$this->assertFalse($this->isPruned($cache, 'qux'));
sleep(30);
$cache->prune();
$this->assertFalse($this->isPruned($cache, 'foo'));
$this->assertTrue($this->isPruned($cache, 'baz'));
$this->assertFalse($this->isPruned($cache, 'qux'));
sleep(30);
$cache->prune();
$this->assertFalse($this->isPruned($cache, 'foo'));
$this->assertTrue($this->isPruned($cache, 'qux'));
$cache->clear();
}
protected function isPruned($cache, $name)
{
if (Psr16Cache::class !== \get_class($cache)) {
$this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.');
}
$pool = ((array) $cache)[sprintf("\0%s\0pool", Psr16Cache::class)];
$getFileMethod = (new \ReflectionObject($pool))->getMethod('getFile');
$getFileMethod->setAccessible(true);
return !file_exists($getFileMethod->invoke($pool, $name));
}
}
class NotUnserializable
{
public function __wakeup()
{
throw new \Exception(__CLASS__);
}
}
@@ -13,9 +13,6 @@ namespace Symfony\Component\Cache\Tests\Simple;
use Symfony\Component\Cache\Simple\RedisCache;
/**
* @group legacy
*/
abstract class AbstractRedisCacheTest extends CacheTestCase
{
protected $skippedTests = [
@@ -31,18 +28,19 @@ abstract class AbstractRedisCacheTest extends CacheTestCase
return new RedisCache(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
}
public static function setUpBeforeClass(): void
public static function setUpBeforeClass()
{
if (!\extension_loaded('redis')) {
self::markTestSkipped('Extension redis required.');
}
if (!@((new \Redis())->connect(getenv('REDIS_HOST')))) {
$e = error_get_last();
self::markTestSkipped($e['message']);
try {
(new \Redis())->connect(getenv('REDIS_HOST'));
} catch (\Exception $e) {
self::markTestSkipped($e->getMessage());
}
}
public static function tearDownAfterClass(): void
public static function tearDownAfterClass()
{
self::$redis = null;
}
+1 -4
View File
@@ -13,9 +13,6 @@ namespace Symfony\Component\Cache\Tests\Simple;
use Symfony\Component\Cache\Simple\ApcuCache;
/**
* @group legacy
*/
class ApcuCacheTest extends CacheTestCase
{
protected $skippedTests = [
@@ -26,7 +23,7 @@ class ApcuCacheTest extends CacheTestCase
public function createSimpleCache($defaultLifetime = 0)
{
if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) || ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN))) {
if (!\function_exists('apcu_fetch') || !filter_var(ini_get('apc.enabled'), \FILTER_VALIDATE_BOOLEAN) || ('cli' === \PHP_SAPI && !filter_var(ini_get('apc.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
$this->markTestSkipped('APCu extension is required.');
}
if ('\\' === \DIRECTORY_SEPARATOR) {
-1
View File
@@ -15,7 +15,6 @@ use Symfony\Component\Cache\Simple\ArrayCache;
/**
* @group time-sensitive
* @group legacy
*/
class ArrayCacheTest extends CacheTestCase
{
+12 -3
View File
@@ -17,7 +17,7 @@ use Symfony\Component\Cache\PruneableInterface;
abstract class CacheTestCase extends SimpleCacheTest
{
protected function setUp(): void
protected function setUp()
{
parent::setUp();
@@ -28,6 +28,10 @@ abstract class CacheTestCase extends SimpleCacheTest
public static function validKeys()
{
if (\defined('HHVM_VERSION')) {
return parent::validKeys();
}
return array_merge(parent::validKeys(), [["a\0b"]]);
}
@@ -132,9 +136,14 @@ abstract class CacheTestCase extends SimpleCacheTest
}
}
class NotUnserializable
class NotUnserializable implements \Serializable
{
public function __wakeup()
public function serialize()
{
return serialize(123);
}
public function unserialize($ser)
{
throw new \Exception(__CLASS__);
}
-1
View File
@@ -20,7 +20,6 @@ use Symfony\Component\Cache\Simple\FilesystemCache;
/**
* @group time-sensitive
* @group legacy
*/
class ChainCacheTest extends CacheTestCase
{
@@ -16,7 +16,6 @@ use Symfony\Component\Cache\Tests\Fixtures\ArrayCache;
/**
* @group time-sensitive
* @group legacy
*/
class DoctrineCacheTest extends CacheTestCase
{

Some files were not shown because too many files have changed in this diff Show More