diff --git a/src/Symfony/Component/Cache/.gitattributes b/src/Symfony/Component/Cache/.gitattributes new file mode 100644 index 0000000000000..80481513cff2c --- /dev/null +++ b/src/Symfony/Component/Cache/.gitattributes @@ -0,0 +1,2 @@ +/Tests export-ignore +phpunit.xml.dist export-ignore diff --git a/src/Symfony/Component/Cache/.gitignore b/src/Symfony/Component/Cache/.gitignore new file mode 100644 index 0000000000000..44de97a36a6df --- /dev/null +++ b/src/Symfony/Component/Cache/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml + diff --git a/src/Symfony/Component/Cache/CacheDriver.php b/src/Symfony/Component/Cache/CacheDriver.php new file mode 100644 index 0000000000000..1d2a986fd7c73 --- /dev/null +++ b/src/Symfony/Component/Cache/CacheDriver.php @@ -0,0 +1,483 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Symfony\Component\Cache\Driver\DriverInterface; +use Symfony\Component\Cache\Driver\BatchDriverInterface; +use Symfony\Component\Cache\Item\CacheItemInterface; +use Symfony\Component\Cache\Item\CacheItem; +use Symfony\Component\HttpKernel\Log\LoggerInterface; + +/** + * This is our cache proxy + * + * @author Florin Patan + */ +class CacheDriver +{ + /** + * Our cache driver + * + * @var DriverInterface|BatchDriverInterface + */ + private $driver; + + /** + * This should store the logger interface + * + * @var null|LoggerInterface + */ + private $logger = null; + + /** + * Cache profiler + * + * @var null|CacheProfiler + */ + private $profiler = null; + + /** + * Name of the cache instance + * + * @var string + */ + private $name; + + /** + * Type of the cache instance + * + * @var string + */ + private $type; + + /** + * The default TTL (in seconds) + * + * @var int + */ + private $defaultTtl = 600; + + /** + * Create our proxy so that we can use objects to our drivers and have other cool things like logging and profiling + * + * @param DriverInterface $driver + * @param string $name + * @param string $type + */ + public function __construct(DriverInterface $driver, $name, $type) + { + $this->driver = $driver; + $this->name = $name; + $this->type = $type; + } + + /** + * Set the cache logger + * + * @param LoggerInterface $logger + * + * @return CacheDriver + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + + return $this; + } + + /** + * Set the profiler + * + * @param CacheProfiler $profiler + * + * @return CacheDriver + */ + public function setProfiler(CacheProfiler $profiler) + { + $this->profiler = $profiler; + + return $this; + } + + /** + * Return the profiler + * + * @return CacheProfiler + */ + public function getProfiler() + { + return $this->profiler; + } + + /** + * Get the default TTL of the instance + * + * @return int + */ + public function getDefaultTtl() + { + return $this->defaultTtl; + } + + /** + * Set the default TTL of the instance + * + * @param $defaultTtl + * + * @return CacheDriver + */ + public function setDefaultTtl($defaultTtl) + { + $this->defaultTtl = $defaultTtl; + + return $this; + } + + /** + * Get cache entry + * + * @param string|CacheItemInterface $key + * @param boolean|null $exists + * + * @return CacheItemInterface + */ + public function get($key, &$exists = null) + { + $key = $this->getKeyValue($key); + + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'get', $key); + } + + $value = $this->driver->get($key, $exists); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'get', $key, $exists); + } + + return $this->ensureCacheItem($key, $value); + } + + /** + * Check if a cache entry exists + * + * @param string|CacheItemInterface $key + * + * @return boolean + */ + public function exists($key) + { + $key = $this->getKeyValue($key); + + if ($this->logger) { + $this->logger->debug('Checking cache for ' . $key); + } + + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'exists', $key); + } + + $result = $this->driver->exists($key); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'exists', $key, $result); + } + + return $result; + } + + /** + * Set a single cache entry + * + * @param CacheItemInterface $cacheItem + * + * @return boolean Result of the operation + */ + public function set(CacheItemInterface $cacheItem) + { + $key = $cacheItem->getKey(); + + if ($this->logger && strpos($key, '__lk_') === false) { + $this->logger->debug('Setting cache ' . $key); + } + + if ($this->driver->hasSerializationSupport()) { + $cacheValue = $cacheItem; + } else { + $cacheValue = serialize($cacheItem); + } + + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'set', $key); + } + + $result = $this->driver->set($key, $cacheValue, $cacheItem->getTtl()); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'set', $key, $result); + } + + return $result; + } + + /** + * Remove a single cache entry + * + * @param string|CacheItemInterface $key + * + * @return boolean Result of the operation + */ + public function remove($key) + { + $key = $this->getKeyValue($key); + + if ($this->logger && strpos($key, '__lk_') === false) { + $this->logger->debug('Deleting from cache ' . $key); + } + + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'delete', $key); + } + + $result = $this->driver->remove($key); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'delete', $key, $result); + } + + return $result; + } + + /** + * Set multiple keys in the cache + * If $ttl is not passed then the default TTL for this driver will be used + * + * @param string[]|CacheItemInterface[]|mixed[] $items + * @param null|int $ttl + */ + public function setMultiple(array $items, $ttl = null) + { + // Check if we have serialization support + $hasSerializationSupport = $this->driver->hasSerializationSupport(); + + if (null == $ttl) { + $ttl = $this->getDefaultTtl(); + } + + // Ensure all items are in the correct format + array_walk($items, function (&$value, $key) use ($ttl, $hasSerializationSupport) { + if (!$value instanceof CacheItemInterface) { + $value = new CacheItem($key, $value, $ttl); + } else { + $value->setTtl($ttl); + } + + if (!$hasSerializationSupport) { + $value = serialize($value); + } + }); + + if ($this->driver instanceof Driver\BatchDriverInterface) { + if ($this->logger) { + $this->logger->debug('Setting cache ' . implode(', ', array_keys($items))); + } + + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'setMulti'); + } + + $this->driver->setMultiple($items, $ttl); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'setMulti', '', true); + } + } else { + foreach ($items as $key => $value) { + $this->driver->set($key, $value, $ttl); + } + } + } + + /** + * Get multiple keys the cache + * + * @param string[]|CacheItemInterface[]|mixed[] $keys + * + * @return CacheItemInterface[] + */ + public function getMultiple($keys) + { + $keys = $this->convertKeysToString($keys); + + if ($this->logger) { + $this->logger->debug('Getting from cache ' . implode(', ', $keys)); + } + + if ($this->driver instanceof Driver\BatchDriverInterface) { + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'getMulti'); + } + + $result = $this->driver->getMultiple($keys); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'getMulti', '', true); + } + } else { + $result = array(); + foreach ($keys as $key) { + $result[$key] = $this->driver->get($key); + } + } + + $that = $this; + array_walk($result, function (&$value, $key) use ($that) { + $value = $that->ensureCacheItem($key, $value); + }); + + return $result; + } + + /** + * Remove multiple keys from the cache + * + * @param string[]|CacheItemInterface[]|mixed[] $keys + */ + public function removeMultiple($keys) + { + $keys = $this->convertKeysToString($keys); + + if ($this->logger) { + $this->logger->debug('Deleting from cache ' . implode(', ', $keys)); + } + + $results = array(); + + if ($this->driver instanceof Driver\BatchDriverInterface) { + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'deleteMulti', ''); + } + + $results = $this->driver->removeMultiple($keys); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'deleteMulti', '', true); + } + } else { + foreach ($keys as $key) { + $results[$key] = $this->driver->remove($key); + } + } + + return $results; + } + + /** + * Check if multiple keys exists in the cache + * + * @param string[]|CacheItemInterface[]|mixed[] $keys + * + * @return boolean[] + */ + public function existsMultiple($keys) + { + $keys = $this->convertKeysToString($keys); + + if ($this->logger) { + $this->logger->debug('Checking cache for ' . implode(', ', $keys)); + } + + if ($this->driver instanceof Driver\BatchDriverInterface) { + if (null !== $this->profiler) { + $this->profiler->start($this->type, $this->name, 'existsMulti', ''); + } + + $result = $this->driver->existsMultiple($keys); + + if (null !== $this->profiler) { + $this->profiler->stop($this->type, $this->name, 'existsMulti', '', true); + } + } else { + $result = array(); + foreach ($keys as $key) { + $result[] = $this->driver->exists($key); + } + } + + return $result; + } + + /** + * Convert the values of an array to strings only + * + * @param string[]|CacheItemInterface[]|mixed[] $keys + * + * @return string[] + */ + private function convertKeysToString($keys) + { + return array_map(function ($value) { + if ($value instanceof CacheItemInterface) { + $key = $value->getKey(); + } else { + $key = (string) $value; + } + + return $key; + }, $keys); + } + + /** + * Ensure that we receive a key string from the item + * + * @param mixed $item + * + * @return string + */ + private function getKeyValue($item) + { + if ($item instanceof CacheItemInterface) { + return $item->getKey(); + } else { + return (string) $item; + } + } + + /** + * Make sure that the value passed is a object + * + * @param string $key + * @param mixed $value + * + * @return CacheItemInterface + */ + private function ensureCacheItem($key, $value) + { + // We have our item as expected + if ($value instanceof CacheItemInterface) { + return $value; + } + + // We don't have a string value + if (!is_string($value)) { + return new CacheItem($key, $value, 0); + } + + // We have a string value on our hands but we are not sure it can be unserialized + if (false === $val = @unserialize($value)) { + return new CacheItem($key, $val, 0); + } + + // Everything else failed??? + return new CacheItem($key, $value, 0); + } +} diff --git a/src/Symfony/Component/Cache/CacheProfiler.php b/src/Symfony/Component/Cache/CacheProfiler.php new file mode 100644 index 0000000000000..1b61ea0002631 --- /dev/null +++ b/src/Symfony/Component/Cache/CacheProfiler.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Cache profiler + * + * @author Florin Patan + */ +class CacheProfiler +{ + /** + * @var Stopwatch + */ + private $stopwatch; + + /** + * Operational results + * + * @var array + */ + private $results = array(); + + /** + * Add a stopwatch + * + * @param Stopwatch $stopwatch + */ + public function __construct(Stopwatch $stopwatch) + { + $this->stopwatch = $stopwatch; + } + + /** + * Start the measurement of a new operation + * + * @param string $driverType + * @param string $driverName + * @param string $operation + * @param string $key + * + * @return CacheProfiler + */ + public function start($driverType, $driverName, $operation, $key = '') + { + $name = sprintf('%s_%s_%s_%s', $driverType, $driverName, $operation, $key); + + $this->stopwatch->start($name, 'cache'); + + return $this; + } + + /** + * Stop the measurement as we've finished the operation + * + * @param string $driverType + * @param string $driverName + * @param string $operation + * @param string $key + * @param boolean $result + * + * @return CacheProfiler + */ + public function stop($driverType, $driverName, $operation, $key = '', $result) + { + $name = sprintf('%s_%s_%s_%s', $driverType, $driverName, $operation, $key); + + if ('' == $key) { + $key = microtime(true); + } + + $profile = $this->stopwatch->stop($name); + + $totalTime = $profile->getDuration(); + $result = (int)$result; + + $this->results['ops'] = isset($this->results['ops']) ? $this->results['ops'] + 1 : 1; + $this->results['hits'] = isset($this->results['hits']) ? $this->results['hits'] + $result : $result; + $this->results['time'] = isset($this->results['time']) ? $this->results['time'] + $totalTime : $totalTime; + + $this->results['drivers'][$driverType][$driverName][$operation][$key]['result'] = isset($this->results['drivers'][$driverType][$driverName][$operation][$key]['hits']) ? $this->results['hits'][$driverType][$driverName][$operation][$key]['hits'] + $result : $result; + $this->results['drivers'][$driverType][$driverName][$operation][$key]['duration'] = isset($this->results['drivers'][$driverType][$driverName][$operation][$key]['time']) ? $this->results['time'][$driverType][$driverName][$operation][$key]['time'] + $result : $totalTime; + + return $this; + } + + /** + * Get the results of running the cache + * + * @static + * + * @return array + */ + public function getResults() + { + return $this->results; + } + +} diff --git a/src/Symfony/Component/Cache/Driver/ApcDriver.php b/src/Symfony/Component/Cache/Driver/ApcDriver.php new file mode 100644 index 0000000000000..b8eec76c76f36 --- /dev/null +++ b/src/Symfony/Component/Cache/Driver/ApcDriver.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Driver; + +/** + * This is our APC cache driver implementation + * + * @author Florin Patan + */ +class ApcDriver implements BatchDriverInterface +{ + /** + * {@inheritdoc} + */ + public function hasSerializationSupport() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function get($key, &$exists = null) + { + return apc_fetch($key, $exists); + } + + /** + * {@inheritdoc} + */ + public function exists($key) + { + return apc_exists($key); + } + + /** + * {@inheritdoc} + */ + public function set($key, $data, $lifeTime = 0) + { + return apc_store($key, $data, (int)$lifeTime); + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + return apc_delete($key); + } + + /** + * {@inheritdoc} + */ + public function setMultiple(array $items, $ttl = 0) + { + $result = apc_store($items, null, $ttl); + + $keys = array_keys($items); + + $results = array(); + + if (is_array($result)) { + foreach ($keys as $key) { + $results[$key] = !in_array($key, $result); + } + } elseif ($result === true || $result === false) { + foreach ($items as $key) { + $results[$key] = $result; + } + } + + return $results; + } + + /** + * {@inheritdoc} + */ + public function getMultiple(array $keys) + { + return apc_fetch($keys); + } + + /** + * {@inheritdoc} + */ + public function removeMultiple(array $keys) + { + $result = apc_delete($keys); + + $results = array(); + + if (is_array($result)) { + foreach ($keys as $key) { + $results[$key] = !in_array($key, $result); + } + } elseif ($result === true || $result === false) { + foreach ($keys as $key) { + $results[$key] = $result; + } + } + + return $results; + } + + /** + * {@inheritdoc} + */ + public function existsMultiple(array $keys) + { + $result = $this->convertToMultiResponse($keys, false); + + return apc_exists($keys) + $result; + } + + /** + * Convert a reponse from APC to a multi-reponse so that we can implement the interface properly + * + * @param array $items + * @param bool|array $state + * + * @return bool[] + */ + private function convertToMultiResponse($items, $state) + { + $results = array(); + + if (is_array($state)) { + foreach ($items as $key) { + $results[$key] = array_key_exists($key, $state) ? $state[$key] : false; + } + } elseif ($state === true || $state === false) { + foreach ($items as $key) { + $results[$key] = $state; + } + } + + return $results; + } + +} diff --git a/src/Symfony/Component/Cache/Driver/BatchDriverInterface.php b/src/Symfony/Component/Cache/Driver/BatchDriverInterface.php new file mode 100644 index 0000000000000..1294abe390197 --- /dev/null +++ b/src/Symfony/Component/Cache/Driver/BatchDriverInterface.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\Driver; + +/** + * Interface for cache drivers that can support multiple operations at once + * + * See PSR proposal + * @link https://github.com/evert/fig-standards/blob/master/proposed/objectcache.md + * + * @author Florin Patan + */ +interface BatchDriverInterface extends DriverInterface +{ + + /** + * Stores multiple items in the cache at once. + * + * The items must be provided as an associative array. + * + * @param array $items + * @param int $ttl + * + * @return boolean[] + */ + public function setMultiple(array $items, $ttl = 0); + + /** + * Fetches multiple items from the cache. + * + * The returned structure must be an associative array. If items were + * not found in the cache, they should not be included in the array. + * + * This means that if none of the items are found, this method must + * return an empty array. + * + * @param array $keys + * + * @return array + */ + public function getMultiple(array $keys); + + /** + * Deletes multiple items from the cache at once. + * + * @param array $keys + * + * @return boolean[] + */ + public function removeMultiple(array $keys); + + /** + * Check for multiple items if they appear in the cache. + * + * All items must be returned as an array. And each must array value + * must either be set to true, or false. + * + * @param array $keys + * + * @return array + */ + public function existsMultiple(array $keys); + +} diff --git a/src/Symfony/Component/Cache/Driver/DriverInterface.php b/src/Symfony/Component/Cache/Driver/DriverInterface.php new file mode 100644 index 0000000000000..9b99005173f5c --- /dev/null +++ b/src/Symfony/Component/Cache/Driver/DriverInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Driver; + +/** + * Interface for cache drivers + * + * This is adapted from the PSR proposal and the Symfony2 talks about the cache component here + * @link https://github.com/evert/fig-standards/blob/master/proposed/objectcache.md + * @link https://github.com/symfony/symfony/pull/3211 + * + * @author Florin Patan + */ +interface DriverInterface +{ + /** + * Set data into cache. + * + * @param string $key Entry id + * @param mixed $value Cache entry + * @param int $lifeTime Life time of the cache entry + * + * @return boolean + */ + public function set($key, $value, $lifeTime = 0); + + /** + * Check if an entry exists in cache + * + * @param string $key Entry id + * + * @return boolean + */ + public function exists($key); + + /** + * Get an entry from the cache + * + * @param string $key Entry id + * @param boolean|null $exists If the operation was succesfull or not + * + * @return mixed The cached data or FALSE + */ + public function get($key, &$exists = null); + + /** + * Removes a cache entry + * + * @param string $key Entry id + * + * @return boolean + */ + public function remove($key); + + /** + * If this driver has support for serialization or not + * + * @return boolean + */ + public function hasSerializationSupport(); + +} diff --git a/src/Symfony/Component/Cache/Driver/MemcachedDriver.php b/src/Symfony/Component/Cache/Driver/MemcachedDriver.php new file mode 100644 index 0000000000000..d10b721944105 --- /dev/null +++ b/src/Symfony/Component/Cache/Driver/MemcachedDriver.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Driver; + +/** + * This is our Memcached cache driver implementation + * + * @author Florin Patan + */ +class MemcachedDriver implements BatchDriverInterface +{ + + /** + * Memcached instance + * + * @var \Memcached + */ + private $memcached; + + /** + * Create our Memcached driver from existing Memcached object + * + * @param \Memcached $memcached + */ + public function __construct(\Memcached $memcached) + { + $this->memcached = $memcached; + } + + /** + * {@inheritdoc} + */ + public function hasSerializationSupport() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function get($key, &$exists = 0) + { + $value = $this->memcached->get($key); + + $exists = \Memcached::RES_NOTFOUND !== $this->memcached->getResultCode(); + + return $value; + } + + /** + * {@inheritdoc} + */ + public function exists($key) + { + $result = null; + + $this->get($key, $result); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function set($key, $data, $lifeTime = null) + { + if (false === $this->memcached->replace($key, $data, $lifeTime)) { + return $this->memcached->set($key, $data, $lifeTime); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + return $this->memcached->delete($key); + } + + /** + * {@inheritdoc} + */ + public function setMultiple(array $items, $ttl = 0) + { + $results = $this->memcached->setMulti($items, $ttl); + + if (!is_array($results)) { + $results = $this->convertToMultiResponse(array_keys($items), $results); + } else { + foreach ($results as $key => $value) { + if ($value === \Memcached::RES_NOTFOUND) { + $results[$key] = false; + } else { + $results[$key] = true; + } + } + } + + return $results; + } + + /** + * {@inheritdoc} + */ + public function getMultiple(array $keys) + { + $results = $this->memcached->getMulti($keys); + + return $this->convertToMultiResponse($keys, $results); + } + + /** + * {@inheritdoc} + */ + public function removeMultiple(array $keys) + { + $results = array(); + + foreach ($keys as $key) { + $results[$key] = $this->remove($key); + } + + return $results; + } + + /** + * {@inheritdoc} + */ + public function existsMultiple(array $keys) + { + $result = array(); + + foreach ($keys as $key) { + $result[$key] = $this->exists($key); + } + + return $result; + } + + + /** + * Convert a reponse from Memcached to a multi-reponse so that we can implement the interface properly + * + * @param array $items + * @param bool|array $state + * + * @return bool[] + */ + private function convertToMultiResponse($items, $state) + { + $results = array(); + + if (is_array($state)) { + foreach ($items as $key) { + $results[$key] = array_key_exists($key, $state) ? $state[$key] : false; + } + } elseif ($state === true || $state === false) { + foreach ($items as $key) { + $results[$key] = $state; + } + } + + return $results; + } +} diff --git a/src/Symfony/Component/Cache/Driver/MemoryDriver.php b/src/Symfony/Component/Cache/Driver/MemoryDriver.php new file mode 100644 index 0000000000000..26324891d8194 --- /dev/null +++ b/src/Symfony/Component/Cache/Driver/MemoryDriver.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Driver; + +/** + * This is our Memory / array / runtime cache driver implementation + * + * @author Florin Patan + */ +class MemoryDriver implements DriverInterface +{ + /** + * @var array + */ + private $cache = array(); + + /** + * {@inheritdoc} + */ + public function hasSerializationSupport() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function get($key, &$exists = null) + { + if (array_key_exists($key, $this->cache)) { + $exists = true; + + return $this->cache[$key]; + } + + $exists = false; + return false; + } + + /** + * {@inheritdoc} + */ + public function exists($key) + { + return array_key_exists($key, $this->cache); + } + + /** + * {@inheritdoc} + */ + public function set($key, $data, $lifeTime = 0) + { + $this->cache[$key] = $data; + + return true; + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + if (array_key_exists($key, $this->cache)) { + unset($this->cache[$key]); + + return true; + } + + return false; + } + +} diff --git a/src/Symfony/Component/Cache/Item/CacheItem.php b/src/Symfony/Component/Cache/Item/CacheItem.php new file mode 100644 index 0000000000000..a175687a6b72c --- /dev/null +++ b/src/Symfony/Component/Cache/Item/CacheItem.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Item; +use DateTime; +use DateTimeZone; + +/** + * Implementation of the cache object/container + * + * @author Florin Patan + */ +class CacheItem implements CacheItemInterface +{ + + /** + * Cache key + * + * @var string + */ + private $key; + + /** + * Cache value + * + * @var mixed + */ + private $value; + + /** + * TTL of the object in cache + * + * @var int + */ + private $ttl; + + /** + * Metadata information for the cached object + * + * @var array + */ + private $metadata = array(); + + /** + * Create our cache container object + * + * @param string $key + * @param mixed $value + * @param int $ttl + */ + public function __construct($key, $value, $ttl) + { + $this->setKey($key)->setValue($value)->setTtl($ttl); + } + + /** + * {@inheritdoc} + */ + public function setKey($cacheKey) + { + $this->key = $cacheKey; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->key; + } + + /** + * {@inheritdoc} + */ + public function setValue($cacheValue) + { + $this->value = $cacheValue; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function setTtl($ttl) + { + $this->ttl = $ttl; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getTtl() + { + return $this->ttl; + } + + /** + * {@inheritdoc} + * + * @return int + */ + public function getRemainingTtl() + { + $saveTime = $this->getMetadata('__tos'); + + if ($saveTime == '') { + return $this->ttl; + } + + $now = new DateTime('now', new DateTimeZone('UTC')); + + return $saveTime + $this->ttl - $now->getTimestamp(); + } + + + /** + * {@inheritdoc} + */ + public function getNamespace() + { + return $this->getMetadata('__ns'); + } + + /** + * {@inheritdoc} + */ + public function setNamespace($namespace) + { + $this->setMetadata('__ns', $namespace); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getTags() + { + return $this->getMetadata('__tags') == '' ? array() : $this->getMetadata('__tags'); + } + + /** + * {@inheritdoc} + */ + public function setTags(array $tags) + { + $this->setMetadata('__tags', $tags); + + return $this; + } + + + /** + * {@inheritdoc} + */ + public function setMetadata($key, $value) + { + $this->metadata[$key] = $value; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasMetadata($key = null) + { + if ($key == null) { + return !empty($this->metadata); + } + + return array_key_exists($key, $this->metadata); + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key = null) + { + if ($key == null) { + return $this->metadata; + } + + return array_key_exists($key, $this->metadata) ? $this->metadata[$key] : ''; + } + + public function __sleep() + { + $timeOfSave = new DateTime('now', new DateTimeZone('UTC')); + $this->setMetadata('__tos', $timeOfSave->getTimestamp()); + + return array('key', 'value', 'ttl', 'metadata'); + } + +} diff --git a/src/Symfony/Component/Cache/Item/CacheItemInterface.php b/src/Symfony/Component/Cache/Item/CacheItemInterface.php new file mode 100644 index 0000000000000..60f944b6eecda --- /dev/null +++ b/src/Symfony/Component/Cache/Item/CacheItemInterface.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\Item; + +/** + * Interface for caching object + * + * @author Florin Patan + */ +interface CacheItemInterface +{ + /** + * Set the value of the key to store our value under + * + * @param string $cacheKey + * + * @return CacheItemInterface + */ + public function setKey($cacheKey); + + /** + * Get the key of the object + * + * @return string + */ + public function getKey(); + + /** + * Set the value to be stored in the cache + * + * @param mixed $cacheValue + * + * @return CacheItemInterface + */ + public function setValue($cacheValue); + + /** + * Get the value of the object + * + * @return mixed + */ + public function getValue(); + + /** + * Set the TTL value + * + * @param int $ttl + * + * @return CacheItemInterface + */ + public function setTtl($ttl); + + /** + * Get the TTL of the object + * + * @return int + */ + public function getTtl(); + + /** + * Get the remaining time in seconds until the item will expire + * The implementation should save the expiry time in the item metadata on save event + * and then retrieve it from the object metadata and substract it from the current time + * *Note* certain delays can occur as the save event won't be able to provide actual save time during the save time + * + * @return int + */ + public function getRemainingTtl(); + + /** + * Set a metadata value + * + * @param string $key + * @param mixed $value + * + * @return CacheItemInterface + */ + public function setMetadata($key, $value); + + /** + * Do we have any metadata with the object + * + * @param string|null $key + * + * @return boolean + */ + public function hasMetadata($key = null); + + /** + * Get parameter/key from the metadata + * + * @param string|null $key + * + * @return mixed + */ + public function getMetadata($key = null); + + /** + * Get the namespace of the cache item + * + * @return string + */ + public function getNamespace(); + + /** + * Set the namespace of the cache driver + * + * @param string $namespace + * + * @return CacheItemInterface + */ + public function setNamespace($namespace); + + /** + * Get the tags of an item + * + * @return string[] + */ + public function getTags(); + + /** + * Set the tags of an item + * + * @param array $tags + * + * @return CacheItemInterface + */ + public function setTags(array $tags); + +} diff --git a/src/Symfony/Component/Cache/LICENSE b/src/Symfony/Component/Cache/LICENSE new file mode 100644 index 0000000000000..cdffe7aebc04a --- /dev/null +++ b/src/Symfony/Component/Cache/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2012 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/Tests/CacheDriver/AbstractCacheDriverTest.php b/src/Symfony/Component/Cache/Tests/CacheDriver/AbstractCacheDriverTest.php new file mode 100644 index 0000000000000..ead22a3639a85 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/CacheDriver/AbstractCacheDriverTest.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\CacheDriver; + +use Symfony\Component\Cache\CacheDriver; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Cache\CacheProfiler; +use Symfony\Component\Cache\Item\CacheItem; +use Symfony\Component\HttpKernel\Tests\Logger; + +abstract class AbstractCacheDriverTest extends \PHPUnit_Framework_TestCase +{ + private $cacheItemNamespace = '\\Symfony\\Component\\Cache\\Item\\CacheItem'; + + /** + * @var CacheDriver + */ + protected $cache; + + protected function setUp() + { + $this->cache = new CacheDriver($this->_getTestDriver(), 'test', 'test'); + + $this->cache->setLogger(new Logger('demo')); + + $this->cache->setProfiler(new CacheProfiler(new Stopwatch())); + } + + protected function tearDown() + { + $this->cache = null; + } + + abstract protected function _getTestDriver(); + + /** + * @covers Symfony\Component\Cache\CacheDriver::setDefaultTtl + * @covers Symfony\Component\Cache\CacheDriver::getDefaultTtl + */ + public function testTtl() + { + $this->assertInstanceOf('\\Symfony\\Component\\Cache\\CacheDriver', $this->cache->setDefaultTtl(4)); + + $this->assertEquals(4, $this->cache->getDefaultTtl()); + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::set + */ + public function testSet() + { + $cacheKeys = $this->generateCacheKeys(); + + foreach ($cacheKeys as $cacheKey) { + $this->assertTrue($this->cache->set($cacheKey)); + } + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::get + * @depends testSet + */ + public function testGet() + { + $cacheItem = new CacheItem('demo', 'demo', 100); + + $exists = null; + $result = $this->cache->get($cacheItem, $exists); + $this->assertFalse($exists, "The value of exists should be false when the key does not exist"); + $this->assertInstanceOf($this->cacheItemNamespace, $result, "A CacheItem should be returned even if the item is not in cache"); + + $exists = null; + $result = $this->cache->get('demo', $exists); + $this->assertFalse($exists, "The value of exists should be false when the key does not exist"); + $this->assertInstanceOf($this->cacheItemNamespace, $result, "A CacheItem should be returned even if the item is not in cache"); + + $cacheKeys = $this->generateCacheKeys(); + $this->populateCache($cacheKeys); + + foreach ($cacheKeys as $cacheKey) { + $exists = null; + $result = $this->cache->get($cacheKey, $exists); + $this->assertTrue($exists, sprintf("The cache key should be present for %s", $cacheKey->getKey())); + $this->assertInstanceOf($this->cacheItemNamespace, $result, sprintf("The value retrieved from cache for key %s should be a instance of CacheItem", $cacheKey->getKey())); + } + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::exists + * @depends testSet + */ + public function testExists() + { + $cacheItem = new CacheItem('demo', 'demo', 100); + $this->assertFalse($this->cache->exists($cacheItem), "The value of exists should be false when the key does not exist"); + + $this->assertFalse($this->cache->exists('demo'), "The value of exists should be false when the key does not exist"); + + $cacheKeys = $this->generateCacheKeys(); + $this->populateCache($cacheKeys); + + foreach ($cacheKeys as $cacheKey) { + $this->assertTrue($this->cache->exists($cacheKey), sprintf("The cache key %s should be present", $cacheKey->getKey())); + } + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::remove + * @depends testSet + */ + public function testRemove() + { + $cacheItem = new CacheItem('demo', 'demo', 100); + $this->assertFalse($this->cache->remove($cacheItem), "One should not be able to remove an inexistent key"); + + $this->assertFalse($this->cache->remove('demo'), "One should not be able to remove an inexistent key"); + + $cacheKeys = $this->generateCacheKeys(); + $this->populateCache($cacheKeys); + foreach ($cacheKeys as $cacheKey) { + $this->assertTrue($this->cache->remove($cacheKey), sprintf("The cache key %s should be deleted", $cacheKey->getKey())); + } + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::setMultiple + * @depends testSet + */ + public function testSetMultiple() + { + $cacheKeys = $this->generateCacheKeys(); + + $this->cache->setMultiple($cacheKeys); + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::getMultiple + * @depends testSet + */ + public function testGetMultiple() + { + $cacheKeys = $this->generateCacheKeys(); + + $this->populateCache($cacheKeys); + $results = $this->cache->getMultiple($cacheKeys); + + foreach ($results as $result) { + $this->assertInstanceOf($this->cacheItemNamespace, $result, 'The retrieved value should always be a CacheItem'); + } + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::removeMultiple + * @depends testSet + */ + public function testRemoveMultiple() + { + $cacheKeys = $this->generateCacheKeys(); + + $this->populateCache($cacheKeys); + $results = $this->cache->removeMultiple($cacheKeys); + + $this->assertEquals(count($cacheKeys), array_sum($results)); + } + + /** + * @covers Symfony\Component\Cache\CacheDriver::existsMultiple + * @depends testSet + */ + public function testExistsMultiple() + { + $cacheKeys = $this->generateCacheKeys(); + + $this->populateCache($cacheKeys); + $results = $this->cache->existsMultiple($cacheKeys); + + foreach ($results as $result) { + $this->assertTrue($result, "One should be able to delete a cache item that exists"); + } + } + + /** + * @return CacheItem[] + */ + public function generateCacheKeys() + { + return array( + 'stringItem' => new CacheItem('demo_string', 'demo value', 100), + 'boolItem' => new CacheItem('demo_bool', false, 100), + 'intItem' => new CacheItem('demo_int', 0, 100), + 'objItem' => new CacheItem('demo_obj', new \stdClass(), 100), + 'nullItem' => new CacheItem('demo_null', null, 100), + ); + } + + /** + * Write the specified items to cache + * + * @param CacheItem[] $items + */ + public function populateCache(array $items) + { + foreach ($items as $item) { + $this->cache->set($item); + } + } +} diff --git a/src/Symfony/Component/Cache/Tests/CacheDriver/ApcCacheDriverTest.php b/src/Symfony/Component/Cache/Tests/CacheDriver/ApcCacheDriverTest.php new file mode 100644 index 0000000000000..c9c7edc62b69f --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/CacheDriver/ApcCacheDriverTest.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\CacheDriver; + +use Symfony\Component\Cache\Driver\ApcDriver; + +class ApcCacheDriverTest extends AbstractCacheDriverTest +{ + public function setUp() + { + if (!extension_loaded('apc') || !ini_get('apc.enabled') || !ini_get('apc.enable_cli')) { + $this->markTestSkipped('Please install and enable APC for CLI to execute this test'); + } + + parent::setUp(); + } + + public function _getTestDriver() + { + return new ApcDriver(); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Cache/Tests/CacheDriver/MemcachedCacheDriverTest.php b/src/Symfony/Component/Cache/Tests/CacheDriver/MemcachedCacheDriverTest.php new file mode 100644 index 0000000000000..1d4d1e1e187cc --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/CacheDriver/MemcachedCacheDriverTest.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\CacheDriver; + +use Symfony\Component\Cache\Driver\MemcachedDriver; + +class MemcachedCacheDriverTest extends AbstractCacheDriverTest +{ + /** + * @var \Memcached + */ + private $memcached = null; + + + public function setUp() + { + if (!extension_loaded('memcached')) { + $this->markTestSkipped('Please install MemcacheD extension to execute this test'); + } + + if (null === $this->memcached) { + $fh = @fsockopen('127.0.0.1', 11211); + if (!$fh) { + $this->markTestSkipped('Memcached server is not on standard 127.0.0.1:11211 configuration'); + } + + $this->memcached = new \Memcached(); + $this->memcached->addServer('127.0.0.1', 11211); + } + + parent::setUp(); + } + + public function _getTestDriver() + { + return new MemcachedDriver($this->memcached); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Cache/Tests/CacheDriver/MemoryCacheDriverTest.php b/src/Symfony/Component/Cache/Tests/CacheDriver/MemoryCacheDriverTest.php new file mode 100644 index 0000000000000..07808f343a478 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/CacheDriver/MemoryCacheDriverTest.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\CacheDriver; + +use Symfony\Component\Cache\Driver\MemoryDriver; + +class MemoryCacheDriverTest extends AbstractCacheDriverTest +{ + public function _getTestDriver() + { + return new MemoryDriver(); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Cache/Tests/CacheItemTest.php b/src/Symfony/Component/Cache/Tests/CacheItemTest.php new file mode 100644 index 0000000000000..2e4110e084580 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/CacheItemTest.php @@ -0,0 +1,214 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests; + +use Symfony\Component\Cache\Item\CacheItem; + +class CacheItemTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var CacheItem + */ + protected $cacheItem; + + private $cacheItemNamespace = '\\Symfony\\Component\\Cache\\Item\\CacheItem'; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->cacheItem = new CacheItem('test', 'test value', 10); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + $this->cacheItem = null; + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::setKey + */ + public function testSetKey() + { + $result = $this->cacheItem->setKey('demo'); + + $this->assertInstanceOf($this->cacheItemNamespace, $result, 'Setting the cache key should return the CacheItem object'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::getKey + * @depends testSetKey + */ + public function testGetKey() + { + $key = $this->cacheItem->getKey(); + $this->assertEquals('test', $key, 'The default key should be present after constructor'); + + $key = $this->cacheItem->setKey('demo')->getKey(); + $this->assertEquals('demo', $key, 'A new key should be possible to be assigned to a existing CacheItem'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::setValue + */ + public function testSetValue() + { + $result = $this->cacheItem->setValue('demo'); + + $this->assertInstanceOf($this->cacheItemNamespace, $result, 'Setting the cache value should return the CacheItem object'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::getValue + * @depends testSetValue + */ + public function testGetValue() + { + $value = $this->cacheItem->getValue(); + $this->assertEquals('test value', $value, 'The default value should be present after constructor'); + + $value = $this->cacheItem->setvalue('demo')->getValue(); + $this->assertEquals('demo', $value, 'A new value should be possible to be assigned to a existing CacheItem'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::setTtl + */ + public function testSetTtl() + { + $result = $this->cacheItem->setTtl(100); + + $this->assertInstanceOf($this->cacheItemNamespace, $result, 'Setting the cache TTL should return the CacheItem object'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::getTtl + */ + public function testGetTtl() + { + $value = $this->cacheItem->getTtl(); + $this->assertEquals(10, $value, 'The default ttl should be present after constructor'); + + $value = $this->cacheItem->setTtl(15)->getTtl(); + $this->assertEquals(15, $value, 'A new ttl should be possible to be assigned to a existing CacheItem'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::getRemainingTtl + */ + public function testGetRemainingTtl() + { + $remainingTtl = $this->cacheItem->getRemainingTtl(); + + $this->assertEquals($this->cacheItem->getTtl(), $remainingTtl, 'The default value of remaining ttl on a unset object should be the same as ttl'); + + // Emulate the save process + $cacheItem = serialize($this->cacheItem); + sleep(1); + $cacheItem = unserialize($cacheItem); + + $this->assertEquals($remainingTtl - 1, $cacheItem->getRemainingTtl(), 'The remaining ttl should be TTL - 1 since we\'ve slept for 1 sec only'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::getNamespace + */ + public function testGetNamespace() + { + $value = $this->cacheItem->getNamespace(); + $this->assertEquals('', $value, 'The default namespace should be empty after constructor'); + + $value = $this->cacheItem->setNamespace('demo\demo')->getNamespace(); + $this->assertEquals('demo\demo', $value, 'A new namespace should be possible to be assigned to a existing CacheItem'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::setNamespace + */ + public function testSetNamespace() + { + $result = $this->cacheItem->setNamespace('demo\demo'); + + $this->assertInstanceOf($this->cacheItemNamespace, $result, 'Setting the cache namespace should return the CacheItem object'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::getTags + */ + public function testGetTags() + { + $value = $this->cacheItem->getTags(); + $this->assertEquals(array(), $value, 'The default tags should be empty after constructor'); + + $value = $this->cacheItem->setTags(array('demo', 'demoTag'))->getTags(); + $this->assertEquals(array('demo', 'demoTag'), $value, 'New tags should be possible to be assigned to a existing CacheItem'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::setTags + */ + public function testSetTags() + { + $result = $this->cacheItem->setTags(array('demo', 'demotag')); + + $this->assertInstanceOf($this->cacheItemNamespace, $result, 'Setting the cache tags should return the CacheItem object'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::setMetadata + */ + public function testSetMetadata() + { + $result = $this->cacheItem->setMetadata('meta', 'demo'); + + $this->assertInstanceOf($this->cacheItemNamespace, $result, 'Setting some cache metadata should return the CacheItem object'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::hasMetadata + */ + public function testHasMetadata() + { + $value = $this->cacheItem->hasMetadata(); + $this->assertFalse($value, 'There should not be metadata for the object after constructor'); + + $value = $this->cacheItem->setMetadata('demo', 'demoTag')->hasMetadata('demo'); + $this->assertTrue($value, 'New metadata item should be possible to be assigned to a existing CacheItem'); + + $value = $this->cacheItem->hasMetadata('demo2'); + $this->assertFalse($value, 'Inexistent metadata item should return empty string always'); + } + + /** + * @covers Symfony\Component\Cache\Item\CacheItem::getMetadata + */ + public function testGetMetadata() + { + $value = $this->cacheItem->hasMetadata(); + $this->assertFalse($value, 'There should not be metadata for the object after constructor'); + + $value = $this->cacheItem->setMetadata('demo', 'demoTag')->getMetadata('demo'); + $this->assertEquals('demoTag', $value, 'New metadata item should be possible to be assigned to a existing CacheItem'); + + $value = $this->cacheItem->setMetadata('demoMeta', 'demoTags')->getMetadata(); + $this->assertEquals(array('demo' => 'demoTag', 'demoMeta' => 'demoTags'), $value, 'Getting metadata without a specific key should return all metadata'); + + $value = $this->cacheItem->getMetadata('demo2'); + $this->assertEquals('', $value, 'Inexistent metadata item should return empty string always'); + } +} diff --git a/src/Symfony/Component/Cache/Tests/CacheProfilerTest.php b/src/Symfony/Component/Cache/Tests/CacheProfilerTest.php new file mode 100644 index 0000000000000..5a7a7fb9c8ab5 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/CacheProfilerTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Symfony\Component\Cache\CacheProfiler; +use Symfony\Component\Stopwatch\Stopwatch; + +class CacheProfilerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var CacheProfiler + */ + protected $cacheProfiler; + + protected function setUp() + { + $this->cacheProfiler = new CacheProfiler(new Stopwatch()); + } + + protected function tearDown() + { + unset($this->cacheProfiler); + } + + /** + * @covers Symfony\Component\Cache\CacheProfiler::start + */ + public function testStart() + { + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->start('type', 'name', 'get')); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->start('type', 'name', 'get', 'demo')); + } + + /** + * @covers Symfony\Component\Cache\CacheProfiler::stop + * @depends testStart + * + */ + public function testStop() + { + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->start('type', 'name', 'get', null)); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->start('type', 'name', 'get2', null)); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->start('type', 'name', 'get', 'demo')); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->start('type', 'name', 'get2', 'demo')); + + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->stop('type', 'name', 'get', null, true)); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->stop('type', 'name', 'get2', null, false)); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->stop('type', 'name', 'get', 'demo', true)); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->stop('type', 'name', 'get2', 'demo', false)); + } + + /** + * @covers Symfony\Component\Cache\CacheProfiler::getResults + */ + public function testGetResults() + { + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->start('type', 'name', 'get', null)); + $this->assertInstanceOf('Symfony\\Component\\Cache\\CacheProfiler', $this->cacheProfiler->stop('type', 'name', 'get', null, true)); + + $results = $this->cacheProfiler->getResults(); + + $this->assertCount(4, $results); + + $this->assertArrayHasKey('hits', $results); + $this->assertArrayHasKey('ops', $results); + $this->assertArrayHasKey('time', $results); + + $this->assertArrayHasKey('drivers', $results); + + $this->assertArrayHasKey('type', $results['drivers']); + $this->assertCount(1, $results['drivers']); + + $this->assertArrayHasKey('name', $results['drivers']['type']); + $this->assertCount(1, $results['drivers']['type']); + + $this->assertTrue(is_array($results['drivers']['type']['name'])); + $this->assertCount(1, $results['drivers']['type']['name']); + + $this->assertTrue(is_array($results['drivers']['type']['name']['get'])); + $this->assertCount(1, $results['drivers']['type']['name']['get']); + + $result = array_pop($results['drivers']['type']['name']['get']); + $this->assertCount(2, $result); + + $this->assertArrayHasKey('result', $result); + $this->assertEquals(1, $result['result']); + + $this->assertArrayHasKey('duration', $result); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Driver/AbstractDriverTest.php b/src/Symfony/Component/Cache/Tests/Driver/AbstractDriverTest.php new file mode 100644 index 0000000000000..5716e1c233add --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Driver/AbstractDriverTest.php @@ -0,0 +1,382 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Driver; + +use Symfony\Component\Cache\Driver\DriverInterface; +use Symfony\Component\Cache\Driver\BatchDriverInterface; + +abstract class AbstractDriverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var DriverInterface|BatchDriverInterface + */ + protected $driver; + + protected function setUp() + { + $this->driver = $this->_getTestDriver(); + } + + protected function tearDown() + { + $this->driver = null; + } + + /** + * @return DriverInterface|BatchDriverInterface + */ + abstract protected function _getTestDriver(); + + public function testSet() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + // More simple keys + $items = $this->generateCacheKeys(); + + foreach ($items as $item) { + $this->assertTrue($this->driver->set($item['key'], $item['value'], $item['ttl'])); + } + } + + public function testSetObject() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $items = $this->generateObjectCachekeys(); + foreach ($items as $item) { + $this->assertTrue($this->driver->set($item['key'], $item['value'], $item['ttl'])); + } + } + + /** + * @depends testSet + */ + public function testGet() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + $cacheItems = $this->generateCacheKeys(); + $this->populateCache($cacheItems); + + foreach ($cacheItems as $cacheItem) { + $this->assertEquals($cacheItem['value'], $this->driver->get($cacheItem['key'])); + } + } + + public function testGetObject() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $cacheItems = $this->generateCacheKeys(); + $this->populateCache($cacheItems); + + foreach ($cacheItems as $cacheItem) { + $this->assertEquals($cacheItem['value'], $this->driver->get($cacheItem['key'])); + } + + $exists = null; + $this->driver->get('random_cache_key' . rand(1, 100), $exists); + $this->assertFalse($exists); + } + + /** + * @depends testSet + */ + public function testExists() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + $cacheItems = $this->generateCacheKeys(); + $this->populateCache($cacheItems); + + foreach ($cacheItems as $cacheItem) { + $this->assertTrue($this->driver->exists($cacheItem['key'])); + } + + $this->assertFalse($this->driver->get('random_cache_key' . rand(1, 100))); + } + + /** + * @depends testSetObject + */ + public function testExistsObject() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $cacheItems = $this->generateObjectCachekeys(); + $this->populateCache($cacheItems); + + foreach ($cacheItems as $cacheItem) { + $this->assertTrue($this->driver->exists($cacheItem['key'])); + } + + $this->assertFalse($this->driver->exists('random_cache_key' . rand(1, 100))); + } + + /** + * @depends testSet + */ + public function testRemove() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + $cacheItems = $this->generateCacheKeys(); + $this->populateCache($cacheItems); + + foreach ($cacheItems as $cacheItem) { + $this->assertTrue($this->driver->remove($cacheItem['key'])); + } + + $this->assertFalse($this->driver->remove('random_cache_key' . rand(1, 100))); + } + + /** + * @depends testSetObject + */ + public function testRemoveObject() + { + if (!$this->driver instanceof DriverInterface) { + $this->markTestSkipped('Driver does not support single operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $cacheItems = $this->generateObjectCachekeys(); + $this->populateCache($cacheItems); + + foreach ($cacheItems as $cacheItem) { + $this->assertTrue($this->driver->remove($cacheItem['key'])); + } + + $this->assertFalse($this->driver->remove('random_cache_key' . rand(1, 100))); + } + + public function testSetMultiple() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + $cacheItems = $this->generateCacheKeys(); + $results = $this->driver->setMultiple($cacheItems, 10); + + foreach ($results as $result) { + $this->assertTrue($result); + } + } + + public function testSetMultipleObject() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $cacheItems = $this->generateObjectCacheKeys(); + $results = $this->driver->setMultiple($cacheItems, 10); + + foreach ($results as $result) { + $this->assertTrue($result); + } + } + + public function testGetMultiple() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + $cacheItems = $this->generateCacheKeys(); + $this->driver->setMultiple($cacheItems, 10); + + $results = $this->driver->getMultiple(array_keys($cacheItems)); + + $this->assertEquals(array_keys($cacheItems), array_keys($results), 'The driver should always return the same set of keys sent'); + + foreach ($results as $key => $result) { + $this->assertEquals($cacheItems[$key]['key'], $result['key']); + $this->assertEquals($cacheItems[$key]['value'], $result['value']); + $this->assertEquals($cacheItems[$key]['ttl'], $result['ttl']); + } + } + + public function testGetMultipleObject() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $cacheItems = $this->generateObjectCacheKeys(); + $this->driver->setMultiple($cacheItems, 10); + + $results = $this->driver->getMultiple(array_keys($cacheItems)); + + $this->assertEquals(array_keys($cacheItems), array_keys($results), 'The driver should always return the same set of keys sent'); + + foreach ($results as $key => $result) { + $this->assertEquals($cacheItems[$key]['key'], $result['key']); + $this->assertEquals($cacheItems[$key]['value'], $result['value']); + $this->assertEquals($cacheItems[$key]['ttl'], $result['ttl']); + } + } + + public function testRemoveMultiple() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + $cacheItems = $this->generateCacheKeys(); + $this->driver->setMultiple($cacheItems, 10); + + $results = $this->driver->removeMultiple(array_keys($cacheItems)); + + $this->assertEquals(array_keys($cacheItems), array_keys($results), 'The driver should always return the same set of keys sent'); + + foreach ($results as $result) { + $this->assertTrue($result); + } + } + + public function testRemoveMultipleObject() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $cacheItems = $this->generateObjectCachekeys(); + $this->driver->setMultiple($cacheItems, 10); + + $results = $this->driver->removeMultiple(array_keys($cacheItems)); + + $this->assertEquals(array_keys($cacheItems), array_keys($results), 'The driver should always return the same set of keys sent'); + + foreach ($results as $result) { + $this->assertTrue($result); + } + } + + public function testExistsMultiple() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + $cacheItems = $this->generateCacheKeys(); + $this->driver->setMultiple($cacheItems, 10); + + $results = $this->driver->existsMultiple(array_keys($cacheItems)); + + $this->assertEquals(array_keys($cacheItems), array_keys($results), 'The driver should always return the same set of keys sent'); + + foreach ($results as $result) { + $this->assertTrue($result); + } + } + + public function testExistsMultipleObject() + { + if (!$this->driver instanceof BatchDriverInterface) { + $this->markTestSkipped('Driver does not support multiple operations'); + } + + if (!$this->driver->hasSerializationSupport()) { + $this->markTestSkipped('Driver does not support serialization'); + } + + $cacheItems = $this->generateObjectCacheKeys(); + $this->driver->setMultiple($cacheItems, 10); + + $results = $this->driver->existsMultiple(array_keys($cacheItems)); + + $this->assertEquals(array_keys($cacheItems), array_keys($results), 'The driver should always return the same set of keys sent'); + + foreach ($results as $result) { + $this->assertTrue($result); + } + } + + /** + * @return array + */ + public function generateCacheKeys() + { + return array( + 'stringItem' => array('key' => 'demo_string', 'value' => 'demo value', 'ttl' => 100), + 'boolFItem' => array('key' => 'demo_boolf', 'value' => false, 'ttl' => 100), + 'boolTItem' => array('key' => 'demo_boolt', 'value' => true, 'ttl' => 100), + 'int0Item' => array('key' => 'demo_int0', 'value' => 0, 'ttl' => 100), + 'int1Item' => array('key' => 'demo_int1', 'value' => 1, 'ttl' => 100), + ); + } + + public function generateObjectCachekeys() + { + return array( + 'nullItem' => array('key' => 'demo_null', 'value' => null, 'ttl' => 100), + 'objItem' => array('key' => 'demo_obj', 'value' => new \stdClass(), 'ttl' => 100), + ); + } + + /** + * Write the specified items to cache + * + * @param array $items + */ + public function populateCache(array $items) + { + foreach ($items as $item) { + $this->driver->set($item['key'], $item['value'], $item['ttl']); + } + } + +} \ No newline at end of file diff --git a/src/Symfony/Component/Cache/Tests/Driver/ApcDriverTest.php b/src/Symfony/Component/Cache/Tests/Driver/ApcDriverTest.php new file mode 100644 index 0000000000000..ad2f3cc408f7e --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Driver/ApcDriverTest.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\Driver\Tests\Driver; + +use Symfony\Component\Cache\Driver\ApcDriver; +use Symfony\Component\Cache\Tests\Driver\AbstractDriverTest; + +class ApcTest extends AbstractDriverTest +{ + protected function setUp() + { + if (!extension_loaded('apc') || !ini_get('apc.enabled') || !ini_get('apc.enable_cli')) { + $this->markTestSkipped('Please install and enable APC for CLI to execute this test'); + } + + parent::setUp(); + } + + public function _getTestDriver() + { + return new ApcDriver(); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Driver/MemcachedDriverTest.php b/src/Symfony/Component/Cache/Tests/Driver/MemcachedDriverTest.php new file mode 100644 index 0000000000000..3e7b8246581fe --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Driver/MemcachedDriverTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Driver\Tests; + +use Symfony\Component\Cache\Driver\MemcachedDriver; +use Symfony\Component\Cache\Tests\Driver\AbstractDriverTest; + +class MemcachedTest extends AbstractDriverTest +{ + + /** + * @var \Memcached + */ + private $memcached = null; + + + public function setUp() + { + if (!extension_loaded('memcached')) { + $this->markTestSkipped('Please install MemcacheD extension to execute this test'); + } + + if (null === $this->memcached) { + $fh = @fsockopen('127.0.0.1', 11211); + if (!$fh) { + $this->markTestSkipped('Memcached server is not on standard 127.0.0.1:11211 configuration'); + } + + $this->memcached = new \Memcached(); + $this->memcached->addServer('127.0.0.1', 11211); + } + + parent::setUp(); + } + + public function _getTestDriver() + { + return new MemcachedDriver($this->memcached); + } + +} diff --git a/src/Symfony/Component/Cache/Tests/Driver/MemoryDriverTest.php b/src/Symfony/Component/Cache/Tests/Driver/MemoryDriverTest.php new file mode 100644 index 0000000000000..bc879f6b14b67 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Driver/MemoryDriverTest.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\Driver; + +use Symfony\Component\Cache\Driver\MemoryDriver; +use Symfony\Component\Cache\Driver\BatchDriverInterface; +use Symfony\Component\Cache\Tests\Driver\AbstractDriverTest; + +class MemoryDriverTest extends AbstractDriverTest +{ + + public function _getTestDriver() + { + return new MemoryDriver(); + } + +} diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json new file mode 100644 index 0000000000000..340664fa50886 --- /dev/null +++ b/src/Symfony/Component/Cache/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/cache", + "type": "library", + "description": "Symfony Cache Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/http-kernel": "2.2.*", + "symfony/stopwatch": "2.2.*" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Cache\\": "" } + }, + "target-dir": "Symfony/Component/Cache", + "extra": { + "branch-alias": { + "dev-master": "2.2-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..b6b454dea6ba6 --- /dev/null +++ b/src/Symfony/Component/Cache/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + +