From 91e482ae8ed5269fa7a2e2078c9075fda4fcdf5f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 18 Jan 2016 15:59:30 +0100 Subject: [PATCH] [Cache] Symfony PSR-6 implementation --- .travis.php | 2 +- .travis.yml | 2 +- composer.json | 4 + phpunit.xml.dist | 2 + src/Symfony/Component/Cache/.gitignore | 3 + .../Cache/Adapter/AbstractAdapter.php | 292 ++++++++++++++++++ .../Component/Cache/Adapter/ApcuAdapter.php | 75 +++++ .../Component/Cache/Adapter/ArrayAdapter.php | 180 +++++++++++ .../Cache/Adapter/DoctrineAdapter.php | 73 +++++ .../Component/Cache/Adapter/ProxyAdapter.php | 138 +++++++++ src/Symfony/Component/Cache/CacheItem.php | 103 ++++++ .../Cache/Exception/CacheException.php | 18 ++ .../Exception/InvalidArgumentException.php | 18 ++ src/Symfony/Component/Cache/LICENSE | 19 ++ src/Symfony/Component/Cache/README.md | 9 + .../Cache/Tests/Adapter/ApcuAdapterTest.php | 30 ++ .../Cache/Tests/Adapter/ArrayAdapterTest.php | 30 ++ .../Tests/Adapter/DoctrineAdapterTest.php | 31 ++ .../Cache/Tests/Adapter/ProxyAdapterTest.php | 31 ++ src/Symfony/Component/Cache/composer.json | 41 +++ src/Symfony/Component/Cache/phpunit.xml.dist | 39 +++ 21 files changed, 1138 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Cache/.gitignore create mode 100644 src/Symfony/Component/Cache/Adapter/AbstractAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/ApcuAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/ArrayAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php create mode 100644 src/Symfony/Component/Cache/Adapter/ProxyAdapter.php create mode 100644 src/Symfony/Component/Cache/CacheItem.php create mode 100644 src/Symfony/Component/Cache/Exception/CacheException.php create mode 100644 src/Symfony/Component/Cache/Exception/InvalidArgumentException.php create mode 100644 src/Symfony/Component/Cache/LICENSE create mode 100644 src/Symfony/Component/Cache/README.md create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/DoctrineAdapterTest.php create mode 100644 src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php create mode 100644 src/Symfony/Component/Cache/composer.json create mode 100644 src/Symfony/Component/Cache/phpunit.xml.dist diff --git a/.travis.php b/.travis.php index fdf223bb7a719..54384e1a96cae 100644 --- a/.travis.php +++ b/.travis.php @@ -39,7 +39,7 @@ $packages[$package->name][$package->version] = $package; - $versions = file_get_contents('https://packagist.org/packages/'.$package->name.'.json'); + $versions = @file_get_contents('https://packagist.org/packages/'.$package->name.'.json') ?: '{"package":{"versions":[]}}'; $versions = json_decode($versions); foreach ($versions->package->versions as $version => $package) { diff --git a/.travis.yml b/.travis.yml index e115908c869ae..6da2fb04da51c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,7 +51,7 @@ install: - if [[ $deps != skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi; - if [[ $deps != skip && $deps ]]; then php .travis.php $TRAVIS_COMMIT_RANGE $TRAVIS_BRANCH $COMPONENTS; fi; - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then SYMFONY_VERSION=$(git ls-remote --heads | grep -o '/[1-9].*' | tail -n 1 | sed s/.//); else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*'); fi; - - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; fi; + - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi; - if [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]]; then LEGACY=,legacy; fi; - export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev; - if [[ ! $deps ]]; then composer update --prefer-dist; else export SYMFONY_DEPRECATIONS_HELPER=weak; fi; diff --git a/composer.json b/composer.json index 6b4f55737f8ef..eca32b8374913 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "php": ">=5.5.9", "doctrine/common": "~2.4", "twig/twig": "~1.23|~2.0", + "psr/cache": "~1.0", "psr/log": "~1.0", "symfony/polyfill-apcu": "~1.0,>=1.0.2", "symfony/polyfill-intl-icu": "~1.0", @@ -30,6 +31,7 @@ "replace": { "symfony/asset": "self.version", "symfony/browser-kit": "self.version", + "symfony/cache": "self.version", "symfony/class-loader": "self.version", "symfony/config": "self.version", "symfony/console": "self.version", @@ -74,6 +76,8 @@ "symfony/yaml": "self.version" }, "require-dev": { + "cache/integration-tests": "^0.6", + "doctrine/cache": "~1.6", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", "doctrine/orm": "~2.4,>=2.4.5", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 69d022fc1614a..ebdaaa6d233b3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -54,6 +54,8 @@ + Cache\IntegrationTests + Doctrine\Common\Cache Symfony\Component\HttpFoundation diff --git a/src/Symfony/Component/Cache/.gitignore b/src/Symfony/Component/Cache/.gitignore new file mode 100644 index 0000000000000..5414c2c655e72 --- /dev/null +++ b/src/Symfony/Component/Cache/.gitignore @@ -0,0 +1,3 @@ +composer.lock +phpunit.xml +vendor/ diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php new file mode 100644 index 0000000000000..25178d3b2dde1 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -0,0 +1,292 @@ + + * + * 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\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + */ +abstract class AbstractAdapter implements CacheItemPoolInterface +{ + private $namespace; + private $deferred = array(); + private $createCacheItem; + private $mergeByLifetime; + + protected function __construct($namespace = '', $defaultLifetime = 0) + { + $this->namespace = $namespace; + $this->createCacheItem = \Closure::bind( + function ($key, $value, $isHit) use ($defaultLifetime) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + $item->defaultLifetime = $defaultLifetime; + + return $item; + }, + $this, + CacheItem::class + ); + $this->mergeByLifetime = \Closure::bind( + function ($deferred, $namespace) { + $byLifetime = array(); + + foreach ($deferred as $key => $item) { + if (0 <= $item->lifetime) { + $byLifetime[(int) $item->lifetime][$namespace.$key] = $item->value; + } + } + + return $byLifetime; + }, + $this, + CacheItem::class + ); + } + + /** + * Fetches several cache items. + * + * @param array $ids The cache identifiers to fetch. + * + * @return array 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. + * + * @return bool True if the pool was successfully cleared, false otherwise. + */ + abstract protected function doClear(); + + /** + * 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 getItem($key) + { + $id = $this->getId($key); + + if ($this->deferred) { + $this->commit(); + } + if (isset($this->deferred[$key])) { + return $this->deferred[$key]; + } + + $f = $this->createCacheItem; + $isHit = false; + $value = null; + + foreach ($this->doFetch(array($id)) as $value) { + $isHit = true; + } + + return $f($key, $value, $isHit); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = array()) + { + if ($this->deferred) { + $this->commit(); + } + $f = $this->createCacheItem; + $ids = array(); + $items = array(); + + foreach ($keys as $key) { + $id = $this->getId($key); + + if (isset($this->deferred[$key])) { + $items[$key] = $this->deferred[$key]; + } else { + $ids[$key] = $id; + } + } + + $values = $this->doFetch($ids); + + foreach ($ids as $key => $id) { + $isHit = isset($values[$id]); + $items[$key] = $f($key, $isHit ? $values[$id] : null, $isHit); + } + + return $items; + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + if ($this->deferred) { + $this->commit(); + } + + return $this->doHave($this->getId($key)); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->deferred = array(); + + return $this->doClear(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + return $this->deleteItems(array($key)); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + $ids = array(); + + foreach ($keys as $key) { + $ids[] = $this->getId($key); + unset($this->deferred[$key]); + } + + return $this->doDelete($ids); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + $key = $item->getKey(); + $this->deferred[$key] = $item; + $this->commit(); + + return !isset($this->deferred[$key]); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + try { + $item = clone $item; + } catch (\Error $e) { + } catch (\Exception $e) { + } + if (isset($e)) { + @trigger_error($e->__toString()); + + return false; + } + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + $f = $this->mergeByLifetime; + $ko = array(); + $namespaceLen = strlen($this->namespace); + + foreach ($f($this->deferred, $this->namespace) as $lifetime => $values) { + if (true === $ok = $this->doSave($values, $lifetime)) { + continue; + } + if (false === $ok) { + $ok = array_keys($values); + } + foreach ($ok as $failedId) { + $key = substr($failedId, $namespaceLen); + $ko[$key] = $this->deferred[$key]; + } + } + + return !$this->deferred = $ko; + } + + public function __destruct() + { + if ($this->deferred) { + $this->commit(); + } + } + + private function getId($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($key[0])) { + throw new InvalidArgumentException('Cache key length must be greater than zero'); + } + if (isset($key[strcspn($key, '{}()/\@:')])) { + throw new InvalidArgumentException('Cache key contains reserved characters {}()/\@:'); + } + + return $this->namespace.$key; + } +} diff --git a/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.php new file mode 100644 index 0000000000000..30ff93109c2c8 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/ApcuAdapter.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 Symfony\Component\Cache\Exception\CacheException; + +/** + * @author Nicolas Grekas + */ +class ApcuAdapter extends AbstractAdapter +{ + public function __construct($namespace = '', $defaultLifetime = 0) + { + if (!function_exists('apcu_fetch') || !ini_get('apc.enabled') || ('cli' === PHP_SAPI && !ini_get('apc.enable_cli'))) { + throw new CacheException('APCu is not enabled'); + } + if ('cli' === PHP_SAPI) { + ini_set('apc.use_request_time', 0); + } + parent::__construct($namespace, $defaultLifetime); + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + return apcu_fetch($ids); + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return apcu_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear() + { + return apcu_clear_cache(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + foreach ($ids as $id) { + apcu_delete($id); + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + return apcu_store($values, null, $lifetime); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php new file mode 100644 index 0000000000000..6f3ba49ec7cfe --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -0,0 +1,180 @@ + + * + * 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\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + */ +class ArrayAdapter implements CacheItemPoolInterface +{ + private $values = array(); + private $expiries = array(); + private $createCacheItem; + + public function __construct($defaultLifetime = 0) + { + $this->createCacheItem = \Closure::bind( + function ($key, $value, $isHit) use ($defaultLifetime) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + $item->defaultLifetime = $defaultLifetime; + + return $item; + }, + $this, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $f = $this->createCacheItem; + $isHit = $this->hasItem($key); + + return $f($key, $isHit ? $this->values[$key] : null, $isHit); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = array()) + { + $f = $this->createCacheItem; + $items = array(); + $now = time(); + + foreach ($keys as $key) { + $isHit = isset($this->expiries[$this->validateKey($key)]) && ($this->expiries[$key] >= $now || !$this->deleteItem($key)); + $items[$key] = $f($key, $isHit ? $this->values[$key] : null, $isHit); + } + + return $items; + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + return isset($this->expiries[$this->validateKey($key)]) && ($this->expiries[$key] >= time() || !$this->deleteItem($key)); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->values = $this->expiries = array(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + unset($this->values[$this->validateKey($key)], $this->expiries[$key]); + + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + $this->deleteItem($key); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + if (!$item instanceof CacheItem) { + return false; + } + static $prefix = "\0Symfony\Component\Cache\CacheItem\0"; + $item = (array) $item; + $key = $item[$prefix.'key']; + $value = $item[$prefix.'value']; + $lifetime = $item[$prefix.'lifetime']; + + if (0 > $lifetime) { + return true; + } + + if (is_object($value)) { + try { + $value = clone $value; + } catch (\Error $e) { + } catch (\Exception $e) { + } + if (isset($e)) { + @trigger_error($e->__toString()); + + return false; + } + } + + $this->values[$key] = $value; + $this->expiries[$key] = $lifetime ? $lifetime + time() : PHP_INT_MAX; + + return true; + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->save($item); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return true; + } + + private function validateKey($key) + { + if (!is_string($key)) { + throw new InvalidArgumentException(sprintf('Cache key must be string, "%s" given', is_object($key) ? get_class($key) : gettype($key))); + } + if (!isset($key[0])) { + throw new InvalidArgumentException('Cache key length must be greater than zero'); + } + if (isset($key[strcspn($key, '{}()/\@:')])) { + throw new InvalidArgumentException('Cache key contains reserved characters {}()/\@:'); + } + + return $key; + } +} diff --git a/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php b/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php new file mode 100644 index 0000000000000..9fcef8693b09a --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/DoctrineAdapter.php @@ -0,0 +1,73 @@ + + * + * 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; + +/** + * @author Nicolas Grekas + */ +class DoctrineAdapter extends AbstractAdapter +{ + private $provider; + + public function __construct(CacheProvider $provider, $defaultLifetime = null) + { + parent::__construct('', $defaultLifetime); + $this->provider = $provider; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + return $this->provider->fetchMultiple($ids); + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + return $this->provider->contains($id); + } + + /** + * {@inheritdoc} + */ + protected function doClear() + { + return $this->provider->flushAll(); + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + foreach ($ids as $id) { + $ok = $this->provider->delete($id) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + return $this->provider->saveMultiple($values, $lifetime); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php new file mode 100644 index 0000000000000..f8f1004015919 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -0,0 +1,138 @@ + + * + * 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\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; + +/** + * @author Nicolas Grekas + */ +class ProxyAdapter implements CacheItemPoolInterface +{ + private $pool; + private $createCacheItem; + + public function __construct(CacheItemPoolInterface $pool) + { + $this->pool = $pool; + $this->createCacheItem = \Closure::bind( + function ($key, $value, $isHit) { + $item = new CacheItem(); + $item->key = $key; + $item->value = $value; + $item->isHit = $isHit; + + return $item; + }, + $this, + CacheItem::class + ); + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + $f = $this->createCacheItem; + $item = $this->pool->getItem($key); + + return $f($key, $item->get(), $item->isHit()); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = array()) + { + $f = $this->createCacheItem; + $items = array(); + + foreach ($this->pool->getItems($keys) as $key => $item) { + $items[$key] = $f($key, $item->get(), $item->isHit()); + } + + return $items; + } + + /** + * {@inheritdoc} + */ + public function hasItem($key) + { + return $this->pool->hasItem($key); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->pool->clear(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($key) + { + return $this->pool->deleteItem($key); + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) + { + return $this->pool->deleteItems($keys); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) + { + return $this->doSave($item, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) + { + return $this->doSave($item, __FUNCTION__); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->pool->commit(); + } + + private function doSave(CacheItemInterface $item, $method) + { + if (!$item instanceof CacheItem) { + return false; + } + static $prefix = "\0Symfony\Component\Cache\CacheItem\0"; + $item = (array) $item; + $poolItem = $this->pool->getItem($item[$prefix.'key']); + $poolItem->set($item[$prefix.'value']); + $poolItem->expiresAfter($item[$prefix.'lifetime']); + + return $this->pool->$method($poolItem); + } +} diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php new file mode 100644 index 0000000000000..fe69c73716724 --- /dev/null +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + */ +final class CacheItem implements CacheItemInterface +{ + private $key; + private $value; + private $isHit; + private $lifetime; + private $defaultLifetime; + + public function __clone() + { + if (is_object($this->value)) { + $this->value = clone $this->value; + } + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->key; + } + + /** + * {@inheritdoc} + */ + public function get() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function isHit() + { + return $this->isHit; + } + + /** + * {@inheritdoc} + */ + public function set($value) + { + $this->value = $value; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) + { + if (null === $expiration) { + $this->lifetime = $this->defaultLifetime; + } elseif ($expiration instanceof \DateTimeInterface) { + $this->lifetime = $expiration->format('U') - time() ?: -1; + } else { + throw new InvalidArgumentException(sprintf('Expiration date must implement DateTimeInterface or be null, "%s" given', is_object($expiration) ? get_class($expiration) : gettype($expiration))); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function expiresAfter($time) + { + if (null === $time) { + $this->lifetime = $this->defaultLifetime; + } elseif ($time instanceof \DateInterval) { + $now = time(); + $this->lifetime = \DateTime::createFromFormat('U', $now)->add($time)->format('U') - $now ?: -1; + } elseif (is_int($time)) { + $this->lifetime = $time ?: -1; + } else { + throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', is_object($time) ? get_class($time) : gettype($time))); + } + + return $this; + } +} diff --git a/src/Symfony/Component/Cache/Exception/CacheException.php b/src/Symfony/Component/Cache/Exception/CacheException.php new file mode 100644 index 0000000000000..d62b3e1213892 --- /dev/null +++ b/src/Symfony/Component/Cache/Exception/CacheException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\CacheException as CacheExceptionInterface; + +class CacheException extends \Exception implements CacheExceptionInterface +{ +} diff --git a/src/Symfony/Component/Cache/Exception/InvalidArgumentException.php b/src/Symfony/Component/Cache/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000..334a3c3e27617 --- /dev/null +++ b/src/Symfony/Component/Cache/Exception/InvalidArgumentException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\InvalidArgumentException as InvalidArgumentExceptionInterface; + +class InvalidArgumentException extends \InvalidArgumentException implements InvalidArgumentExceptionInterface +{ +} diff --git a/src/Symfony/Component/Cache/LICENSE b/src/Symfony/Component/Cache/LICENSE new file mode 100644 index 0000000000000..0564c5a9b7f1f --- /dev/null +++ b/src/Symfony/Component/Cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Cache/README.md b/src/Symfony/Component/Cache/README.md new file mode 100644 index 0000000000000..3ece466aec9af --- /dev/null +++ b/src/Symfony/Component/Cache/README.md @@ -0,0 +1,9 @@ +Symfony PSR-6 implementation for caching +======================================== + +This component provides a strict [PSR-6](http://www.php-fig.org/psr/psr-6/) +implementation for adding cache to your applications. It is designed to have a +low overhead so that caching is fastest. It ships with a few caching adapters +for the most widespread and suited to caching backends. It also provides a +`doctrine/cache` proxy adapter to cover more advanced caching needs and a proxy +adapter for greater interoperability between PSR-6 implementations. diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php new file mode 100644 index 0000000000000..6fc69f99b19b2 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/ApcuAdapterTest.php @@ -0,0 +1,30 @@ + + * + * 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 Cache\IntegrationTests\CachePoolTest; +use Symfony\Component\Cache\Adapter\ApcuAdapter; + +class ApcuAdapterTest extends CachePoolTest +{ + public function createCachePool() + { + if (defined('HHVM_VERSION')) { + $this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM'; + } + if (!function_exists('apcu_fetch') || !ini_get('apc.enabled') || ('cli' === PHP_SAPI && !ini_get('apc.enable_cli'))) { + $this->markTestSkipped('APCu extension is required.'); + } + + return new ApcuAdapter(__CLASS__); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php new file mode 100644 index 0000000000000..019e740f934b7 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/ArrayAdapterTest.php @@ -0,0 +1,30 @@ + + * + * 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 Cache\IntegrationTests\CachePoolTest; +use Symfony\Component\Cache\Adapter\ArrayAdapter; + +/** + * @group time-sensitive + */ +class ArrayAdapterTest extends CachePoolTest +{ + protected $skippedTests = array( + 'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.', + ); + + public function createCachePool() + { + return new ArrayAdapter(); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/DoctrineAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineAdapterTest.php new file mode 100644 index 0000000000000..f633c81b2a8a1 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/DoctrineAdapterTest.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\Adapter; + +use Cache\IntegrationTests\CachePoolTest; +use Doctrine\Common\Cache\ArrayCache; +use Symfony\Component\Cache\Adapter\DoctrineAdapter; + +/** + * @group time-sensitive + */ +class DoctrineAdapterTest extends CachePoolTest +{ + protected $skippedTests = array( + 'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayCache is not.', + ); + + public function createCachePool() + { + return new DoctrineAdapter(new ArrayCache()); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.php new file mode 100644 index 0000000000000..dcb4ad7290af0 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/ProxyAdapterTest.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\Adapter; + +use Cache\IntegrationTests\CachePoolTest; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Component\Cache\Adapter\ProxyAdapter; + +/** + * @group time-sensitive + */ +class ProxyAdapterTest extends CachePoolTest +{ + protected $skippedTests = array( + 'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.', + ); + + public function createCachePool() + { + return new ProxyAdapter(new ArrayAdapter()); + } +} diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json new file mode 100644 index 0000000000000..2a1455a1813ca --- /dev/null +++ b/src/Symfony/Component/Cache/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/cache", + "type": "library", + "description": "Symfony implementation of PSR-6", + "keywords": ["caching", "psr6"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "provide": { + "psr/cache-implementation": "1.0" + }, + "require": { + "php": ">=5.5.9", + "psr/cache": "~1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.6", + "doctrine/cache": "~1.6" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + } +} diff --git a/src/Symfony/Component/Cache/phpunit.xml.dist b/src/Symfony/Component/Cache/phpunit.xml.dist new file mode 100644 index 0000000000000..480bb6aeef0e0 --- /dev/null +++ b/src/Symfony/Component/Cache/phpunit.xml.dist @@ -0,0 +1,39 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + + + + + + Cache\IntegrationTests + Doctrine\Common\Cache + + + + +