minor #31760 [Cache] remove deprecated PSR-16 implementations et al. (nicolas-grekas)
This PR was merged into the 5.0-dev branch.
Discussion
----------
[Cache] remove deprecated PSR-16 implementations et al.
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | no
| BC breaks? | yes
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | -
\o/
(use `Psr16Cache` instead)
Commits
-------
38a67f84cd
[Cache] remove deprecated PSR-16 implementations et al.
This commit is contained in:
commit
4a437ab870
@ -11,17 +11,112 @@
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Traits\ApcuTrait;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class ApcuAdapter extends AbstractAdapter
|
||||
{
|
||||
use ApcuTrait;
|
||||
|
||||
/**
|
||||
* @throws CacheException if APCu is not enabled
|
||||
*/
|
||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $version = null)
|
||||
{
|
||||
$this->init($namespace, $defaultLifetime, $version);
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('APCu is not enabled');
|
||||
}
|
||||
if ('cli' === \PHP_SAPI) {
|
||||
ini_set('apc.use_request_time', 0);
|
||||
}
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
|
||||
if (null !== $version) {
|
||||
CacheItem::validateKey($version);
|
||||
|
||||
if (!apcu_exists($version.'@'.$namespace)) {
|
||||
$this->doClear($namespace);
|
||||
apcu_add($version.'@'.$namespace, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
||||
try {
|
||||
$values = [];
|
||||
foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) {
|
||||
if (null !== $v || $ok) {
|
||||
$values[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
return apcu_exists($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN))
|
||||
? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY))
|
||||
: apcu_clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
apcu_delete($id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
try {
|
||||
if (false === $failures = apcu_store($values, null, $lifetime)) {
|
||||
$failures = $values;
|
||||
}
|
||||
|
||||
return array_keys($failures);
|
||||
} catch (\Throwable $e) {
|
||||
if (1 === \count($values)) {
|
||||
// Workaround https://github.com/krakjoe/apcu/issues/170
|
||||
apcu_delete(key($values));
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\ResettableInterface;
|
||||
use Symfony\Component\Cache\Traits\ArrayTrait;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
@ -23,8 +23,11 @@ use Symfony\Contracts\Cache\CacheInterface;
|
||||
*/
|
||||
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
|
||||
{
|
||||
use ArrayTrait;
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $storeSerialized;
|
||||
private $values = [];
|
||||
private $expiries = [];
|
||||
private $createCacheItem;
|
||||
|
||||
/**
|
||||
@ -65,6 +68,27 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
|
||||
return $item->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(string $key): bool
|
||||
{
|
||||
return $this->deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
|
||||
return true;
|
||||
}
|
||||
CacheItem::validateKey($key);
|
||||
|
||||
return isset($this->expiries[$key]) && !$this->deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -94,6 +118,19 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
|
||||
return $this->generateItems($keys, microtime(true), $this->createCacheItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
if (!\is_string($key) || !isset($this->expiries[$key])) {
|
||||
CacheItem::validateKey($key);
|
||||
}
|
||||
unset($this->values[$key], $this->expiries[$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -156,8 +193,110 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(string $key): bool
|
||||
public function clear()
|
||||
{
|
||||
return $this->deleteItem($key);
|
||||
$this->values = $this->expiries = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all cached values, with cache miss as null.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
if (!$this->storeSerialized) {
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
$values = $this->values;
|
||||
foreach ($values as $k => $v) {
|
||||
if (null === $v || 'N;' === $v) {
|
||||
continue;
|
||||
}
|
||||
if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
|
||||
$values[$k] = serialize($v);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->clear();
|
||||
}
|
||||
|
||||
private function generateItems(array $keys, $now, $f)
|
||||
{
|
||||
foreach ($keys as $i => $key) {
|
||||
if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
|
||||
$this->values[$key] = $value = null;
|
||||
} else {
|
||||
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
|
||||
}
|
||||
unset($keys[$i]);
|
||||
|
||||
yield $key => $f($key, $value, $isHit);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
yield $key => $f($key, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
private function freeze($value, $key)
|
||||
{
|
||||
if (null === $value) {
|
||||
return 'N;';
|
||||
}
|
||||
if (\is_string($value)) {
|
||||
// Serialize strings if they could be confused with serialized objects or arrays
|
||||
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
|
||||
return serialize($value);
|
||||
}
|
||||
} elseif (!\is_scalar($value)) {
|
||||
try {
|
||||
$serialized = serialize($value);
|
||||
} catch (\Exception $e) {
|
||||
$type = \is_object($value) ? \get_class($value) : \gettype($value);
|
||||
$message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
|
||||
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e]);
|
||||
|
||||
return;
|
||||
}
|
||||
// Keep value serialized if it contains any objects or any internal references
|
||||
if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function unfreeze(string $key, bool &$isHit)
|
||||
{
|
||||
if ('N;' === $value = $this->values[$key]) {
|
||||
return null;
|
||||
}
|
||||
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
||||
try {
|
||||
$value = unserialize($value);
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
|
||||
$value = false;
|
||||
}
|
||||
if (false === $value) {
|
||||
$this->values[$key] = $value = null;
|
||||
$isHit = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
@ -12,11 +12,13 @@
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Doctrine\Common\Cache\CacheProvider;
|
||||
use Symfony\Component\Cache\Traits\DoctrineTrait;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class DoctrineAdapter extends AbstractAdapter
|
||||
{
|
||||
use DoctrineTrait;
|
||||
private $provider;
|
||||
|
||||
public function __construct(CacheProvider $provider, string $namespace = '', int $defaultLifetime = 0)
|
||||
{
|
||||
@ -24,4 +26,80 @@ class DoctrineAdapter extends AbstractAdapter
|
||||
$this->provider = $provider;
|
||||
$provider->setNamespace($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
$this->provider->setNamespace($this->provider->getNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback');
|
||||
try {
|
||||
return $this->provider->fetchMultiple($ids);
|
||||
} catch (\Error $e) {
|
||||
$trace = $e->getTrace();
|
||||
|
||||
if (isset($trace[0]['function']) && !isset($trace[0]['class'])) {
|
||||
switch ($trace[0]['function']) {
|
||||
case 'unserialize':
|
||||
case 'apcu_fetch':
|
||||
case 'apc_fetch':
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
return $this->provider->contains($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
$namespace = $this->provider->getNamespace();
|
||||
|
||||
return isset($namespace[0])
|
||||
? $this->provider->deleteAll()
|
||||
: $this->provider->flushAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$ok = true;
|
||||
foreach ($ids as $id) {
|
||||
$ok = $this->provider->delete($id) && $ok;
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
return $this->provider->saveMultiple($values, $lifetime);
|
||||
}
|
||||
}
|
||||
|
@ -11,15 +11,30 @@
|
||||
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\Traits\MemcachedTrait;
|
||||
|
||||
/**
|
||||
* @author Rob Frawley 2nd <rmf@src.run>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class MemcachedAdapter extends AbstractAdapter
|
||||
{
|
||||
use MemcachedTrait;
|
||||
|
||||
protected $maxIdLength = 250;
|
||||
|
||||
private static $defaultClientOptions = [
|
||||
'persistent_id' => null,
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
'serializer' => 'php',
|
||||
];
|
||||
|
||||
private $marshaller;
|
||||
private $client;
|
||||
private $lazyClient;
|
||||
|
||||
/**
|
||||
* Using a MemcachedAdapter with a TagAwareAdapter for storing tags is discouraged.
|
||||
* Using a RedisAdapter is recommended instead. If you cannot do otherwise, be aware that:
|
||||
@ -32,6 +47,292 @@ class MemcachedAdapter extends AbstractAdapter
|
||||
*/
|
||||
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->init($client, $namespace, $defaultLifetime, $marshaller);
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Memcached >= 2.2.0 is required');
|
||||
}
|
||||
if ('Memcached' === \get_class($client)) {
|
||||
$opt = $client->getOption(\Memcached::OPT_SERIALIZER);
|
||||
if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
|
||||
throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
|
||||
}
|
||||
$this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
|
||||
$this->client = $client;
|
||||
} else {
|
||||
$this->lazyClient = $client;
|
||||
}
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
$this->enableVersioning();
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
}
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Memcached instance.
|
||||
*
|
||||
* By default, the binary protocol, no block, and libketama compatible options are enabled.
|
||||
*
|
||||
* Examples for servers:
|
||||
* - 'memcached://user:pass@localhost?weight=33'
|
||||
* - [['localhost', 11211, 33]]
|
||||
*
|
||||
* @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return \Memcached
|
||||
*
|
||||
* @throws \ErrorException When invalid options or servers are provided
|
||||
*/
|
||||
public static function createConnection($servers, array $options = [])
|
||||
{
|
||||
if (\is_string($servers)) {
|
||||
$servers = [$servers];
|
||||
} elseif (!\is_array($servers)) {
|
||||
throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, %s given.', \gettype($servers)));
|
||||
}
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Memcached >= 2.2.0 is required');
|
||||
}
|
||||
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
|
||||
try {
|
||||
$options += static::$defaultClientOptions;
|
||||
$client = new \Memcached($options['persistent_id']);
|
||||
$username = $options['username'];
|
||||
$password = $options['password'];
|
||||
|
||||
// parse any DSN in $servers
|
||||
foreach ($servers as $i => $dsn) {
|
||||
if (\is_array($dsn)) {
|
||||
continue;
|
||||
}
|
||||
if (0 !== strpos($dsn, 'memcached:')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached:"', $dsn));
|
||||
}
|
||||
$params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
|
||||
if (!empty($m[2])) {
|
||||
list($username, $password) = explode(':', $m[2], 2) + [1 => null];
|
||||
}
|
||||
|
||||
return 'file:'.($m[1] ?? '');
|
||||
}, $dsn);
|
||||
if (false === $params = parse_url($params)) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
|
||||
}
|
||||
$query = $hosts = [];
|
||||
if (isset($params['query'])) {
|
||||
parse_str($params['query'], $query);
|
||||
|
||||
if (isset($query['host'])) {
|
||||
if (!\is_array($hosts = $query['host'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
|
||||
}
|
||||
foreach ($hosts as $host => $weight) {
|
||||
if (false === $port = strrpos($host, ':')) {
|
||||
$hosts[$host] = [$host, 11211, (int) $weight];
|
||||
} else {
|
||||
$hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight];
|
||||
}
|
||||
}
|
||||
$hosts = array_values($hosts);
|
||||
unset($query['host']);
|
||||
}
|
||||
if ($hosts && !isset($params['host']) && !isset($params['path'])) {
|
||||
unset($servers[$i]);
|
||||
$servers = array_merge($servers, $hosts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isset($params['host']) && !isset($params['path'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
|
||||
}
|
||||
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
|
||||
$params['weight'] = $m[1];
|
||||
$params['path'] = substr($params['path'], 0, -\strlen($m[0]));
|
||||
}
|
||||
$params += [
|
||||
'host' => isset($params['host']) ? $params['host'] : $params['path'],
|
||||
'port' => isset($params['host']) ? 11211 : null,
|
||||
'weight' => 0,
|
||||
];
|
||||
if ($query) {
|
||||
$params += $query;
|
||||
$options = $query + $options;
|
||||
}
|
||||
|
||||
$servers[$i] = [$params['host'], $params['port'], $params['weight']];
|
||||
|
||||
if ($hosts) {
|
||||
$servers = array_merge($servers, $hosts);
|
||||
}
|
||||
}
|
||||
|
||||
// set client's options
|
||||
unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']);
|
||||
$options = array_change_key_case($options, CASE_UPPER);
|
||||
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
|
||||
$client->setOption(\Memcached::OPT_NO_BLOCK, true);
|
||||
$client->setOption(\Memcached::OPT_TCP_NODELAY, true);
|
||||
if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
|
||||
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
|
||||
}
|
||||
foreach ($options as $name => $value) {
|
||||
if (\is_int($name)) {
|
||||
continue;
|
||||
}
|
||||
if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
|
||||
$value = \constant('Memcached::'.$name.'_'.strtoupper($value));
|
||||
}
|
||||
$opt = \constant('Memcached::OPT_'.$name);
|
||||
|
||||
unset($options[$name]);
|
||||
$options[$opt] = $value;
|
||||
}
|
||||
$client->setOptions($options);
|
||||
|
||||
// set client's servers, taking care of persistent connections
|
||||
if (!$client->isPristine()) {
|
||||
$oldServers = [];
|
||||
foreach ($client->getServerList() as $server) {
|
||||
$oldServers[] = [$server['host'], $server['port']];
|
||||
}
|
||||
|
||||
$newServers = [];
|
||||
foreach ($servers as $server) {
|
||||
if (1 < \count($server)) {
|
||||
$server = array_values($server);
|
||||
unset($server[2]);
|
||||
$server[1] = (int) $server[1];
|
||||
}
|
||||
$newServers[] = $server;
|
||||
}
|
||||
|
||||
if ($oldServers !== $newServers) {
|
||||
$client->resetServerList();
|
||||
$client->addServers($servers);
|
||||
}
|
||||
} else {
|
||||
$client->addServers($servers);
|
||||
}
|
||||
|
||||
if (null !== $username || null !== $password) {
|
||||
if (!method_exists($client, 'setSaslAuthData')) {
|
||||
trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
|
||||
}
|
||||
$client->setSaslAuthData($username, $password);
|
||||
}
|
||||
|
||||
return $client;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
if ($lifetime && $lifetime > 30 * 86400) {
|
||||
$lifetime += time();
|
||||
}
|
||||
|
||||
$encodedValues = [];
|
||||
foreach ($values as $key => $value) {
|
||||
$encodedValues[rawurlencode($key)] = $value;
|
||||
}
|
||||
|
||||
return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
try {
|
||||
$encodedIds = array_map('rawurlencode', $ids);
|
||||
|
||||
$encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
|
||||
|
||||
$result = [];
|
||||
foreach ($encodedResult as $key => $value) {
|
||||
$result[rawurldecode($key)] = $this->marshaller->unmarshall($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$ok = true;
|
||||
$encodedIds = array_map('rawurlencode', $ids);
|
||||
foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) {
|
||||
if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
return '' === $namespace && $this->getClient()->flush();
|
||||
}
|
||||
|
||||
private function checkResultCode($result)
|
||||
{
|
||||
$code = $this->client->getResultCode();
|
||||
|
||||
if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage())));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Memcached
|
||||
*/
|
||||
private function getClient()
|
||||
{
|
||||
if ($this->client) {
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
$opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER);
|
||||
if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
|
||||
throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
|
||||
}
|
||||
if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) {
|
||||
throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix));
|
||||
}
|
||||
|
||||
return $this->client = $this->lazyClient;
|
||||
}
|
||||
}
|
||||
|
@ -12,17 +12,34 @@
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
|
||||
use Doctrine\DBAL\Exception\TableNotFoundException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\Traits\PdoTrait;
|
||||
|
||||
class PdoAdapter extends AbstractAdapter implements PruneableInterface
|
||||
{
|
||||
use PdoTrait;
|
||||
|
||||
protected $maxIdLength = 255;
|
||||
|
||||
private $marshaller;
|
||||
private $conn;
|
||||
private $dsn;
|
||||
private $driver;
|
||||
private $serverVersion;
|
||||
private $table = 'cache_items';
|
||||
private $idCol = 'item_id';
|
||||
private $dataCol = 'item_data';
|
||||
private $lifetimeCol = 'item_lifetime';
|
||||
private $timeCol = 'item_time';
|
||||
private $username = '';
|
||||
private $password = '';
|
||||
private $connectionOptions = [];
|
||||
private $namespace;
|
||||
|
||||
/**
|
||||
* You can either pass an existing database connection as PDO instance or
|
||||
* a Doctrine DBAL Connection or a DSN string that will be used to
|
||||
@ -49,6 +66,380 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
|
||||
*/
|
||||
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = [], MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
|
||||
if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
|
||||
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
|
||||
}
|
||||
|
||||
if ($connOrDsn instanceof \PDO) {
|
||||
if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
|
||||
throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
|
||||
}
|
||||
|
||||
$this->conn = $connOrDsn;
|
||||
} elseif ($connOrDsn instanceof Connection) {
|
||||
$this->conn = $connOrDsn;
|
||||
} elseif (\is_string($connOrDsn)) {
|
||||
$this->dsn = $connOrDsn;
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \is_object($connOrDsn) ? \get_class($connOrDsn) : \gettype($connOrDsn)));
|
||||
}
|
||||
|
||||
$this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
|
||||
$this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
|
||||
$this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
|
||||
$this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
|
||||
$this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
|
||||
$this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
|
||||
$this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
|
||||
$this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
|
||||
$this->namespace = $namespace;
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the table to store cache items which can be called once for setup.
|
||||
*
|
||||
* Cache ID are saved in a column of maximum length 255. Cache data is
|
||||
* saved in a BLOB.
|
||||
*
|
||||
* @throws \PDOException When the table already exists
|
||||
* @throws DBALException When the table already exists
|
||||
* @throws \DomainException When an unsupported PDO driver is used
|
||||
*/
|
||||
public function createTable()
|
||||
{
|
||||
// connect if we are not yet
|
||||
$conn = $this->getConnection();
|
||||
|
||||
if ($conn instanceof Connection) {
|
||||
$types = [
|
||||
'mysql' => 'binary',
|
||||
'sqlite' => 'text',
|
||||
'pgsql' => 'string',
|
||||
'oci' => 'string',
|
||||
'sqlsrv' => 'string',
|
||||
];
|
||||
if (!isset($types[$this->driver])) {
|
||||
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
|
||||
}
|
||||
|
||||
$schema = new Schema();
|
||||
$table = $schema->createTable($this->table);
|
||||
$table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
|
||||
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
|
||||
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
|
||||
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
|
||||
$table->setPrimaryKey([$this->idCol]);
|
||||
|
||||
foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
|
||||
$conn->exec($sql);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
// We use varbinary for the ID column because it prevents unwanted conversions:
|
||||
// - character set conversions between server and client
|
||||
// - trailing space removal
|
||||
// - case-insensitivity
|
||||
// - language processing like é == e
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
|
||||
break;
|
||||
case 'sqlite':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'pgsql':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'oci':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'sqlsrv':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
default:
|
||||
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
|
||||
}
|
||||
|
||||
$conn->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time";
|
||||
|
||||
if ('' !== $this->namespace) {
|
||||
$deleteSql .= " AND $this->idCol LIKE :namespace";
|
||||
}
|
||||
|
||||
try {
|
||||
$delete = $this->getConnection()->prepare($deleteSql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
return true;
|
||||
}
|
||||
$delete->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
|
||||
if ('' !== $this->namespace) {
|
||||
$delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
|
||||
}
|
||||
try {
|
||||
return $delete->execute();
|
||||
} catch (TableNotFoundException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$now = time();
|
||||
$expired = [];
|
||||
|
||||
$sql = str_pad('', (\count($ids) << 1) - 1, '?,');
|
||||
$sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)";
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
|
||||
foreach ($ids as $id) {
|
||||
$stmt->bindValue(++$i, $id);
|
||||
}
|
||||
$stmt->execute();
|
||||
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_NUM)) {
|
||||
if (null === $row[1]) {
|
||||
$expired[] = $row[0];
|
||||
} else {
|
||||
yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($expired) {
|
||||
$sql = str_pad('', (\count($expired) << 1) - 1, '?,');
|
||||
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)";
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
|
||||
foreach ($expired as $id) {
|
||||
$stmt->bindValue(++$i, $id);
|
||||
}
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
$sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
|
||||
$stmt->bindValue(':id', $id);
|
||||
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
return (bool) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
$conn = $this->getConnection();
|
||||
|
||||
if ('' === $namespace) {
|
||||
if ('sqlite' === $this->driver) {
|
||||
$sql = "DELETE FROM $this->table";
|
||||
} else {
|
||||
$sql = "TRUNCATE TABLE $this->table";
|
||||
}
|
||||
} else {
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
|
||||
}
|
||||
|
||||
try {
|
||||
$conn->exec($sql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$sql = str_pad('', (\count($ids) << 1) - 1, '?,');
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)";
|
||||
try {
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->execute(array_values($ids));
|
||||
} catch (TableNotFoundException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
$conn = $this->getConnection();
|
||||
$driver = $this->driver;
|
||||
$insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
|
||||
|
||||
switch (true) {
|
||||
case 'mysql' === $driver:
|
||||
$sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
|
||||
break;
|
||||
case 'oci' === $driver:
|
||||
// DUAL is Oracle specific dummy table
|
||||
$sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
|
||||
break;
|
||||
case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='):
|
||||
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
|
||||
// It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
|
||||
$sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
|
||||
break;
|
||||
case 'sqlite' === $driver:
|
||||
$sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
|
||||
break;
|
||||
case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='):
|
||||
$sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
|
||||
break;
|
||||
default:
|
||||
$driver = null;
|
||||
$sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
|
||||
break;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$lifetime = $lifetime ?: null;
|
||||
try {
|
||||
$stmt = $conn->prepare($sql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$stmt = $conn->prepare($sql);
|
||||
}
|
||||
|
||||
if ('sqlsrv' === $driver || 'oci' === $driver) {
|
||||
$stmt->bindParam(1, $id);
|
||||
$stmt->bindParam(2, $id);
|
||||
$stmt->bindParam(3, $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(4, $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(5, $now, \PDO::PARAM_INT);
|
||||
$stmt->bindParam(6, $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(7, $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(8, $now, \PDO::PARAM_INT);
|
||||
} else {
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
||||
}
|
||||
if (null === $driver) {
|
||||
$insertStmt = $conn->prepare($insertSql);
|
||||
|
||||
$insertStmt->bindParam(':id', $id);
|
||||
$insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||
$insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
|
||||
$insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
foreach ($values as $id => $data) {
|
||||
try {
|
||||
$stmt->execute();
|
||||
} catch (TableNotFoundException $e) {
|
||||
if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$stmt->execute();
|
||||
}
|
||||
if (null === $driver && !$stmt->rowCount()) {
|
||||
try {
|
||||
$insertStmt->execute();
|
||||
} catch (DBALException $e) {
|
||||
} catch (\PDOException $e) {
|
||||
// A concurrent write won, let it be
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \PDO|Connection
|
||||
*/
|
||||
private function getConnection()
|
||||
{
|
||||
if (null === $this->conn) {
|
||||
$this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
|
||||
$this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
if (null === $this->driver) {
|
||||
if ($this->conn instanceof \PDO) {
|
||||
$this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||
} else {
|
||||
switch ($this->driver = $this->conn->getDriver()->getName()) {
|
||||
case 'mysqli':
|
||||
case 'pdo_mysql':
|
||||
case 'drizzle_pdo_mysql':
|
||||
$this->driver = 'mysql';
|
||||
break;
|
||||
case 'pdo_sqlite':
|
||||
$this->driver = 'sqlite';
|
||||
break;
|
||||
case 'pdo_pgsql':
|
||||
$this->driver = 'pgsql';
|
||||
break;
|
||||
case 'oci8':
|
||||
case 'pdo_oracle':
|
||||
$this->driver = 'oci';
|
||||
break;
|
||||
case 'pdo_sqlsrv':
|
||||
$this->driver = 'sqlsrv';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
private function getServerVersion(): string
|
||||
{
|
||||
if (null === $this->serverVersion) {
|
||||
$conn = $this->conn instanceof \PDO ? $this->conn : $this->conn->getWrappedConnection();
|
||||
if ($conn instanceof \PDO) {
|
||||
$this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
} elseif ($conn instanceof ServerInfoAwareConnection) {
|
||||
$this->serverVersion = $conn->getServerVersion();
|
||||
} else {
|
||||
$this->serverVersion = '0';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->serverVersion;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ 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\Component\Cache\Traits\ProxyTrait;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
/**
|
||||
@ -30,9 +31,12 @@ use Symfony\Contracts\Cache\CacheInterface;
|
||||
*/
|
||||
class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
|
||||
{
|
||||
use PhpArrayTrait;
|
||||
use ContractsTrait;
|
||||
use ProxyTrait;
|
||||
|
||||
private $file;
|
||||
private $keys;
|
||||
private $values;
|
||||
private $createCacheItem;
|
||||
|
||||
/**
|
||||
@ -255,6 +259,127 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
|
||||
return $this->pool->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->keys = $this->values = [];
|
||||
|
||||
$cleared = @unlink($this->file) || !file_exists($this->file);
|
||||
|
||||
return $this->pool->clear() && $cleared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an array of cached values.
|
||||
*
|
||||
* @param array $values The cached values
|
||||
*/
|
||||
public function warmUp(array $values)
|
||||
{
|
||||
if (file_exists($this->file)) {
|
||||
if (!is_file($this->file)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: %s.', $this->file));
|
||||
}
|
||||
|
||||
if (!is_writable($this->file)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache file is not writable: %s.', $this->file));
|
||||
}
|
||||
} else {
|
||||
$directory = \dirname($this->file);
|
||||
|
||||
if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: %s.', $directory));
|
||||
}
|
||||
|
||||
if (!is_writable($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache directory is not writable: %s.', $directory));
|
||||
}
|
||||
}
|
||||
|
||||
$dumpedValues = '';
|
||||
$dumpedMap = [];
|
||||
$dump = <<<'EOF'
|
||||
<?php
|
||||
|
||||
// This file has been auto-generated by the Symfony Cache Component.
|
||||
|
||||
return [[
|
||||
|
||||
|
||||
EOF;
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
|
||||
$isStaticValue = true;
|
||||
|
||||
if (null === $value) {
|
||||
$value = "'N;'";
|
||||
} elseif (\is_object($value) || \is_array($value)) {
|
||||
try {
|
||||
$value = VarExporter::export($value, $isStaticValue);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
|
||||
}
|
||||
} elseif (\is_string($value)) {
|
||||
// Wrap "N;" in a closure to not confuse it with an encoded `null`
|
||||
if ('N;' === $value) {
|
||||
$isStaticValue = false;
|
||||
}
|
||||
$value = var_export($value, true);
|
||||
} elseif (!\is_scalar($value)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
|
||||
} else {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
|
||||
if (!$isStaticValue) {
|
||||
$value = str_replace("\n", "\n ", $value);
|
||||
$value = "static function () {\n return {$value};\n}";
|
||||
}
|
||||
$hash = hash('md5', $value);
|
||||
|
||||
if (null === $id = $dumpedMap[$hash] ?? null) {
|
||||
$id = $dumpedMap[$hash] = \count($dumpedMap);
|
||||
$dumpedValues .= "{$id} => {$value},\n";
|
||||
}
|
||||
|
||||
$dump .= var_export($key, true)." => {$id},\n";
|
||||
}
|
||||
|
||||
$dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
|
||||
|
||||
$tmpFile = uniqid($this->file, true);
|
||||
|
||||
file_put_contents($tmpFile, $dump);
|
||||
@chmod($tmpFile, 0666 & ~umask());
|
||||
unset($serialized, $value, $dump);
|
||||
|
||||
@rename($tmpFile, $this->file);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the cache file.
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if (!file_exists($this->file)) {
|
||||
$this->keys = $this->values = [];
|
||||
|
||||
return;
|
||||
}
|
||||
$values = (include $this->file) ?: [[], []];
|
||||
|
||||
if (2 !== \count($values) || !isset($values[0], $values[1])) {
|
||||
$this->keys = $this->values = [];
|
||||
} else {
|
||||
list($this->keys, $this->values) = $values;
|
||||
}
|
||||
}
|
||||
|
||||
private function generateItems(array $keys): \Generator
|
||||
{
|
||||
$f = $this->createCacheItem;
|
||||
|
@ -12,12 +12,29 @@
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\Traits\PhpFilesTrait;
|
||||
use Symfony\Component\Cache\Traits\FilesystemCommonTrait;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
|
||||
/**
|
||||
* @author Piotr Stankowski <git@trakos.pl>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author Rob Frawley 2nd <rmf@src.run>
|
||||
*/
|
||||
class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
|
||||
{
|
||||
use PhpFilesTrait;
|
||||
use FilesystemCommonTrait {
|
||||
doClear as private doCommonClear;
|
||||
doDelete as private doCommonDelete;
|
||||
}
|
||||
|
||||
private $includeHandler;
|
||||
private $appendOnly;
|
||||
private $values = [];
|
||||
private $files = [];
|
||||
|
||||
private static $startTime;
|
||||
|
||||
/**
|
||||
* @param $appendOnly Set to `true` to gain extra performance when the items stored in this pool never expire.
|
||||
@ -35,4 +52,209 @@ class PhpFilesAdapter extends AbstractAdapter implements PruneableInterface
|
||||
throw new \ErrorException($msg, 0, $type, $file, $line);
|
||||
};
|
||||
}
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
|
||||
|
||||
return \function_exists('opcache_invalidate') && ('cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)) && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$time = time();
|
||||
$pruned = true;
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||
try {
|
||||
list($expiresAt) = include $file;
|
||||
} catch (\ErrorException $e) {
|
||||
$expiresAt = $time;
|
||||
}
|
||||
|
||||
if ($time >= $expiresAt) {
|
||||
$pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return $pruned;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
if ($this->appendOnly) {
|
||||
$now = 0;
|
||||
$missingIds = [];
|
||||
} else {
|
||||
$now = time();
|
||||
$missingIds = $ids;
|
||||
$ids = [];
|
||||
}
|
||||
$values = [];
|
||||
|
||||
begin:
|
||||
foreach ($ids as $id) {
|
||||
if (null === $value = $this->values[$id] ?? null) {
|
||||
$missingIds[] = $id;
|
||||
} elseif ('N;' === $value) {
|
||||
$values[$id] = null;
|
||||
} elseif ($value instanceof \Closure) {
|
||||
$values[$id] = $value();
|
||||
} else {
|
||||
$values[$id] = $value;
|
||||
}
|
||||
if (!$this->appendOnly) {
|
||||
unset($this->values[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$missingIds) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
foreach ($missingIds as $k => $id) {
|
||||
try {
|
||||
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
|
||||
list($expiresAt, $this->values[$id]) = include $file;
|
||||
if ($now >= $expiresAt) {
|
||||
unset($this->values[$id], $missingIds[$k]);
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
unset($missingIds[$k]);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$ids = $missingIds;
|
||||
$missingIds = [];
|
||||
goto begin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
if ($this->appendOnly && isset($this->values[$id])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
|
||||
list($expiresAt, $value) = include $file;
|
||||
} catch (\ErrorException $e) {
|
||||
return false;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
if ($this->appendOnly) {
|
||||
$now = 0;
|
||||
$this->values[$id] = $value;
|
||||
} else {
|
||||
$now = time();
|
||||
}
|
||||
|
||||
return $now < $expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
$ok = true;
|
||||
$expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
|
||||
$allowCompile = self::isSupported();
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
unset($this->values[$key]);
|
||||
$isStaticValue = true;
|
||||
if (null === $value) {
|
||||
$value = "'N;'";
|
||||
} elseif (\is_object($value) || \is_array($value)) {
|
||||
try {
|
||||
$value = VarExporter::export($value, $isStaticValue);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
|
||||
}
|
||||
} elseif (\is_string($value)) {
|
||||
// Wrap "N;" in a closure to not confuse it with an encoded `null`
|
||||
if ('N;' === $value) {
|
||||
$isStaticValue = false;
|
||||
}
|
||||
$value = var_export($value, true);
|
||||
} elseif (!\is_scalar($value)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
|
||||
} else {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
|
||||
if (!$isStaticValue) {
|
||||
$value = str_replace("\n", "\n ", $value);
|
||||
$value = "static function () {\n\n return {$value};\n\n}";
|
||||
}
|
||||
|
||||
$file = $this->files[$key] = $this->getFile($key, true);
|
||||
// Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
|
||||
$ok = $this->write($file, "<?php return [{$expiry}, {$value}];\n", self::$startTime - 10) && $ok;
|
||||
|
||||
if ($allowCompile) {
|
||||
@opcache_invalidate($file, true);
|
||||
@opcache_compile_file($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ok && !is_writable($this->directory)) {
|
||||
throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
$this->values = [];
|
||||
|
||||
return $this->doCommonClear($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
unset($this->values[$id]);
|
||||
}
|
||||
|
||||
return $this->doCommonDelete($ids);
|
||||
}
|
||||
|
||||
protected function doUnlink($file)
|
||||
{
|
||||
if (self::isSupported()) {
|
||||
@opcache_invalidate($file, true);
|
||||
}
|
||||
|
||||
return @unlink($file);
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +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;
|
||||
|
||||
@trigger_error(sprintf('The "%s" class is @deprecated since Symfony 4.3, use "Psr16Adapter" instead.', SimpleCacheAdapter::class), E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 4.3, use Psr16Adapter instead.
|
||||
*/
|
||||
class SimpleCacheAdapter extends Psr16Adapter
|
||||
{
|
||||
}
|
@ -1,6 +1,14 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* removed all PSR-16 implementations in the `Simple` namespace
|
||||
* removed `SimpleCacheAdapter`
|
||||
* removed `AbstractAdapter::unserialize()`
|
||||
* removed `CacheItem::getPreviousTags()`
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
|
@ -140,20 +140,6 @@ final class CacheItem implements ItemInterface
|
||||
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] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a cache key according to PSR-6.
|
||||
*
|
||||
|
@ -51,7 +51,6 @@ final class LockRegistry
|
||||
__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',
|
||||
|
@ -1,186 +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\Simple;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
|
||||
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||
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.
|
||||
*/
|
||||
abstract class AbstractCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
|
||||
{
|
||||
use AbstractTrait {
|
||||
deleteItems as private;
|
||||
AbstractTrait::deleteItem as delete;
|
||||
AbstractTrait::hasItem as has;
|
||||
}
|
||||
|
||||
private $defaultLifetime;
|
||||
|
||||
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
|
||||
{
|
||||
$this->defaultLifetime = max(0, $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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
$id = $this->getId($key);
|
||||
|
||||
try {
|
||||
foreach ($this->doFetch([$id]) as $value) {
|
||||
return $value;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
CacheItem::validateKey($key);
|
||||
|
||||
return $this->setMultiple([$key => $value], $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)));
|
||||
}
|
||||
$ids = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$ids[] = $this->getId($key);
|
||||
}
|
||||
try {
|
||||
$values = $this->doFetch($ids);
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => $keys, 'exception' => $e]);
|
||||
$values = [];
|
||||
}
|
||||
$ids = array_combine($ids, $keys);
|
||||
|
||||
return $this->generateValues($values, $ids, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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)));
|
||||
}
|
||||
$valuesById = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if (\is_int($key)) {
|
||||
$key = (string) $key;
|
||||
}
|
||||
$valuesById[$this->getId($key)] = $value;
|
||||
}
|
||||
if (false === $ttl = $this->normalizeTtl($ttl)) {
|
||||
return $this->doDelete(array_keys($valuesById));
|
||||
}
|
||||
|
||||
try {
|
||||
$e = $this->doSave($valuesById, $ttl);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
if (true === $e || [] === $e) {
|
||||
return true;
|
||||
}
|
||||
$keys = [];
|
||||
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]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)));
|
||||
}
|
||||
|
||||
return $this->deleteItems($keys);
|
||||
}
|
||||
|
||||
private function normalizeTtl($ttl)
|
||||
{
|
||||
if (null === $ttl) {
|
||||
return $this->defaultLifetime;
|
||||
}
|
||||
if ($ttl instanceof \DateInterval) {
|
||||
$ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
|
||||
}
|
||||
if (\is_int($ttl)) {
|
||||
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)));
|
||||
}
|
||||
|
||||
private function generateValues($values, &$keys, $default)
|
||||
{
|
||||
try {
|
||||
foreach ($values as $id => $value) {
|
||||
if (!isset($keys[$id])) {
|
||||
$id = key($keys);
|
||||
}
|
||||
$key = $keys[$id];
|
||||
unset($keys[$id]);
|
||||
yield $key => $value;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to fetch values: '.$e->getMessage(), ['keys' => array_values($keys), 'exception' => $e]);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
yield $key => $default;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Traits\ApcuTrait;
|
||||
|
||||
@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)
|
||||
{
|
||||
$this->init($namespace, $defaultLifetime, $version);
|
||||
}
|
||||
}
|
@ -1,160 +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\Simple;
|
||||
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
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.
|
||||
*/
|
||||
class ArrayCache implements Psr16CacheInterface, LoggerAwareInterface, ResettableInterface
|
||||
{
|
||||
use ArrayTrait {
|
||||
ArrayTrait::deleteItem as delete;
|
||||
ArrayTrait::hasItem as has;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$this->defaultLifetime = $defaultLifetime;
|
||||
$this->storeSerialized = $storeSerialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
if (!\is_string($key) || !isset($this->expiries[$key])) {
|
||||
CacheItem::validateKey($key);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)));
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
if (!\is_string($key) || !isset($this->expiries[$key])) {
|
||||
CacheItem::validateKey($key);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->generateItems($keys, microtime(true), function ($k, $v, $hit) use ($default) { return $hit ? $v : $default; });
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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)));
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
$this->delete($key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
CacheItem::validateKey($key);
|
||||
}
|
||||
|
||||
return $this->setMultiple([$key => $value], $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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)));
|
||||
}
|
||||
$valuesArray = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if (!\is_int($key) && !(\is_string($key) && isset($this->expiries[$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;
|
||||
|
||||
foreach ($valuesArray as $key => $value) {
|
||||
if ($this->storeSerialized && null === $value = $this->freeze($value, $key)) {
|
||||
return false;
|
||||
}
|
||||
$this->values[$key] = $value;
|
||||
$this->expiries[$key] = $expiry;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function normalizeTtl($ttl)
|
||||
{
|
||||
if (null === $ttl) {
|
||||
return $this->defaultLifetime;
|
||||
}
|
||||
if ($ttl instanceof \DateInterval) {
|
||||
$ttl = (int) \DateTime::createFromFormat('U', 0)->add($ttl)->format('U');
|
||||
}
|
||||
if (\is_int($ttl)) {
|
||||
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)));
|
||||
}
|
||||
}
|
@ -1,257 +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\Simple;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
|
||||
use Symfony\Component\Cache\Adapter\ChainAdapter;
|
||||
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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
class ChainCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
|
||||
{
|
||||
private $miss;
|
||||
private $caches = [];
|
||||
private $defaultLifetime;
|
||||
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
|
||||
*/
|
||||
public function __construct(array $caches, int $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));
|
||||
}
|
||||
}
|
||||
|
||||
$this->miss = new \stdClass();
|
||||
$this->caches = array_values($caches);
|
||||
$this->cacheCount = \count($this->caches);
|
||||
$this->defaultLifetime = 0 < $defaultLifetime ? $defaultLifetime : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
$miss = null !== $default && \is_object($default) ? $default : $this->miss;
|
||||
|
||||
foreach ($this->caches as $i => $cache) {
|
||||
$value = $cache->get($key, $miss);
|
||||
|
||||
if ($miss !== $value) {
|
||||
while (0 <= --$i) {
|
||||
$this->caches[$i]->set($key, $value, $this->defaultLifetime);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple($keys, $default = null)
|
||||
{
|
||||
$miss = null !== $default && \is_object($default) ? $default : $this->miss;
|
||||
|
||||
return $this->generateItems($this->caches[0]->getMultiple($keys, $miss), 0, $miss, $default);
|
||||
}
|
||||
|
||||
private function generateItems($values, $cacheIndex, $miss, $default)
|
||||
{
|
||||
$missing = [];
|
||||
$nextCacheIndex = $cacheIndex + 1;
|
||||
$nextCache = isset($this->caches[$nextCacheIndex]) ? $this->caches[$nextCacheIndex] : null;
|
||||
|
||||
foreach ($values as $k => $value) {
|
||||
if ($miss !== $value) {
|
||||
yield $k => $value;
|
||||
} elseif (!$nextCache) {
|
||||
yield $k => $default;
|
||||
} else {
|
||||
$missing[] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing) {
|
||||
$cache = $this->caches[$cacheIndex];
|
||||
$values = $this->generateItems($nextCache->getMultiple($missing, $miss), $nextCacheIndex, $miss, $default);
|
||||
|
||||
foreach ($values as $k => $value) {
|
||||
if ($miss !== $value) {
|
||||
$cache->set($k, $value, $this->defaultLifetime);
|
||||
yield $k => $value;
|
||||
} else {
|
||||
yield $k => $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
foreach ($this->caches as $cache) {
|
||||
if ($cache->has($key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$cleared = true;
|
||||
$i = $this->cacheCount;
|
||||
|
||||
while ($i--) {
|
||||
$cleared = $this->caches[$i]->clear() && $cleared;
|
||||
}
|
||||
|
||||
return $cleared;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
$deleted = true;
|
||||
$i = $this->cacheCount;
|
||||
|
||||
while ($i--) {
|
||||
$deleted = $this->caches[$i]->delete($key) && $deleted;
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteMultiple($keys)
|
||||
{
|
||||
if ($keys instanceof \Traversable) {
|
||||
$keys = iterator_to_array($keys, false);
|
||||
}
|
||||
$deleted = true;
|
||||
$i = $this->cacheCount;
|
||||
|
||||
while ($i--) {
|
||||
$deleted = $this->caches[$i]->deleteMultiple($keys) && $deleted;
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
$saved = true;
|
||||
$i = $this->cacheCount;
|
||||
|
||||
while ($i--) {
|
||||
$saved = $this->caches[$i]->set($key, $value, $ttl) && $saved;
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
if ($values instanceof \Traversable) {
|
||||
$valuesIterator = $values;
|
||||
$values = function () use ($valuesIterator, &$values) {
|
||||
$generatedValues = [];
|
||||
|
||||
foreach ($valuesIterator as $key => $value) {
|
||||
yield $key => $value;
|
||||
$generatedValues[$key] = $value;
|
||||
}
|
||||
|
||||
$values = $generatedValues;
|
||||
};
|
||||
$values = $values();
|
||||
}
|
||||
$saved = true;
|
||||
$i = $this->cacheCount;
|
||||
|
||||
while ($i--) {
|
||||
$saved = $this->caches[$i]->setMultiple($values, $ttl) && $saved;
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$pruned = true;
|
||||
|
||||
foreach ($this->caches as $cache) {
|
||||
if ($cache instanceof PruneableInterface) {
|
||||
$pruned = $cache->prune() && $pruned;
|
||||
}
|
||||
}
|
||||
|
||||
return $pruned;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
foreach ($this->caches as $cache) {
|
||||
if ($cache instanceof ResetInterface) {
|
||||
$cache->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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\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)
|
||||
{
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->provider = $provider;
|
||||
$provider->setNamespace($namespace);
|
||||
}
|
||||
}
|
@ -1,36 +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\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)
|
||||
{
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->init($namespace, $directory);
|
||||
}
|
||||
}
|
@ -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\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)
|
||||
{
|
||||
$this->init($client, $namespace, $defaultLifetime, $marshaller);
|
||||
}
|
||||
}
|
@ -1,90 +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\Simple;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
|
||||
use Symfony\Components\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);
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 4.3, use NullAdapter and type-hint for CacheInterface instead.
|
||||
*/
|
||||
class NullCache implements Psr16CacheInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple($keys, $default = null)
|
||||
{
|
||||
foreach ($keys as $key) {
|
||||
yield $key => $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteMultiple($keys)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,59 +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\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;
|
||||
|
||||
protected $maxIdLength = 255;
|
||||
|
||||
/**
|
||||
* You can either pass an existing database connection as PDO instance or
|
||||
* 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]
|
||||
* * db_data_col: The column where to store the cache data [default: item_data]
|
||||
* * db_lifetime_col: The column where to store the lifetime [default: item_lifetime]
|
||||
* * db_time_col: The column where to store the timestamp [default: item_time]
|
||||
* * db_username: The username when lazy-connect [default: '']
|
||||
* * 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
|
||||
*
|
||||
* @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)
|
||||
{
|
||||
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
|
||||
}
|
||||
}
|
@ -1,248 +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\Simple;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
|
||||
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
|
||||
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.
|
||||
*/
|
||||
class PhpArrayCache implements Psr16CacheInterface, 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
|
||||
*/
|
||||
public function __construct(string $file, Psr16CacheInterface $fallbackPool)
|
||||
{
|
||||
$this->file = $file;
|
||||
$this->pool = $fallbackPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* This adapter takes advantage of how PHP stores arrays in its latest versions.
|
||||
*
|
||||
* @param string $file The PHP file were values are cached
|
||||
*
|
||||
* @return Psr16CacheInterface
|
||||
*/
|
||||
public static function create($file, Psr16CacheInterface $fallbackPool)
|
||||
{
|
||||
// Shared memory is available in PHP 7.0+ with OPCache enabled
|
||||
if (filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
|
||||
return new static($file, $fallbackPool);
|
||||
}
|
||||
|
||||
return $fallbackPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
if (!isset($this->keys[$key])) {
|
||||
return $this->pool->get($key, $default);
|
||||
}
|
||||
$value = $this->values[$this->keys[$key]];
|
||||
|
||||
if ('N;' === $value) {
|
||||
return null;
|
||||
}
|
||||
if ($value instanceof \Closure) {
|
||||
try {
|
||||
return $value();
|
||||
} catch (\Throwable $e) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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)));
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
|
||||
}
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return $this->generateItems($keys, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return isset($this->keys[$key]) || $this->pool->has($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return !isset($this->keys[$key]) && $this->pool->delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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)));
|
||||
}
|
||||
|
||||
$deleted = true;
|
||||
$fallbackKeys = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
|
||||
}
|
||||
|
||||
if (isset($this->keys[$key])) {
|
||||
$deleted = false;
|
||||
} else {
|
||||
$fallbackKeys[] = $key;
|
||||
}
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if ($fallbackKeys) {
|
||||
$deleted = $this->pool->deleteMultiple($fallbackKeys) && $deleted;
|
||||
}
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
if (!\is_string($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
|
||||
}
|
||||
if (null === $this->values) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return !isset($this->keys[$key]) && $this->pool->set($key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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)));
|
||||
}
|
||||
|
||||
$saved = true;
|
||||
$fallbackValues = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if (!\is_string($key) && !\is_int($key)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given.', \is_object($key) ? \get_class($key) : \gettype($key)));
|
||||
}
|
||||
|
||||
if (isset($this->keys[$key])) {
|
||||
$saved = false;
|
||||
} else {
|
||||
$fallbackValues[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fallbackValues) {
|
||||
$saved = $this->pool->setMultiple($fallbackValues, $ttl) && $saved;
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
private function generateItems(array $keys, $default)
|
||||
{
|
||||
$fallbackKeys = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if (isset($this->keys[$key])) {
|
||||
$value = $this->values[$this->keys[$key]];
|
||||
|
||||
if ('N;' === $value) {
|
||||
yield $key => null;
|
||||
} elseif ($value instanceof \Closure) {
|
||||
try {
|
||||
yield $key => $value();
|
||||
} catch (\Throwable $e) {
|
||||
yield $key => $default;
|
||||
}
|
||||
} else {
|
||||
yield $key => $value;
|
||||
}
|
||||
} else {
|
||||
$fallbackKeys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($fallbackKeys) {
|
||||
yield from $this->pool->getMultiple($fallbackKeys, $default);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +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\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.
|
||||
*
|
||||
* @throws CacheException if OPcache is not enabled
|
||||
*/
|
||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, bool $appendOnly = false)
|
||||
{
|
||||
$this->appendOnly = $appendOnly;
|
||||
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
|
||||
parent::__construct('', $defaultLifetime);
|
||||
$this->init($namespace, $directory);
|
||||
$this->includeHandler = static function ($type, $msg, $file, $line) {
|
||||
throw new \ErrorException($msg, 0, $type, $file, $line);
|
||||
};
|
||||
}
|
||||
}
|
@ -1,23 +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\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);
|
||||
|
||||
/**
|
||||
* @deprecated since Symfony 4.3, use Psr16Cache instead.
|
||||
*/
|
||||
class Psr6Cache extends Psr16Cache
|
||||
{
|
||||
}
|
@ -1,37 +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\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\Client $redisClient
|
||||
* @param string $namespace
|
||||
* @param int $defaultLifetime
|
||||
*/
|
||||
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||
{
|
||||
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
|
||||
}
|
||||
}
|
@ -1,243 +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\Simple;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface as Psr16CacheInterface;
|
||||
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.
|
||||
*/
|
||||
class TraceableCache implements Psr16CacheInterface, PruneableInterface, ResettableInterface
|
||||
{
|
||||
private $pool;
|
||||
private $miss;
|
||||
private $calls = [];
|
||||
|
||||
public function __construct(Psr16CacheInterface $pool)
|
||||
{
|
||||
$this->pool = $pool;
|
||||
$this->miss = new \stdClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
$miss = null !== $default && \is_object($default) ? $default : $this->miss;
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
$value = $this->pool->get($key, $miss);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
if ($event->result[$key] = $miss !== $value) {
|
||||
++$event->hits;
|
||||
} else {
|
||||
++$event->misses;
|
||||
$value = $default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$key] = $this->pool->has($key);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$key] = $this->pool->delete($key);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result[$key] = $this->pool->set($key, $value, $ttl);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
$event->result['keys'] = [];
|
||||
|
||||
if ($values instanceof \Traversable) {
|
||||
$values = function () use ($values, $event) {
|
||||
foreach ($values as $k => $v) {
|
||||
$event->result['keys'][] = $k;
|
||||
yield $k => $v;
|
||||
}
|
||||
};
|
||||
$values = $values();
|
||||
} elseif (\is_array($values)) {
|
||||
$event->result['keys'] = array_keys($values);
|
||||
}
|
||||
|
||||
try {
|
||||
return $event->result['result'] = $this->pool->setMultiple($values, $ttl);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMultiple($keys, $default = null)
|
||||
{
|
||||
$miss = null !== $default && \is_object($default) ? $default : $this->miss;
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
$result = $this->pool->getMultiple($keys, $miss);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
$f = function () use ($result, $event, $miss, $default) {
|
||||
$event->result = [];
|
||||
foreach ($result as $key => $value) {
|
||||
if ($event->result[$key] = $miss !== $value) {
|
||||
++$event->hits;
|
||||
} else {
|
||||
++$event->misses;
|
||||
$value = $default;
|
||||
}
|
||||
yield $key => $value;
|
||||
}
|
||||
};
|
||||
|
||||
return $f();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result = $this->pool->clear();
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteMultiple($keys)
|
||||
{
|
||||
$event = $this->start(__FUNCTION__);
|
||||
if ($keys instanceof \Traversable) {
|
||||
$keys = $event->result['keys'] = iterator_to_array($keys, false);
|
||||
} else {
|
||||
$event->result['keys'] = $keys;
|
||||
}
|
||||
try {
|
||||
return $event->result['result'] = $this->pool->deleteMultiple($keys);
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
if (!$this->pool instanceof PruneableInterface) {
|
||||
return false;
|
||||
}
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
return $event->result = $this->pool->prune();
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
if (!$this->pool instanceof ResetInterface) {
|
||||
return;
|
||||
}
|
||||
$event = $this->start(__FUNCTION__);
|
||||
try {
|
||||
$this->pool->reset();
|
||||
} finally {
|
||||
$event->end = microtime(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCalls()
|
||||
{
|
||||
try {
|
||||
return $this->calls;
|
||||
} finally {
|
||||
$this->calls = [];
|
||||
}
|
||||
}
|
||||
|
||||
private function start($name)
|
||||
{
|
||||
$this->calls[] = $event = new TraceableCacheEvent();
|
||||
$event->name = $name;
|
||||
$event->start = microtime(true);
|
||||
|
||||
return $event;
|
||||
}
|
||||
}
|
||||
|
||||
class TraceableCacheEvent
|
||||
{
|
||||
public $name;
|
||||
public $start;
|
||||
public $end;
|
||||
public $result;
|
||||
public $hits = 0;
|
||||
public $misses = 0;
|
||||
}
|
@ -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 Symfony\Component\Cache\Adapter\SimpleCacheAdapter;
|
||||
use Symfony\Component\Cache\Simple\FilesystemCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class SimpleCacheAdapterTest extends AdapterTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testPrune' => 'SimpleCache just proxies',
|
||||
];
|
||||
|
||||
public function createCachePool($defaultLifetime = 0)
|
||||
{
|
||||
return new SimpleCacheAdapter(new FilesystemCache(), '', $defaultLifetime);
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\RedisCache;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
abstract class AbstractRedisCacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testSetTtl' => 'Testing expiration slows down the test suite',
|
||||
'testSetMultipleTtl' => 'Testing expiration slows down the test suite',
|
||||
'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
|
||||
];
|
||||
|
||||
protected static $redis;
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new RedisCache(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
self::$redis = null;
|
||||
}
|
||||
}
|
@ -1,38 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\ApcuCache;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class ApcuCacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testSetTtl' => 'Testing expiration slows down the test suite',
|
||||
'testSetMultipleTtl' => 'Testing expiration slows down the test suite',
|
||||
'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
|
||||
];
|
||||
|
||||
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))) {
|
||||
$this->markTestSkipped('APCu extension is required.');
|
||||
}
|
||||
if ('\\' === \DIRECTORY_SEPARATOR) {
|
||||
$this->markTestSkipped('Fails transiently on Windows.');
|
||||
}
|
||||
|
||||
return new ApcuCache(str_replace('\\', '.', __CLASS__), $defaultLifetime);
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\ArrayCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class ArrayCacheTest extends CacheTestCase
|
||||
{
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new ArrayCache($defaultLifetime);
|
||||
}
|
||||
}
|
@ -1,141 +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\Simple;
|
||||
|
||||
use Cache\IntegrationTests\SimpleCacheTest;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
|
||||
abstract class CacheTestCase extends SimpleCacheTest
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (!\array_key_exists('testPrune', $this->skippedTests) && !$this->createSimpleCache() instanceof PruneableInterface) {
|
||||
$this->skippedTests['testPrune'] = 'Not a pruneable cache pool.';
|
||||
}
|
||||
}
|
||||
|
||||
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__]);
|
||||
}
|
||||
|
||||
if (!method_exists($this, 'isPruned')) {
|
||||
$this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.');
|
||||
}
|
||||
|
||||
/** @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();
|
||||
}
|
||||
}
|
||||
|
||||
class NotUnserializable
|
||||
{
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \Exception(__CLASS__);
|
||||
}
|
||||
}
|
@ -1,117 +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\Simple;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Component\Cache\PruneableInterface;
|
||||
use Symfony\Component\Cache\Simple\ArrayCache;
|
||||
use Symfony\Component\Cache\Simple\ChainCache;
|
||||
use Symfony\Component\Cache\Simple\FilesystemCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class ChainCacheTest extends CacheTestCase
|
||||
{
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new ChainCache([new ArrayCache($defaultLifetime), new FilesystemCache('', $defaultLifetime)], $defaultLifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage At least one cache must be specified.
|
||||
*/
|
||||
public function testEmptyCachesException()
|
||||
{
|
||||
new ChainCache([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage The class "stdClass" does not implement
|
||||
*/
|
||||
public function testInvalidCacheException()
|
||||
{
|
||||
new ChainCache([new \stdClass()]);
|
||||
}
|
||||
|
||||
public function testPrune()
|
||||
{
|
||||
if (isset($this->skippedTests[__FUNCTION__])) {
|
||||
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
|
||||
}
|
||||
|
||||
$cache = new ChainCache([
|
||||
$this->getPruneableMock(),
|
||||
$this->getNonPruneableMock(),
|
||||
$this->getPruneableMock(),
|
||||
]);
|
||||
$this->assertTrue($cache->prune());
|
||||
|
||||
$cache = new ChainCache([
|
||||
$this->getPruneableMock(),
|
||||
$this->getFailingPruneableMock(),
|
||||
$this->getPruneableMock(),
|
||||
]);
|
||||
$this->assertFalse($cache->prune());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface
|
||||
*/
|
||||
private function getPruneableMock()
|
||||
{
|
||||
$pruneable = $this
|
||||
->getMockBuilder(PruneableCacheInterface::class)
|
||||
->getMock();
|
||||
|
||||
$pruneable
|
||||
->expects($this->atLeastOnce())
|
||||
->method('prune')
|
||||
->willReturn(true);
|
||||
|
||||
return $pruneable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface
|
||||
*/
|
||||
private function getFailingPruneableMock()
|
||||
{
|
||||
$pruneable = $this
|
||||
->getMockBuilder(PruneableCacheInterface::class)
|
||||
->getMock();
|
||||
|
||||
$pruneable
|
||||
->expects($this->atLeastOnce())
|
||||
->method('prune')
|
||||
->willReturn(false);
|
||||
|
||||
return $pruneable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \PHPUnit_Framework_MockObject_MockObject|CacheInterface
|
||||
*/
|
||||
private function getNonPruneableMock()
|
||||
{
|
||||
return $this
|
||||
->getMockBuilder(CacheInterface::class)
|
||||
->getMock();
|
||||
}
|
||||
}
|
||||
|
||||
interface PruneableCacheInterface extends PruneableInterface, CacheInterface
|
||||
{
|
||||
}
|
@ -1,32 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\DoctrineCache;
|
||||
use Symfony\Component\Cache\Tests\Fixtures\ArrayCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class DoctrineCacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testObjectDoesNotChangeInCache' => 'ArrayCache does not use serialize/unserialize',
|
||||
'testNotUnserializable' => 'ArrayCache does not use serialize/unserialize',
|
||||
];
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new DoctrineCache(new ArrayCache($defaultLifetime), '', $defaultLifetime);
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Component\Cache\Simple\FilesystemCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class FilesystemCacheTest extends CacheTestCase
|
||||
{
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new FilesystemCache('', $defaultLifetime);
|
||||
}
|
||||
|
||||
protected function isPruned(CacheInterface $cache, $name)
|
||||
{
|
||||
$getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
|
||||
$getFileMethod->setAccessible(true);
|
||||
|
||||
return !file_exists($getFileMethod->invoke($cache, $name));
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||
use Symfony\Component\Cache\Simple\MemcachedCache;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class MemcachedCacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testSetTtl' => 'Testing expiration slows down the test suite',
|
||||
'testSetMultipleTtl' => 'Testing expiration slows down the test suite',
|
||||
'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
|
||||
];
|
||||
|
||||
protected static $client;
|
||||
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
if (!MemcachedCache::isSupported()) {
|
||||
self::markTestSkipped('Extension memcached >=2.2.0 required.');
|
||||
}
|
||||
self::$client = AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'));
|
||||
self::$client->get('foo');
|
||||
$code = self::$client->getResultCode();
|
||||
|
||||
if (\Memcached::RES_SUCCESS !== $code && \Memcached::RES_NOTFOUND !== $code) {
|
||||
self::markTestSkipped('Memcached error: '.strtolower(self::$client->getResultMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
$client = $defaultLifetime ? AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['binary_protocol' => false]) : self::$client;
|
||||
|
||||
return new MemcachedCache($client, str_replace('\\', '.', __CLASS__), $defaultLifetime);
|
||||
}
|
||||
|
||||
public function testCreatePersistentConnectionShouldNotDupServerList()
|
||||
{
|
||||
$instance = MemcachedCache::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['persistent_id' => 'persistent']);
|
||||
$this->assertCount(1, $instance->getServerList());
|
||||
|
||||
$instance = MemcachedCache::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['persistent_id' => 'persistent']);
|
||||
$this->assertCount(1, $instance->getServerList());
|
||||
}
|
||||
|
||||
public function testOptions()
|
||||
{
|
||||
$client = MemcachedCache::createConnection([], [
|
||||
'libketama_compatible' => false,
|
||||
'distribution' => 'modula',
|
||||
'compression' => true,
|
||||
'serializer' => 'php',
|
||||
'hash' => 'md5',
|
||||
]);
|
||||
|
||||
$this->assertSame(\Memcached::SERIALIZER_PHP, $client->getOption(\Memcached::OPT_SERIALIZER));
|
||||
$this->assertSame(\Memcached::HASH_MD5, $client->getOption(\Memcached::OPT_HASH));
|
||||
$this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
|
||||
$this->assertSame(0, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
|
||||
$this->assertSame(\Memcached::DISTRIBUTION_MODULA, $client->getOption(\Memcached::OPT_DISTRIBUTION));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideBadOptions
|
||||
* @expectedException \ErrorException
|
||||
* @expectedExceptionMessage constant(): Couldn't find constant Memcached::
|
||||
*/
|
||||
public function testBadOptions($name, $value)
|
||||
{
|
||||
MemcachedCache::createConnection([], [$name => $value]);
|
||||
}
|
||||
|
||||
public function provideBadOptions()
|
||||
{
|
||||
return [
|
||||
['foo', 'bar'],
|
||||
['hash', 'zyx'],
|
||||
['serializer', 'zyx'],
|
||||
['distribution', 'zyx'],
|
||||
];
|
||||
}
|
||||
|
||||
public function testDefaultOptions()
|
||||
{
|
||||
$this->assertTrue(MemcachedCache::isSupported());
|
||||
|
||||
$client = MemcachedCache::createConnection([]);
|
||||
|
||||
$this->assertTrue($client->getOption(\Memcached::OPT_COMPRESSION));
|
||||
$this->assertSame(1, $client->getOption(\Memcached::OPT_BINARY_PROTOCOL));
|
||||
$this->assertSame(1, $client->getOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Cache\Exception\CacheException
|
||||
* @expectedExceptionMessage MemcachedAdapter: "serializer" option must be "php" or "igbinary".
|
||||
*/
|
||||
public function testOptionSerializer()
|
||||
{
|
||||
if (!\Memcached::HAVE_JSON) {
|
||||
$this->markTestSkipped('Memcached::HAVE_JSON required');
|
||||
}
|
||||
|
||||
new MemcachedCache(MemcachedCache::createConnection([], ['serializer' => 'json']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideServersSetting
|
||||
*/
|
||||
public function testServersSetting($dsn, $host, $port)
|
||||
{
|
||||
$client1 = MemcachedCache::createConnection($dsn);
|
||||
$client2 = MemcachedCache::createConnection([$dsn]);
|
||||
$client3 = MemcachedCache::createConnection([[$host, $port]]);
|
||||
$expect = [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
];
|
||||
|
||||
$f = function ($s) { return ['host' => $s['host'], 'port' => $s['port']]; };
|
||||
$this->assertSame([$expect], array_map($f, $client1->getServerList()));
|
||||
$this->assertSame([$expect], array_map($f, $client2->getServerList()));
|
||||
$this->assertSame([$expect], array_map($f, $client3->getServerList()));
|
||||
}
|
||||
|
||||
public function provideServersSetting()
|
||||
{
|
||||
yield [
|
||||
'memcached://127.0.0.1/50',
|
||||
'127.0.0.1',
|
||||
11211,
|
||||
];
|
||||
yield [
|
||||
'memcached://localhost:11222?weight=25',
|
||||
'localhost',
|
||||
11222,
|
||||
];
|
||||
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',
|
||||
11211,
|
||||
];
|
||||
}
|
||||
yield [
|
||||
'memcached:///var/run/memcached.sock?weight=25',
|
||||
'/var/run/memcached.sock',
|
||||
0,
|
||||
];
|
||||
yield [
|
||||
'memcached:///var/local/run/memcached.socket?weight=25',
|
||||
'/var/local/run/memcached.socket',
|
||||
0,
|
||||
];
|
||||
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',
|
||||
0,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||
use Symfony\Component\Cache\Simple\MemcachedCache;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class MemcachedCacheTextModeTest extends MemcachedCacheTest
|
||||
{
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
$client = AbstractAdapter::createConnection('memcached://'.getenv('MEMCACHED_HOST'), ['binary_protocol' => false]);
|
||||
|
||||
return new MemcachedCache($client, str_replace('\\', '.', __CLASS__), $defaultLifetime);
|
||||
}
|
||||
}
|
@ -1,97 +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\Simple;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Cache\Simple\NullCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class NullCacheTest extends TestCase
|
||||
{
|
||||
public function createCachePool()
|
||||
{
|
||||
return new NullCache();
|
||||
}
|
||||
|
||||
public function testGetItem()
|
||||
{
|
||||
$cache = $this->createCachePool();
|
||||
|
||||
$this->assertNull($cache->get('key'));
|
||||
}
|
||||
|
||||
public function testHas()
|
||||
{
|
||||
$this->assertFalse($this->createCachePool()->has('key'));
|
||||
}
|
||||
|
||||
public function testGetMultiple()
|
||||
{
|
||||
$cache = $this->createCachePool();
|
||||
|
||||
$keys = ['foo', 'bar', 'baz', 'biz'];
|
||||
|
||||
$default = new \stdClass();
|
||||
$items = $cache->getMultiple($keys, $default);
|
||||
$count = 0;
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
$this->assertContains($key, $keys, 'Cache key can not change.');
|
||||
$this->assertSame($default, $item);
|
||||
|
||||
// Remove $key for $keys
|
||||
foreach ($keys as $k => $v) {
|
||||
if ($v === $key) {
|
||||
unset($keys[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
++$count;
|
||||
}
|
||||
|
||||
$this->assertSame(4, $count);
|
||||
}
|
||||
|
||||
public function testClear()
|
||||
{
|
||||
$this->assertTrue($this->createCachePool()->clear());
|
||||
}
|
||||
|
||||
public function testDelete()
|
||||
{
|
||||
$this->assertTrue($this->createCachePool()->delete('key'));
|
||||
}
|
||||
|
||||
public function testDeleteMultiple()
|
||||
{
|
||||
$this->assertTrue($this->createCachePool()->deleteMultiple(['key', 'foo', 'bar']));
|
||||
}
|
||||
|
||||
public function testSet()
|
||||
{
|
||||
$cache = $this->createCachePool();
|
||||
|
||||
$this->assertFalse($cache->set('key', 'val'));
|
||||
$this->assertNull($cache->get('key'));
|
||||
}
|
||||
|
||||
public function testSetMultiple()
|
||||
{
|
||||
$cache = $this->createCachePool();
|
||||
|
||||
$this->assertFalse($cache->setMultiple(['key' => 'val']));
|
||||
$this->assertNull($cache->get('key'));
|
||||
}
|
||||
}
|
@ -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\Tests\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\PdoCache;
|
||||
use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class PdoCacheTest extends CacheTestCase
|
||||
{
|
||||
use PdoPruneableTrait;
|
||||
|
||||
protected static $dbFile;
|
||||
|
||||
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 PdoCache('sqlite:'.self::$dbFile);
|
||||
$pool->createTable();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
@unlink(self::$dbFile);
|
||||
}
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new PdoCache('sqlite:'.self::$dbFile, 'ns', $defaultLifetime);
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Symfony\Component\Cache\Simple\PdoCache;
|
||||
use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class PdoDbalCacheTest extends CacheTestCase
|
||||
{
|
||||
use PdoPruneableTrait;
|
||||
|
||||
protected static $dbFile;
|
||||
|
||||
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 PdoCache(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]));
|
||||
$pool->createTable();
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
@unlink(self::$dbFile);
|
||||
}
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new PdoCache(DriverManager::getConnection(['driver' => 'pdo_sqlite', 'path' => self::$dbFile]), '', $defaultLifetime);
|
||||
}
|
||||
}
|
@ -1,130 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\NullCache;
|
||||
use Symfony\Component\Cache\Simple\PhpArrayCache;
|
||||
use Symfony\Component\Cache\Tests\Adapter\FilesystemAdapterTest;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class PhpArrayCacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testBasicUsageWithLongKey' => 'PhpArrayCache does no writes',
|
||||
|
||||
'testDelete' => 'PhpArrayCache does no writes',
|
||||
'testDeleteMultiple' => 'PhpArrayCache does no writes',
|
||||
'testDeleteMultipleGenerator' => 'PhpArrayCache does no writes',
|
||||
|
||||
'testSetTtl' => 'PhpArrayCache does no expiration',
|
||||
'testSetMultipleTtl' => 'PhpArrayCache does no expiration',
|
||||
'testSetExpiredTtl' => 'PhpArrayCache does no expiration',
|
||||
'testSetMultipleExpiredTtl' => 'PhpArrayCache does no expiration',
|
||||
|
||||
'testGetInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testGetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testSetInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testDeleteInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testDeleteMultipleInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testSetInvalidTtl' => 'PhpArrayCache does no validation',
|
||||
'testSetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testSetMultipleInvalidTtl' => 'PhpArrayCache does no validation',
|
||||
'testHasInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testSetValidData' => 'PhpArrayCache does no validation',
|
||||
|
||||
'testDefaultLifeTime' => 'PhpArrayCache does not allow configuring a default lifetime.',
|
||||
'testPrune' => 'PhpArrayCache just proxies',
|
||||
];
|
||||
|
||||
protected static $file;
|
||||
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
|
||||
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
|
||||
}
|
||||
}
|
||||
|
||||
public function createSimpleCache()
|
||||
{
|
||||
return new PhpArrayCacheWrapper(self::$file, new NullCache());
|
||||
}
|
||||
|
||||
public function testStore()
|
||||
{
|
||||
$arrayWithRefs = [];
|
||||
$arrayWithRefs[0] = 123;
|
||||
$arrayWithRefs[1] = &$arrayWithRefs[0];
|
||||
|
||||
$object = (object) [
|
||||
'foo' => 'bar',
|
||||
'foo2' => 'bar2',
|
||||
];
|
||||
|
||||
$expected = [
|
||||
'null' => null,
|
||||
'serializedString' => serialize($object),
|
||||
'arrayWithRefs' => $arrayWithRefs,
|
||||
'object' => $object,
|
||||
'arrayWithObject' => ['bar' => $object],
|
||||
];
|
||||
|
||||
$cache = new PhpArrayCache(self::$file, new NullCache());
|
||||
$cache->warmUp($expected);
|
||||
|
||||
foreach ($expected as $key => $value) {
|
||||
$this->assertSame(serialize($value), serialize($cache->get($key)), 'Warm up should create a PHP file that OPCache can load in memory');
|
||||
}
|
||||
}
|
||||
|
||||
public function testStoredFile()
|
||||
{
|
||||
$data = [
|
||||
'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'],
|
||||
],
|
||||
];
|
||||
|
||||
$cache = new PhpArrayCache(self::$file, new NullCache());
|
||||
$cache->warmUp($data);
|
||||
|
||||
$values = eval(substr(file_get_contents(self::$file), 6));
|
||||
|
||||
$this->assertSame($expected, $values, 'Warm up should create a PHP file that OPCache can load in memory');
|
||||
}
|
||||
}
|
@ -1,56 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\FilesystemCache;
|
||||
use Symfony\Component\Cache\Simple\PhpArrayCache;
|
||||
use Symfony\Component\Cache\Tests\Adapter\FilesystemAdapterTest;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class PhpArrayCacheWithFallbackTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testGetInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testGetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testDeleteInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testDeleteMultipleInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
//'testSetValidData' => 'PhpArrayCache does no validation',
|
||||
'testSetInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testSetInvalidTtl' => 'PhpArrayCache does no validation',
|
||||
'testSetMultipleInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testSetMultipleInvalidTtl' => 'PhpArrayCache does no validation',
|
||||
'testHasInvalidKeys' => 'PhpArrayCache does no validation',
|
||||
'testPrune' => 'PhpArrayCache just proxies',
|
||||
];
|
||||
|
||||
protected static $file;
|
||||
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
self::$file = sys_get_temp_dir().'/symfony-cache/php-array-adapter-test.php';
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
if (file_exists(sys_get_temp_dir().'/symfony-cache')) {
|
||||
FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache');
|
||||
}
|
||||
}
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new PhpArrayCache(self::$file, new FilesystemCache('php-array-fallback', $defaultLifetime));
|
||||
}
|
||||
}
|
@ -1,46 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\PhpArrayCache;
|
||||
|
||||
class PhpArrayCacheWrapper extends PhpArrayCache
|
||||
{
|
||||
protected $data = [];
|
||||
|
||||
public function set($key, $value, $ttl = null)
|
||||
{
|
||||
(\Closure::bind(function () use ($key, $value) {
|
||||
$this->data[$key] = $value;
|
||||
$this->warmUp($this->data);
|
||||
list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
|
||||
}, $this, PhpArrayCache::class))();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setMultiple($values, $ttl = null)
|
||||
{
|
||||
if (!\is_array($values) && !$values instanceof \Traversable) {
|
||||
return parent::setMultiple($values, $ttl);
|
||||
}
|
||||
(\Closure::bind(function () use ($values) {
|
||||
foreach ($values as $key => $value) {
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
$this->warmUp($this->data);
|
||||
list($this->keys, $this->values) = eval(substr(file_get_contents($this->file), 6));
|
||||
}, $this, PhpArrayCache::class))();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,39 +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\Simple;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Symfony\Component\Cache\Simple\PhpFilesCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class PhpFilesCacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testDefaultLifeTime' => 'PhpFilesCache does not allow configuring a default lifetime.',
|
||||
];
|
||||
|
||||
public function createSimpleCache()
|
||||
{
|
||||
return new PhpFilesCache('sf-cache');
|
||||
}
|
||||
|
||||
protected function isPruned(CacheInterface $cache, $name)
|
||||
{
|
||||
$getFileMethod = (new \ReflectionObject($cache))->getMethod('getFile');
|
||||
$getFileMethod->setAccessible(true);
|
||||
|
||||
return !file_exists($getFileMethod->invoke($cache, $name));
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\Psr6Cache;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
abstract class Psr6CacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testPrune' => 'Psr6Cache just proxies',
|
||||
];
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new Psr6Cache($this->createCacheItemPool($defaultLifetime));
|
||||
}
|
||||
|
||||
abstract protected function createCacheItemPool($defaultLifetime = 0);
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class Psr6CacheWithAdapterTest extends Psr6CacheTest
|
||||
{
|
||||
protected function createCacheItemPool($defaultLifetime = 0)
|
||||
{
|
||||
return new FilesystemAdapter('', $defaultLifetime);
|
||||
}
|
||||
}
|
@ -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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class Psr6CacheWithoutAdapterTest extends Psr6CacheTest
|
||||
{
|
||||
protected function createCacheItemPool($defaultLifetime = 0)
|
||||
{
|
||||
return new ExternalAdapter($defaultLifetime);
|
||||
}
|
||||
}
|
@ -1,27 +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\Simple;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class RedisArrayCacheTest extends AbstractRedisCacheTest
|
||||
{
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
parent::setupBeforeClass();
|
||||
if (!class_exists('RedisArray')) {
|
||||
self::markTestSkipped('The RedisArray class is required.');
|
||||
}
|
||||
self::$redis = new \RedisArray([getenv('REDIS_HOST')], ['lazy_connect' => true]);
|
||||
}
|
||||
}
|
@ -1,85 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\RedisCache;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class RedisCacheTest extends AbstractRedisCacheTest
|
||||
{
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
parent::setupBeforeClass();
|
||||
self::$redis = RedisCache::createConnection('redis://'.getenv('REDIS_HOST'));
|
||||
}
|
||||
|
||||
public function testCreateConnection()
|
||||
{
|
||||
$redisHost = getenv('REDIS_HOST');
|
||||
|
||||
$redis = RedisCache::createConnection('redis://'.$redisHost);
|
||||
$this->assertInstanceOf(\Redis::class, $redis);
|
||||
$this->assertTrue($redis->isConnected());
|
||||
$this->assertSame(0, $redis->getDbNum());
|
||||
|
||||
$redis = RedisCache::createConnection('redis://'.$redisHost.'/2');
|
||||
$this->assertSame(2, $redis->getDbNum());
|
||||
|
||||
$redis = RedisCache::createConnection('redis://'.$redisHost, ['timeout' => 3]);
|
||||
$this->assertEquals(3, $redis->getTimeout());
|
||||
|
||||
$redis = RedisCache::createConnection('redis://'.$redisHost.'?timeout=4');
|
||||
$this->assertEquals(4, $redis->getTimeout());
|
||||
|
||||
$redis = RedisCache::createConnection('redis://'.$redisHost, ['read_timeout' => 5]);
|
||||
$this->assertEquals(5, $redis->getReadTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideFailedCreateConnection
|
||||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage Redis connection failed
|
||||
*/
|
||||
public function testFailedCreateConnection($dsn)
|
||||
{
|
||||
RedisCache::createConnection($dsn);
|
||||
}
|
||||
|
||||
public function provideFailedCreateConnection()
|
||||
{
|
||||
return [
|
||||
['redis://localhost:1234'],
|
||||
['redis://foo@localhost'],
|
||||
['redis://localhost/123'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideInvalidCreateConnection
|
||||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage Invalid Redis DSN
|
||||
*/
|
||||
public function testInvalidCreateConnection($dsn)
|
||||
{
|
||||
RedisCache::createConnection($dsn);
|
||||
}
|
||||
|
||||
public function provideInvalidCreateConnection()
|
||||
{
|
||||
return [
|
||||
['foo://localhost'],
|
||||
['redis://'],
|
||||
];
|
||||
}
|
||||
}
|
@ -1,30 +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\Simple;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class RedisClusterCacheTest extends AbstractRedisCacheTest
|
||||
{
|
||||
public static function setupBeforeClass()
|
||||
{
|
||||
if (!class_exists('RedisCluster')) {
|
||||
self::markTestSkipped('The RedisCluster class is required.');
|
||||
}
|
||||
if (!$hosts = getenv('REDIS_CLUSTER_HOSTS')) {
|
||||
self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.');
|
||||
}
|
||||
|
||||
self::$redis = new \RedisCluster(null, explode(' ', $hosts));
|
||||
}
|
||||
}
|
@ -1,172 +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\Simple;
|
||||
|
||||
use Symfony\Component\Cache\Simple\FilesystemCache;
|
||||
use Symfony\Component\Cache\Simple\TraceableCache;
|
||||
|
||||
/**
|
||||
* @group time-sensitive
|
||||
* @group legacy
|
||||
*/
|
||||
class TraceableCacheTest extends CacheTestCase
|
||||
{
|
||||
protected $skippedTests = [
|
||||
'testPrune' => 'TraceableCache just proxies',
|
||||
];
|
||||
|
||||
public function createSimpleCache($defaultLifetime = 0)
|
||||
{
|
||||
return new TraceableCache(new FilesystemCache('', $defaultLifetime));
|
||||
}
|
||||
|
||||
public function testGetMissTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->get('k');
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(1, $calls);
|
||||
|
||||
$call = $calls[0];
|
||||
$this->assertSame('get', $call->name);
|
||||
$this->assertSame(['k' => false], $call->result);
|
||||
$this->assertSame(0, $call->hits);
|
||||
$this->assertSame(1, $call->misses);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
|
||||
public function testGetHitTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->set('k', 'foo');
|
||||
$pool->get('k');
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(2, $calls);
|
||||
|
||||
$call = $calls[1];
|
||||
$this->assertSame(1, $call->hits);
|
||||
$this->assertSame(0, $call->misses);
|
||||
}
|
||||
|
||||
public function testGetMultipleMissTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->set('k1', 123);
|
||||
$values = $pool->getMultiple(['k0', 'k1']);
|
||||
foreach ($values as $value) {
|
||||
}
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(2, $calls);
|
||||
|
||||
$call = $calls[1];
|
||||
$this->assertSame('getMultiple', $call->name);
|
||||
$this->assertSame(['k1' => true, 'k0' => false], $call->result);
|
||||
$this->assertSame(1, $call->misses);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
|
||||
public function testHasMissTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->has('k');
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(1, $calls);
|
||||
|
||||
$call = $calls[0];
|
||||
$this->assertSame('has', $call->name);
|
||||
$this->assertSame(['k' => false], $call->result);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
|
||||
public function testHasHitTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->set('k', 'foo');
|
||||
$pool->has('k');
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(2, $calls);
|
||||
|
||||
$call = $calls[1];
|
||||
$this->assertSame('has', $call->name);
|
||||
$this->assertSame(['k' => true], $call->result);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
|
||||
public function testDeleteTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->delete('k');
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(1, $calls);
|
||||
|
||||
$call = $calls[0];
|
||||
$this->assertSame('delete', $call->name);
|
||||
$this->assertSame(['k' => true], $call->result);
|
||||
$this->assertSame(0, $call->hits);
|
||||
$this->assertSame(0, $call->misses);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
|
||||
public function testDeleteMultipleTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$arg = ['k0', 'k1'];
|
||||
$pool->deleteMultiple($arg);
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(1, $calls);
|
||||
|
||||
$call = $calls[0];
|
||||
$this->assertSame('deleteMultiple', $call->name);
|
||||
$this->assertSame(['keys' => $arg, 'result' => true], $call->result);
|
||||
$this->assertSame(0, $call->hits);
|
||||
$this->assertSame(0, $call->misses);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
|
||||
public function testTraceSetTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->set('k', 'foo');
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(1, $calls);
|
||||
|
||||
$call = $calls[0];
|
||||
$this->assertSame('set', $call->name);
|
||||
$this->assertSame(['k' => true], $call->result);
|
||||
$this->assertSame(0, $call->hits);
|
||||
$this->assertSame(0, $call->misses);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
|
||||
public function testSetMultipleTrace()
|
||||
{
|
||||
$pool = $this->createSimpleCache();
|
||||
$pool->setMultiple(['k' => 'foo']);
|
||||
$calls = $pool->getCalls();
|
||||
$this->assertCount(1, $calls);
|
||||
|
||||
$call = $calls[0];
|
||||
$this->assertSame('setMultiple', $call->name);
|
||||
$this->assertSame(['keys' => ['k'], 'result' => true], $call->result);
|
||||
$this->assertSame(0, $call->hits);
|
||||
$this->assertSame(0, $call->misses);
|
||||
$this->assertNotEmpty($call->start);
|
||||
$this->assertNotEmpty($call->end);
|
||||
}
|
||||
}
|
@ -133,20 +133,6 @@ trait TagAwareTestTrait
|
||||
$this->assertFalse($pool->getItem('foo')->isHit());
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
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 testGetMetadata()
|
||||
{
|
||||
$pool = $this->createCachePool();
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Cache\Traits;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
|
||||
/**
|
||||
@ -21,7 +22,7 @@ use Symfony\Component\Cache\CacheItem;
|
||||
*/
|
||||
trait AbstractAdapterTrait
|
||||
{
|
||||
use AbstractTrait;
|
||||
use LoggerAwareTrait;
|
||||
|
||||
/**
|
||||
* @var \Closure needs to be set by class, signature is function(string <key>, mixed <value>, bool <isHit>)
|
||||
@ -33,6 +34,157 @@ trait AbstractAdapterTrait
|
||||
*/
|
||||
private $mergeByLifetime;
|
||||
|
||||
private $namespace;
|
||||
private $namespaceVersion = '';
|
||||
private $versioningIsEnabled = false;
|
||||
private $deferred = [];
|
||||
private $ids = [];
|
||||
|
||||
/**
|
||||
* @var int|null The maximum length to enforce for identifiers or null when no limit applies
|
||||
*/
|
||||
protected $maxIdLength;
|
||||
|
||||
/**
|
||||
* Fetches several cache items.
|
||||
*
|
||||
* @param array $ids The cache identifiers to fetch
|
||||
*
|
||||
* @return array|\Traversable The corresponding values found in the cache
|
||||
*/
|
||||
abstract protected function doFetch(array $ids);
|
||||
|
||||
/**
|
||||
* Confirms if the cache contains specified cache item.
|
||||
*
|
||||
* @param string $id The identifier for which to check existence
|
||||
*
|
||||
* @return bool True if item exists in the cache, false otherwise
|
||||
*/
|
||||
abstract protected function doHave($id);
|
||||
|
||||
/**
|
||||
* Deletes all items in the pool.
|
||||
*
|
||||
* @param string $namespace The prefix used for all identifiers managed by this pool
|
||||
*
|
||||
* @return bool True if the pool was successfully cleared, false otherwise
|
||||
*/
|
||||
abstract protected function doClear($namespace);
|
||||
|
||||
/**
|
||||
* Removes multiple items from the pool.
|
||||
*
|
||||
* @param array $ids An array of 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
|
||||
*/
|
||||
abstract protected function doSave(array $values, $lifetime);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
$id = $this->getId($key);
|
||||
|
||||
if (isset($this->deferred[$key])) {
|
||||
$this->commit();
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->doHave($id);
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->deferred = [];
|
||||
if ($cleared = $this->versioningIsEnabled) {
|
||||
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), ':', 5);
|
||||
try {
|
||||
$cleared = $this->doSave(['/'.$this->namespace => $namespaceVersion], 0);
|
||||
} catch (\Exception $e) {
|
||||
$cleared = false;
|
||||
}
|
||||
if ($cleared = true === $cleared || [] === $cleared) {
|
||||
$this->namespaceVersion = $namespaceVersion;
|
||||
$this->ids = [];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->doClear($this->namespace) || $cleared;
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
return $this->deleteItems([$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
$ids = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$ids[$key] = $this->getId($key);
|
||||
unset($this->deferred[$key]);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->doDelete($ids)) {
|
||||
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}
|
||||
*/
|
||||
@ -108,6 +260,40 @@ trait AbstractAdapterTrait
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables versioning of items.
|
||||
*
|
||||
* When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed,
|
||||
* but old keys may need garbage collection and extra round-trips to the back-end are required.
|
||||
*
|
||||
* Calling this method also clears the memoized namespace version and thus forces a resynchonization of it.
|
||||
*
|
||||
* @param bool $enable
|
||||
*
|
||||
* @return bool the previous state of versioning
|
||||
*/
|
||||
public function enableVersioning($enable = true)
|
||||
{
|
||||
$wasEnabled = $this->versioningIsEnabled;
|
||||
$this->versioningIsEnabled = (bool) $enable;
|
||||
$this->namespaceVersion = '';
|
||||
$this->ids = [];
|
||||
|
||||
return $wasEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
if ($this->deferred) {
|
||||
$this->commit();
|
||||
}
|
||||
$this->namespaceVersion = '';
|
||||
$this->ids = [];
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->deferred) {
|
||||
@ -136,4 +322,47 @@ trait AbstractAdapterTrait
|
||||
yield $key => $f($key, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
private function getId($key)
|
||||
{
|
||||
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
|
||||
$this->ids = [];
|
||||
$this->namespaceVersion = '1/';
|
||||
try {
|
||||
foreach ($this->doFetch(['/'.$this->namespace]) as $v) {
|
||||
$this->namespaceVersion = $v;
|
||||
}
|
||||
if ('1:' === $this->namespaceVersion) {
|
||||
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), ':', 5);
|
||||
$this->doSave(['@'.$this->namespace => $this->namespaceVersion], 0);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_string($key) && isset($this->ids[$key])) {
|
||||
return $this->namespace.$this->namespaceVersion.$this->ids[$key];
|
||||
}
|
||||
CacheItem::validateKey($key);
|
||||
$this->ids[$key] = $key;
|
||||
|
||||
if (null === $this->maxIdLength) {
|
||||
return $this->namespace.$this->namespaceVersion.$key;
|
||||
}
|
||||
if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
|
||||
// Use MD5 to favor speed over security, which is not an issue here
|
||||
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -(\strlen($this->namespaceVersion) + 2));
|
||||
$id = $this->namespace.$this->namespaceVersion.$id;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function handleUnserializeCallback($class)
|
||||
{
|
||||
throw new \DomainException('Class not found: '.$class);
|
||||
}
|
||||
}
|
||||
|
@ -1,284 +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\Traits;
|
||||
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait AbstractTrait
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $namespace;
|
||||
private $namespaceVersion = '';
|
||||
private $versioningIsEnabled = false;
|
||||
private $deferred = [];
|
||||
private $ids = [];
|
||||
|
||||
/**
|
||||
* @var int|null The maximum length to enforce for identifiers or null when no limit applies
|
||||
*/
|
||||
protected $maxIdLength;
|
||||
|
||||
/**
|
||||
* Fetches several cache items.
|
||||
*
|
||||
* @param array $ids The cache identifiers to fetch
|
||||
*
|
||||
* @return array|\Traversable The corresponding values found in the cache
|
||||
*/
|
||||
abstract protected function doFetch(array $ids);
|
||||
|
||||
/**
|
||||
* Confirms if the cache contains specified cache item.
|
||||
*
|
||||
* @param string $id The identifier for which to check existence
|
||||
*
|
||||
* @return bool True if item exists in the cache, false otherwise
|
||||
*/
|
||||
abstract protected function doHave($id);
|
||||
|
||||
/**
|
||||
* Deletes all items in the pool.
|
||||
*
|
||||
* @param string $namespace The prefix used for all identifiers managed by this pool
|
||||
*
|
||||
* @return bool True if the pool was successfully cleared, false otherwise
|
||||
*/
|
||||
abstract protected function doClear($namespace);
|
||||
|
||||
/**
|
||||
* Removes multiple items from the pool.
|
||||
*
|
||||
* @param array $ids An array of 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @return array|bool The identifiers that failed to be cached or a boolean stating if caching succeeded or not
|
||||
*/
|
||||
abstract protected function doSave(array $values, $lifetime);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
$id = $this->getId($key);
|
||||
|
||||
if (isset($this->deferred[$key])) {
|
||||
$this->commit();
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->doHave($id);
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to check if key "{key}" is cached: '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->deferred = [];
|
||||
if ($cleared = $this->versioningIsEnabled) {
|
||||
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), ':', 5);
|
||||
try {
|
||||
$cleared = $this->doSave(['/'.$this->namespace => $namespaceVersion], 0);
|
||||
} catch (\Exception $e) {
|
||||
$cleared = false;
|
||||
}
|
||||
if ($cleared = true === $cleared || [] === $cleared) {
|
||||
$this->namespaceVersion = $namespaceVersion;
|
||||
$this->ids = [];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->doClear($this->namespace) || $cleared;
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
return $this->deleteItems([$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems(array $keys)
|
||||
{
|
||||
$ids = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$ids[$key] = $this->getId($key);
|
||||
unset($this->deferred[$key]);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->doDelete($ids)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables/disables versioning of items.
|
||||
*
|
||||
* When versioning is enabled, clearing the cache is atomic and doesn't require listing existing keys to proceed,
|
||||
* but old keys may need garbage collection and extra round-trips to the back-end are required.
|
||||
*
|
||||
* Calling this method also clears the memoized namespace version and thus forces a resynchonization of it.
|
||||
*
|
||||
* @param bool $enable
|
||||
*
|
||||
* @return bool the previous state of versioning
|
||||
*/
|
||||
public function enableVersioning($enable = true)
|
||||
{
|
||||
$wasEnabled = $this->versioningIsEnabled;
|
||||
$this->versioningIsEnabled = (bool) $enable;
|
||||
$this->namespaceVersion = '';
|
||||
$this->ids = [];
|
||||
|
||||
return $wasEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
if ($this->deferred) {
|
||||
$this->commit();
|
||||
}
|
||||
$this->namespaceVersion = '';
|
||||
$this->ids = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Like the native unserialize() function but throws an exception if anything goes wrong.
|
||||
*
|
||||
* @param string $value
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @deprecated since Symfony 4.2, use DefaultMarshaller instead.
|
||||
*/
|
||||
protected static function unserialize($value)
|
||||
{
|
||||
@trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use DefaultMarshaller instead.', __CLASS__), E_USER_DEPRECATED);
|
||||
|
||||
if ('b:0;' === $value) {
|
||||
return false;
|
||||
}
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
||||
try {
|
||||
if (false !== $value = unserialize($value)) {
|
||||
return $value;
|
||||
}
|
||||
throw new \DomainException('Failed to unserialize cached value');
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private function getId($key)
|
||||
{
|
||||
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
|
||||
$this->ids = [];
|
||||
$this->namespaceVersion = '1/';
|
||||
try {
|
||||
foreach ($this->doFetch(['/'.$this->namespace]) as $v) {
|
||||
$this->namespaceVersion = $v;
|
||||
}
|
||||
if ('1:' === $this->namespaceVersion) {
|
||||
$this->namespaceVersion = substr_replace(base64_encode(pack('V', time())), ':', 5);
|
||||
$this->doSave(['@'.$this->namespace => $this->namespaceVersion], 0);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_string($key) && isset($this->ids[$key])) {
|
||||
return $this->namespace.$this->namespaceVersion.$this->ids[$key];
|
||||
}
|
||||
CacheItem::validateKey($key);
|
||||
$this->ids[$key] = $key;
|
||||
|
||||
if (null === $this->maxIdLength) {
|
||||
return $this->namespace.$this->namespaceVersion.$key;
|
||||
}
|
||||
if (\strlen($id = $this->namespace.$this->namespaceVersion.$key) > $this->maxIdLength) {
|
||||
// Use MD5 to favor speed over security, which is not an issue here
|
||||
$this->ids[$key] = $id = substr_replace(base64_encode(hash('md5', $key, true)), ':', -(\strlen($this->namespaceVersion) + 2));
|
||||
$id = $this->namespace.$this->namespaceVersion.$id;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function handleUnserializeCallback($class)
|
||||
{
|
||||
throw new \DomainException('Class not found: '.$class);
|
||||
}
|
||||
}
|
@ -1,121 +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\Traits;
|
||||
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait ApcuTrait
|
||||
{
|
||||
public static function isSupported()
|
||||
{
|
||||
return \function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
private function init($namespace, $defaultLifetime, $version)
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('APCu is not enabled');
|
||||
}
|
||||
if ('cli' === \PHP_SAPI) {
|
||||
ini_set('apc.use_request_time', 0);
|
||||
}
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
|
||||
if (null !== $version) {
|
||||
CacheItem::validateKey($version);
|
||||
|
||||
if (!apcu_exists($version.'@'.$namespace)) {
|
||||
$this->doClear($namespace);
|
||||
apcu_add($version.'@'.$namespace, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
||||
try {
|
||||
$values = [];
|
||||
foreach (apcu_fetch($ids, $ok) ?: [] as $k => $v) {
|
||||
if (null !== $v || $ok) {
|
||||
$values[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
return apcu_exists($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
return isset($namespace[0]) && class_exists('APCuIterator', false) && ('cli' !== \PHP_SAPI || filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN))
|
||||
? apcu_delete(new \APCuIterator(sprintf('/^%s/', preg_quote($namespace, '/')), APC_ITER_KEY))
|
||||
: apcu_clear_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
apcu_delete($id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
try {
|
||||
if (false === $failures = apcu_store($values, null, $lifetime)) {
|
||||
$failures = $values;
|
||||
}
|
||||
|
||||
return array_keys($failures);
|
||||
} catch (\Throwable $e) {
|
||||
if (1 === \count($values)) {
|
||||
// Workaround https://github.com/krakjoe/apcu/issues/170
|
||||
apcu_delete(key($values));
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,165 +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\Traits;
|
||||
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait ArrayTrait
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $storeSerialized;
|
||||
private $values = [];
|
||||
private $expiries = [];
|
||||
|
||||
/**
|
||||
* Returns all cached values, with cache miss as null.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
if (!$this->storeSerialized) {
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
$values = $this->values;
|
||||
foreach ($values as $k => $v) {
|
||||
if (null === $v || 'N;' === $v) {
|
||||
continue;
|
||||
}
|
||||
if (!\is_string($v) || !isset($v[2]) || ':' !== $v[1]) {
|
||||
$values[$k] = serialize($v);
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasItem($key)
|
||||
{
|
||||
if (\is_string($key) && isset($this->expiries[$key]) && $this->expiries[$key] > microtime(true)) {
|
||||
return true;
|
||||
}
|
||||
CacheItem::validateKey($key);
|
||||
|
||||
return isset($this->expiries[$key]) && !$this->deleteItem($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->values = $this->expiries = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItem($key)
|
||||
{
|
||||
if (!\is_string($key) || !isset($this->expiries[$key])) {
|
||||
CacheItem::validateKey($key);
|
||||
}
|
||||
unset($this->values[$key], $this->expiries[$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->clear();
|
||||
}
|
||||
|
||||
private function generateItems(array $keys, $now, $f)
|
||||
{
|
||||
foreach ($keys as $i => $key) {
|
||||
if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] > $now || !$this->deleteItem($key))) {
|
||||
$this->values[$key] = $value = null;
|
||||
} else {
|
||||
$value = $this->storeSerialized ? $this->unfreeze($key, $isHit) : $this->values[$key];
|
||||
}
|
||||
unset($keys[$i]);
|
||||
|
||||
yield $key => $f($key, $value, $isHit);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
yield $key => $f($key, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
private function freeze($value, $key)
|
||||
{
|
||||
if (null === $value) {
|
||||
return 'N;';
|
||||
}
|
||||
if (\is_string($value)) {
|
||||
// Serialize strings if they could be confused with serialized objects or arrays
|
||||
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
|
||||
return serialize($value);
|
||||
}
|
||||
} elseif (!\is_scalar($value)) {
|
||||
try {
|
||||
$serialized = serialize($value);
|
||||
} catch (\Exception $e) {
|
||||
$type = \is_object($value) ? \get_class($value) : \gettype($value);
|
||||
$message = sprintf('Failed to save key "{key}" of type %s: %s', $type, $e->getMessage());
|
||||
CacheItem::log($this->logger, $message, ['key' => substr($id, \strlen($this->namespace)), 'exception' => $e]);
|
||||
|
||||
return;
|
||||
}
|
||||
// Keep value serialized if it contains any objects or any internal references
|
||||
if ('C' === $serialized[0] || 'O' === $serialized[0] || preg_match('/;[OCRr]:[1-9]/', $serialized)) {
|
||||
return $serialized;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function unfreeze(string $key, bool &$isHit)
|
||||
{
|
||||
if ('N;' === $value = $this->values[$key]) {
|
||||
return null;
|
||||
}
|
||||
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
||||
try {
|
||||
$value = unserialize($value);
|
||||
} catch (\Exception $e) {
|
||||
CacheItem::log($this->logger, 'Failed to unserialize key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
|
||||
$value = false;
|
||||
}
|
||||
if (false === $value) {
|
||||
$this->values[$key] = $value = null;
|
||||
$isHit = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
@ -1,98 +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\Traits;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait DoctrineTrait
|
||||
{
|
||||
private $provider;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
parent::reset();
|
||||
$this->provider->setNamespace($this->provider->getNamespace());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', parent::class.'::handleUnserializeCallback');
|
||||
try {
|
||||
return $this->provider->fetchMultiple($ids);
|
||||
} catch (\Error $e) {
|
||||
$trace = $e->getTrace();
|
||||
|
||||
if (isset($trace[0]['function']) && !isset($trace[0]['class'])) {
|
||||
switch ($trace[0]['function']) {
|
||||
case 'unserialize':
|
||||
case 'apcu_fetch':
|
||||
case 'apc_fetch':
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
return $this->provider->contains($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
$namespace = $this->provider->getNamespace();
|
||||
|
||||
return isset($namespace[0])
|
||||
? $this->provider->deleteAll()
|
||||
: $this->provider->flushAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$ok = true;
|
||||
foreach ($ids as $id) {
|
||||
$ok = $this->provider->delete($id) && $ok;
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
return $this->provider->saveMultiple($values, $lifetime);
|
||||
}
|
||||
}
|
@ -1,328 +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\Traits;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
|
||||
/**
|
||||
* @author Rob Frawley 2nd <rmf@src.run>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait MemcachedTrait
|
||||
{
|
||||
private static $defaultClientOptions = [
|
||||
'persistent_id' => null,
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
'serializer' => 'php',
|
||||
];
|
||||
|
||||
private $marshaller;
|
||||
private $client;
|
||||
private $lazyClient;
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
return \extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
|
||||
}
|
||||
|
||||
private function init(\Memcached $client, $namespace, $defaultLifetime, ?MarshallerInterface $marshaller)
|
||||
{
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Memcached >= 2.2.0 is required');
|
||||
}
|
||||
if ('Memcached' === \get_class($client)) {
|
||||
$opt = $client->getOption(\Memcached::OPT_SERIALIZER);
|
||||
if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
|
||||
throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
|
||||
}
|
||||
$this->maxIdLength -= \strlen($client->getOption(\Memcached::OPT_PREFIX_KEY));
|
||||
$this->client = $client;
|
||||
} else {
|
||||
$this->lazyClient = $client;
|
||||
}
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
$this->enableVersioning();
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Memcached instance.
|
||||
*
|
||||
* By default, the binary protocol, no block, and libketama compatible options are enabled.
|
||||
*
|
||||
* Examples for servers:
|
||||
* - 'memcached://user:pass@localhost?weight=33'
|
||||
* - [['localhost', 11211, 33]]
|
||||
*
|
||||
* @param array[]|string|string[] $servers An array of servers, a DSN, or an array of DSNs
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return \Memcached
|
||||
*
|
||||
* @throws \ErrorException When invalid options or servers are provided
|
||||
*/
|
||||
public static function createConnection($servers, array $options = [])
|
||||
{
|
||||
if (\is_string($servers)) {
|
||||
$servers = [$servers];
|
||||
} elseif (!\is_array($servers)) {
|
||||
throw new InvalidArgumentException(sprintf('MemcachedAdapter::createClient() expects array or string as first argument, %s given.', \gettype($servers)));
|
||||
}
|
||||
if (!static::isSupported()) {
|
||||
throw new CacheException('Memcached >= 2.2.0 is required');
|
||||
}
|
||||
set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); });
|
||||
try {
|
||||
$options += static::$defaultClientOptions;
|
||||
$client = new \Memcached($options['persistent_id']);
|
||||
$username = $options['username'];
|
||||
$password = $options['password'];
|
||||
|
||||
// parse any DSN in $servers
|
||||
foreach ($servers as $i => $dsn) {
|
||||
if (\is_array($dsn)) {
|
||||
continue;
|
||||
}
|
||||
if (0 !== strpos($dsn, 'memcached:')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s does not start with "memcached:"', $dsn));
|
||||
}
|
||||
$params = preg_replace_callback('#^memcached:(//)?(?:([^@]*+)@)?#', function ($m) use (&$username, &$password) {
|
||||
if (!empty($m[2])) {
|
||||
list($username, $password) = explode(':', $m[2], 2) + [1 => null];
|
||||
}
|
||||
|
||||
return 'file:'.($m[1] ?? '');
|
||||
}, $dsn);
|
||||
if (false === $params = parse_url($params)) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
|
||||
}
|
||||
$query = $hosts = [];
|
||||
if (isset($params['query'])) {
|
||||
parse_str($params['query'], $query);
|
||||
|
||||
if (isset($query['host'])) {
|
||||
if (!\is_array($hosts = $query['host'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
|
||||
}
|
||||
foreach ($hosts as $host => $weight) {
|
||||
if (false === $port = strrpos($host, ':')) {
|
||||
$hosts[$host] = [$host, 11211, (int) $weight];
|
||||
} else {
|
||||
$hosts[$host] = [substr($host, 0, $port), (int) substr($host, 1 + $port), (int) $weight];
|
||||
}
|
||||
}
|
||||
$hosts = array_values($hosts);
|
||||
unset($query['host']);
|
||||
}
|
||||
if ($hosts && !isset($params['host']) && !isset($params['path'])) {
|
||||
unset($servers[$i]);
|
||||
$servers = array_merge($servers, $hosts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isset($params['host']) && !isset($params['path'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid Memcached DSN: %s', $dsn));
|
||||
}
|
||||
if (isset($params['path']) && preg_match('#/(\d+)$#', $params['path'], $m)) {
|
||||
$params['weight'] = $m[1];
|
||||
$params['path'] = substr($params['path'], 0, -\strlen($m[0]));
|
||||
}
|
||||
$params += [
|
||||
'host' => isset($params['host']) ? $params['host'] : $params['path'],
|
||||
'port' => isset($params['host']) ? 11211 : null,
|
||||
'weight' => 0,
|
||||
];
|
||||
if ($query) {
|
||||
$params += $query;
|
||||
$options = $query + $options;
|
||||
}
|
||||
|
||||
$servers[$i] = [$params['host'], $params['port'], $params['weight']];
|
||||
|
||||
if ($hosts) {
|
||||
$servers = array_merge($servers, $hosts);
|
||||
}
|
||||
}
|
||||
|
||||
// set client's options
|
||||
unset($options['persistent_id'], $options['username'], $options['password'], $options['weight'], $options['lazy']);
|
||||
$options = array_change_key_case($options, CASE_UPPER);
|
||||
$client->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
|
||||
$client->setOption(\Memcached::OPT_NO_BLOCK, true);
|
||||
$client->setOption(\Memcached::OPT_TCP_NODELAY, true);
|
||||
if (!\array_key_exists('LIBKETAMA_COMPATIBLE', $options) && !\array_key_exists(\Memcached::OPT_LIBKETAMA_COMPATIBLE, $options)) {
|
||||
$client->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
|
||||
}
|
||||
foreach ($options as $name => $value) {
|
||||
if (\is_int($name)) {
|
||||
continue;
|
||||
}
|
||||
if ('HASH' === $name || 'SERIALIZER' === $name || 'DISTRIBUTION' === $name) {
|
||||
$value = \constant('Memcached::'.$name.'_'.strtoupper($value));
|
||||
}
|
||||
$opt = \constant('Memcached::OPT_'.$name);
|
||||
|
||||
unset($options[$name]);
|
||||
$options[$opt] = $value;
|
||||
}
|
||||
$client->setOptions($options);
|
||||
|
||||
// set client's servers, taking care of persistent connections
|
||||
if (!$client->isPristine()) {
|
||||
$oldServers = [];
|
||||
foreach ($client->getServerList() as $server) {
|
||||
$oldServers[] = [$server['host'], $server['port']];
|
||||
}
|
||||
|
||||
$newServers = [];
|
||||
foreach ($servers as $server) {
|
||||
if (1 < \count($server)) {
|
||||
$server = array_values($server);
|
||||
unset($server[2]);
|
||||
$server[1] = (int) $server[1];
|
||||
}
|
||||
$newServers[] = $server;
|
||||
}
|
||||
|
||||
if ($oldServers !== $newServers) {
|
||||
$client->resetServerList();
|
||||
$client->addServers($servers);
|
||||
}
|
||||
} else {
|
||||
$client->addServers($servers);
|
||||
}
|
||||
|
||||
if (null !== $username || null !== $password) {
|
||||
if (!method_exists($client, 'setSaslAuthData')) {
|
||||
trigger_error('Missing SASL support: the memcached extension must be compiled with --enable-memcached-sasl.');
|
||||
}
|
||||
$client->setSaslAuthData($username, $password);
|
||||
}
|
||||
|
||||
return $client;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
if ($lifetime && $lifetime > 30 * 86400) {
|
||||
$lifetime += time();
|
||||
}
|
||||
|
||||
$encodedValues = [];
|
||||
foreach ($values as $key => $value) {
|
||||
$encodedValues[rawurlencode($key)] = $value;
|
||||
}
|
||||
|
||||
return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
try {
|
||||
$encodedIds = array_map('rawurlencode', $ids);
|
||||
|
||||
$encodedResult = $this->checkResultCode($this->getClient()->getMulti($encodedIds));
|
||||
|
||||
$result = [];
|
||||
foreach ($encodedResult as $key => $value) {
|
||||
$result[rawurldecode($key)] = $this->marshaller->unmarshall($value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
} catch (\Error $e) {
|
||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
return false !== $this->getClient()->get(rawurlencode($id)) || $this->checkResultCode(\Memcached::RES_SUCCESS === $this->client->getResultCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$ok = true;
|
||||
$encodedIds = array_map('rawurlencode', $ids);
|
||||
foreach ($this->checkResultCode($this->getClient()->deleteMulti($encodedIds)) as $result) {
|
||||
if (\Memcached::RES_SUCCESS !== $result && \Memcached::RES_NOTFOUND !== $result) {
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
return '' === $namespace && $this->getClient()->flush();
|
||||
}
|
||||
|
||||
private function checkResultCode($result)
|
||||
{
|
||||
$code = $this->client->getResultCode();
|
||||
|
||||
if (\Memcached::RES_SUCCESS === $code || \Memcached::RES_NOTFOUND === $code) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw new CacheException(sprintf('MemcachedAdapter client error: %s.', strtolower($this->client->getResultMessage())));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Memcached
|
||||
*/
|
||||
private function getClient()
|
||||
{
|
||||
if ($this->client) {
|
||||
return $this->client;
|
||||
}
|
||||
|
||||
$opt = $this->lazyClient->getOption(\Memcached::OPT_SERIALIZER);
|
||||
if (\Memcached::SERIALIZER_PHP !== $opt && \Memcached::SERIALIZER_IGBINARY !== $opt) {
|
||||
throw new CacheException('MemcachedAdapter: "serializer" option must be "php" or "igbinary".');
|
||||
}
|
||||
if ('' !== $prefix = (string) $this->lazyClient->getOption(\Memcached::OPT_PREFIX_KEY)) {
|
||||
throw new CacheException(sprintf('MemcachedAdapter: "prefix_key" option must be empty when using proxified connections, "%s" given.', $prefix));
|
||||
}
|
||||
|
||||
return $this->client = $this->lazyClient;
|
||||
}
|
||||
}
|
@ -1,424 +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\Traits;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DBALException;
|
||||
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
|
||||
use Doctrine\DBAL\Exception\TableNotFoundException;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait PdoTrait
|
||||
{
|
||||
private $marshaller;
|
||||
private $conn;
|
||||
private $dsn;
|
||||
private $driver;
|
||||
private $serverVersion;
|
||||
private $table = 'cache_items';
|
||||
private $idCol = 'item_id';
|
||||
private $dataCol = 'item_data';
|
||||
private $lifetimeCol = 'item_lifetime';
|
||||
private $timeCol = 'item_time';
|
||||
private $username = '';
|
||||
private $password = '';
|
||||
private $connectionOptions = [];
|
||||
private $namespace;
|
||||
|
||||
private function init($connOrDsn, $namespace, $defaultLifetime, array $options, ?MarshallerInterface $marshaller)
|
||||
{
|
||||
if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
|
||||
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
|
||||
}
|
||||
|
||||
if ($connOrDsn instanceof \PDO) {
|
||||
if (\PDO::ERRMODE_EXCEPTION !== $connOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) {
|
||||
throw new InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__));
|
||||
}
|
||||
|
||||
$this->conn = $connOrDsn;
|
||||
} elseif ($connOrDsn instanceof Connection) {
|
||||
$this->conn = $connOrDsn;
|
||||
} elseif (\is_string($connOrDsn)) {
|
||||
$this->dsn = $connOrDsn;
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('"%s" requires PDO or Doctrine\DBAL\Connection instance or DSN string as first argument, "%s" given.', __CLASS__, \is_object($connOrDsn) ? \get_class($connOrDsn) : \gettype($connOrDsn)));
|
||||
}
|
||||
|
||||
$this->table = isset($options['db_table']) ? $options['db_table'] : $this->table;
|
||||
$this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol;
|
||||
$this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol;
|
||||
$this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol;
|
||||
$this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol;
|
||||
$this->username = isset($options['db_username']) ? $options['db_username'] : $this->username;
|
||||
$this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
|
||||
$this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
|
||||
$this->namespace = $namespace;
|
||||
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||
|
||||
parent::__construct($namespace, $defaultLifetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the table to store cache items which can be called once for setup.
|
||||
*
|
||||
* Cache ID are saved in a column of maximum length 255. Cache data is
|
||||
* saved in a BLOB.
|
||||
*
|
||||
* @throws \PDOException When the table already exists
|
||||
* @throws DBALException When the table already exists
|
||||
* @throws \DomainException When an unsupported PDO driver is used
|
||||
*/
|
||||
public function createTable()
|
||||
{
|
||||
// connect if we are not yet
|
||||
$conn = $this->getConnection();
|
||||
|
||||
if ($conn instanceof Connection) {
|
||||
$types = [
|
||||
'mysql' => 'binary',
|
||||
'sqlite' => 'text',
|
||||
'pgsql' => 'string',
|
||||
'oci' => 'string',
|
||||
'sqlsrv' => 'string',
|
||||
];
|
||||
if (!isset($types[$this->driver])) {
|
||||
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
|
||||
}
|
||||
|
||||
$schema = new Schema();
|
||||
$table = $schema->createTable($this->table);
|
||||
$table->addColumn($this->idCol, $types[$this->driver], ['length' => 255]);
|
||||
$table->addColumn($this->dataCol, 'blob', ['length' => 16777215]);
|
||||
$table->addColumn($this->lifetimeCol, 'integer', ['unsigned' => true, 'notnull' => false]);
|
||||
$table->addColumn($this->timeCol, 'integer', ['unsigned' => true]);
|
||||
$table->setPrimaryKey([$this->idCol]);
|
||||
|
||||
foreach ($schema->toSql($conn->getDatabasePlatform()) as $sql) {
|
||||
$conn->exec($sql);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->driver) {
|
||||
case 'mysql':
|
||||
// We use varbinary for the ID column because it prevents unwanted conversions:
|
||||
// - character set conversions between server and client
|
||||
// - trailing space removal
|
||||
// - case-insensitivity
|
||||
// - language processing like é == e
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(255) NOT NULL PRIMARY KEY, $this->dataCol MEDIUMBLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB";
|
||||
break;
|
||||
case 'sqlite':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'pgsql':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'oci':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(255) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
case 'sqlsrv':
|
||||
$sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(255) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER, $this->timeCol INTEGER NOT NULL)";
|
||||
break;
|
||||
default:
|
||||
throw new \DomainException(sprintf('Creating the cache table is currently not implemented for PDO driver "%s".', $this->driver));
|
||||
}
|
||||
|
||||
$conn->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time";
|
||||
|
||||
if ('' !== $this->namespace) {
|
||||
$deleteSql .= " AND $this->idCol LIKE :namespace";
|
||||
}
|
||||
|
||||
try {
|
||||
$delete = $this->getConnection()->prepare($deleteSql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
return true;
|
||||
}
|
||||
$delete->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
|
||||
if ('' !== $this->namespace) {
|
||||
$delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR);
|
||||
}
|
||||
try {
|
||||
return $delete->execute();
|
||||
} catch (TableNotFoundException $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
$now = time();
|
||||
$expired = [];
|
||||
|
||||
$sql = str_pad('', (\count($ids) << 1) - 1, '?,');
|
||||
$sql = "SELECT $this->idCol, CASE WHEN $this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > ? THEN $this->dataCol ELSE NULL END FROM $this->table WHERE $this->idCol IN ($sql)";
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
|
||||
foreach ($ids as $id) {
|
||||
$stmt->bindValue(++$i, $id);
|
||||
}
|
||||
$stmt->execute();
|
||||
|
||||
while ($row = $stmt->fetch(\PDO::FETCH_NUM)) {
|
||||
if (null === $row[1]) {
|
||||
$expired[] = $row[0];
|
||||
} else {
|
||||
yield $row[0] => $this->marshaller->unmarshall(\is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($expired) {
|
||||
$sql = str_pad('', (\count($expired) << 1) - 1, '?,');
|
||||
$sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= ? AND $this->idCol IN ($sql)";
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->bindValue($i = 1, $now, \PDO::PARAM_INT);
|
||||
foreach ($expired as $id) {
|
||||
$stmt->bindValue(++$i, $id);
|
||||
}
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
$sql = "SELECT 1 FROM $this->table WHERE $this->idCol = :id AND ($this->lifetimeCol IS NULL OR $this->lifetimeCol + $this->timeCol > :time)";
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
|
||||
$stmt->bindValue(':id', $id);
|
||||
$stmt->bindValue(':time', time(), \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
return (bool) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
$conn = $this->getConnection();
|
||||
|
||||
if ('' === $namespace) {
|
||||
if ('sqlite' === $this->driver) {
|
||||
$sql = "DELETE FROM $this->table";
|
||||
} else {
|
||||
$sql = "TRUNCATE TABLE $this->table";
|
||||
}
|
||||
} else {
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol LIKE '$namespace%'";
|
||||
}
|
||||
|
||||
try {
|
||||
$conn->exec($sql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
$sql = str_pad('', (\count($ids) << 1) - 1, '?,');
|
||||
$sql = "DELETE FROM $this->table WHERE $this->idCol IN ($sql)";
|
||||
try {
|
||||
$stmt = $this->getConnection()->prepare($sql);
|
||||
$stmt->execute(array_values($ids));
|
||||
} catch (TableNotFoundException $e) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||
return $failed;
|
||||
}
|
||||
|
||||
$conn = $this->getConnection();
|
||||
$driver = $this->driver;
|
||||
$insertSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)";
|
||||
|
||||
switch (true) {
|
||||
case 'mysql' === $driver:
|
||||
$sql = $insertSql." ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)";
|
||||
break;
|
||||
case 'oci' === $driver:
|
||||
// DUAL is Oracle specific dummy table
|
||||
$sql = "MERGE INTO $this->table USING DUAL ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?";
|
||||
break;
|
||||
case 'sqlsrv' === $driver && version_compare($this->getServerVersion(), '10', '>='):
|
||||
// MERGE is only available since SQL Server 2008 and must be terminated by semicolon
|
||||
// It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx
|
||||
$sql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ".
|
||||
"WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ".
|
||||
"WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;";
|
||||
break;
|
||||
case 'sqlite' === $driver:
|
||||
$sql = 'INSERT OR REPLACE'.substr($insertSql, 6);
|
||||
break;
|
||||
case 'pgsql' === $driver && version_compare($this->getServerVersion(), '9.5', '>='):
|
||||
$sql = $insertSql." ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)";
|
||||
break;
|
||||
default:
|
||||
$driver = null;
|
||||
$sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id";
|
||||
break;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$lifetime = $lifetime ?: null;
|
||||
try {
|
||||
$stmt = $conn->prepare($sql);
|
||||
} catch (TableNotFoundException $e) {
|
||||
if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$stmt = $conn->prepare($sql);
|
||||
}
|
||||
|
||||
if ('sqlsrv' === $driver || 'oci' === $driver) {
|
||||
$stmt->bindParam(1, $id);
|
||||
$stmt->bindParam(2, $id);
|
||||
$stmt->bindParam(3, $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(4, $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(5, $now, \PDO::PARAM_INT);
|
||||
$stmt->bindParam(6, $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(7, $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(8, $now, \PDO::PARAM_INT);
|
||||
} else {
|
||||
$stmt->bindParam(':id', $id);
|
||||
$stmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||
$stmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
|
||||
$stmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
||||
}
|
||||
if (null === $driver) {
|
||||
$insertStmt = $conn->prepare($insertSql);
|
||||
|
||||
$insertStmt->bindParam(':id', $id);
|
||||
$insertStmt->bindParam(':data', $data, \PDO::PARAM_LOB);
|
||||
$insertStmt->bindValue(':lifetime', $lifetime, \PDO::PARAM_INT);
|
||||
$insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
||||
}
|
||||
|
||||
foreach ($values as $id => $data) {
|
||||
try {
|
||||
$stmt->execute();
|
||||
} catch (TableNotFoundException $e) {
|
||||
if (!$conn->isTransactionActive() || \in_array($this->driver, ['pgsql', 'sqlite', 'sqlsrv'], true)) {
|
||||
$this->createTable();
|
||||
}
|
||||
$stmt->execute();
|
||||
}
|
||||
if (null === $driver && !$stmt->rowCount()) {
|
||||
try {
|
||||
$insertStmt->execute();
|
||||
} catch (DBALException $e) {
|
||||
} catch (\PDOException $e) {
|
||||
// A concurrent write won, let it be
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \PDO|Connection
|
||||
*/
|
||||
private function getConnection()
|
||||
{
|
||||
if (null === $this->conn) {
|
||||
$this->conn = new \PDO($this->dsn, $this->username, $this->password, $this->connectionOptions);
|
||||
$this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
if (null === $this->driver) {
|
||||
if ($this->conn instanceof \PDO) {
|
||||
$this->driver = $this->conn->getAttribute(\PDO::ATTR_DRIVER_NAME);
|
||||
} else {
|
||||
switch ($this->driver = $this->conn->getDriver()->getName()) {
|
||||
case 'mysqli':
|
||||
case 'pdo_mysql':
|
||||
case 'drizzle_pdo_mysql':
|
||||
$this->driver = 'mysql';
|
||||
break;
|
||||
case 'pdo_sqlite':
|
||||
$this->driver = 'sqlite';
|
||||
break;
|
||||
case 'pdo_pgsql':
|
||||
$this->driver = 'pgsql';
|
||||
break;
|
||||
case 'oci8':
|
||||
case 'pdo_oracle':
|
||||
$this->driver = 'oci';
|
||||
break;
|
||||
case 'pdo_sqlsrv':
|
||||
$this->driver = 'sqlsrv';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getServerVersion()
|
||||
{
|
||||
if (null === $this->serverVersion) {
|
||||
$conn = $this->conn instanceof \PDO ? $this->conn : $this->conn->getWrappedConnection();
|
||||
if ($conn instanceof \PDO) {
|
||||
$this->serverVersion = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION);
|
||||
} elseif ($conn instanceof ServerInfoAwareConnection) {
|
||||
$this->serverVersion = $conn->getServerVersion();
|
||||
} else {
|
||||
$this->serverVersion = '0';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->serverVersion;
|
||||
}
|
||||
}
|
@ -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\Traits;
|
||||
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
|
||||
/**
|
||||
* @author Titouan Galopin <galopintitouan@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait PhpArrayTrait
|
||||
{
|
||||
use ProxyTrait;
|
||||
|
||||
private $file;
|
||||
private $keys;
|
||||
private $values;
|
||||
|
||||
/**
|
||||
* Store an array of cached values.
|
||||
*
|
||||
* @param array $values The cached values
|
||||
*/
|
||||
public function warmUp(array $values)
|
||||
{
|
||||
if (file_exists($this->file)) {
|
||||
if (!is_file($this->file)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache path exists and is not a file: %s.', $this->file));
|
||||
}
|
||||
|
||||
if (!is_writable($this->file)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache file is not writable: %s.', $this->file));
|
||||
}
|
||||
} else {
|
||||
$directory = \dirname($this->file);
|
||||
|
||||
if (!is_dir($directory) && !@mkdir($directory, 0777, true)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache directory does not exist and cannot be created: %s.', $directory));
|
||||
}
|
||||
|
||||
if (!is_writable($directory)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache directory is not writable: %s.', $directory));
|
||||
}
|
||||
}
|
||||
|
||||
$dumpedValues = '';
|
||||
$dumpedMap = [];
|
||||
$dump = <<<'EOF'
|
||||
<?php
|
||||
|
||||
// This file has been auto-generated by the Symfony Cache Component.
|
||||
|
||||
return [[
|
||||
|
||||
|
||||
EOF;
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
|
||||
$isStaticValue = true;
|
||||
|
||||
if (null === $value) {
|
||||
$value = "'N;'";
|
||||
} elseif (\is_object($value) || \is_array($value)) {
|
||||
try {
|
||||
$value = VarExporter::export($value, $isStaticValue);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
|
||||
}
|
||||
} elseif (\is_string($value)) {
|
||||
// Wrap "N;" in a closure to not confuse it with an encoded `null`
|
||||
if ('N;' === $value) {
|
||||
$isStaticValue = false;
|
||||
}
|
||||
$value = var_export($value, true);
|
||||
} elseif (!\is_scalar($value)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
|
||||
} else {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
|
||||
if (!$isStaticValue) {
|
||||
$value = str_replace("\n", "\n ", $value);
|
||||
$value = "static function () {\n return {$value};\n}";
|
||||
}
|
||||
$hash = hash('md5', $value);
|
||||
|
||||
if (null === $id = $dumpedMap[$hash] ?? null) {
|
||||
$id = $dumpedMap[$hash] = \count($dumpedMap);
|
||||
$dumpedValues .= "{$id} => {$value},\n";
|
||||
}
|
||||
|
||||
$dump .= var_export($key, true)." => {$id},\n";
|
||||
}
|
||||
|
||||
$dump .= "\n], [\n\n{$dumpedValues}\n]];\n";
|
||||
|
||||
$tmpFile = uniqid($this->file, true);
|
||||
|
||||
file_put_contents($tmpFile, $dump);
|
||||
@chmod($tmpFile, 0666 & ~umask());
|
||||
unset($serialized, $value, $dump);
|
||||
|
||||
@rename($tmpFile, $this->file);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->keys = $this->values = [];
|
||||
|
||||
$cleared = @unlink($this->file) || !file_exists($this->file);
|
||||
|
||||
return $this->pool->clear() && $cleared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the cache file.
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
if (!file_exists($this->file)) {
|
||||
$this->keys = $this->values = [];
|
||||
|
||||
return;
|
||||
}
|
||||
$values = (include $this->file) ?: [[], []];
|
||||
|
||||
if (2 !== \count($values) || !isset($values[0], $values[1])) {
|
||||
$this->keys = $this->values = [];
|
||||
} else {
|
||||
list($this->keys, $this->values) = $values;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,243 +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\Traits;
|
||||
|
||||
use Symfony\Component\Cache\Exception\CacheException;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
|
||||
/**
|
||||
* @author Piotr Stankowski <git@trakos.pl>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
* @author Rob Frawley 2nd <rmf@src.run>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait PhpFilesTrait
|
||||
{
|
||||
use FilesystemCommonTrait {
|
||||
doClear as private doCommonClear;
|
||||
doDelete as private doCommonDelete;
|
||||
}
|
||||
|
||||
private $includeHandler;
|
||||
private $appendOnly;
|
||||
private $values = [];
|
||||
private $files = [];
|
||||
|
||||
private static $startTime;
|
||||
|
||||
public static function isSupported()
|
||||
{
|
||||
self::$startTime = self::$startTime ?? $_SERVER['REQUEST_TIME'] ?? time();
|
||||
|
||||
return \function_exists('opcache_invalidate') && ('cli' !== \PHP_SAPI || filter_var(ini_get('opcache.enable_cli'), FILTER_VALIDATE_BOOLEAN)) && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function prune()
|
||||
{
|
||||
$time = time();
|
||||
$pruned = true;
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
|
||||
try {
|
||||
list($expiresAt) = include $file;
|
||||
} catch (\ErrorException $e) {
|
||||
$expiresAt = $time;
|
||||
}
|
||||
|
||||
if ($time >= $expiresAt) {
|
||||
$pruned = $this->doUnlink($file) && !file_exists($file) && $pruned;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return $pruned;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doFetch(array $ids)
|
||||
{
|
||||
if ($this->appendOnly) {
|
||||
$now = 0;
|
||||
$missingIds = [];
|
||||
} else {
|
||||
$now = time();
|
||||
$missingIds = $ids;
|
||||
$ids = [];
|
||||
}
|
||||
$values = [];
|
||||
|
||||
begin:
|
||||
foreach ($ids as $id) {
|
||||
if (null === $value = $this->values[$id] ?? null) {
|
||||
$missingIds[] = $id;
|
||||
} elseif ('N;' === $value) {
|
||||
$values[$id] = null;
|
||||
} elseif ($value instanceof \Closure) {
|
||||
$values[$id] = $value();
|
||||
} else {
|
||||
$values[$id] = $value;
|
||||
}
|
||||
if (!$this->appendOnly) {
|
||||
unset($this->values[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$missingIds) {
|
||||
return $values;
|
||||
}
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
foreach ($missingIds as $k => $id) {
|
||||
try {
|
||||
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
|
||||
list($expiresAt, $this->values[$id]) = include $file;
|
||||
if ($now >= $expiresAt) {
|
||||
unset($this->values[$id], $missingIds[$k]);
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
unset($missingIds[$k]);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$ids = $missingIds;
|
||||
$missingIds = [];
|
||||
goto begin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doHave($id)
|
||||
{
|
||||
if ($this->appendOnly && isset($this->values[$id])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
set_error_handler($this->includeHandler);
|
||||
try {
|
||||
$file = $this->files[$id] ?? $this->files[$id] = $this->getFile($id);
|
||||
list($expiresAt, $value) = include $file;
|
||||
} catch (\ErrorException $e) {
|
||||
return false;
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
if ($this->appendOnly) {
|
||||
$now = 0;
|
||||
$this->values[$id] = $value;
|
||||
} else {
|
||||
$now = time();
|
||||
}
|
||||
|
||||
return $now < $expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doSave(array $values, $lifetime)
|
||||
{
|
||||
$ok = true;
|
||||
$expiry = $lifetime ? time() + $lifetime : 'PHP_INT_MAX';
|
||||
$allowCompile = self::isSupported();
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
unset($this->values[$key]);
|
||||
$isStaticValue = true;
|
||||
if (null === $value) {
|
||||
$value = "'N;'";
|
||||
} elseif (\is_object($value) || \is_array($value)) {
|
||||
try {
|
||||
$value = VarExporter::export($value, $isStaticValue);
|
||||
} catch (\Exception $e) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
|
||||
}
|
||||
} elseif (\is_string($value)) {
|
||||
// Wrap "N;" in a closure to not confuse it with an encoded `null`
|
||||
if ('N;' === $value) {
|
||||
$isStaticValue = false;
|
||||
}
|
||||
$value = var_export($value, true);
|
||||
} elseif (!\is_scalar($value)) {
|
||||
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
|
||||
} else {
|
||||
$value = var_export($value, true);
|
||||
}
|
||||
|
||||
if (!$isStaticValue) {
|
||||
$value = str_replace("\n", "\n ", $value);
|
||||
$value = "static function () {\n\n return {$value};\n\n}";
|
||||
}
|
||||
|
||||
$file = $this->files[$key] = $this->getFile($key, true);
|
||||
// Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
|
||||
$ok = $this->write($file, "<?php return [{$expiry}, {$value}];\n", self::$startTime - 10) && $ok;
|
||||
|
||||
if ($allowCompile) {
|
||||
@opcache_invalidate($file, true);
|
||||
@opcache_compile_file($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ok && !is_writable($this->directory)) {
|
||||
throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doClear($namespace)
|
||||
{
|
||||
$this->values = [];
|
||||
|
||||
return $this->doCommonClear($namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doDelete(array $ids)
|
||||
{
|
||||
foreach ($ids as $id) {
|
||||
unset($this->values[$id]);
|
||||
}
|
||||
|
||||
return $this->doCommonDelete($ids);
|
||||
}
|
||||
|
||||
protected function doUnlink($file)
|
||||
{
|
||||
if (self::isSupported()) {
|
||||
@opcache_invalidate($file, true);
|
||||
}
|
||||
|
||||
return @unlink($file);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user