From 848a33ed3e4d06c50ee9f39d71a9aabf044d39bb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Nov 2016 17:25:06 +0100 Subject: [PATCH 1/3] [Cache] Implement PSR-16 SimpleCache v1.0 --- composer.json | 6 +- phpunit.xml.dist | 2 + .../Cache/Adapter/SimpleCacheAdapter.php | 75 ++++++ .../Cache/Exception/CacheException.php | 5 +- .../Exception/InvalidArgumentException.php | 5 +- .../Component/Cache/Simple/Psr6Cache.php | 225 ++++++++++++++++++ .../Tests/Adapter/SimpleCacheAdapterTest.php | 27 +++ src/Symfony/Component/Cache/composer.json | 10 +- src/Symfony/Component/Cache/phpunit.xml.dist | 2 + 9 files changed, 347 insertions(+), 10 deletions(-) create mode 100644 src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php create mode 100644 src/Symfony/Component/Cache/Simple/Psr6Cache.php create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php diff --git a/composer.json b/composer.json index 5fdff2193785..832c87c0e110 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "twig/twig": "~1.28|~2.0", "psr/cache": "~1.0", "psr/log": "~1.0", + "psr/simple-cache": "^1.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php56": "~1.0", @@ -79,7 +80,7 @@ "symfony/yaml": "self.version" }, "require-dev": { - "cache/integration-tests": "dev-master", + "cache/integration-tests": "^0.15.0", "doctrine/cache": "~1.6", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", @@ -99,7 +100,8 @@ "phpdocumentor/type-resolver": "<0.2.0" }, "provide": { - "psr/cache-implementation": "1.0" + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 336c320de95d..ce219c21e26c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -62,6 +62,8 @@ Cache\IntegrationTests Doctrine\Common\Cache + Symfony\Component\Cache + Symfony\Component\Cache\Traits Symfony\Component\Console Symfony\Component\HttpFoundation diff --git a/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php new file mode 100644 index 000000000000..f17662441041 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/SimpleCacheAdapter.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Psr\SimpleCache\CacheInterface; + +/** + * @author Nicolas Grekas + */ +class SimpleCacheAdapter extends AbstractAdapter +{ + private $pool; + private $miss; + + public function __construct(CacheInterface $pool, $namespace = '', $defaultLifetime = 0) + { + parent::__construct($namespace, $defaultLifetime); + + $this->pool = $pool; + $this->miss = new \stdClass(); + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + foreach ($this->pool->getMultiple($ids, $this->miss) as $key => $value) { + if ($this->miss !== $value) { + yield $key => $value; + } + } + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return $this->pool->has($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + return $this->pool->deleteMultiple($ids); + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + return $this->pool->setMultiple($values, 0 === $lifetime ? null : $lifetime); + } +} diff --git a/src/Symfony/Component/Cache/Exception/CacheException.php b/src/Symfony/Component/Cache/Exception/CacheException.php index d62b3e121389..e87b2db8fe73 100644 --- a/src/Symfony/Component/Cache/Exception/CacheException.php +++ b/src/Symfony/Component/Cache/Exception/CacheException.php @@ -11,8 +11,9 @@ namespace Symfony\Component\Cache\Exception; -use Psr\Cache\CacheException as CacheExceptionInterface; +use Psr\Cache\CacheException as Psr6CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheInterface; -class CacheException extends \Exception implements CacheExceptionInterface +class CacheException extends \Exception implements Psr6CacheInterface, SimpleCacheInterface { } diff --git a/src/Symfony/Component/Cache/Exception/InvalidArgumentException.php b/src/Symfony/Component/Cache/Exception/InvalidArgumentException.php index 334a3c3e2761..828bf3ed7799 100644 --- a/src/Symfony/Component/Cache/Exception/InvalidArgumentException.php +++ b/src/Symfony/Component/Cache/Exception/InvalidArgumentException.php @@ -11,8 +11,9 @@ namespace Symfony\Component\Cache\Exception; -use Psr\Cache\InvalidArgumentException as InvalidArgumentExceptionInterface; +use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; -class InvalidArgumentException extends \InvalidArgumentException implements InvalidArgumentExceptionInterface +class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface { } diff --git a/src/Symfony/Component/Cache/Simple/Psr6Cache.php b/src/Symfony/Component/Cache/Simple/Psr6Cache.php new file mode 100644 index 000000000000..d23af54069d8 --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/Psr6Cache.php @@ -0,0 +1,225 @@ + + * + * 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\Cache\CacheItemPoolInterface; +use Psr\Cache\CacheException as Psr6CacheException; +use Psr\SimpleCache\CacheInterface; +use Psr\SimpleCache\CacheException as SimpleCacheException; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + */ +class Psr6Cache implements CacheInterface +{ + private $pool; + private $createCacheItem; + + public function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + + if ($pool instanceof Adapter\AdapterInterface) { + $this->createCacheItem = \Closure::bind( + function ($key, $value, $allowInt = false) { + if ($allowInt && is_int($key)) { + $key = (string) $key; + } else { + CacheItem::validateKey($key); + } + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + + return $item; + }, + null, + CacheItem::class + ); + } + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + try { + $item = $this->pool->getItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + + return $item->isHit() ? $item->get() : $default; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + try { + if (null !== $f = $this->createCacheItem) { + $item = $f($key, $value); + } else { + $item = $this->pool->getItem($key)->set($value); + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + + return $this->pool->save($item); + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + try { + return $this->pool->deleteItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); + } + + try { + $items = $this->pool->getItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $values = array(); + + foreach ($items as $key => $item) { + $values[$key] = $item->isHit() ? $item->get() : $default; + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $valuesIsArray = is_array($values); + if (!$valuesIsArray && !$values instanceof \Traversable) { + throw new InvalidArgumentException(sprintf('Cache values must be array or Traversable, "%s" given', is_object($values) ? get_class($values) : gettype($values))); + } + $items = array(); + + try { + if (null !== $f = $this->createCacheItem) { + $valuesIsArray = false; + foreach ($values as $key => $value) { + $items[$key] = $f($key, $value, true); + } + } elseif ($valuesIsArray) { + $items = array(); + foreach ($values as $key => $value) { + $items[] = (string) $key; + } + $items = $this->pool->getItems($items); + } else { + foreach ($values as $key => $value) { + if (is_int($key)) { + $key = (string) $key; + } + $items[$key] = $this->pool->getItem($key)->set($value); + } + } + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + $ok = true; + + foreach ($items as $key => $item) { + if ($valuesIsArray) { + $item->set($values[$key]); + } + if (null !== $ttl) { + $item->expiresAfter($ttl); + } + $ok = $this->pool->saveDeferred($item) && $ok; + } + + return $this->pool->commit() && $ok; + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + if ($keys instanceof \Traversable) { + $keys = iterator_to_array($keys, false); + } elseif (!is_array($keys)) { + throw new InvalidArgumentException(sprintf('Cache keys must be array or Traversable, "%s" given', is_object($keys) ? get_class($keys) : gettype($keys))); + } + + try { + return $this->pool->deleteItems($keys); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + try { + return $this->pool->hasItem($key); + } catch (SimpleCacheException $e) { + throw $e; + } catch (Psr6CacheException $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php new file mode 100644 index 000000000000..3f3f17b88347 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php @@ -0,0 +1,27 @@ + + * + * 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\FilesystemAdapter; +use Symfony\Component\Cache\Adapter\SimpleCacheAdapter; +use Symfony\Component\Cache\Simple\Psr6Cache; + +/** + * @group time-sensitive + */ +class SimpleCacheAdapterTest extends AdapterTestCase +{ + public function createCachePool($defaultLifetime = 0) + { + return new SimpleCacheAdapter(new Psr6Cache(new FilesystemAdapter()), '', $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index 6cb772a80fc9..f3bc3988fa42 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/cache", "type": "library", - "description": "Symfony implementation of PSR-6", + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", "keywords": ["caching", "psr6"], "homepage": "https://symfony.com", "license": "MIT", @@ -16,15 +16,17 @@ } ], "provide": { - "psr/cache-implementation": "1.0" + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0" }, "require": { "php": ">=5.5.9", "psr/cache": "~1.0", - "psr/log": "~1.0" + "psr/log": "~1.0", + "psr/simple-cache": "^1.0" }, "require-dev": { - "cache/integration-tests": "dev-master", + "cache/integration-tests": "^0.15.0", "doctrine/cache": "~1.6", "doctrine/dbal": "~2.4", "predis/predis": "~1.0" diff --git a/src/Symfony/Component/Cache/phpunit.xml.dist b/src/Symfony/Component/Cache/phpunit.xml.dist index 19b549627721..c5884dd62506 100644 --- a/src/Symfony/Component/Cache/phpunit.xml.dist +++ b/src/Symfony/Component/Cache/phpunit.xml.dist @@ -34,6 +34,8 @@ Cache\IntegrationTests Doctrine\Common\Cache + Symfony\Component\Cache + Symfony\Component\Cache\Traits From 99ae9d6a35d2094946f37b4e7094fd5b619770dd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Jan 2017 19:50:14 +0100 Subject: [PATCH 2/3] [Cache] Move adapter implementations to traits --- .../Cache/Adapter/AbstractAdapter.php | 187 +--------------- .../Component/Cache/Adapter/ArrayAdapter.php | 82 +------ .../Cache/Adapter/PhpArrayAdapter.php | 109 +-------- .../Component/Cache/Traits/AbstractTrait.php | 209 ++++++++++++++++++ .../ApcuAdapter.php => Traits/ApcuTrait.php} | 6 +- .../Component/Cache/Traits/ArrayTrait.php | 100 +++++++++ .../DoctrineTrait.php} | 6 +- .../FilesystemCommonTrait.php} | 4 +- .../FilesystemTrait.php} | 8 +- .../MemcachedTrait.php} | 8 +- .../PdoAdapter.php => Traits/PdoTrait.php} | 9 +- .../Component/Cache/Traits/PhpArrayTrait.php | 131 +++++++++++ .../PhpFilesTrait.php} | 8 +- .../RedisTrait.php} | 6 +- 14 files changed, 481 insertions(+), 392 deletions(-) create mode 100644 src/Symfony/Component/Cache/Traits/AbstractTrait.php rename src/Symfony/Component/Cache/{Adapter/ApcuAdapter.php => Traits/ApcuTrait.php} (96%) create mode 100644 src/Symfony/Component/Cache/Traits/ArrayTrait.php rename src/Symfony/Component/Cache/{Adapter/DoctrineAdapter.php => Traits/DoctrineTrait.php} (96%) rename src/Symfony/Component/Cache/{Adapter/FilesystemAdapterTrait.php => Traits/FilesystemCommonTrait.php} (97%) rename src/Symfony/Component/Cache/{Adapter/FilesystemAdapter.php => Traits/FilesystemTrait.php} (94%) rename src/Symfony/Component/Cache/{Adapter/MemcachedAdapter.php => Traits/MemcachedTrait.php} (98%) rename src/Symfony/Component/Cache/{Adapter/PdoAdapter.php => Traits/PdoTrait.php} (99%) create mode 100644 src/Symfony/Component/Cache/Traits/PhpArrayTrait.php rename src/Symfony/Component/Cache/{Adapter/PhpFilesAdapter.php => Traits/PhpFilesTrait.php} (97%) rename src/Symfony/Component/Cache/{Adapter/RedisAdapter.php => Traits/RedisTrait.php} (99%) diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 2134a0efb7cd..c761b9a2010b 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -13,31 +13,24 @@ use Psr\Cache\CacheItemInterface; use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\AbstractTrait; /** * @author Nicolas Grekas */ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface { - use LoggerAwareTrait; + use AbstractTrait; private static $apcuSupported; private static $phpFilesSupported; - private $namespace; - private $deferred = array(); private $createCacheItem; private $mergeByLifetime; - /** - * @var int|null The maximum length to enforce for identifiers or null when no limit applies - */ - protected $maxIdLength; - protected function __construct($namespace = '', $defaultLifetime = 0) { $this->namespace = '' === $namespace ? '' : $this->getId($namespace).':'; @@ -130,52 +123,6 @@ public static function createConnection($dsn, array $options = array()) throw new InvalidArgumentException(sprintf('Unsupported DSN: %s.', $dsn)); } - /** - * 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 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} */ @@ -225,87 +172,6 @@ public function getItems(array $keys = array()) return $this->generateItems($items, $ids); } - /** - * {@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', array('key' => $key, 'exception' => $e)); - - return false; - } - } - - /** - * {@inheritdoc} - */ - public function clear() - { - $this->deferred = array(); - - try { - return $this->doClear($this->namespace); - } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to clear the cache', array('exception' => $e)); - - return false; - } - } - - /** - * {@inheritdoc} - */ - public function deleteItem($key) - { - return $this->deleteItems(array($key)); - } - - /** - * {@inheritdoc} - */ - public function deleteItems(array $keys) - { - $ids = array(); - - 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(array($id))) { - continue; - } - } catch (\Exception $e) { - } - CacheItem::log($this->logger, 'Failed to delete key "{key}"', array('key' => $key, 'exception' => $e)); - $ok = false; - } - - return $ok; - } - /** * {@inheritdoc} */ @@ -394,47 +260,6 @@ public function __destruct() } } - /** - * Like the native unserialize() function but throws an exception if anything goes wrong. - * - * @param string $value - * - * @return mixed - * - * @throws \Exception - */ - protected static function unserialize($value) - { - 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) - { - CacheItem::validateKey($key); - - if (null === $this->maxIdLength) { - return $this->namespace.$key; - } - if (strlen($id = $this->namespace.$key) > $this->maxIdLength) { - $id = $this->namespace.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22); - } - - return $id; - } - private function generateItems($items, &$keys) { $f = $this->createCacheItem; @@ -453,12 +278,4 @@ private function generateItems($items, &$keys) yield $key => $f($key, null, false); } } - - /** - * @internal - */ - public static function handleUnserializeCallback($class) - { - throw new \DomainException('Class not found: '.$class); - } } diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index 2898ba50cdc9..45c19c7a6c7a 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -13,19 +13,16 @@ use Psr\Cache\CacheItemInterface; use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Traits\ArrayTrait; /** * @author Nicolas Grekas */ class ArrayAdapter implements AdapterInterface, LoggerAwareInterface { - use LoggerAwareTrait; + use ArrayTrait; - private $storeSerialized; - private $values = array(); - private $expiries = array(); private $createCacheItem; /** @@ -86,49 +83,7 @@ public function getItems(array $keys = array()) CacheItem::validateKey($key); } - return $this->generateItems($keys, time()); - } - - /** - * Returns all cached values, with cache miss as null. - * - * @return array - */ - public function getValues() - { - return $this->values; - } - - /** - * {@inheritdoc} - */ - public function hasItem($key) - { - CacheItem::validateKey($key); - - return isset($this->expiries[$key]) && ($this->expiries[$key] >= time() || !$this->deleteItem($key)); - } - - /** - * {@inheritdoc} - */ - public function clear() - { - $this->values = $this->expiries = array(); - - return true; - } - - /** - * {@inheritdoc} - */ - public function deleteItem($key) - { - CacheItem::validateKey($key); - - unset($this->values[$key], $this->expiries[$key]); - - return true; + return $this->generateItems($keys, time(), $this->createCacheItem); } /** @@ -196,35 +151,4 @@ public function commit() { return true; } - - private function generateItems(array $keys, $now) - { - $f = $this->createCacheItem; - - foreach ($keys as $i => $key) { - try { - if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] >= $now || !$this->deleteItem($key))) { - $this->values[$key] = $value = null; - } elseif (!$this->storeSerialized) { - $value = $this->values[$key]; - } elseif ('b:0;' === $value = $this->values[$key]) { - $value = false; - } elseif (false === $value = unserialize($value)) { - $this->values[$key] = $value = null; - $isHit = false; - } - } catch (\Exception $e) { - CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', array('key' => $key, 'exception' => $e)); - $this->values[$key] = $value = null; - $isHit = false; - } - unset($keys[$i]); - - yield $key => $f($key, $value, $isHit); - } - - foreach ($keys as $key) { - yield $key => $f($key, null, false); - } - } } diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index e4d8ad5eea31..ead021386461 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -15,6 +15,7 @@ use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\PhpArrayTrait; /** * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. @@ -25,10 +26,9 @@ */ class PhpArrayAdapter implements AdapterInterface { - private $file; - private $values; + use PhpArrayTrait; + private $createCacheItem; - private $fallbackPool; /** * @param string $file The PHP file were values are cached @@ -75,89 +75,6 @@ public static function create($file, CacheItemPoolInterface $fallbackPool) return $fallbackPool; } - /** - * 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)); - } - } - - $dump = <<<'EOF' - $value) { - CacheItem::validateKey(is_int($key) ? (string) $key : $key); - - if (null === $value || is_object($value)) { - try { - $value = serialize($value); - } catch (\Exception $e) { - throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, get_class($value)), 0, $e); - } - } elseif (is_array($value)) { - try { - $serialized = serialize($value); - $unserialized = unserialize($serialized); - } catch (\Exception $e) { - throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 0, $e); - } - // Store arrays serialized if they contain any objects or references - if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) { - $value = $serialized; - } - } elseif (is_string($value)) { - // Serialize strings if they could be confused with serialized objects or arrays - if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { - $value = serialize($value); - } - } elseif (!is_scalar($value)) { - throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, gettype($value))); - } - - $dump .= var_export($key, true).' => '.var_export($value, true).",\n"; - } - - $dump .= "\n);\n"; - $dump = str_replace("' . \"\\0\" . '", "\0", $dump); - - $tmpFile = uniqid($this->file, true); - - file_put_contents($tmpFile, $dump); - @chmod($tmpFile, 0666); - unset($serialized, $unserialized, $value, $dump); - - @rename($tmpFile, $this->file); - - $this->values = (include $this->file) ?: array(); - } - /** * {@inheritdoc} */ @@ -228,18 +145,6 @@ public function hasItem($key) return isset($this->values[$key]) || $this->fallbackPool->hasItem($key); } - /** - * {@inheritdoc} - */ - public function clear() - { - $this->values = array(); - - $cleared = @unlink($this->file) || !file_exists($this->file); - - return $this->fallbackPool->clear() && $cleared; - } - /** * {@inheritdoc} */ @@ -317,14 +222,6 @@ public function commit() return $this->fallbackPool->commit(); } - /** - * Load the cache file. - */ - private function initialize() - { - $this->values = file_exists($this->file) ? (include $this->file ?: array()) : array(); - } - /** * Generator for items. * diff --git a/src/Symfony/Component/Cache/Traits/AbstractTrait.php b/src/Symfony/Component/Cache/Traits/AbstractTrait.php new file mode 100644 index 000000000000..375ccf7620d8 --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/AbstractTrait.php @@ -0,0 +1,209 @@ + + * + * 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 + * + * @internal + */ +trait AbstractTrait +{ + use LoggerAwareTrait; + + private $namespace; + private $deferred = array(); + + /** + * @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 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', array('key' => $key, 'exception' => $e)); + + return false; + } + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->deferred = array(); + + try { + return $this->doClear($this->namespace); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to clear the cache', array('exception' => $e)); + + return false; + } + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + return $this->deleteItems(array($key)); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + $ids = array(); + + 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(array($id))) { + continue; + } + } catch (\Exception $e) { + } + CacheItem::log($this->logger, 'Failed to delete key "{key}"', array('key' => $key, 'exception' => $e)); + $ok = false; + } + + return $ok; + } + + /** + * Like the native unserialize() function but throws an exception if anything goes wrong. + * + * @param string $value + * + * @return mixed + * + * @throws \Exception + */ + protected static function unserialize($value) + { + 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) + { + CacheItem::validateKey($key); + + if (null === $this->maxIdLength) { + return $this->namespace.$key; + } + if (strlen($id = $this->namespace.$key) > $this->maxIdLength) { + $id = $this->namespace.substr_replace(base64_encode(hash('sha256', $key, true)), ':', -22); + } + + return $id; + } + + /** + * @internal + */ + public static function handleUnserializeCallback($class) + { + throw new \DomainException('Class not found: '.$class); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php b/src/Symfony/Component/Cache/Traits/ApcuTrait.php similarity index 96% rename from src/Symfony/Component/Cache/Adapter/ApcuAdapter.php rename to src/Symfony/Component/Cache/Traits/ApcuTrait.php index 67afd5c72a89..f0ca04d76b56 100644 --- a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php +++ b/src/Symfony/Component/Cache/Traits/ApcuTrait.php @@ -9,15 +9,17 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\CacheException; /** * @author Nicolas Grekas + * + * @internal */ -class ApcuAdapter extends AbstractAdapter +trait ApcuTrait { public static function isSupported() { diff --git a/src/Symfony/Component/Cache/Traits/ArrayTrait.php b/src/Symfony/Component/Cache/Traits/ArrayTrait.php new file mode 100644 index 000000000000..3fb5fa36bef2 --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/ArrayTrait.php @@ -0,0 +1,100 @@ + + * + * 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 + * + * @internal + */ +trait ArrayTrait +{ + use LoggerAwareTrait; + + private $storeSerialized; + private $values = array(); + private $expiries = array(); + + /** + * Returns all cached values, with cache miss as null. + * + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + CacheItem::validateKey($key); + + return isset($this->expiries[$key]) && ($this->expiries[$key] >= time() || !$this->deleteItem($key)); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->values = $this->expiries = array(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + CacheItem::validateKey($key); + + unset($this->values[$key], $this->expiries[$key]); + + return true; + } + + private function generateItems(array $keys, $now, $f) + { + foreach ($keys as $i => $key) { + try { + if (!$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] >= $now || !$this->deleteItem($key))) { + $this->values[$key] = $value = null; + } elseif (!$this->storeSerialized) { + $value = $this->values[$key]; + } elseif ('b:0;' === $value = $this->values[$key]) { + $value = false; + } elseif (false === $value = unserialize($value)) { + $this->values[$key] = $value = null; + $isHit = false; + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to unserialize key "{key}"', array('key' => $key, 'exception' => $e)); + $this->values[$key] = $value = null; + $isHit = false; + } + unset($keys[$i]); + + yield $key => $f($key, $value, $isHit); + } + + foreach ($keys as $key) { + yield $key => $f($key, null, false); + } + } +} diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php b/src/Symfony/Component/Cache/Traits/DoctrineTrait.php similarity index 96% rename from src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php rename to src/Symfony/Component/Cache/Traits/DoctrineTrait.php index ed91bf56cd0e..3655af3bcfb9 100644 --- a/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php +++ b/src/Symfony/Component/Cache/Traits/DoctrineTrait.php @@ -9,14 +9,16 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Doctrine\Common\Cache\CacheProvider; /** * @author Nicolas Grekas + * + * @internal */ -class DoctrineAdapter extends AbstractAdapter +trait DoctrineTrait { private $provider; diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php similarity index 97% rename from src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php rename to src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php index 156fc5c1fb63..f9c9b396fc6e 100644 --- a/src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Symfony\Component\Cache\Exception\InvalidArgumentException; @@ -18,7 +18,7 @@ * * @internal */ -trait FilesystemAdapterTrait +trait FilesystemCommonTrait { private $directory; private $tmp; diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php similarity index 94% rename from src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php rename to src/Symfony/Component/Cache/Traits/FilesystemTrait.php index 1c62641cf6d6..a06c964adb14 100644 --- a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php @@ -9,16 +9,18 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Symfony\Component\Cache\Exception\CacheException; /** * @author Nicolas Grekas + * + * @internal */ -class FilesystemAdapter extends AbstractAdapter +trait FilesystemTrait { - use FilesystemAdapterTrait; + use FilesystemCommonTrait; public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) { diff --git a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php similarity index 98% rename from src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php rename to src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 46b523f726ac..957595e2cb04 100644 --- a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; @@ -17,8 +17,10 @@ /** * @author Rob Frawley 2nd * @author Nicolas Grekas + * + * @internal */ -class MemcachedAdapter extends AbstractAdapter +trait MemcachedTrait { private static $defaultClientOptions = array( 'persistent_id' => null, @@ -26,8 +28,6 @@ class MemcachedAdapter extends AbstractAdapter 'password' => null, ); - protected $maxIdLength = 250; - private $client; public static function isSupported() diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Traits/PdoTrait.php similarity index 99% rename from src/Symfony/Component/Cache/Adapter/PdoAdapter.php rename to src/Symfony/Component/Cache/Traits/PdoTrait.php index 3fa3a40533d9..90796e015314 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Traits/PdoTrait.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\ServerInfoAwareConnection; @@ -17,10 +17,11 @@ use Doctrine\DBAL\Schema\Schema; use Symfony\Component\Cache\Exception\InvalidArgumentException; -class PdoAdapter extends AbstractAdapter +/** + * @internal + */ +trait PdoTrait { - protected $maxIdLength = 255; - private $conn; private $dsn; private $driver; diff --git a/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php new file mode 100644 index 000000000000..97a923bfe124 --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/PhpArrayTrait.php @@ -0,0 +1,131 @@ + + * + * 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; + +/** + * @author Titouan Galopin + * @author Nicolas Grekas + * + * @internal + */ +trait PhpArrayTrait +{ + private $file; + private $values; + private $fallbackPool; + + /** + * 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)); + } + } + + $dump = <<<'EOF' + $value) { + CacheItem::validateKey(is_int($key) ? (string) $key : $key); + + if (null === $value || is_object($value)) { + try { + $value = serialize($value); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, get_class($value)), 0, $e); + } + } elseif (is_array($value)) { + try { + $serialized = serialize($value); + $unserialized = unserialize($serialized); + } catch (\Exception $e) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable array value.', $key), 0, $e); + } + // Store arrays serialized if they contain any objects or references + if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) { + $value = $serialized; + } + } elseif (is_string($value)) { + // Serialize strings if they could be confused with serialized objects or arrays + if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { + $value = serialize($value); + } + } elseif (!is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, gettype($value))); + } + + $dump .= var_export($key, true).' => '.var_export($value, true).",\n"; + } + + $dump .= "\n);\n"; + $dump = str_replace("' . \"\\0\" . '", "\0", $dump); + + $tmpFile = uniqid($this->file, true); + + file_put_contents($tmpFile, $dump); + @chmod($tmpFile, 0666); + unset($serialized, $unserialized, $value, $dump); + + @rename($tmpFile, $this->file); + + $this->values = (include $this->file) ?: array(); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->values = array(); + + $cleared = @unlink($this->file) || !file_exists($this->file); + + return $this->fallbackPool->clear() && $cleared; + } + + /** + * Load the cache file. + */ + private function initialize() + { + $this->values = @(include $this->file) ?: array(); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php similarity index 97% rename from src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php rename to src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index befa38d8d46d..d83587e3bcf6 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Symfony\Component\Cache\Exception\CacheException; use Symfony\Component\Cache\Exception\InvalidArgumentException; @@ -17,10 +17,12 @@ /** * @author Piotr Stankowski * @author Nicolas Grekas + * + * @internal */ -class PhpFilesAdapter extends AbstractAdapter +trait PhpFilesTrait { - use FilesystemAdapterTrait; + use FilesystemCommonTrait; private $includeHandler; diff --git a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php similarity index 99% rename from src/Symfony/Component/Cache/Adapter/RedisAdapter.php rename to src/Symfony/Component/Cache/Traits/RedisTrait.php index 7fd6921e3f3d..4ca468a68fde 100644 --- a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Cache\Adapter; +namespace Symfony\Component\Cache\Traits; use Predis\Connection\Factory; use Predis\Connection\Aggregate\PredisCluster; @@ -19,8 +19,10 @@ /** * @author Aurimas Niekis * @author Nicolas Grekas + * + * @internal */ -class RedisAdapter extends AbstractAdapter +trait RedisTrait { private static $defaultConnectionOptions = array( 'class' => null, From 6219dd6b6243f18c5fa3033804a0dc87eced5944 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Jan 2017 19:51:00 +0100 Subject: [PATCH 3/3] [Cache] Create PSR-16 variants of all PSR-6 adapters --- .../Component/Cache/Adapter/ApcuAdapter.php | 24 ++ .../Cache/Adapter/DoctrineAdapter.php | 27 ++ .../Cache/Adapter/FilesystemAdapter.php | 25 ++ .../Cache/Adapter/MemcachedAdapter.php | 26 ++ .../Component/Cache/Adapter/PdoAdapter.php | 52 ++++ .../Cache/Adapter/PhpFilesAdapter.php | 32 +++ .../Component/Cache/Adapter/RedisAdapter.php | 27 ++ src/Symfony/Component/Cache/CHANGELOG.md | 8 + .../Component/Cache/Simple/AbstractCache.php | 177 ++++++++++++ .../Component/Cache/Simple/ApcuCache.php | 24 ++ .../Component/Cache/Simple/ArrayCache.php | 147 ++++++++++ .../Component/Cache/Simple/ChainCache.php | 222 +++++++++++++++ .../Component/Cache/Simple/DoctrineCache.php | 27 ++ .../Cache/Simple/FilesystemCache.php | 25 ++ .../Component/Cache/Simple/MemcachedCache.php | 26 ++ .../Component/Cache/Simple/NullCache.php | 86 ++++++ .../Component/Cache/Simple/PdoCache.php | 52 ++++ .../Component/Cache/Simple/PhpArrayCache.php | 256 ++++++++++++++++++ .../Component/Cache/Simple/PhpFilesCache.php | 32 +++ .../Component/Cache/Simple/RedisCache.php | 27 ++ .../Component/Cache/Simple/TraceableCache.php | 190 +++++++++++++ .../Cache/Tests/Adapter/ApcuAdapterTest.php | 2 +- .../Tests/Adapter/MemcachedAdapterTest.php | 2 +- .../Tests/Adapter/PhpArrayAdapterTest.php | 2 +- .../PhpArrayAdapterWithFallbackTest.php | 7 +- .../Tests/Adapter/SimpleCacheAdapterTest.php | 5 +- .../Tests/Adapter/TraceableAdapterTest.php | 64 ++--- .../Tests/Simple/AbstractRedisCacheTest.php | 47 ++++ .../Cache/Tests/Simple/ApcuCacheTest.php | 35 +++ .../Cache/Tests/Simple/ArrayCacheTest.php | 25 ++ .../Cache/Tests/Simple/CacheTestCase.php | 66 +++++ .../Cache/Tests/Simple/ChainCacheTest.php | 45 +++ .../Cache/Tests/Simple/DoctrineCacheTest.php | 31 +++ .../Tests/Simple/FilesystemCacheTest.php | 25 ++ .../Cache/Tests/Simple/MemcachedCacheTest.php | 165 +++++++++++ .../Cache/Tests/Simple/NullCacheTest.php | 95 +++++++ .../Cache/Tests/Simple/PdoCacheTest.php | 44 +++ .../Cache/Tests/Simple/PdoDbalCacheTest.php | 45 +++ .../Cache/Tests/Simple/PhpArrayCacheTest.php | 139 ++++++++++ .../Simple/PhpArrayCacheWithFallbackTest.php | 54 ++++ .../Cache/Tests/Simple/PhpFilesCacheTest.php | 33 +++ .../Cache/Tests/Simple/Psr6CacheTest.php | 26 ++ .../Tests/Simple/RedisArrayCacheTest.php | 24 ++ .../Cache/Tests/Simple/RedisCacheTest.php | 82 ++++++ .../Cache/Tests/Simple/TraceableCacheTest.php | 170 ++++++++++++ .../Component/Cache/Traits/ApcuTrait.php | 2 +- .../Component/Cache/Traits/DoctrineTrait.php | 9 - .../Cache/Traits/FilesystemTrait.php | 6 - .../Component/Cache/Traits/MemcachedTrait.php | 2 +- .../Component/Cache/Traits/PdoTrait.php | 28 +- .../Component/Cache/Traits/PhpFilesTrait.php | 12 - .../Component/Cache/Traits/RedisTrait.php | 2 +- 52 files changed, 2707 insertions(+), 99 deletions(-) create mode 100644 src/Symfony/Component/Cache/Adapter/ApcuAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/PdoAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/RedisAdapter.php create mode 100644 src/Symfony/Component/Cache/Simple/AbstractCache.php create mode 100644 src/Symfony/Component/Cache/Simple/ApcuCache.php create mode 100644 src/Symfony/Component/Cache/Simple/ArrayCache.php create mode 100644 src/Symfony/Component/Cache/Simple/ChainCache.php create mode 100644 src/Symfony/Component/Cache/Simple/DoctrineCache.php create mode 100644 src/Symfony/Component/Cache/Simple/FilesystemCache.php create mode 100644 src/Symfony/Component/Cache/Simple/MemcachedCache.php create mode 100644 src/Symfony/Component/Cache/Simple/NullCache.php create mode 100644 src/Symfony/Component/Cache/Simple/PdoCache.php create mode 100644 src/Symfony/Component/Cache/Simple/PhpArrayCache.php create mode 100644 src/Symfony/Component/Cache/Simple/PhpFilesCache.php create mode 100644 src/Symfony/Component/Cache/Simple/RedisCache.php create mode 100644 src/Symfony/Component/Cache/Simple/TraceableCache.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/AbstractRedisCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/ApcuCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/ArrayCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/DoctrineCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/FilesystemCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/Psr6CacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/RedisArrayCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/RedisCacheTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Simple/TraceableCacheTest.php diff --git a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php new file mode 100644 index 000000000000..713e9fd7d8e8 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Traits\ApcuTrait; + +class ApcuAdapter extends AbstractAdapter +{ + use ApcuTrait; + + public function __construct($namespace = '', $defaultLifetime = 0, $version = null) + { + $this->init($namespace, $defaultLifetime, $version); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php b/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php new file mode 100644 index 000000000000..befff7ca8ec7 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Doctrine\Common\Cache\CacheProvider; +use Symfony\Component\Cache\Traits\DoctrineTrait; + +class DoctrineAdapter extends AbstractAdapter +{ + use DoctrineTrait; + + public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0) + { + parent::__construct('', $defaultLifetime); + $this->provider = $provider; + $provider->setNamespace($namespace); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php new file mode 100644 index 000000000000..f37cde290f92 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Traits\FilesystemTrait; + +class FilesystemAdapter extends AbstractAdapter +{ + use FilesystemTrait; + + public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) + { + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php new file mode 100644 index 000000000000..5c8784e69cf4 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/MemcachedAdapter.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Traits\MemcachedTrait; + +class MemcachedAdapter extends AbstractAdapter +{ + use MemcachedTrait; + + protected $maxIdLength = 250; + + public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0) + { + $this->init($client, $namespace, $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php new file mode 100644 index 000000000000..832185629b05 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Traits\PdoTrait; + +class PdoAdapter extends AbstractAdapter +{ + use PdoTrait; + + protected $maxIdLength = 255; + + /** + * Constructor. + * + * 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. + * + * 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: array()] + * + * @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null + * @param string $namespace + * @param int $defaultLifetime + * @param array $options An associative array of options + * + * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string + * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = array()) + { + $this->init($connOrDsn, $namespace, $defaultLifetime, $options); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php new file mode 100644 index 000000000000..12480c7436f0 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Traits\PhpFilesTrait; + +class PhpFilesAdapter extends AbstractAdapter +{ + use PhpFilesTrait; + + public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) + { + if (!static::isSupported()) { + throw new CacheException('OPcache is not enabled'); + } + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + + $e = new \Exception(); + $this->includeHandler = function () use ($e) { throw $e; }; + } +} diff --git a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php new file mode 100644 index 000000000000..75cb764f40c6 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Traits\RedisTrait; + +class RedisAdapter extends AbstractAdapter +{ + use RedisTrait; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient + */ + public function __construct($redisClient, $namespace = '', $defaultLifetime = 0) + { + $this->init($redisClient, $namespace, $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 94bbe13699b4..57a0780ae207 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +3.3.0 +----- + + * added PSR-16 "Simple Cache" implementations for all existing PSR-6 adapters + * added Psr6Cache and SimpleCacheAdapter for bidirectional interoperability between PSR-6 and PSR-16 + * added MemcachedAdapter (PSR-6) and MemcachedCache (PSR-16) + * added TraceableAdapter (PSR-6) and TraceableCache (PSR-16) + 3.2.0 ----- diff --git a/src/Symfony/Component/Cache/Simple/AbstractCache.php b/src/Symfony/Component/Cache/Simple/AbstractCache.php new file mode 100644 index 000000000000..4c44b9b323bb --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/AbstractCache.php @@ -0,0 +1,177 @@ + + * + * 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; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\AbstractTrait; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractCache implements CacheInterface, LoggerAwareInterface +{ + use AbstractTrait { + deleteItems as private; + AbstractTrait::deleteItem as delete; + AbstractTrait::hasItem as has; + } + + private $defaultLifetime; + + protected function __construct($namespace = '', $defaultLifetime = 0) + { + $this->defaultLifetime = max(0, (int) $defaultLifetime); + $this->namespace = '' === $namespace ? '' : $this->getId($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(array($id)) as $value) { + return $value; + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch key "{key}"', array('key' => $key, 'exception' => $e)); + } + + return $default; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + CacheItem::validateKey($key); + + return $this->setMultiple(array($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 = array(); + + foreach ($keys as $key) { + $ids[] = $this->getId($key); + } + try { + $values = $this->doFetch($ids); + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch requested values', array('keys' => $keys, 'exception' => $e)); + $values = array(); + } + $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 = array(); + + 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 || array() === $e) { + return true; + } + $keys = array(); + foreach (is_array($e) ? $e : array_keys($valuesById) as $id) { + $keys[] = substr($id, strlen($this->namespace)); + } + CacheItem::log($this->logger, 'Failed to save values', array('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) { + $key = $keys[$id]; + unset($keys[$id]); + yield $key => $value; + } + } catch (\Exception $e) { + CacheItem::log($this->logger, 'Failed to fetch requested values', array('keys' => array_values($keys), 'exception' => $e)); + } + + foreach ($keys as $key) { + yield $key => $default; + } + } +} diff --git a/src/Symfony/Component/Cache/Simple/ApcuCache.php b/src/Symfony/Component/Cache/Simple/ApcuCache.php new file mode 100644 index 000000000000..16aa8661f07a --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/ApcuCache.php @@ -0,0 +1,24 @@ + + * + * 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; + +class ApcuCache extends AbstractCache +{ + use ApcuTrait; + + public function __construct($namespace = '', $defaultLifetime = 0, $version = null) + { + $this->init($namespace, $defaultLifetime, $version); + } +} diff --git a/src/Symfony/Component/Cache/Simple/ArrayCache.php b/src/Symfony/Component/Cache/Simple/ArrayCache.php new file mode 100644 index 000000000000..a89768b0e233 --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/ArrayCache.php @@ -0,0 +1,147 @@ + + * + * 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; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\ArrayTrait; + +/** + * @author Nicolas Grekas + */ +class ArrayCache implements CacheInterface, LoggerAwareInterface +{ + 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($defaultLifetime = 0, $storeSerialized = true) + { + $this->defaultLifetime = (int) $defaultLifetime; + $this->storeSerialized = $storeSerialized; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + foreach ($this->getMultiple(array($key), $default) as $v) { + return $v; + } + } + + /** + * {@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) { + CacheItem::validateKey($key); + } + + return $this->generateItems($keys, time(), 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) + { + CacheItem::validateKey($key); + + return $this->setMultiple(array($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 = array(); + + foreach ($values as $key => $value) { + is_int($key) || CacheItem::validateKey($key); + $valuesArray[$key] = $value; + } + if (false === $ttl = $this->normalizeTtl($ttl)) { + return $this->deleteMultiple(array_keys($valuesArray)); + } + if ($this->storeSerialized) { + foreach ($valuesArray as $key => $value) { + try { + $valuesArray[$key] = serialize($value); + } catch (\Exception $e) { + $type = is_object($value) ? get_class($value) : gettype($value); + CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', array('key' => $key, 'type' => $type, 'exception' => $e)); + + return false; + } + } + } + $expiry = 0 < $ttl ? time() + $ttl : PHP_INT_MAX; + + foreach ($valuesArray as $key => $value) { + $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))); + } +} diff --git a/src/Symfony/Component/Cache/Simple/ChainCache.php b/src/Symfony/Component/Cache/Simple/ChainCache.php new file mode 100644 index 000000000000..08bb4881b463 --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/ChainCache.php @@ -0,0 +1,222 @@ + + * + * 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; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * 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. + * + * @author Nicolas Grekas + */ +class ChainCache implements CacheInterface +{ + private $miss; + private $caches = array(); + private $defaultLifetime; + private $cacheCount; + + /** + * @param CacheInterface[] $caches The ordered list of caches used to fetch cached items + * @param int $defaultLifetime The lifetime of items propagated from lower caches to upper ones + */ + public function __construct(array $caches, $defaultLifetime = 0) + { + if (!$caches) { + throw new InvalidArgumentException('At least one cache must be specified.'); + } + + foreach ($caches as $cache) { + if (!$cache instanceof CacheInterface) { + throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_class($cache), CacheInterface::class)); + } + } + + $this->miss = new \stdClass(); + $this->caches = array_values($caches); + $this->cacheCount = count($this->caches); + $this->defaultLifetime = 0 < $defaultLifetime ? (int) $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 = array(); + $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 = array(); + + 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; + } +} diff --git a/src/Symfony/Component/Cache/Simple/DoctrineCache.php b/src/Symfony/Component/Cache/Simple/DoctrineCache.php new file mode 100644 index 000000000000..395c34dd81bd --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/DoctrineCache.php @@ -0,0 +1,27 @@ + + * + * 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\Traits\DoctrineTrait; + +class DoctrineCache extends AbstractCache +{ + use DoctrineTrait; + + public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0) + { + parent::__construct('', $defaultLifetime); + $this->provider = $provider; + $provider->setNamespace($namespace); + } +} diff --git a/src/Symfony/Component/Cache/Simple/FilesystemCache.php b/src/Symfony/Component/Cache/Simple/FilesystemCache.php new file mode 100644 index 000000000000..a60312ea57fe --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/FilesystemCache.php @@ -0,0 +1,25 @@ + + * + * 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\FilesystemTrait; + +class FilesystemCache extends AbstractCache +{ + use FilesystemTrait; + + public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) + { + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + } +} diff --git a/src/Symfony/Component/Cache/Simple/MemcachedCache.php b/src/Symfony/Component/Cache/Simple/MemcachedCache.php new file mode 100644 index 000000000000..1d5ee73c31d2 --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/MemcachedCache.php @@ -0,0 +1,26 @@ + + * + * 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\MemcachedTrait; + +class MemcachedCache extends AbstractCache +{ + use MemcachedTrait; + + protected $maxIdLength = 250; + + public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0) + { + $this->init($client, $namespace, $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Simple/NullCache.php b/src/Symfony/Component/Cache/Simple/NullCache.php new file mode 100644 index 000000000000..fa986aebd11b --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/NullCache.php @@ -0,0 +1,86 @@ + + * + * 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; + +/** + * @author Nicolas Grekas + */ +class NullCache implements CacheInterface +{ + /** + * {@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; + } +} diff --git a/src/Symfony/Component/Cache/Simple/PdoCache.php b/src/Symfony/Component/Cache/Simple/PdoCache.php new file mode 100644 index 000000000000..3e698e2f952c --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/PdoCache.php @@ -0,0 +1,52 @@ + + * + * 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\PdoTrait; + +class PdoCache extends AbstractCache +{ + use PdoTrait; + + protected $maxIdLength = 255; + + /** + * Constructor. + * + * 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. + * + * 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: array()] + * + * @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null + * @param string $namespace + * @param int $defaultLifetime + * @param array $options An associative array of options + * + * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string + * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + * @throws InvalidArgumentException When namespace contains invalid characters + */ + public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = array()) + { + $this->init($connOrDsn, $namespace, $defaultLifetime, $options); + } +} diff --git a/src/Symfony/Component/Cache/Simple/PhpArrayCache.php b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php new file mode 100644 index 000000000000..3c61f5e8f645 --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/PhpArrayCache.php @@ -0,0 +1,256 @@ + + * + * 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; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Traits\PhpArrayTrait; + +/** + * Caches items at warm up time using a PHP array that is stored in shared memory by OPCache since PHP 7.0. + * Warmed up items are read-only and run-time discovered items are cached using a fallback adapter. + * + * @author Titouan Galopin + * @author Nicolas Grekas + */ +class PhpArrayCache implements CacheInterface +{ + use PhpArrayTrait; + + /** + * @param string $file The PHP file were values are cached + * @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit + */ + public function __construct($file, CacheInterface $fallbackPool) + { + $this->file = $file; + $this->fallbackPool = $fallbackPool; + } + + /** + * This adapter should only be used on PHP 7.0+ to take advantage of how PHP + * stores arrays in its latest versions. This factory method decorates the given + * fallback pool with this adapter only if the current PHP version is supported. + * + * @param string $file The PHP file were values are cached + * + * @return CacheInterface + */ + public static function create($file, CacheInterface $fallbackPool) + { + // Shared memory is available in PHP 7.0+ with OPCache enabled and in HHVM + if ((PHP_VERSION_ID >= 70000 && ini_get('opcache.enable')) || defined('HHVM_VERSION')) { + 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->values[$key])) { + return $this->fallbackPool->get($key, $default); + } + + $value = $this->values[$key]; + + if ('N;' === $value) { + $value = null; + } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + try { + $e = null; + $value = unserialize($value); + } catch (\Error $e) { + } catch (\Exception $e) { + } + if (null !== $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->values[$key]) || $this->fallbackPool->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->values[$key]) && $this->fallbackPool->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 = array(); + + 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->values[$key])) { + $deleted = false; + } else { + $fallbackKeys[] = $key; + } + } + if (null === $this->values) { + $this->initialize(); + } + + if ($fallbackKeys) { + $deleted = $this->fallbackPool->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->values[$key]) && $this->fallbackPool->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 = array(); + + 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->values[$key])) { + $saved = false; + } else { + $fallbackValues[$key] = $value; + } + } + + if ($fallbackValues) { + $saved = $this->fallbackPool->setMultiple($fallbackValues, $ttl) && $saved; + } + + return $saved; + } + + private function generateItems(array $keys, $default) + { + $fallbackKeys = array(); + + foreach ($keys as $key) { + if (isset($this->values[$key])) { + $value = $this->values[$key]; + + if ('N;' === $value) { + yield $key => null; + } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + try { + yield $key => unserialize($value); + } catch (\Error $e) { + yield $key => $default; + } catch (\Exception $e) { + yield $key => $default; + } + } else { + yield $key => $value; + } + } else { + $fallbackKeys[] = $key; + } + } + + if ($fallbackKeys) { + foreach ($this->fallbackPool->getMultiple($fallbackKeys, $default) as $key => $item) { + yield $key => $item; + } + } + } +} diff --git a/src/Symfony/Component/Cache/Simple/PhpFilesCache.php b/src/Symfony/Component/Cache/Simple/PhpFilesCache.php new file mode 100644 index 000000000000..c4d120080637 --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/PhpFilesCache.php @@ -0,0 +1,32 @@ + + * + * 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\Exception\CacheException; +use Symfony\Component\Cache\Traits\PhpFilesTrait; + +class PhpFilesCache extends AbstractCache +{ + use PhpFilesTrait; + + public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) + { + if (!static::isSupported()) { + throw new CacheException('OPcache is not enabled'); + } + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + + $e = new \Exception(); + $this->includeHandler = function () use ($e) { throw $e; }; + } +} diff --git a/src/Symfony/Component/Cache/Simple/RedisCache.php b/src/Symfony/Component/Cache/Simple/RedisCache.php new file mode 100644 index 000000000000..799a3d082fed --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/RedisCache.php @@ -0,0 +1,27 @@ + + * + * 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\RedisTrait; + +class RedisCache extends AbstractCache +{ + use RedisTrait; + + /** + * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient + */ + public function __construct($redisClient, $namespace = '', $defaultLifetime = 0) + { + $this->init($redisClient, $namespace, $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Simple/TraceableCache.php b/src/Symfony/Component/Cache/Simple/TraceableCache.php new file mode 100644 index 000000000000..40b689dd5d09 --- /dev/null +++ b/src/Symfony/Component/Cache/Simple/TraceableCache.php @@ -0,0 +1,190 @@ + + * + * 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; + +/** + * An adapter that collects data about all cache calls. + * + * @author Nicolas Grekas + */ +class TraceableCache implements CacheInterface +{ + private $pool; + private $miss; + private $calls = array(); + + public function __construct(CacheInterface $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__, compact('key', 'default')); + try { + $value = $this->pool->get($key, $miss); + } finally { + $event->end = microtime(true); + } + if ($miss !== $value) { + ++$event->hits; + } else { + ++$event->misses; + $value = $default; + } + + return $event->result = $value; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + $event = $this->start(__FUNCTION__, compact('key')); + try { + return $event->result = $this->pool->has($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + $event = $this->start(__FUNCTION__, compact('key')); + try { + return $event->result = $this->pool->delete($key); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $event = $this->start(__FUNCTION__, compact('key', 'value', 'ttl')); + try { + return $event->result = $this->pool->set($key, $value, $ttl); + } finally { + $event->end = microtime(true); + } + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $event = $this->start(__FUNCTION__, compact('values', 'ttl')); + try { + return $event->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__, compact('keys', 'default')); + try { + $result = $this->pool->getMultiple($keys, $miss); + } finally { + $event->end = microtime(true); + } + $f = function () use ($result, $event, $miss, $default) { + $event->result = array(); + foreach ($result as $key => $value) { + if ($miss !== $value) { + ++$event->hits; + } else { + ++$event->misses; + $value = $default; + } + yield $key => $event->result[$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__, compact('keys')); + try { + return $event->result = $this->pool->deleteMultiple($keys); + } finally { + $event->end = microtime(true); + } + } + + public function getCalls() + { + try { + return $this->calls; + } finally { + $this->calls = array(); + } + } + + private function start($name, array $arguments = null) + { + $this->calls[] = $event = new TraceableCacheEvent(); + $event->name = $name; + $event->arguments = $arguments; + $event->start = microtime(true); + + return $event; + } +} + +class TraceableCacheEvent +{ + public $name; + public $arguments; + public $start; + public $end; + public $result; + public $hits = 0; + public $misses = 0; +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php index 50206bb278b5..7ebc36f0a581 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php @@ -48,7 +48,7 @@ public function testUnserializable() public function testVersion() { - $namespace = str_replace('\\', '.', __CLASS__); + $namespace = str_replace('\\', '.', get_class($this)); $pool1 = new ApcuAdapter($namespace, 0, 'p1'); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php index 6567740d686e..82b41c3b4d87 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/MemcachedAdapterTest.php @@ -22,7 +22,7 @@ class MemcachedAdapterTest extends AdapterTestCase 'testDefaultLifeTime' => 'Testing expiration slows down the test suite', ); - private static $client; + protected static $client; public static function setupBeforeClass() { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php index ff3351ddf61d..ae0edb7d11dd 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php @@ -51,7 +51,7 @@ class PhpArrayAdapterTest extends AdapterTestCase 'testDefaultLifeTime' => 'PhpArrayAdapter does not allow configuring a default lifetime.', ); - private static $file; + protected static $file; public static function setupBeforeClass() { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php index 7030c0e9c5a6..45a50d2323a6 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterWithFallbackTest.php @@ -25,10 +25,9 @@ class PhpArrayAdapterWithFallbackTest extends AdapterTestCase 'testHasItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.', 'testDeleteItemInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.', 'testDeleteItemsInvalidKeys' => 'PhpArrayAdapter does not throw exceptions on invalid key.', - 'testDefaultLifeTime' => 'PhpArrayAdapter does not allow configuring a default lifetime.', ); - private static $file; + protected static $file; public static function setupBeforeClass() { @@ -42,8 +41,8 @@ protected function tearDown() } } - public function createCachePool() + public function createCachePool($defaultLifetime = 0) { - return new PhpArrayAdapter(self::$file, new FilesystemAdapter('php-array-fallback')); + return new PhpArrayAdapter(self::$file, new FilesystemAdapter('php-array-fallback', $defaultLifetime)); } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php index 3f3f17b88347..1e0297c69e99 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/SimpleCacheAdapterTest.php @@ -11,9 +11,8 @@ namespace Symfony\Component\Cache\Tests\Adapter; -use Symfony\Component\Cache\Adapter\FilesystemAdapter; +use Symfony\Component\Cache\Simple\FilesystemCache; use Symfony\Component\Cache\Adapter\SimpleCacheAdapter; -use Symfony\Component\Cache\Simple\Psr6Cache; /** * @group time-sensitive @@ -22,6 +21,6 @@ class SimpleCacheAdapterTest extends AdapterTestCase { public function createCachePool($defaultLifetime = 0) { - return new SimpleCacheAdapter(new Psr6Cache(new FilesystemAdapter()), '', $defaultLifetime); + return new SimpleCacheAdapter(new FilesystemCache(), '', $defaultLifetime); } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/TraceableAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/TraceableAdapterTest.php index ad55218b0d07..f05fbf9cfb47 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/TraceableAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/TraceableAdapterTest.php @@ -32,10 +32,10 @@ public function testGetItemMiss() $this->assertCount(1, $calls); $call = $calls[0]; - $this->assertEquals('getItem', $call->name); - $this->assertEquals('k', $call->argument); - $this->assertEquals(0, $call->hits); - $this->assertEquals(1, $call->misses); + $this->assertSame('getItem', $call->name); + $this->assertSame('k', $call->argument); + $this->assertSame(0, $call->hits); + $this->assertSame(1, $call->misses); $this->assertNull($call->result); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); @@ -51,8 +51,8 @@ public function testGetItemHit() $this->assertCount(3, $calls); $call = $calls[2]; - $this->assertEquals(1, $call->hits); - $this->assertEquals(0, $call->misses); + $this->assertSame(1, $call->hits); + $this->assertSame(0, $call->misses); } public function testGetItemsMiss() @@ -66,9 +66,9 @@ public function testGetItemsMiss() $this->assertCount(1, $calls); $call = $calls[0]; - $this->assertEquals('getItems', $call->name); - $this->assertEquals($arg, $call->argument); - $this->assertEquals(2, $call->misses); + $this->assertSame('getItems', $call->name); + $this->assertSame($arg, $call->argument); + $this->assertSame(2, $call->misses); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); } @@ -81,8 +81,8 @@ public function testHasItemMiss() $this->assertCount(1, $calls); $call = $calls[0]; - $this->assertEquals('hasItem', $call->name); - $this->assertEquals('k', $call->argument); + $this->assertSame('hasItem', $call->name); + $this->assertSame('k', $call->argument); $this->assertFalse($call->result); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); @@ -98,8 +98,8 @@ public function testHasItemHit() $this->assertCount(3, $calls); $call = $calls[2]; - $this->assertEquals('hasItem', $call->name); - $this->assertEquals('k', $call->argument); + $this->assertSame('hasItem', $call->name); + $this->assertSame('k', $call->argument); $this->assertTrue($call->result); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); @@ -113,10 +113,10 @@ public function testDeleteItem() $this->assertCount(1, $calls); $call = $calls[0]; - $this->assertEquals('deleteItem', $call->name); - $this->assertEquals('k', $call->argument); - $this->assertEquals(0, $call->hits); - $this->assertEquals(0, $call->misses); + $this->assertSame('deleteItem', $call->name); + $this->assertSame('k', $call->argument); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); } @@ -130,10 +130,10 @@ public function testDeleteItems() $this->assertCount(1, $calls); $call = $calls[0]; - $this->assertEquals('deleteItems', $call->name); - $this->assertEquals($arg, $call->argument); - $this->assertEquals(0, $call->hits); - $this->assertEquals(0, $call->misses); + $this->assertSame('deleteItems', $call->name); + $this->assertSame($arg, $call->argument); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); } @@ -147,10 +147,10 @@ public function testSave() $this->assertCount(2, $calls); $call = $calls[1]; - $this->assertEquals('save', $call->name); - $this->assertEquals($item, $call->argument); - $this->assertEquals(0, $call->hits); - $this->assertEquals(0, $call->misses); + $this->assertSame('save', $call->name); + $this->assertSame($item, $call->argument); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); } @@ -164,10 +164,10 @@ public function testSaveDeferred() $this->assertCount(2, $calls); $call = $calls[1]; - $this->assertEquals('saveDeferred', $call->name); - $this->assertEquals($item, $call->argument); - $this->assertEquals(0, $call->hits); - $this->assertEquals(0, $call->misses); + $this->assertSame('saveDeferred', $call->name); + $this->assertSame($item, $call->argument); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); } @@ -180,10 +180,10 @@ public function testCommit() $this->assertCount(1, $calls); $call = $calls[0]; - $this->assertEquals('commit', $call->name); + $this->assertSame('commit', $call->name); $this->assertNull(null, $call->argument); - $this->assertEquals(0, $call->hits); - $this->assertEquals(0, $call->misses); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); $this->assertNotEmpty($call->start); $this->assertNotEmpty($call->end); } diff --git a/src/Symfony/Component/Cache/Tests/Simple/AbstractRedisCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/AbstractRedisCacheTest.php new file mode 100644 index 000000000000..1d097fff85fc --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/AbstractRedisCacheTest.php @@ -0,0 +1,47 @@ + + * + * 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; + +abstract class AbstractRedisCacheTest extends CacheTestCase +{ + protected $skippedTests = array( + '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->flushDB(); + self::$redis = null; + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/ApcuCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/ApcuCacheTest.php new file mode 100644 index 000000000000..297a41756f42 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/ApcuCacheTest.php @@ -0,0 +1,35 @@ + + * + * 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; + +class ApcuCacheTest extends CacheTestCase +{ + protected $skippedTests = array( + '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') || !ini_get('apc.enabled') || ('cli' === PHP_SAPI && !ini_get('apc.enable_cli'))) { + $this->markTestSkipped('APCu extension is required.'); + } + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Fails transiently on Windows.'); + } + + return new ApcuCache(str_replace('\\', '.', __CLASS__), $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/ArrayCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/ArrayCacheTest.php new file mode 100644 index 000000000000..26c3e14d0965 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/ArrayCacheTest.php @@ -0,0 +1,25 @@ + + * + * 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 + */ +class ArrayCacheTest extends CacheTestCase +{ + public function createSimpleCache($defaultLifetime = 0) + { + return new ArrayCache($defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php b/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php new file mode 100644 index 000000000000..81d412bd6635 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php @@ -0,0 +1,66 @@ + + * + * 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; + +abstract class CacheTestCase extends SimpleCacheTest +{ + public function testDefaultLifeTime() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = $this->createSimpleCache(2); + + $cache->set('key.dlt', 'value'); + sleep(1); + + $this->assertSame('value', $cache->get('key.dlt')); + + sleep(2); + $this->assertNull($cache->get('key.dlt')); + } + + public function testNotUnserializable() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = $this->createSimpleCache(); + + $cache->set('foo', new NotUnserializable()); + + $this->assertNull($cache->get('foo')); + + $cache->setMultiple(array('foo' => new NotUnserializable())); + + foreach ($cache->getMultiple(array('foo')) as $value) { + } + $this->assertNull($value); + } +} + +class NotUnserializable implements \Serializable +{ + public function serialize() + { + return serialize(123); + } + + public function unserialize($ser) + { + throw new \Exception(__CLASS__); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php new file mode 100644 index 000000000000..282bb62a6530 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php @@ -0,0 +1,45 @@ + + * + * 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; +use Symfony\Component\Cache\Simple\ChainCache; +use Symfony\Component\Cache\Simple\FilesystemCache; + +/** + * @group time-sensitive + */ +class ChainCacheTest extends CacheTestCase +{ + public function createSimpleCache($defaultLifetime = 0) + { + return new ChainCache(array(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(array()); + } + + /** + * @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException + * @expectedExceptionMessage The class "stdClass" does not implement + */ + public function testInvalidCacheException() + { + new Chaincache(array(new \stdClass())); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/DoctrineCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/DoctrineCacheTest.php new file mode 100644 index 000000000000..0a185297ab45 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/DoctrineCacheTest.php @@ -0,0 +1,31 @@ + + * + * 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\Common\Cache\ArrayCache; +use Symfony\Component\Cache\Simple\DoctrineCache; + +/** + * @group time-sensitive + */ +class DoctrineCacheTest extends CacheTestCase +{ + protected $skippedTests = array( + '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); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/FilesystemCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/FilesystemCacheTest.php new file mode 100644 index 000000000000..0f2d519cada4 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/FilesystemCacheTest.php @@ -0,0 +1,25 @@ + + * + * 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; + +/** + * @group time-sensitive + */ +class FilesystemCacheTest extends CacheTestCase +{ + public function createSimpleCache($defaultLifetime = 0) + { + return new FilesystemCache('', $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php new file mode 100644 index 000000000000..c4af891af7ba --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/MemcachedCacheTest.php @@ -0,0 +1,165 @@ + + * + * 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; + +class MemcachedCacheTest extends CacheTestCase +{ + protected $skippedTests = array( + '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'), array('binary_protocol' => false)) : self::$client; + + return new MemcachedCache($client, str_replace('\\', '.', __CLASS__), $defaultLifetime); + } + + public function testOptions() + { + $client = MemcachedCache::createConnection(array(), array( + '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(array(), array($name => $value)); + } + + public function provideBadOptions() + { + return array( + array('foo', 'bar'), + array('hash', 'zyx'), + array('serializer', 'zyx'), + array('distribution', 'zyx'), + ); + } + + public function testDefaultOptions() + { + $this->assertTrue(MemcachedCache::isSupported()); + + $client = MemcachedCache::createConnection(array()); + + $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(array(), array('serializer' => 'json'))); + } + + /** + * @dataProvider provideServersSetting + */ + public function testServersSetting($dsn, $host, $port) + { + $client1 = MemcachedCache::createConnection($dsn); + $client2 = MemcachedCache::createConnection(array($dsn)); + $client3 = MemcachedCache::createConnection(array(array($host, $port))); + $expect = array( + 'host' => $host, + 'port' => $port, + ); + + $f = function ($s) { return array('host' => $s['host'], 'port' => $s['port']); }; + $this->assertSame(array($expect), array_map($f, $client1->getServerList())); + $this->assertSame(array($expect), array_map($f, $client2->getServerList())); + $this->assertSame(array($expect), array_map($f, $client3->getServerList())); + } + + public function provideServersSetting() + { + yield array( + 'memcached://127.0.0.1/50', + '127.0.0.1', + 11211, + ); + yield array( + 'memcached://localhost:11222?weight=25', + 'localhost', + 11222, + ); + if (ini_get('memcached.use_sasl')) { + yield array( + 'memcached://user:password@127.0.0.1?weight=50', + '127.0.0.1', + 11211, + ); + } + yield array( + 'memcached:///var/run/memcached.sock?weight=25', + '/var/run/memcached.sock', + 0, + ); + yield array( + 'memcached:///var/local/run/memcached.socket?weight=25', + '/var/local/run/memcached.socket', + 0, + ); + if (ini_get('memcached.use_sasl')) { + yield array( + 'memcached://user:password@/var/local/run/memcached.socket?weight=25', + '/var/local/run/memcached.socket', + 0, + ); + } + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php new file mode 100644 index 000000000000..e7b9674ff1fc --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/NullCacheTest.php @@ -0,0 +1,95 @@ + + * + * 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; + +/** + * @group time-sensitive + */ +class NullCacheTest extends \PHPUnit_Framework_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 = array('foo', 'bar', 'baz', 'biz'); + + $default = new \stdClass(); + $items = $cache->getMultiple($keys, $default); + $count = 0; + + foreach ($items as $key => $item) { + $this->assertTrue(in_array($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(array('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(array('key' => 'val'))); + $this->assertNull($cache->get('key')); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php new file mode 100644 index 000000000000..2605ba9201dd --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php @@ -0,0 +1,44 @@ + + * + * 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; + +/** + * @group time-sensitive + */ +class PdoCacheTest extends CacheTestCase +{ + protected static $dbFile; + + public static function setupBeforeClass() + { + if (!extension_loaded('pdo_sqlite')) { + throw new \PHPUnit_Framework_SkippedTestError('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); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php new file mode 100644 index 000000000000..18847ad92588 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php @@ -0,0 +1,45 @@ + + * + * 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; + +/** + * @group time-sensitive + */ +class PdoDbalCacheTest extends CacheTestCase +{ + protected static $dbFile; + + public static function setupBeforeClass() + { + if (!extension_loaded('pdo_sqlite')) { + throw new \PHPUnit_Framework_SkippedTestError('Extension pdo_sqlite required.'); + } + + self::$dbFile = tempnam(sys_get_temp_dir(), 'sf_sqlite_cache'); + + $pool = new PdoCache(DriverManager::getConnection(array('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(array('driver' => 'pdo_sqlite', 'path' => self::$dbFile)), '', $defaultLifetime); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php new file mode 100644 index 000000000000..3016ac560ed0 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheTest.php @@ -0,0 +1,139 @@ + + * + * 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\Adapter\FilesystemAdapterTest; +use Symfony\Component\Cache\Simple\NullCache; +use Symfony\Component\Cache\Simple\PhpArrayCache; + +/** + * @group time-sensitive + */ +class PhpArrayCacheTest extends CacheTestCase +{ + protected $skippedTests = array( + '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.', + ); + + 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 = array(); + $arrayWithRefs[0] = 123; + $arrayWithRefs[1] = &$arrayWithRefs[0]; + + $object = (object) array( + 'foo' => 'bar', + 'foo2' => 'bar2', + ); + + $expected = array( + 'null' => null, + 'serializedString' => serialize($object), + 'arrayWithRefs' => $arrayWithRefs, + 'object' => $object, + 'arrayWithObject' => array('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() + { + $expected = array( + 'integer' => 42, + 'float' => 42.42, + 'boolean' => true, + 'array_simple' => array('foo', 'bar'), + 'array_associative' => array('foo' => 'bar', 'foo2' => 'bar2'), + ); + + $cache = new PhpArrayCache(self::$file, new NullCache()); + $cache->warmUp($expected); + + $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'); + } +} + +class PhpArrayCacheWrapper extends PhpArrayCache +{ + public function set($key, $value, $ttl = null) + { + call_user_func(\Closure::bind(function () use ($key, $value) { + $this->values[$key] = $value; + $this->warmUp($this->values); + $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); + } + call_user_func(\Closure::bind(function () use ($values) { + foreach ($values as $key => $value) { + $this->values[$key] = $value; + } + $this->warmUp($this->values); + $this->values = eval(substr(file_get_contents($this->file), 6)); + }, $this, PhpArrayCache::class)); + + return true; + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php new file mode 100644 index 000000000000..a624fa73e783 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/PhpArrayCacheWithFallbackTest.php @@ -0,0 +1,54 @@ + + * + * 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 + */ +class PhpArrayCacheWithFallbackTest extends CacheTestCase +{ + protected $skippedTests = array( + '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', + ); + + 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)); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php new file mode 100644 index 000000000000..3118fcf94e2c --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/PhpFilesCacheTest.php @@ -0,0 +1,33 @@ + + * + * 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\PhpFilesCache; + +/** + * @group time-sensitive + */ +class PhpFilesCacheTest extends CacheTestCase +{ + protected $skippedTests = array( + 'testDefaultLifeTime' => 'PhpFilesCache does not allow configuring a default lifetime.', + ); + + public function createSimpleCache() + { + if (!PhpFilesCache::isSupported()) { + $this->markTestSkipped('OPcache extension is not enabled.'); + } + + return new PhpFilesCache('sf-cache'); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/Psr6CacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/Psr6CacheTest.php new file mode 100644 index 000000000000..16e21d0c0b63 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/Psr6CacheTest.php @@ -0,0 +1,26 @@ + + * + * 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; +use Symfony\Component\Cache\Simple\Psr6Cache; + +/** + * @group time-sensitive + */ +class Psr6CacheTest extends CacheTestCase +{ + public function createSimpleCache($defaultLifetime = 0) + { + return new Psr6Cache(new FilesystemAdapter('', $defaultLifetime)); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/RedisArrayCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/RedisArrayCacheTest.php new file mode 100644 index 000000000000..3c903c8a9b4a --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/RedisArrayCacheTest.php @@ -0,0 +1,24 @@ + + * + * 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; + +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(array(getenv('REDIS_HOST')), array('lazy_connect' => true)); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/RedisCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/RedisCacheTest.php new file mode 100644 index 000000000000..d33421f9aae4 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/RedisCacheTest.php @@ -0,0 +1,82 @@ + + * + * 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; + +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, array('timeout' => 3)); + $this->assertEquals(3, $redis->getTimeout()); + + $redis = RedisCache::createConnection('redis://'.$redisHost.'?timeout=4'); + $this->assertEquals(4, $redis->getTimeout()); + + $redis = RedisCache::createConnection('redis://'.$redisHost, array('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 array( + array('redis://localhost:1234'), + array('redis://foo@localhost'), + array('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 array( + array('foo://localhost'), + array('redis://'), + ); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Simple/TraceableCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/TraceableCacheTest.php new file mode 100644 index 000000000000..ebdc770add06 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Simple/TraceableCacheTest.php @@ -0,0 +1,170 @@ + + * + * 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 + */ +class TraceableCacheTest extends CacheTestCase +{ + public function createSimpleCache($defaultLifetime = 0) + { + return new TraceableCache(new FilesystemCache('', $defaultLifetime)); + } + + public function testGetMiss() + { + $pool = $this->createSimpleCache(); + $pool->get('k'); + $calls = $pool->getCalls(); + $this->assertCount(1, $calls); + + $call = $calls[0]; + $this->assertSame('get', $call->name); + $this->assertSame(array('key' => 'k', 'default' => null), $call->arguments); + $this->assertSame(0, $call->hits); + $this->assertSame(1, $call->misses); + $this->assertNull($call->result); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } + + public function testGetHit() + { + $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 testGetMultipleMiss() + { + $pool = $this->createSimpleCache(); + $arg = array('k0', 'k1'); + $values = $pool->getMultiple($arg); + foreach ($values as $value) { + } + $calls = $pool->getCalls(); + $this->assertCount(1, $calls); + + $call = $calls[0]; + $this->assertSame('getMultiple', $call->name); + $this->assertSame(array('keys' => $arg, 'default' => null), $call->arguments); + $this->assertSame(2, $call->misses); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } + + public function testHasMiss() + { + $pool = $this->createSimpleCache(); + $pool->has('k'); + $calls = $pool->getCalls(); + $this->assertCount(1, $calls); + + $call = $calls[0]; + $this->assertSame('has', $call->name); + $this->assertSame(array('key' => 'k'), $call->arguments); + $this->assertFalse($call->result); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } + + public function testHasHit() + { + $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(array('key' => 'k'), $call->arguments); + $this->assertTrue($call->result); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } + + public function testDelete() + { + $pool = $this->createSimpleCache(); + $pool->delete('k'); + $calls = $pool->getCalls(); + $this->assertCount(1, $calls); + + $call = $calls[0]; + $this->assertSame('delete', $call->name); + $this->assertSame(array('key' => 'k'), $call->arguments); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } + + public function testDeleteMultiple() + { + $pool = $this->createSimpleCache(); + $arg = array('k0', 'k1'); + $pool->deleteMultiple($arg); + $calls = $pool->getCalls(); + $this->assertCount(1, $calls); + + $call = $calls[0]; + $this->assertSame('deleteMultiple', $call->name); + $this->assertSame(array('keys' => $arg), $call->arguments); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } + + public function testSet() + { + $pool = $this->createSimpleCache(); + $pool->set('k', 'foo'); + $calls = $pool->getCalls(); + $this->assertCount(1, $calls); + + $call = $calls[0]; + $this->assertSame('set', $call->name); + $this->assertSame(array('key' => 'k', 'value' => 'foo', 'ttl' => null), $call->arguments); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } + + public function testSetMultiple() + { + $pool = $this->createSimpleCache(); + $pool->setMultiple(array('k' => 'foo')); + $calls = $pool->getCalls(); + $this->assertCount(1, $calls); + + $call = $calls[0]; + $this->assertSame('setMultiple', $call->name); + $this->assertSame(array('values' => array('k' => 'foo'), 'ttl' => null), $call->arguments); + $this->assertSame(0, $call->hits); + $this->assertSame(0, $call->misses); + $this->assertNotEmpty($call->start); + $this->assertNotEmpty($call->end); + } +} diff --git a/src/Symfony/Component/Cache/Traits/ApcuTrait.php b/src/Symfony/Component/Cache/Traits/ApcuTrait.php index f0ca04d76b56..578aee881e6f 100644 --- a/src/Symfony/Component/Cache/Traits/ApcuTrait.php +++ b/src/Symfony/Component/Cache/Traits/ApcuTrait.php @@ -26,7 +26,7 @@ public static function isSupported() return function_exists('apcu_fetch') && ini_get('apc.enabled') && !('cli' === PHP_SAPI && !ini_get('apc.enable_cli')); } - public function __construct($namespace = '', $defaultLifetime = 0, $version = null) + private function init($namespace, $defaultLifetime, $version) { if (!static::isSupported()) { throw new CacheException('APCu is not enabled'); diff --git a/src/Symfony/Component/Cache/Traits/DoctrineTrait.php b/src/Symfony/Component/Cache/Traits/DoctrineTrait.php index 3655af3bcfb9..be351cf53a55 100644 --- a/src/Symfony/Component/Cache/Traits/DoctrineTrait.php +++ b/src/Symfony/Component/Cache/Traits/DoctrineTrait.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Cache\Traits; -use Doctrine\Common\Cache\CacheProvider; - /** * @author Nicolas Grekas * @@ -22,13 +20,6 @@ trait DoctrineTrait { private $provider; - public function __construct(CacheProvider $provider, $namespace = '', $defaultLifetime = 0) - { - parent::__construct('', $defaultLifetime); - $this->provider = $provider; - $provider->setNamespace($namespace); - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php index a06c964adb14..1db720452fff 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php @@ -22,12 +22,6 @@ trait FilesystemTrait { use FilesystemCommonTrait; - public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) - { - parent::__construct('', $defaultLifetime); - $this->init($namespace, $directory); - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php index 957595e2cb04..ac5c385f9f19 100644 --- a/src/Symfony/Component/Cache/Traits/MemcachedTrait.php +++ b/src/Symfony/Component/Cache/Traits/MemcachedTrait.php @@ -35,7 +35,7 @@ public static function isSupported() return extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>='); } - public function __construct(\Memcached $client, $namespace = '', $defaultLifetime = 0) + private function init(\Memcached $client, $namespace, $defaultLifetime) { if (!static::isSupported()) { throw new CacheException('Memcached >= 2.2.0 is required'); diff --git a/src/Symfony/Component/Cache/Traits/PdoTrait.php b/src/Symfony/Component/Cache/Traits/PdoTrait.php index 90796e015314..08b55ab72c00 100644 --- a/src/Symfony/Component/Cache/Traits/PdoTrait.php +++ b/src/Symfony/Component/Cache/Traits/PdoTrait.php @@ -35,33 +35,7 @@ trait PdoTrait private $password = ''; private $connectionOptions = array(); - /** - * Constructor. - * - * 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. - * - * 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: array()] - * - * @param \PDO|Connection|string $connOrDsn A \PDO or Connection instance or DSN string or null - * @param string $namespace - * @param int $defaultLifetime - * @param array $options An associative array of options - * - * @throws InvalidArgumentException When first argument is not PDO nor Connection nor string - * @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION - * @throws InvalidArgumentException When namespace contains invalid characters - */ - public function __construct($connOrDsn, $namespace = '', $defaultLifetime = 0, array $options = array()) + private function init($connOrDsn, $namespace, $defaultLifetime, array $options) { 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])); diff --git a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index d83587e3bcf6..f915cd46c873 100644 --- a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -31,18 +31,6 @@ public static function isSupported() return function_exists('opcache_compile_file') && ini_get('opcache.enable'); } - public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) - { - if (!static::isSupported()) { - throw new CacheException('OPcache is not enabled'); - } - parent::__construct('', $defaultLifetime); - $this->init($namespace, $directory); - - $e = new \Exception(); - $this->includeHandler = function () use ($e) { throw $e; }; - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index 4ca468a68fde..803e3aa93ef1 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -37,7 +37,7 @@ trait RedisTrait /** * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient */ - public function __construct($redisClient, $namespace = '', $defaultLifetime = 0) + public function init($redisClient, $namespace = '', $defaultLifetime = 0) { parent::__construct($namespace, $defaultLifetime);