From 848a33ed3e4d06c50ee9f39d71a9aabf044d39bb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Nov 2016 17:25:06 +0100 Subject: [PATCH] [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 5fdff21937..832c87c0e1 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 336c320de9..ce219c21e2 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 0000000000..f176624410 --- /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 d62b3e1213..e87b2db8fe 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 334a3c3e27..828bf3ed77 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 0000000000..d23af54069 --- /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 0000000000..3f3f17b883 --- /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 6cb772a80f..f3bc3988fa 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 19b5496277..c5884dd625 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