From 8b3ce6f2d39937f7ea29af0df96b9f316e7eee19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 20 Aug 2015 11:11:07 +0200 Subject: [PATCH 01/51] fix performance file --- composer.json | 72 +++++++++++++++++++----------------- src/Adapter/File.php | 25 +++++++------ tests/performance/common.php | 14 ++----- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/composer.json b/composer.json index 6ada70e..0d4690a 100644 --- a/composer.json +++ b/composer.json @@ -1,35 +1,41 @@ { - "name": "desarrolla2/cache", - "description": "Provides an cache interface for several adapters (Apc, File, Mongo, Memcached, Mysql, ... )", - "keywords": ["cache", "apc", "file", "memcached", "mysql", "mongo", "redis"], - "type": "library", - "license": "MIT", - "homepage": "https://github.com/desarrolla2/Cache/blob/master/README.md", - "authors": [ - { - "name": "Daniel González", - "homepage": "http://desarrolla2.com/", - "role": "Developer" - } - ], - "support": { - "issues": "https://github.com/desarrolla2/Cache/issues" - }, - "require": - { - "php": ">=5.3.0" - }, - "require-dev": - { - "raulfraile/ladybug": "v0.7", - "symfony/yaml": "dev-master", - "predis/predis": "*" - }, - "autoload": - { - "psr-4": { - "Desarrolla2\\Cache\\": "src/" - } - }, - "minimum-stability": "dev" + "name": "desarrolla2/cache", + "description": "Provides an cache interface for several adapters (Apc, File, Mongo, Memcached, Mysql, ... )", + "keywords": [ + "cache", + "apc", + "file", + "memcached", + "mysql", + "mongo", + "redis" + ], + "type": "library", + "license": "MIT", + "homepage": "https://github.com/desarrolla2/Cache/blob/master/README.md", + "authors": [ + { + "name": "Daniel González", + "homepage": "http://desarrolla2.com/", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/desarrolla2/Cache/issues" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "desarrolla2/timer": "dev-master", + "raulfraile/ladybug": "~1.0", + "symfony/yaml": "dev-master", + "predis/predis": "*" + }, + "autoload": { + "psr-4": { + "Desarrolla2\\Cache\\": "src/" + } + }, + "minimum-stability": "dev" } diff --git a/src/Adapter/File.php b/src/Adapter/File.php index a9ebe0a..9621e25 100644 --- a/src/Adapter/File.php +++ b/src/Adapter/File.php @@ -29,7 +29,8 @@ class File extends AbstractAdapter protected $cacheDir; /** - * @param null $cacheDir + * @param null $cacheDir + * * @throws FileCacheException */ public function __construct($cacheDir = null) @@ -37,7 +38,7 @@ public function __construct($cacheDir = null) if (!$cacheDir) { $cacheDir = realpath(sys_get_temp_dir()).'/cache'; } - $this->cacheDir = (string) $cacheDir; + $this->cacheDir = (string)$cacheDir; if (!is_dir($this->cacheDir)) { if (!mkdir($this->cacheDir, 0777, true)) { throw new FileCacheException($this->cacheDir.' is not writable'); @@ -73,15 +74,15 @@ public function get($key) } /** - * {@inheritdoc } + * {@inheritdoc} */ public function has($key) { - if ($this->getData($key)) { - return true; + if (!$this->getData($key)) { + return false; } - return false; + return true; } /** @@ -96,10 +97,10 @@ public function set($key, $value, $ttl = null) $ttl = $this->ttl; } $item = $this->serialize( - array( + [ 'value' => $tValue, - 'ttl' => (int) $ttl + time(), - ) + 'ttl' => (int)$ttl + time(), + ] ); if (!file_put_contents($cacheFile, $item)) { throw new FileCacheException('Error saving data with the key "'.$key.'" to the cache file.'); @@ -113,7 +114,7 @@ public function setOption($key, $value) { switch ($key) { case 'ttl': - $value = (int) $value; + $value = (int)$value; if ($value < 1) { throw new FileCacheException('ttl cant be lower than 1'); } @@ -151,6 +152,7 @@ public function dropCache() * Delete file * * @param string $cacheFile + * * @return bool */ protected function deleteFile($cacheFile) @@ -177,7 +179,8 @@ protected function getCacheFile($fileName) /** * Get data value from file cache * - * @param string $key + * @param string $key + * * @return mixed * @throws FileCacheException */ diff --git a/tests/performance/common.php b/tests/performance/common.php index 4e6cb63..414f718 100644 --- a/tests/performance/common.php +++ b/tests/performance/common.php @@ -11,15 +11,14 @@ * @author Daniel González */ -use Desarrolla2\Timer\Timer; //build test data outside of timing loop -$data = array(); +$data = []; for ($i = 1; $i <= 10000; $i++) { $data[$i] = md5($i); } -$timer = new Timer(); +$timer = new \Desarrolla2\Timer\Timer(new \Desarrolla2\Timer\Formatter\Human()); for ($i = 1; $i <= 10000; $i++) { $cache->set($data[$i], $data[$i], 3600); } @@ -38,12 +37,7 @@ } $timer->mark('10.000 has+get combos'); -$benchmarks = $timer->get(); +$benchmarks = $timer->getAll(); foreach ($benchmarks as $benchmark) { - printf( - "%30s : duration %0.2fms memory %s\n", - $benchmark['text'], - $benchmark['from_previous']*1000, - $benchmark['memory'] - ); + ld($benchmark); } From 0a172f6be701ff277daecfc0ac7cf87a5400675e Mon Sep 17 00:00:00 2001 From: Krishnaprasad MG Date: Wed, 2 Sep 2015 12:38:15 +0200 Subject: [PATCH 02/51] Added use statement for MemoryCacheException --- src/Adapter/Memory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Adapter/Memory.php b/src/Adapter/Memory.php index 8617936..7efa5dd 100644 --- a/src/Adapter/Memory.php +++ b/src/Adapter/Memory.php @@ -1,5 +1,4 @@ Date: Wed, 21 Oct 2015 17:06:50 +0100 Subject: [PATCH 03/51] Initial round of refactoring to ensure that has() works without false negatives --- src/Adapter/File.php | 147 ++++++++++++++++++++++++++++++------------- 1 file changed, 103 insertions(+), 44 deletions(-) diff --git a/src/Adapter/File.php b/src/Adapter/File.php index fca8054..ab3c26a 100644 --- a/src/Adapter/File.php +++ b/src/Adapter/File.php @@ -38,14 +38,22 @@ public function __construct($cacheDir = null) if (!$cacheDir) { $cacheDir = realpath(sys_get_temp_dir()).'/cache'; } - $this->cacheDir = (string)$cacheDir; - if (!is_dir($this->cacheDir)) { - if (!mkdir($this->cacheDir, 0777, true)) { - throw new FileCacheException($this->cacheDir.' is not writable'); + + $this->cacheDir = (string) $cacheDir; + + $this->createCacheDirectory($cacheDir); + } + + protected function createCacheDirectory($path) + { + if (! is_dir($path)) { + if (! mkdir($path, 0777, true)) { + throw new FileCacheException($path.' is not writable'); } } - if (!is_writable($this->cacheDir)) { - throw new FileCacheException($this->cacheDir.' is not writable'); + + if (! is_writable($path)) { + throw new FileCacheException($path.' is not writable'); } } @@ -66,11 +74,13 @@ public function delete($key) */ public function get($key) { - if ($data = $this->getData($key)) { - return $data; + $data = $this->getCacheData($key); + + if (isset($data['value'])) { + return $data['value']; } - return false; + return null; } /** @@ -78,11 +88,7 @@ public function get($key) */ public function has($key) { - if (!$this->getData($key)) { - return false; - } - - return true; + return ! is_null($this->getData($key)); } /** @@ -165,53 +171,106 @@ protected function deleteFile($cacheFile) } /** - * Get the specified cache file + * Get the specified cache file path */ protected function getCacheFile($fileName) { return $this->cacheDir. - DIRECTORY_SEPARATOR. - self::CACHE_FILE_PREFIX. - $fileName. - self::CACHE_FILE_SUBFIX; + DIRECTORY_SEPARATOR. + self::CACHE_FILE_PREFIX. + $fileName. + self::CACHE_FILE_SUBFIX; } /** - * Get data value from file cache + * Load the contents of a file at a given path and unserialize the contents + * @param string $path The path of the file to load + * @return array Unserialized file contents * - * @param string $key - * - * @return mixed - * @throws FileCacheException + * @throws Desarrolla2\Cache\Exception\FileCacheException */ - protected function getData($key) + protected function loadFile($path) { - $tKey = $this->getKey($key); - $cacheFile = $this->getCacheFile($tKey); - if (!file_exists($cacheFile)) { + if (! file_exists($path)) { return; } - if (!$data = unserialize(file_get_contents($cacheFile))) { - throw new FileCacheException( - 'Error with the key "'.$key.'" in cache file '.$cacheFile - ); - } - if (!array_key_exists('value', $data)) { - throw new FileCacheException( - 'Error with the key "'.$key.'" in cache file '.$cacheFile.', value not exist' - ); + + $data = unserialize(file_get_contents($path)); + + if (! $data) { + throw new FileCacheException("Unable to load cache file at {$path}"); } - if (!array_key_exists('ttl', $data)) { - throw new FileCacheException( - 'Error with the key "'.$key.'" in cache file '.$cacheFile.', ttl not exist' - ); + + return $data; + } + + /** + * Get a standardised data structure irrespective of failure in + * order to better determine hits/misses etc when using has() + * @param string $key Cache key to attempt to load + * @return array Standardised array format with keys 'value' and 'ttl' + */ + protected function getCacheData($key) + { + $path = $this->getCacheFile($this->getKey($key)); + + $data = $this->loadFile($path); + + if (! $data) { + return ['value' => null, 'ttl' => null]; } - if (time() > $data['ttl']) { + + // Determine if the structure of our data is correct + // Leaving this throwing exceptions to prevent BC breaks + $this->validateCacheData($data, $path); + + // Expire old cache values + if ($this->ttlHasExpired($data['ttl'])) { $this->delete($key); - return; + return ['value' => null, 'ttl' => null]; } - return $this->unserialize($data['value']); + return [ + 'value' => unserialize($data['value']), + 'ttl' => $data['ttl'] + ]; + } + + /** + * Determine if a given timestamp is in the past + * @param int $ttl Unix timestamp denoting the desired expiry time + * @return [type] [description] + */ + private function ttlHasExpired($ttl) + { + return (time() > $ttl); + } + + /** + * [validateCacheData description] + * @param [type] $data [description] + * @param [type] $path [description] + * @return void + * + * @throws Desarrolla2\Cache\Exception\FileCacheException + */ + protected function validateCacheData($data, $path = null) + { + foreach (['value', 'ttl'] as $key) { + if (! array_key_exists($key, $data)) { + throw new FileCacheException("{$key} missing from cache file {$path}"); + } + } + } + + /** + * Get data value from file cache + * @param string $key + * @return mixed + */ + protected function getData($key) + { + return $this->getCacheData($key)['value']; } } From b3e0e0ade44003f98413a420f676fae4245dca99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 22 Oct 2015 11:05:56 +0200 Subject: [PATCH 04/51] fix #20 --- composer.json | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 0d4690a..e177f7a 100644 --- a/composer.json +++ b/composer.json @@ -1,35 +1,30 @@ { "name": "desarrolla2/cache", - "description": "Provides an cache interface for several adapters (Apc, File, Mongo, Memcached, Mysql, ... )", + "description": "Provides an cache interface for several adapters Apc, Apcu, File, Mongo, Memcache, Memcached, Mysql, Mongo, Redis is supported. New adapters is comming!", "keywords": [ "cache", "apc", + "apcu", "file", "memcached", + "memcache", "mysql", "mongo", "redis" ], "type": "library", "license": "MIT", - "homepage": "https://github.com/desarrolla2/Cache/blob/master/README.md", + "homepage": "https://github.com/desarrolla2/Cache/", "authors": [ { "name": "Daniel González", - "homepage": "http://desarrolla2.com/", - "role": "Developer" + "homepage": "http://desarrolla2.com/" } ], - "support": { - "issues": "https://github.com/desarrolla2/Cache/issues" - }, "require": { - "php": ">=5.3.0" + "php": ">=5.4.0" }, "require-dev": { - "desarrolla2/timer": "dev-master", - "raulfraile/ladybug": "~1.0", - "symfony/yaml": "dev-master", "predis/predis": "*" }, "autoload": { From 8800d2bb92669661d43f749aeb87a05e340b42f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 22 Oct 2015 11:53:35 +0200 Subject: [PATCH 05/51] fixed redis tests --- .gitignore | 1 + composer.json | 5 ++--- src/Adapter/AbstractAdapter.php | 22 ++++++++++++++----- src/Adapter/AdapterInterface.php | 10 +++++++++ src/Adapter/Apc.php | 8 +++---- src/Adapter/Apcu.php | 16 ++++++++++++++ src/Adapter/File.php | 22 +++++++++---------- src/Adapter/Redis.php | 16 ++++++++++---- src/Exception/AdapterNotSetException.php | 2 +- src/Exception/ApcCacheException.php | 21 ------------------ src/Exception/CacheException.php | 2 +- src/Exception/FileCacheException.php | 21 ------------------ src/Exception/MemcacheException.php | 21 ------------------ src/Exception/MemoryCacheException.php | 21 ------------------ src/Exception/MongoCacheException.php | 21 ------------------ src/Exception/MySQLCacheException.php | 21 ------------------ .../Cache/Adapter/Test/FileTest.php | 4 ++-- tests/bootstrap.php | 1 - 18 files changed, 77 insertions(+), 158 deletions(-) create mode 100644 src/Adapter/Apcu.php delete mode 100644 src/Exception/ApcCacheException.php delete mode 100644 src/Exception/FileCacheException.php delete mode 100644 src/Exception/MemcacheException.php delete mode 100644 src/Exception/MemoryCacheException.php delete mode 100644 src/Exception/MongoCacheException.php delete mode 100644 src/Exception/MySQLCacheException.php diff --git a/.gitignore b/.gitignore index d2e3f0a..931a0af 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ /tests/config.yml /vendor /.idea +/TODO.md *~ *.swp diff --git a/composer.json b/composer.json index e177f7a..7be1414 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,11 @@ "php": ">=5.4.0" }, "require-dev": { - "predis/predis": "*" + "predis/predis": "~1.0.0" }, "autoload": { "psr-4": { "Desarrolla2\\Cache\\": "src/" } - }, - "minimum-stability": "dev" + } } diff --git a/src/Adapter/AbstractAdapter.php b/src/Adapter/AbstractAdapter.php index b97ce76..51349ff 100644 --- a/src/Adapter/AbstractAdapter.php +++ b/src/Adapter/AbstractAdapter.php @@ -42,17 +42,17 @@ public function setOption($key, $value) { switch ($key) { case 'ttl': - $value = (int) $value; + $value = (int)$value; if ($value < 1) { throw new CacheException('ttl cant be lower than 1'); } $this->ttl = $value; break; case 'prefix': - $this->prefix = (string) $value; + $this->prefix = (string)$value; break; case 'serialize': - $this->serialize = (bool) $value; + $this->serialize = (bool)$value; break; default: throw new CacheException('option not valid '.$key); @@ -66,7 +66,7 @@ public function setOption($key, $value) */ public function clearCache() { - throw new Exception('not ready yet'); + throw new CacheException('not ready yet'); } /** @@ -74,12 +74,21 @@ public function clearCache() */ public function dropCache() { - throw new Exception('not ready yet'); + throw new CacheException('not ready yet'); + } + + /** + * {@inheritdoc } + */ + public function check() + { + return true; } /** * * @param string $key + * * @return string */ protected function getKey($key) @@ -92,6 +101,7 @@ protected function getKey($key) * Builds the key according to the prefix and other options * * @param string key + * * @return string */ protected function buildKey($key) @@ -104,6 +114,7 @@ protected function buildKey($key) * according to the options on the adapter. * * @param mixed $data + * * @return mixed */ protected function packData($data) @@ -121,6 +132,7 @@ protected function packData($data) * of packData IF the options are set correctly. * * @param mixed $data + * * @return mixed */ protected function unpackData($data) diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php index 73be169..b5bef6c 100644 --- a/src/Adapter/AdapterInterface.php +++ b/src/Adapter/AdapterInterface.php @@ -18,6 +18,14 @@ */ interface AdapterInterface { + + /** + * Check if adapter is working + * + * @return boolean + */ + public function check(); + /** * Delete a value from the cache * @@ -29,6 +37,7 @@ public function delete($key); * Retrieve the value corresponding to a provided key * * @param string $key + * * @return mixed */ public function get($key); @@ -37,6 +46,7 @@ public function get($key); * Retrieve the if value corresponding to a provided key exist * * @param string $key + * * @return bool */ public function has($key); diff --git a/src/Adapter/Apc.php b/src/Adapter/Apc.php index 68549e7..2b953c1 100644 --- a/src/Adapter/Apc.php +++ b/src/Adapter/Apc.php @@ -22,12 +22,12 @@ class Apc extends AbstractAdapter { private $apcu; - + public function __construct() { $this->apcu = extension_loaded('apcu'); } - + /** * Delete a value from the cache * @@ -80,14 +80,14 @@ public function has($key) /** * {@inheritdoc } - */ + */ public function set($key, $value, $ttl = null) { $tKey = $this->getKey($key); $tValue = $this->serialize($value); if (!$ttl) { $ttl = $this->ttl; - } + } if (!($this->apcu ? \apcu_store($tKey, $tValue, $ttl) : \apc_store($tKey, $tValue, $ttl))) { throw new ApcCacheException('Error saving data with the key "'.$key.'" to the APC cache.'); } diff --git a/src/Adapter/Apcu.php b/src/Adapter/Apcu.php new file mode 100644 index 0000000..e48a31c --- /dev/null +++ b/src/Adapter/Apcu.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Desarrolla2\Cache\Adapter; + + +class Apcu { + +} \ No newline at end of file diff --git a/src/Adapter/File.php b/src/Adapter/File.php index ab3c26a..3eae3e7 100644 --- a/src/Adapter/File.php +++ b/src/Adapter/File.php @@ -13,7 +13,7 @@ namespace Desarrolla2\Cache\Adapter; -use Desarrolla2\Cache\Exception\FileCacheException; +use Desarrolla2\Cache\Exception\CacheException; /** * File @@ -31,7 +31,7 @@ class File extends AbstractAdapter /** * @param null $cacheDir * - * @throws FileCacheException + * @throws CacheException */ public function __construct($cacheDir = null) { @@ -48,12 +48,12 @@ protected function createCacheDirectory($path) { if (! is_dir($path)) { if (! mkdir($path, 0777, true)) { - throw new FileCacheException($path.' is not writable'); + throw new CacheException($path.' is not writable'); } } if (! is_writable($path)) { - throw new FileCacheException($path.' is not writable'); + throw new CacheException($path.' is not writable'); } } @@ -109,7 +109,7 @@ public function set($key, $value, $ttl = null) ] ); if (!file_put_contents($cacheFile, $item)) { - throw new FileCacheException('Error saving data with the key "'.$key.'" to the cache file.'); + throw new CacheException('Error saving data with the key "'.$key.'" to the cache file.'); } } @@ -122,12 +122,12 @@ public function setOption($key, $value) case 'ttl': $value = (int)$value; if ($value < 1) { - throw new FileCacheException('ttl cant be lower than 1'); + throw new CacheException('ttl cant be lower than 1'); } $this->ttl = $value; break; default: - throw new FileCacheException('option not valid '.$key); + throw new CacheException('option not valid '.$key); } return true; @@ -187,7 +187,7 @@ protected function getCacheFile($fileName) * @param string $path The path of the file to load * @return array Unserialized file contents * - * @throws Desarrolla2\Cache\Exception\FileCacheException + * @throws Desarrolla2\Cache\Exception\CacheException */ protected function loadFile($path) { @@ -198,7 +198,7 @@ protected function loadFile($path) $data = unserialize(file_get_contents($path)); if (! $data) { - throw new FileCacheException("Unable to load cache file at {$path}"); + throw new CacheException("Unable to load cache file at {$path}"); } return $data; @@ -253,13 +253,13 @@ private function ttlHasExpired($ttl) * @param [type] $path [description] * @return void * - * @throws Desarrolla2\Cache\Exception\FileCacheException + * @throws Desarrolla2\Cache\Exception\CacheException */ protected function validateCacheData($data, $path = null) { foreach (['value', 'ttl'] as $key) { if (! array_key_exists($key, $data)) { - throw new FileCacheException("{$key} missing from cache file {$path}"); + throw new CacheException("{$key} missing from cache file {$path}"); } } } diff --git a/src/Adapter/Redis.php b/src/Adapter/Redis.php index c2d496f..27dc3d9 100644 --- a/src/Adapter/Redis.php +++ b/src/Adapter/Redis.php @@ -27,7 +27,13 @@ class Redis extends AbstractAdapter public function __construct() { - $this->client = new Client(); + $this->client = new Client( + [ + 'scheme' => 'tcp', + 'host' => '127.0.0.1', + 'port' => 6379, + ] + ); } public function __destruct() @@ -43,7 +49,7 @@ public function __destruct() public function delete($key) { $cmd = $this->client->createCommand('DEL'); - $cmd->setArguments(array($key)); + $cmd->setArguments([$key]); $this->client->executeCommand($cmd); } @@ -52,6 +58,7 @@ public function delete($key) * Retrieve the value corresponding to a provided key * * @param string $key + * * @return mixed */ public function get($key) @@ -63,12 +70,13 @@ public function get($key) * Retrieve the if value corresponding to a provided key exist * * @param string $key + * * @return bool */ public function has($key) { $cmd = $this->client->createCommand('EXISTS'); - $cmd->setArguments(array($key)); + $cmd->setArguments([$key]); return $this->client->executeCommand($cmd); } @@ -85,7 +93,7 @@ public function set($key, $value, $ttl = null) $this->client->set($key, $value); if ($ttl) { $cmd = $this->client->createCommand('EXPIRE'); - $cmd->setArguments(array($key, $ttl)); + $cmd->setArguments([$key, $ttl]); $this->client->executeCommand($cmd); } } diff --git a/src/Exception/AdapterNotSetException.php b/src/Exception/AdapterNotSetException.php index 7f1d439..abf2673 100644 --- a/src/Exception/AdapterNotSetException.php +++ b/src/Exception/AdapterNotSetException.php @@ -16,6 +16,6 @@ /** * AdapterNotSetException */ -class AdapterNotSetException extends \Exception +class AdapterNotSetException extends CacheException { } diff --git a/src/Exception/ApcCacheException.php b/src/Exception/ApcCacheException.php deleted file mode 100644 index 99232fc..0000000 --- a/src/Exception/ApcCacheException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Exception; - -/** - * ApcCacheException - */ -class ApcCacheException extends \Exception -{ -} diff --git a/src/Exception/CacheException.php b/src/Exception/CacheException.php index 717d0b2..b2d43d1 100644 --- a/src/Exception/CacheException.php +++ b/src/Exception/CacheException.php @@ -16,6 +16,6 @@ /** * CacheException */ -class CacheException extends \Exception +class CacheException extends \RuntimeException { } diff --git a/src/Exception/FileCacheException.php b/src/Exception/FileCacheException.php deleted file mode 100644 index ef3a956..0000000 --- a/src/Exception/FileCacheException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Exception; - -/** - * FileCacheException - */ -class FileCacheException extends \Exception -{ -} diff --git a/src/Exception/MemcacheException.php b/src/Exception/MemcacheException.php deleted file mode 100644 index 06ee948..0000000 --- a/src/Exception/MemcacheException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Exception; - -/** - * MemcacheException - */ -class MemcacheException extends \Exception -{ -} diff --git a/src/Exception/MemoryCacheException.php b/src/Exception/MemoryCacheException.php deleted file mode 100644 index 98cdb81..0000000 --- a/src/Exception/MemoryCacheException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Exception; - -/** - * MemoryCacheException - */ -class MemoryCacheException extends \Exception -{ -} diff --git a/src/Exception/MongoCacheException.php b/src/Exception/MongoCacheException.php deleted file mode 100644 index 3108995..0000000 --- a/src/Exception/MongoCacheException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Exception; - -/** - * MongoCacheException - */ -class MongoCacheException extends \Exception -{ -} diff --git a/src/Exception/MySQLCacheException.php b/src/Exception/MySQLCacheException.php deleted file mode 100644 index 41e5b58..0000000 --- a/src/Exception/MySQLCacheException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Exception; - -/** - * MySQLCacheException - */ -class MySQLCacheException extends \Exception -{ -} diff --git a/tests/Desarrolla2/Cache/Adapter/Test/FileTest.php b/tests/Desarrolla2/Cache/Adapter/Test/FileTest.php index 9aac98f..2e06f1a 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/FileTest.php +++ b/tests/Desarrolla2/Cache/Adapter/Test/FileTest.php @@ -33,8 +33,8 @@ public function setUp() public function dataProviderForOptionsException() { return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\FileCacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\FileCacheException'), + array('ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'), + array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), ); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f394c44..6cfb6fe 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -12,4 +12,3 @@ */ $loader = require_once __DIR__.'/../vendor/autoload.php'; -Ladybug\Loader::loadHelpers(); From 33e217097acb2d5864336f475e3b5b9f894d0695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 22 Oct 2015 17:58:21 +0200 Subject: [PATCH 06/51] [v.2.0.0] wip --- .gitignore | 2 +- .travis.yml | 8 +- README.md | 47 ++++- doc/performance.md | 2 +- phpunit.xml | 7 - phpunit.xml.dist | 20 ++ src/Adapter/AbstractAdapter.php | 78 +------ src/Adapter/AdapterInterface.php | 2 +- src/Adapter/Apc.php | 123 ----------- src/Adapter/Apcu.php | 121 ++++++++++- src/Adapter/File.php | 192 +++++------------- src/Adapter/MemCache.php | 97 --------- src/Adapter/Memcache.php | 87 ++++++++ src/Adapter/Memcached.php | 103 +++------- src/Adapter/Memory.php | 42 ++-- src/Adapter/Mongo.php | 167 --------------- src/Adapter/MySQL.php | 168 --------------- src/Adapter/Mysqli.php | 189 +++++++++++++++++ src/Adapter/NotCache.php | 10 +- src/Adapter/Predis.php | 93 +++++++++ src/Adapter/Redis.php | 100 --------- src/Cache.php | 20 +- .../Cache/Adapter/Test/AbstractCacheTest.php | 44 ++-- .../{ApcCacheTest.php => ApcuCacheTest.php} | 27 ++- .../Test/{MongoTest.php => MemcacheTest.php} | 22 +- .../{MemCacheTest.php => MemcachedTest.php} | 21 +- .../Cache/Adapter/Test/MemoryTest.php | 16 +- .../Cache/Adapter/Test/MySQLTest.php | 63 ------ .../Cache/Adapter/Test/MysqliTest.php | 66 ++++++ .../Test/{RedisTest.php => PredisTest.php} | 28 ++- tests/config.json.dist | 19 ++ tests/config.yml.dist | 13 -- tests/performance/Apc.php | 4 +- 33 files changed, 841 insertions(+), 1160 deletions(-) create mode 100644 phpunit.xml.dist delete mode 100644 src/Adapter/Apc.php delete mode 100644 src/Adapter/MemCache.php create mode 100644 src/Adapter/Memcache.php delete mode 100644 src/Adapter/Mongo.php delete mode 100644 src/Adapter/MySQL.php create mode 100644 src/Adapter/Mysqli.php create mode 100644 src/Adapter/Predis.php delete mode 100644 src/Adapter/Redis.php rename tests/Desarrolla2/Cache/Adapter/Test/{ApcCacheTest.php => ApcuCacheTest.php} (50%) rename tests/Desarrolla2/Cache/Adapter/Test/{MongoTest.php => MemcacheTest.php} (54%) rename tests/Desarrolla2/Cache/Adapter/Test/{MemCacheTest.php => MemcachedTest.php} (61%) delete mode 100644 tests/Desarrolla2/Cache/Adapter/Test/MySQLTest.php create mode 100644 tests/Desarrolla2/Cache/Adapter/Test/MysqliTest.php rename tests/Desarrolla2/Cache/Adapter/Test/{RedisTest.php => PredisTest.php} (56%) create mode 100644 tests/config.json.dist delete mode 100644 tests/config.yml.dist diff --git a/.gitignore b/.gitignore index 931a0af..2a66a32 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /build /composer.lock -/tests/config.yml +/tests/config.json /vendor /.idea /TODO.md diff --git a/.travis.yml b/.travis.yml index 4b91d35..6e9909c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ language: php php: - - 5.3 - 5.4 - 5.5 - 5.6 + - 7 - hhvm services: @@ -18,12 +18,12 @@ notifications: before_script: -# Create MySQL Database +# Create Mysqli Database - mysql -e 'CREATE DATABASE IF NOT EXISTS `cache`;' - - mysql -e 'USE `cache`; CREATE TABLE `cache` (`hash` varchar(255) NOT NULL, `value` text NOT NULL, `ttl` int(11) NOT NULL, PRIMARY KEY (`hash`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' + - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' # Install dependencies - composer install --prefer-source # Set Configuration - - cp tests/config.yml.dist tests/config.yml \ No newline at end of file + - cp tests/config.json.dist tests/config.json \ No newline at end of file diff --git a/README.md b/README.md index 21d162e..3e7b45b 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ $cache = new Cache($adapter); ``` -### Apc +### Apcu Use it if you will you have APC cache available in your system. @@ -81,9 +81,9 @@ Use it if you will you have APC cache available in your system. setOption('ttl', 3600); $cache = new Cache($adapter); @@ -127,7 +127,7 @@ $cache = new Cache($adapter); ``` -### MySQL +### Mysqli Use it if you will you have mysqlnd available in your system. @@ -135,30 +135,55 @@ Use it if you will you have mysqlnd available in your system. setOption('ttl', 3600); $cache = new Cache($adapter); ``` -### Redis +### Predis Use it if you will you have redis available in your system. +You need to add predis as dependency in your composer file. + +``` json +"require": { + //... + "predis/predis": "~1.0.0" +} +``` + +other version will have compatibility issues. + ``` php setOption('ttl', 3600); $cache = new Cache($adapter); ``` +If you need to configure your predis client, you will instantiate it and pass it to constructor. + +``` php +setOption('ttl', 3600); $cache = new Cache($adapter); diff --git a/doc/performance.md b/doc/performance.md index 5e79728..4b01b2e 100644 --- a/doc/performance.md +++ b/doc/performance.md @@ -5,7 +5,7 @@ Here are my performance tests, you can view the results ordered from faster to s | Adapter | 10.000 set | 10.000 has | 10.000 get | | :-------------- | -----------: | -----------: | ---------: | | NoCache | 0.0637 | 0.0482 | 0.0488 | -| Apc | 0.0961 | 0.0556 | 0.0770 | +| Apcu | 0.0961 | 0.0556 | 0.0770 | | File | 0.6881 | 0.3426 | 0.3107 | | Mongo | 13.8144 | 30.0203 | 24.4214 | diff --git a/phpunit.xml b/phpunit.xml index a253f39..020c2f9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,6 @@ - ./tests @@ -18,10 +17,4 @@ ./build - - - - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..020c2f9 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + + ./tests + + + + + + ./Test + + + /usr/share/pear/ + ./vendor + ./build + + + diff --git a/src/Adapter/AbstractAdapter.php b/src/Adapter/AbstractAdapter.php index 51349ff..34638f0 100644 --- a/src/Adapter/AbstractAdapter.php +++ b/src/Adapter/AbstractAdapter.php @@ -31,12 +31,7 @@ abstract class AbstractAdapter implements AdapterInterface protected $prefix = ''; /** - * @var bool - */ - protected $serialize = true; - - /** - * {@inheritdoc } + * {@inheritdoc} */ public function setOption($key, $value) { @@ -51,9 +46,6 @@ public function setOption($key, $value) case 'prefix': $this->prefix = (string)$value; break; - case 'serialize': - $this->serialize = (bool)$value; - break; default: throw new CacheException('option not valid '.$key); } @@ -62,7 +54,7 @@ public function setOption($key, $value) } /** - * {@inheritdoc } + * {@inheritdoc} */ public function clearCache() { @@ -70,7 +62,7 @@ public function clearCache() } /** - * {@inheritdoc } + * {@inheritdoc} */ public function dropCache() { @@ -78,78 +70,24 @@ public function dropCache() } /** - * {@inheritdoc } + * {@inheritdoc} */ public function check() { - return true; + throw new CacheException('not ready yet'); } - /** - * - * @param string $key - * - * @return string - */ protected function getKey($key) { - //return md5($key); - return $key; - } - - /** - * Builds the key according to the prefix and other options - * - * @param string key - * - * @return string - */ - protected function buildKey($key) - { - return $this->prefix.$key; - } - - /** - * Packages the data to be stored by the internal caching driver - * according to the options on the adapter. - * - * @param mixed $data - * - * @return mixed - */ - protected function packData($data) - { - if ($this->serialize) { - return serialize($data); - } - - return $data; - } - - /** - * Unpackages the data retrieved by the internal caching driver - * according to the options on the adapter. This will be the inverse - * of packData IF the options are set correctly. - * - * @param mixed $data - * - * @return mixed - */ - protected function unpackData($data) - { - if ($this->serialize) { - return unserialize($data); - } - - return $data; + return sprintf('%s%s', $this->prefix, $key); } - protected function serialize($value) + protected function pack($value) { return serialize($value); } - protected function unserialize($value) + protected function unPack($value) { return unserialize($value); } diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php index b5bef6c..e8b82c7 100644 --- a/src/Adapter/AdapterInterface.php +++ b/src/Adapter/AdapterInterface.php @@ -31,7 +31,7 @@ public function check(); * * @param string $key */ - public function delete($key); + public function del($key); /** * Retrieve the value corresponding to a provided key diff --git a/src/Adapter/Apc.php b/src/Adapter/Apc.php deleted file mode 100644 index 2b953c1..0000000 --- a/src/Adapter/Apc.php +++ /dev/null @@ -1,123 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -use Desarrolla2\Cache\Exception\ApcCacheException; - -/** - * Apc - */ -class Apc extends AbstractAdapter -{ - - private $apcu; - - public function __construct() - { - $this->apcu = extension_loaded('apcu'); - } - - /** - * Delete a value from the cache - * - * @param string $key - * @throws \Desarrolla2\Cache\Exception\ApcCacheException - */ - public function delete($key) - { - if ($this->has($key)) { - $tKey = $this->getKey($key); - if (!($this->apcu ? \apcu_delete($tKey) : \apc_delete($tKey))) { - throw new ApcCacheException('Error deleting data with the key "'.$key.'" from the APC cache.'); - } - } - } - - /** - * {@inheritdoc } - */ - public function get($key) - { - if ($this->has($key)) { - $tKey = $this->getKey($key); - if (!$data = ($this->apcu ? \apcu_fetch($tKey) : \apc_fetch($tKey))) { - throw new ApcCacheException('Error fetching data with the key "'.$key.'" from the APC cache.'); - } - - return $this->unserialize($data); - } - - return null; - } - - /** - * {@inheritdoc } - */ - public function has($key) - { - $tKey = $this->getKey($key); - - if (function_exists("\apcu_exists") || function_exists("\apc_exists")) { - return (boolean) ($this->apcu ? \apcu_exists($tKey) : \apc_exists($tKey)); - } else { - $result = false; - ($this->apcu ? \apcu_fetch($tKey, $result) : \apc_fetch($tKey, $result)); - - return (boolean) $result; - } - } - - /** - * {@inheritdoc } - */ - public function set($key, $value, $ttl = null) - { - $tKey = $this->getKey($key); - $tValue = $this->serialize($value); - if (!$ttl) { - $ttl = $this->ttl; - } - if (!($this->apcu ? \apcu_store($tKey, $tValue, $ttl) : \apc_store($tKey, $tValue, $ttl))) { - throw new ApcCacheException('Error saving data with the key "'.$key.'" to the APC cache.'); - } - } - - /** - * {@inheritdoc } - */ - public function setOption($key, $value) - { - switch ($key) { - case 'ttl': - $value = (int) $value; - if ($value < 1) { - throw new ApcCacheException('ttl cant be lower than 1'); - } - $this->ttl = $value; - break; - default: - throw new ApcCacheException('option not valid '.$key); - } - - return true; - } - - /** - * {@inheritdoc } - */ - public function dropCache() - { - ($this->apcu ? apcu_clear_cache("user") : apc_clear_cache("user")); - } -} diff --git a/src/Adapter/Apcu.php b/src/Adapter/Apcu.php index e48a31c..6edaba8 100644 --- a/src/Adapter/Apcu.php +++ b/src/Adapter/Apcu.php @@ -1,16 +1,131 @@ + * Copyright (c) Daniel González * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. + * + * @author Daniel González */ namespace Desarrolla2\Cache\Adapter; +use Desarrolla2\Cache\Exception\CacheException; + +/** + * Apcu + */ +class Apcu extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function del($key) + { + apc_delete($this->getKey($key)); + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + return $this->getValueFromCache($key); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + $value = $this->getValueFromCache($key); + if (is_null($value)) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + if (!$ttl) { + $ttl = $this->ttl; + } + if (!apc_store( + $this->getKey($key), + $this->pack( + [ + 'value' => $value, + 'ttl' => (int)$ttl + time(), + ] + ), + $ttl + ) + ) { + throw new CacheException(sprintf('Error saving data with the key "%s" to the apcu cache.', $key)); + } + } + + /** + * {@inheritdoc} + */ + public function setOption($key, $value) + { + switch ($key) { + case 'ttl': + $value = (int)$value; + if ($value < 1) { + throw new CacheException('ttl cant be lower than 1'); + } + $this->ttl = $value; + break; + default: + throw new CacheException('option not valid '.$key); + } + + return true; + } + + protected function getValueFromCache($key) + { + $data = $this->unPack(apc_fetch($this->getKey($key))); + if (!$this->validateDataFromCache($data, $key)) { + $this->del($key); + + return; + } + if ($this->ttlHasExpired($data['ttl'])) { + $this->del($key); + + return; + } + + return $data['value']; + } + + protected function validateDataFromCache($data) + { + if (!is_array($data)) { + return false; + } + foreach (['value', 'ttl'] as $missing) { + if (!array_key_exists($missing, $data)) { + return false; + } + } + + return true; + } -class Apcu { -} \ No newline at end of file + protected function ttlHasExpired($ttl) + { + return (time() > $ttl); + } +} diff --git a/src/Adapter/File.php b/src/Adapter/File.php index 3eae3e7..4e1e507 100644 --- a/src/Adapter/File.php +++ b/src/Adapter/File.php @@ -21,6 +21,7 @@ class File extends AbstractAdapter { const CACHE_FILE_PREFIX = '__'; + const CACHE_FILE_SUBFIX = '.php.cache'; /** @@ -29,7 +30,7 @@ class File extends AbstractAdapter protected $cacheDir; /** - * @param null $cacheDir + * @param string $cacheDir * * @throws CacheException */ @@ -39,48 +40,27 @@ public function __construct($cacheDir = null) $cacheDir = realpath(sys_get_temp_dir()).'/cache'; } - $this->cacheDir = (string) $cacheDir; - - $this->createCacheDirectory($cacheDir); - } - - protected function createCacheDirectory($path) - { - if (! is_dir($path)) { - if (! mkdir($path, 0777, true)) { - throw new CacheException($path.' is not writable'); - } - } + $this->cacheDir = (string)$cacheDir; - if (! is_writable($path)) { - throw new CacheException($path.' is not writable'); - } + $this->createCacheDirectory($cacheDir); } /** - * Delete a value from the cache - * - * @param string $key + * {@inheritdoc} */ - public function delete($key) + public function del($key) { $tKey = $this->getKey($key); - $cacheFile = $this->getCacheFile($tKey); + $cacheFile = $this->getFileName($tKey); $this->deleteFile($cacheFile); } /** - * {@inheritdoc } + * {@inheritdoc} */ public function get($key) { - $data = $this->getCacheData($key); - - if (isset($data['value'])) { - return $data['value']; - } - - return null; + return $this->getValueFromCache($key); } /** @@ -88,33 +68,31 @@ public function get($key) */ public function has($key) { - return ! is_null($this->getData($key)); + return !is_null($this->getValueFromCache($key)); } /** - * {@inheritdoc } + * {@inheritdoc} */ public function set($key, $value, $ttl = null) { - $tKey = $this->getKey($key); - $cacheFile = $this->getCacheFile($tKey); - $tValue = $this->serialize($value); - if (!($ttl)) { + $cacheFile = $this->getFileName($key); + if (!$ttl) { $ttl = $this->ttl; } - $item = $this->serialize( + $item = $this->pack( [ - 'value' => $tValue, + 'value' => $value, 'ttl' => (int)$ttl + time(), ] ); if (!file_put_contents($cacheFile, $item)) { - throw new CacheException('Error saving data with the key "'.$key.'" to the cache file.'); + throw new CacheException(sprintf('Error saving data with the key "%s" to the cache file.', $key)); } } /** - * {@inheritdoc } + * {@inheritdoc} */ public function setOption($key, $value) { @@ -133,34 +111,20 @@ public function setOption($key, $value) return true; } - /** - * {@inheritdoc } - */ - public function clearCache() + protected function createCacheDirectory($path) { - throw new Exception('not ready yet'); - } + if (!is_dir($path)) { + if (!mkdir($path, 0777, true)) { + throw new CacheException($path.' is not writable'); + } + } - /** - * {@inheritdoc } - */ - public function dropCache() - { - foreach (scandir($this->cacheDir) as $fileName) { - $cacheFile = $this->cacheDir. - DIRECTORY_SEPARATOR. - $fileName; - $this->deleteFile($cacheFile); + if (!is_writable($path)) { + throw new CacheException($path.' is not writable'); } } - /** - * Delete file - * - * @param string $cacheFile - * - * @return bool - */ + protected function deleteFile($cacheFile) { if (is_file($cacheFile)) { @@ -170,107 +134,47 @@ protected function deleteFile($cacheFile) return false; } - /** - * Get the specified cache file path - */ - protected function getCacheFile($fileName) + protected function getFileName($key) { return $this->cacheDir. - DIRECTORY_SEPARATOR. - self::CACHE_FILE_PREFIX. - $fileName. - self::CACHE_FILE_SUBFIX; + DIRECTORY_SEPARATOR. + self::CACHE_FILE_PREFIX. + $this->getKey($key). + self::CACHE_FILE_SUBFIX; } - /** - * Load the contents of a file at a given path and unserialize the contents - * @param string $path The path of the file to load - * @return array Unserialized file contents - * - * @throws Desarrolla2\Cache\Exception\CacheException - */ - protected function loadFile($path) + protected function getValueFromCache($key) { - if (! file_exists($path)) { + $path = $this->getFileName($this->getKey($key)); + + if (!file_exists($path)) { return; } - $data = unserialize(file_get_contents($path)); - - if (! $data) { - throw new CacheException("Unable to load cache file at {$path}"); + $data = $this->unPack(file_get_contents($path)); + if (!$data || !$this->validateDataFromCache($data) || $this->ttlHasExpired($data['ttl'])) { + return; } - return $data; + return $data['value']; } - /** - * Get a standardised data structure irrespective of failure in - * order to better determine hits/misses etc when using has() - * @param string $key Cache key to attempt to load - * @return array Standardised array format with keys 'value' and 'ttl' - */ - protected function getCacheData($key) + protected function validateDataFromCache($data) { - $path = $this->getCacheFile($this->getKey($key)); - - $data = $this->loadFile($path); - - if (! $data) { - return ['value' => null, 'ttl' => null]; + if (!is_array($data)) { + return false; } - - // Determine if the structure of our data is correct - // Leaving this throwing exceptions to prevent BC breaks - $this->validateCacheData($data, $path); - - // Expire old cache values - if ($this->ttlHasExpired($data['ttl'])) { - $this->delete($key); - - return ['value' => null, 'ttl' => null]; + foreach (['value', 'ttl'] as $missing) { + if (!array_key_exists($missing, $data)) { + return false; + } } - return [ - 'value' => unserialize($data['value']), - 'ttl' => $data['ttl'] - ]; + return true; } - /** - * Determine if a given timestamp is in the past - * @param int $ttl Unix timestamp denoting the desired expiry time - * @return [type] [description] - */ - private function ttlHasExpired($ttl) + protected function ttlHasExpired($ttl) { return (time() > $ttl); } - - /** - * [validateCacheData description] - * @param [type] $data [description] - * @param [type] $path [description] - * @return void - * - * @throws Desarrolla2\Cache\Exception\CacheException - */ - protected function validateCacheData($data, $path = null) - { - foreach (['value', 'ttl'] as $key) { - if (! array_key_exists($key, $data)) { - throw new CacheException("{$key} missing from cache file {$path}"); - } - } - } - - /** - * Get data value from file cache - * @param string $key - * @return mixed - */ - protected function getData($key) - { - return $this->getCacheData($key)['value']; - } } diff --git a/src/Adapter/MemCache.php b/src/Adapter/MemCache.php deleted file mode 100644 index 433aac0..0000000 --- a/src/Adapter/MemCache.php +++ /dev/null @@ -1,97 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -use \Memcache as BaseMemCache; - -/** - * MemCache - */ -class MemCache extends AbstractAdapter -{ - /** - * - * @var \Memcache - */ - protected $server; - - public function __construct() - { - $this->server = new BaseMemcache(); - $this->server->addServer('localhost', 11211); - } - - /** - * - * @param string $host - * @param string $port - */ - public function addServer($host, $port) - { - $this->server->addServer($host, $port); - } - - /** - * Delete a value from the cache - * - * @param string $key - */ - public function delete($key) - { - $tKey = $this->getKey($key); - $this->server->delete($tKey); - } - - /** - * {@inheritdoc } - */ - public function get($key) - { - $tKey = $this->getKey($key); - $data = $this->server->get($tKey); - - return $this->unserialize($data); - } - - /** - * {@inheritdoc } - */ - public function has($key) - { - $tKey = $this->getKey($key); - if ($this->server->get($tKey)) { - return true; - } - - return false; - } - - /** - * {@inheritdoc } - */ - public function set($key, $value, $ttl = null) - { - $tKey = $this->getKey($key); - $tValue = $this->serialize($value); - if (!$ttl) { - $ttl = $this->ttl; - } - $this->server->set($tKey, $tValue, false, time() + $ttl); - } - - public function dropCache($delay = 0) - { - $this->server->flush($delay); - } -} diff --git a/src/Adapter/Memcache.php b/src/Adapter/Memcache.php new file mode 100644 index 0000000..b0051f7 --- /dev/null +++ b/src/Adapter/Memcache.php @@ -0,0 +1,87 @@ + + */ + +namespace Desarrolla2\Cache\Adapter; + +use \Memcache as BaseMemcache; + +/** + * Memcache + */ +class Memcache extends AbstractAdapter +{ + /** + * + * @var BaseMemcache + */ + protected $server; + + /** + * @param BaseMemCache|null $server + */ + public function __construct(BaseMemcache $server = null) + { + if ($server) { + $this->server = $server; + + return; + } + $this->server = new BaseMemcache(); + $this->server->addServer('localhost', 11211); + } + + /** + * {@inheritdoc} + */ + public function del($key) + { + $this->server->delete($this->getKey($key)); + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + $data = $this->server->get($this->getKey($key)); + if (!$data) { + return; + } + + return $this->unPack($data); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + $data = $this->server->get($this->getKey($key)); + if (!$data) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + if (!$ttl) { + $ttl = $this->ttl; + } + $this->server->set($this->getKey($key), $this->pack($value), false, time() + $ttl); + } +} diff --git a/src/Adapter/Memcached.php b/src/Adapter/Memcached.php index ff44e56..ef3257d 100644 --- a/src/Adapter/Memcached.php +++ b/src/Adapter/Memcached.php @@ -17,113 +17,70 @@ /** * Memcached - * - * @author : RJ Garcia */ class Memcached extends AbstractAdapter { /** - * - * @var \Memcached + * @var BaseMemcached */ - public $adapter; + protected $server; /** - * Accept three types of inputs: a \Memcached instance already to use - * as the internal adapter, an array of servers which will be added to - * a memcached instance, or null, and we'll just build a default memcached - * instance - * - * @param mixed $data - * + * @param BaseMemcached|null $server */ - public function __construct($data = null, $options = []) + public function __construct(BaseMemcached $server = null) { - if ($data instanceof BaseMemcached) { - $this->adapter = $data; - } else { - $this->adapter = new BaseMemcached(); - } + if ($server) { + $this->server = $server; - if (is_array($data)) { - /* if array, then the user supplied an array of servers */ - foreach ($data as $s) { - $this->adapter->addServer($s['host'], $s['port'], $s['weight']); - } + return; } - - $this->setOption($options); - } - - /** - * - * @param string $host - * @param int $port - * @param int $weight - * - */ - public function addServer($host, $port, $weight = 0) - { - $this->adapter->addServer($host, $port, $weight); + $this->server = new BaseMemcached(); + $this->server->addServer('localhost', 11211); } /** - * Delete a value from the cache - * - * @param string $key + * {@inheritdoc} */ - public function delete($key) + public function del($key) { - $this->adapter->delete($this->buildKey($key)); + $this->server->delete($this->getKey($key)); } /** - * {@inheritdoc } + * {@inheritdoc} */ public function get($key) { - return $this->unpackData( - $this->adapter->get( - $this->buildKey($key) - ) - ); + $data = $this->server->get($this->getKey($key)); + if (!$data) { + return; + } + + return $this->unPack($data); } /** - * {@inheritdoc } + * {@inheritdoc} */ public function has($key) { - /* It seems that the most efficient way to check has in memcached is - by using an append with an empty string. However, we need to make - sure that OPT_COMPRESSION is turned off because you can't append - if you compressing data */ - - /* store for later use */ - $cur_compression = $this->adapter->getOption(BaseMemcached::OPT_COMPRESSION); - - /* set compression off */ - $this->adapter->setOption(BaseMemcached::OPT_COMPRESSION, false); - - $res = $this->adapter->append( - $this->buildKey($key), - '' - ); - - $this->adapter->setOption(BaseMemcached::OPT_COMPRESSION, $cur_compression); + $data = $this->server->get($this->getKey($key)); + if (!$data) { + return false; + } - return $res; + return true; } /** - * {@inheritdoc } + * {@inheritdoc} */ public function set($key, $value, $ttl = null) { - $this->adapter->set( - $this->buildKey($key), - $this->packData($value), - $ttl ?: $this->ttl - ); + if (!$ttl) { + $ttl = $this->ttl; + } + $this->server->set($this->getKey($key), $this->pack($value), time() + $ttl); } } diff --git a/src/Adapter/Memory.php b/src/Adapter/Memory.php index 7efa5dd..869777b 100644 --- a/src/Adapter/Memory.php +++ b/src/Adapter/Memory.php @@ -20,44 +20,39 @@ class Memory extends AbstractAdapter { /** - * * @var int */ - protected $limit = 100; + protected $limit = false; /** - * * @var array */ - protected $cache = array(); + protected $cache = []; /** - * Delete a value from the cache - * - * @param string $key + * {@inheritdoc} */ - public function delete($key) + public function del($key) { - $tKey = $this->getKey($key); - unset($this->cache[$tKey]); + unset($this->cache[$this->getKey($key)]); } /** - * {@inheritdoc } + * {@inheritdoc} */ public function get($key) { if ($this->has($key)) { $tKey = $this->getKey($key); - return $this->unserialize($this->cache[$tKey]['value']); + return $this->unPack($this->cache[$tKey]['value']); } return false; } /** - * {@inheritdoc } + * {@inheritdoc} */ public function has($key) { @@ -66,43 +61,38 @@ public function has($key) $data = $this->cache[$tKey]; if (time() < $data['ttl']) { return true; - } else { - $this->delete($key); } + $this->del($key); } return false; } /** - * {@inheritdoc } + * {@inheritdoc} */ public function set($key, $value, $ttl = null) { - while (count($this->cache) >= $this->limit) { + if ($this->limit && count($this->cache) > $this->limit) { array_shift($this->cache); } - $tKey = $this->getKey($key); if (!$ttl) { $ttl = $this->ttl; } - $this->cache[$tKey] = array( + $this->cache[$this->getKey($key)] = [ 'value' => serialize($value), - 'ttl' => $ttl + time(), - ); + 'ttl' => (int)$ttl + time(), + ]; } /** - * {@inheritdoc } + * {@inheritdoc} */ public function setOption($key, $value) { switch ($key) { case 'limit': - $value = (int) $value; - if ($value < 1) { - throw new MemoryCacheException('limit cant be lower than 1'); - } + $value = (int)$value; $this->limit = $value; return true; diff --git a/src/Adapter/Mongo.php b/src/Adapter/Mongo.php deleted file mode 100644 index 530c342..0000000 --- a/src/Adapter/Mongo.php +++ /dev/null @@ -1,167 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -use Desarrolla2\Cache\Exception\MongoCacheException; -use Mongo as MongoBase; - -/** - * Mongo - */ -class Mongo extends AbstractAdapter -{ - /** - * @var \MongoDB - */ - protected $database; - - /** - * @var \Mongo - */ - protected $mongo; - - /** - * - * @param string $server - * @param array $options - * @param string $database - * @throws \Desarrolla2\Cache\Exception\MongoCacheException - */ - public function __construct( - $server = 'mongodb://localhost:27017', - $options = array('connect' => true), - $database = '__cache' - ) { - $this->mongo = new MongoBase($server, $options); - if (!$this->mongo) { - throw new MongoCacheException(' Mongo connection fails '); - } - $this->database = $this->mongo->selectDB($database); - } - - /** - * Delete a value from the cache - * - * @param string $key - */ - public function delete($key) - { - $tKey = $this->getKey($key); - $this->database->items->remove(array('key' => $tKey)); - } - - /** - * {@inheritdoc } - * - * @param string $key - */ - public function get($key) - { - if ($data = $this->getData($key)) { - return $data; - } - - return false; - } - - /** - * {@inheritdoc } - * - * @param string $key - * @return bool - */ - public function has($key) - { - if ($this->getData($key)) { - return true; - } - - return false; - } - - /** - * {@inheritdoc } - * - * @param string $key - * @param mixed $value - * @param null $ttl - */ - public function set($key, $value, $ttl = null) - { - $tKey = $this->getKey($key); - $tValue = $this->serialize($value); - if (!$ttl) { - $ttl = $this->ttl; - } - $item = array( - 'key' => $tKey, - 'value' => $tValue, - 'ttl' => (int) $ttl + time(), - ); - $this->delete($key); - $this->database->items->insert($item); - } - - /** - * {@inheritdoc } - * - * @param string $key - * @param string $value - * @return bool - * @throws \Desarrolla2\Cache\Exception\MongoCacheException - */ - public function setOption($key, $value) - { - switch ($key) { - case 'ttl': - $value = (int) $value; - if ($value < 1) { - throw new MongoCacheException('ttl cant be lower than 1'); - } - $this->ttl = $value; - break; - default: - throw new MongoCacheException('option not valid '.$key); - } - - return true; - } - - /** - * Get data value from file cache - * - * @param string $key - * @param bool $delete - * @return mixed - */ - protected function getData($key, $delete = true) - { - $tKey = $this->getKey($key); - $data = $this->database->items->findOne(array('key' => $tKey)); - if (count($data)) { - $data = array_values($data); - if (time() > $data[3]) { - if ($delete) { - $this->delete($key); - } - - return false; - } - - return $this->unserialize($data[2]); - } - - return false; - } -} diff --git a/src/Adapter/MySQL.php b/src/Adapter/MySQL.php deleted file mode 100644 index 51e9118..0000000 --- a/src/Adapter/MySQL.php +++ /dev/null @@ -1,168 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -use \mysqli; - -/** - * MySQL - */ -class MySQL extends AbstractAdapter implements AdapterInterface -{ - /** - * - * @var \mysqli - */ - protected $mysql; - - public function __construct( - $host = 'localhost', - $user = 'root', - $password = '', - $database = 'cache', - $port = '3306' - ) { - $this->mysql = new mysqli($host, $user, $password, $database, $port); - } - - /** - * Destructor - */ - public function __destruct() - { - $this->mysql->close(); - } - - /** - * Delete a value from the cache - * - * @param string $key - */ - public function delete($key) - { - $tKey = $this->getKey($key); - $query = 'DELETE FROM cache WHERE hash = \''.$tKey.'\';'; - - $this->query($query); - } - - /** - * {@inheritdoc } - */ - public function get($key) - { - $tKey = $this->getKey($key); - $query = 'SELECT value FROM cache WHERE hash = \''.$tKey.'\''. - ' AND ttl >= '.time().';'; - $res = $this->fetchObject($query); - if ($res) { - return $this->unserialize($res->value); - } - - return false; - } - - /** - * {@inheritdoc } - */ - public function has($key) - { - $tKey = $this->getKey($key); - $query = 'SELECT COUNT(*) AS items FROM cache WHERE hash = '. - '\''.$tKey.'\' AND '. - ' ttl >= '.time().';'; - $res = $this->fetchObject($query); - if (!$res) { - return false; - } - if ($res->items == '0') { - return false; - } - - return true; - } - - /** - * {@inheritdoc } - */ - public function set($key, $value, $ttl = null) - { - $this->delete($key); - $tKey = $this->getKey($key); - $tValue = $this->escape( - $this->serialize($value) - ); - if (!($ttl)) { - $ttl = $this->ttl; - } - $tTtl = $ttl + time(); - $query = ' INSERT INTO cache (hash, value, ttl) VALUES ('. - '\''.$tKey.'\', '. - '\''.$tValue.'\', '. - '\''.$tTtl.'\' );'; - $this->query($query); - } - - /** - * {@inheritdoc } - */ - protected function getKey($key) - { - $tKey = parent::getKey($key); - - return $this->escape($tKey); - } - - /** - * - * @param string $query - * @param int|string $mode - * @return mixed - */ - protected function fetchObject($query, $mode = MYSQLI_STORE_RESULT) - { - $res = $this->query($query, $mode); - if ($res) { - return $res->fetch_object(); - } - - return false; - } - - /** - * - * @param string $query - * @param int|string $mode - * @return mixed - */ - protected function query($query, $mode = MYSQLI_STORE_RESULT) - { - $res = $this->mysql->query($query, $mode); - if ($res) { - return $res; - } - - return false; - } - - /** - * - * @param string $key - * @return string - */ - private function escape($key) - { - return $this->mysql->real_escape_string($key); - } -} diff --git a/src/Adapter/Mysqli.php b/src/Adapter/Mysqli.php new file mode 100644 index 0000000..e4d01f1 --- /dev/null +++ b/src/Adapter/Mysqli.php @@ -0,0 +1,189 @@ + + */ + +namespace Desarrolla2\Cache\Adapter; + +use \mysqli as Server; + +/** + * Mysqli + */ +class Mysqli extends AbstractAdapter implements AdapterInterface +{ + /** + * @var \mysqli + */ + protected $server; + + /** + * @var string + */ + protected $database = 'cache'; + + /** + * @param Server|null $server + */ + public function __construct(Server $server = null) + { + if ($server) { + $this->server = $server; + + return; + } + $this->server = new server(); + } + + public function __destruct() + { + $this->server->close(); + } + + /** + * {@inheritdoc} + */ + public function del($key) + { + $this->query( + sprintf( + 'DELETE FROM %s WHERE k = "%s" OR t < %d', + $this->database, + $this->getKey($key), + $this->database, + time() + ) + ); + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + $res = $this->fetchObject( + sprintf( + 'SELECT v FROM %s WHERE k = "%s" AND t >= %d LIMIT 1;', + $this->database, + $this->getKey($key), + time() + ) + ); + if ($res) { + return $this->unPack($res->v); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + $res = $this->fetchObject( + sprintf( + 'SELECT COUNT(*) AS n FROM %s WHERE k = "%s" AND t >= %d;', + $this->database, + $this->getKey($key), + time() + ) + ); + if (!$res) { + return false; + } + if ($res->n == '0') { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $this->del($key); + if (!($ttl)) { + $ttl = $this->ttl; + } + $tTtl = (int)$ttl + time(); + $this->query( + sprintf( + 'INSERT INTO %s (k, v, t) VALUES ("%s", "%s", %d)', + $this->database, + $this->getKey($key), + $this->pack($value), + $tTtl + ) + ); + } + + /** + * {@inheritdoc} + */ + protected function getKey($key) + { + return $this->escape(parent::getKey($key)); + } + + protected function pack($value) + { + return $this->escape(parent::pack($value)); + } + + + /** + * + * @param string $query + * @param int|string $mode + * + * @return mixed + */ + protected function fetchObject($query, $mode = MYSQLI_STORE_RESULT) + { + $res = $this->query($query, $mode); + if ($res) { + return $res->fetch_object(); + } + + return false; + } + + /** + * + * @param string $query + * @param int|string $mode + * + * @return mixed + */ + protected function query($query, $mode = MYSQLI_STORE_RESULT) + { + $res = $this->server->query($query, $mode); + if ($res) { + return $res; + } + + return false; + } + + /** + * + * @param string $key + * + * @return string + */ + private function escape($key) + { + return $this->server->real_escape_string($key); + } +} diff --git a/src/Adapter/NotCache.php b/src/Adapter/NotCache.php index c920b82..ff7c5b7 100644 --- a/src/Adapter/NotCache.php +++ b/src/Adapter/NotCache.php @@ -23,12 +23,12 @@ class NotCache extends AbstractAdapter * * @param string $key */ - public function delete($key) + public function del($key) { } /** - * {@inheritdoc } + * {@inheritdoc} */ public function get($key) { @@ -36,7 +36,7 @@ public function get($key) } /** - * {@inheritdoc } + * {@inheritdoc} */ public function has($key) { @@ -44,7 +44,7 @@ public function has($key) } /** - * {@inheritdoc } + * {@inheritdoc} */ public function set($key, $value, $ttl = null) { @@ -52,7 +52,7 @@ public function set($key, $value, $ttl = null) } /** - * {@inheritdoc } + * {@inheritdoc} */ public function setOption($key, $value) { diff --git a/src/Adapter/Predis.php b/src/Adapter/Predis.php new file mode 100644 index 0000000..da0d503 --- /dev/null +++ b/src/Adapter/Predis.php @@ -0,0 +1,93 @@ + + */ + +namespace Desarrolla2\Cache\Adapter; + +use Predis\Client; + +/** + * Predis + */ +class Predis extends AbstractAdapter +{ + /** + * @var Client + */ + protected $predis; + + /** + * @param Client $client + * + * @see predis documentation about how know your configuration + * https://github.com/nrk/predis + */ + public function __construct(Client $client = null) + { + if ($client) { + $this->predis = $client; + + return; + } + $this->predis = new Client(); + + } + + public function __destruct() + { + $this->predis->disconnect(); + } + + /** + * {@inheritdoc} + */ + public function del($key) + { + $cmd = $this->predis->createCommand('DEL'); + $cmd->setArguments([$key]); + + $this->predis->executeCommand($cmd); + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + return $this->unPack($this->predis->get($key)); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + $cmd = $this->predis->createCommand('EXISTS'); + $cmd->setArguments([$key]); + + return $this->predis->executeCommand($cmd); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $this->predis->set($key, $this->pack($value)); + if (!$ttl) { + $ttl = $this->ttl; + } + $cmd = $this->predis->createCommand('EXPIRE'); + $cmd->setArguments([$key, $ttl]); + $this->predis->executeCommand($cmd); + } +} diff --git a/src/Adapter/Redis.php b/src/Adapter/Redis.php deleted file mode 100644 index 27dc3d9..0000000 --- a/src/Adapter/Redis.php +++ /dev/null @@ -1,100 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -use Predis\Client; - -/** - * Redis - */ -class Redis extends AbstractAdapter -{ - /** - * @var Client - */ - protected $client; - - public function __construct() - { - $this->client = new Client( - [ - 'scheme' => 'tcp', - 'host' => '127.0.0.1', - 'port' => 6379, - ] - ); - } - - public function __destruct() - { - $this->client->disconnect(); - } - - /** - * Delete a value from the cache - * - * @param string $key - */ - public function delete($key) - { - $cmd = $this->client->createCommand('DEL'); - $cmd->setArguments([$key]); - - $this->client->executeCommand($cmd); - } - - /** - * Retrieve the value corresponding to a provided key - * - * @param string $key - * - * @return mixed - */ - public function get($key) - { - return $this->client->get($key); - } - - /** - * Retrieve the if value corresponding to a provided key exist - * - * @param string $key - * - * @return bool - */ - public function has($key) - { - $cmd = $this->client->createCommand('EXISTS'); - $cmd->setArguments([$key]); - - return $this->client->executeCommand($cmd); - } - - /** - * * Add a value to the cache under a unique key - * - * @param string $key - * @param mixed $value - * @param int $ttl - */ - public function set($key, $value, $ttl = null) - { - $this->client->set($key, $value); - if ($ttl) { - $cmd = $this->client->createCommand('EXPIRE'); - $cmd->setArguments([$key, $ttl]); - $this->client->executeCommand($cmd); - } - } -} diff --git a/src/Cache.php b/src/Cache.php index 8eb12f9..a0a4f3b 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -38,17 +38,17 @@ public function __construct(AdapterInterface $adapter = null) } /** - * {@inheritdoc } + * {@inheritdoc} * * @param string $key */ public function delete($key) { - $this->getAdapter()->delete($key); + $this->getAdapter()->del($key); } /** - * {@inheritdoc } + * {@inheritdoc} * * @param string $key */ @@ -58,7 +58,7 @@ public function get($key) } /** - * {@inheritdoc } + * {@inheritdoc} */ public function getAdapter() { @@ -70,7 +70,7 @@ public function getAdapter() } /** - * {@inheritdoc } + * {@inheritdoc} * * @param string $key */ @@ -80,7 +80,7 @@ public function has($key) } /** - * {@inheritdoc } + * {@inheritdoc} * * @param string $key * @param mixed $value @@ -92,7 +92,7 @@ public function set($key, $value, $ttl = null) } /** - * {@inheritdoc } + * {@inheritdoc} * * @param Adapter\AdapterInterface $adapter */ @@ -102,7 +102,7 @@ public function setAdapter(AdapterInterface $adapter) } /** - * {@inheritdoc } + * {@inheritdoc} * * @param string $key * @param string $value @@ -114,7 +114,7 @@ public function setOption($key, $value) } /** - * {@inheritdoc } + * {@inheritdoc} */ public function clearCache() { @@ -122,7 +122,7 @@ public function clearCache() } /** - * {@inheritdoc } + * {@inheritdoc} */ public function dropCache() { diff --git a/tests/Desarrolla2/Cache/Adapter/Test/AbstractCacheTest.php b/tests/Desarrolla2/Cache/Adapter/Test/AbstractCacheTest.php index 4b0c705..faa9620 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/AbstractCacheTest.php +++ b/tests/Desarrolla2/Cache/Adapter/Test/AbstractCacheTest.php @@ -13,8 +13,6 @@ namespace Desarrolla2\Cache\Adapter\Test; -use Symfony\Component\Yaml\Yaml; - /** * AbstractCacheTest */ @@ -28,16 +26,16 @@ abstract class AbstractCacheTest extends \PHPUnit_Framework_TestCase /** * @var array */ - protected $config = array(); + protected $config = []; public function setup() { - $configurationFile = realpath(__DIR__.'/../../../../').'/config.yml'; + $configurationFile = realpath(__DIR__.'/../../../../').'/config.json'; if (!is_file($configurationFile)) { throw new \Exception(' Configuration file not found in "'.$configurationFile.'" '); } - $this->config = Yaml::parse(file_get_contents($configurationFile)); + $this->config = json_decode(file_get_contents($configurationFile), true); } /** @@ -45,11 +43,15 @@ public function setup() */ public function dataProvider() { - return array( - array('key1', 'value', 1), - array('key2', 'value', 100), - array('key3', 'value', null), - ); + return [ + ['key1', 'value1', 1], + ['key2', 'value2', 100], + ['key3', 'value3', null], + ['key4', true, null], + ['key5', false, null], + ['key6', [], null], + ['key7', new \DateTime(), null], + ]; } /** @@ -57,19 +59,20 @@ public function dataProvider() */ public function dataProviderForOptions() { - return array( - array('ttl', 100), - ); + return [ + ['ttl', 100], + ]; } /** * * @dataProvider dataProvider + * * @param string $key * @param mixed $value * @param int|null $ttl */ - public function testHash($key, $value, $ttl) + public function testHas($key, $value, $ttl) { $this->assertNull($this->cache->delete($key)); $this->assertFalse($this->cache->has($key)); @@ -80,6 +83,7 @@ public function testHash($key, $value, $ttl) /** * * @dataProvider dataProvider + * * @param string $key * @param mixed $value * @param int|null $ttl @@ -93,6 +97,7 @@ public function testGet($key, $value, $ttl) /** * * @dataProvider dataProvider + * * @param string $key * @param mixed $value * @param int|null $ttl @@ -106,6 +111,7 @@ public function testDelete($key, $value, $ttl) /** * @dataProvider dataProviderForOptions + * * @param string $key * @param mixed $value */ @@ -116,6 +122,7 @@ public function testSetOption($key, $value) /** * @dataProvider dataProviderForOptionsException + * * @param string $key * @param mixed $value * @param \Exception $expectedException @@ -128,11 +135,8 @@ public function testSetOptionException($key, $value, $expectedException) public function testHasWithTtlExpired() { - $key = 'key1'; - $value = 'value1'; - $ttl = 1; - $this->cache->set($key, $value, $ttl); - sleep($ttl + 1); - $this->assertFalse($this->cache->has($key)); + $this->cache->set('key1', 'value1', 1); + sleep(2); + $this->assertFalse($this->cache->has('key1')); } } diff --git a/tests/Desarrolla2/Cache/Adapter/Test/ApcCacheTest.php b/tests/Desarrolla2/Cache/Adapter/Test/ApcuCacheTest.php similarity index 50% rename from tests/Desarrolla2/Cache/Adapter/Test/ApcCacheTest.php rename to tests/Desarrolla2/Cache/Adapter/Test/ApcuCacheTest.php index f97f1b2..afb6c22 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/ApcCacheTest.php +++ b/tests/Desarrolla2/Cache/Adapter/Test/ApcuCacheTest.php @@ -14,22 +14,27 @@ namespace Desarrolla2\Cache\Adapter\Test; use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Apc; +use Desarrolla2\Cache\Adapter\Apcu; /** - * ApcCacheTest + * ApcuCacheTest */ -class ApcCacheTest extends AbstractCacheTest +class ApcuCacheTest extends AbstractCacheTest { public function setUp() { - parent::setup(); - if (!extension_loaded('apc') || !ini_get('apc.enable_cli')) { + if (!extension_loaded('apcu')) { $this->markTestSkipped( - 'The APC extension is not available.' + 'The APCu extension is not available.' ); } - $this->cache = new Cache(new Apc()); + if (!ini_get('apc.enable_cli')) { + $this->markTestSkipped( + 'You need to enable apc.enable_cli' + ); + } + + $this->cache = new Cache(new Apcu()); } /** @@ -37,9 +42,9 @@ public function setUp() */ public function dataProviderForOptionsException() { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\ApcCacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\ApcCacheException'), - ); + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; } } diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MongoTest.php b/tests/Desarrolla2/Cache/Adapter/Test/MemcacheTest.php similarity index 54% rename from tests/Desarrolla2/Cache/Adapter/Test/MongoTest.php rename to tests/Desarrolla2/Cache/Adapter/Test/MemcacheTest.php index 3f12df8..eb83f49 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/MongoTest.php +++ b/tests/Desarrolla2/Cache/Adapter/Test/MemcacheTest.php @@ -14,22 +14,24 @@ namespace Desarrolla2\Cache\Adapter\Test; use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Mongo; +use Desarrolla2\Cache\Adapter\Memcache; /** - * MongoTest + * MemcacheTest */ -class MongoTest extends AbstractCacheTest +class MemcacheTest extends AbstractCacheTest { public function setUp() { parent::setup(); - if (!class_exists('Mongo')) { + if (!extension_loaded('memcache') || !class_exists('\Memcache')) { $this->markTestSkipped( - 'The Mongo extension is not available.' + 'The Memcache extension is not available.' ); } - $this->cache = new Cache(new Mongo($this->config['mongo']['dns'])); + + $adapter = new Memcache(); + $this->cache = new Cache($adapter); } /** @@ -37,9 +39,9 @@ public function setUp() */ public function dataProviderForOptionsException() { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\MongoCacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\MongoCacheException'), - ); + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; } } diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MemCacheTest.php b/tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php similarity index 61% rename from tests/Desarrolla2/Cache/Adapter/Test/MemCacheTest.php rename to tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php index 82609b8..8c0cf2e 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/MemCacheTest.php +++ b/tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php @@ -14,24 +14,23 @@ namespace Desarrolla2\Cache\Adapter\Test; use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\MemCache; +use Desarrolla2\Cache\Adapter\Memcached; /** - * MemCacheTest + * MemcachedTest */ -class MemCacheTest extends AbstractCacheTest +class MemcachedTest extends AbstractCacheTest { public function setUp() { parent::setup(); - if (!extension_loaded('memcached') || !class_exists('\Memcache')) { + if (!extension_loaded('memcached') || !class_exists('\Memcached')) { $this->markTestSkipped( - 'The Memcache extension is not available.' + 'The Memcached extension is not available.' ); } - $adapter = new MemCache(); - $adapter->addServer('localhost', 11211); + $adapter = new Memcached(); $this->cache = new Cache($adapter); } @@ -40,9 +39,9 @@ public function setUp() */ public function dataProviderForOptionsException() { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), - ); + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; } } diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MemoryTest.php b/tests/Desarrolla2/Cache/Adapter/Test/MemoryTest.php index 6616689..bea0c6e 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/MemoryTest.php +++ b/tests/Desarrolla2/Cache/Adapter/Test/MemoryTest.php @@ -31,10 +31,10 @@ public function setUp() */ public function dataProviderForOptions() { - return array( - array('ttl', 100), - array('limit', 100), - ); + return [ + ['ttl', 100], + ['limit', 100], + ]; } /** @@ -42,10 +42,10 @@ public function dataProviderForOptions() */ public function dataProviderForOptionsException() { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), - ); + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; } public function testExceededLimit() diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MySQLTest.php b/tests/Desarrolla2/Cache/Adapter/Test/MySQLTest.php deleted file mode 100644 index 90f1459..0000000 --- a/tests/Desarrolla2/Cache/Adapter/Test/MySQLTest.php +++ /dev/null @@ -1,63 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter\Test; - -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\MySQL; - -/** - * MySQLTest - */ -class MySQLTest extends AbstractCacheTest -{ - public function setUp() - { - parent::setup(); - if (!extension_loaded('mysqlnd')) { - $this->markTestSkipped( - 'The MySQLnd extension is not available.' - ); - } - $this->cache = new Cache( - new MySQL( - $this->config['mysql']['host'], - $this->config['mysql']['user'], - $this->config['mysql']['password'], - $this->config['mysql']['database'], - $this->config['mysql']['port'] - ) - ); - } - - /** - * @return array - */ - public function dataProviderForOptions() - { - return array( - array('ttl', 100), - ); - } - - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), - ); - } -} diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MysqliTest.php b/tests/Desarrolla2/Cache/Adapter/Test/MysqliTest.php new file mode 100644 index 0000000..2dc385f --- /dev/null +++ b/tests/Desarrolla2/Cache/Adapter/Test/MysqliTest.php @@ -0,0 +1,66 @@ + + */ + +namespace Desarrolla2\Cache\Adapter\Test; + +use Desarrolla2\Cache\Cache; +use Desarrolla2\Cache\Adapter\Mysqli; + +/** + * MysqliTest + */ +class MysqliTest extends AbstractCacheTest +{ + public function setUp() + { + parent::setup(); + if (!extension_loaded('mysqli')) { + $this->markTestSkipped( + 'The mysqli extension is not available.' + ); + } + + $this->cache = new Cache( + new Mysqli( + new \mysqli( + $this->config['mysql']['host'], + $this->config['mysql']['user'], + $this->config['mysql']['password'], + $this->config['mysql']['database'], + $this->config['mysql']['port'] + ) + ) + ); + } + + /** + * @return array + */ + public function dataProviderForOptions() + { + return [ + ['ttl', 100], + ]; + } + + /** + * @return array + */ + public function dataProviderForOptionsException() + { + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; + } +} diff --git a/tests/Desarrolla2/Cache/Adapter/Test/RedisTest.php b/tests/Desarrolla2/Cache/Adapter/Test/PredisTest.php similarity index 56% rename from tests/Desarrolla2/Cache/Adapter/Test/RedisTest.php rename to tests/Desarrolla2/Cache/Adapter/Test/PredisTest.php index 815272d..9a124ad 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/RedisTest.php +++ b/tests/Desarrolla2/Cache/Adapter/Test/PredisTest.php @@ -14,19 +14,25 @@ namespace Desarrolla2\Cache\Adapter\Test; use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Redis; +use Desarrolla2\Cache\Adapter\Predis; /** - * RedisTest + * PredisTest */ -class RedisTest extends AbstractCacheTest +class PredisTest extends AbstractCacheTest { public function setUp() { parent::setup(); + if (!class_exists('\Predis\Client')) { + $this->markTestSkipped( + 'The predis library is not available.' + ); + } $this->cache = new Cache( - new Redis() + new Predis() ); + } /** @@ -34,9 +40,9 @@ public function setUp() */ public function dataProviderForOptions() { - return array( - array('ttl', 100), - ); + return [ + ['ttl', 100], + ]; } /** @@ -44,9 +50,9 @@ public function dataProviderForOptions() */ public function dataProviderForOptionsException() { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), - ); + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; } } diff --git a/tests/config.json.dist b/tests/config.json.dist new file mode 100644 index 0000000..07a9c90 --- /dev/null +++ b/tests/config.json.dist @@ -0,0 +1,19 @@ +{ + "file": { + "dir": "" + }, + "memcached": { + "host": "127.0.0.1", + "port": "11211" + }, + "mongo": { + "dns": "mongodb://localhost:27017" + }, + "mysql": { + "user": "root", + "password": "", + "host": "127.0.0.1", + "port": "3306", + "database": "cache" + } +} \ No newline at end of file diff --git a/tests/config.yml.dist b/tests/config.yml.dist deleted file mode 100644 index ce88605..0000000 --- a/tests/config.yml.dist +++ /dev/null @@ -1,13 +0,0 @@ -file: - dir: '' -memcached: - host: '127.0.0.1' - port: '11211' -mongo: - dns: 'mongodb://localhost:27017' -mysql: - user: 'root' - password: '' - host: '127.0.0.1' - port: '3306' - database: 'cache' diff --git a/tests/performance/Apc.php b/tests/performance/Apc.php index fca822f..230eb6f 100644 --- a/tests/performance/Apc.php +++ b/tests/performance/Apc.php @@ -14,8 +14,8 @@ require_once __DIR__.'/../bootstrap.php'; use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Apc; +use Desarrolla2\Cache\Adapter\Apcu; -$cache = new Cache(new Apc()); +$cache = new Cache(new Apcu()); require_once __DIR__.'/common.php'; From 488721192d7319014eb46993232dca1d123cddb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 22 Oct 2015 23:18:38 +0200 Subject: [PATCH 07/51] fix travis --- .travis.yml | 53 +++++++++++++------ composer.json | 6 ++- .../Test => Adapter}/AbstractCacheTest.php | 4 +- .../Test => Adapter}/ApcuCacheTest.php | 2 +- .../Adapter/Test => Adapter}/FileTest.php | 2 +- .../Adapter/Test => Adapter}/MemcacheTest.php | 2 +- .../Test => Adapter}/MemcachedTest.php | 2 +- .../Adapter/Test => Adapter}/MemoryTest.php | 2 +- .../Adapter/Test => Adapter}/MysqliTest.php | 2 +- .../Adapter/Test => Adapter}/NotCacheTest.php | 2 +- .../Adapter/Test => Adapter}/PredisTest.php | 2 +- .../Cache/Test => }/CacheTest.php | 2 +- tests/travis/php.ini | 7 +++ tests/travis/phpunit.xml | 34 ++++++++++++ 14 files changed, 93 insertions(+), 29 deletions(-) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/AbstractCacheTest.php (96%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/ApcuCacheTest.php (96%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/FileTest.php (95%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/MemcacheTest.php (96%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/MemcachedTest.php (96%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/MemoryTest.php (96%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/MysqliTest.php (97%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/NotCacheTest.php (97%) rename tests/{Desarrolla2/Cache/Adapter/Test => Adapter}/PredisTest.php (96%) rename tests/{Desarrolla2/Cache/Test => }/CacheTest.php (95%) create mode 100644 tests/travis/php.ini create mode 100644 tests/travis/phpunit.xml diff --git a/.travis.yml b/.travis.yml index 6e9909c..bb70862 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,29 +1,48 @@ language: php +#sudo: false php: - - 5.4 - - 5.5 - - 5.6 - - 7 - - hhvm + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +matrix: + allow_failures: + - php: 7.0 + - php: hhvm services: - - mongodb - - redis-server - - memcached + - mongodb + - redis-server + - memcached notifications: - email: - - daniel.gonzalez@freelancemadrid.es + email: + - daniel.gonzalez@freelancemadrid.es before_script: -# Create Mysqli Database - - mysql -e 'CREATE DATABASE IF NOT EXISTS `cache`;' - - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' + # Create Mysql Database + - mysql -e 'CREATE DATABASE IF NOT EXISTS `cache`;' + - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' + + # Install apcu + - sh -c "pear config-set preferred_state beta" + - sh -c "yes '' | pecl install apcu" + - sh -c "if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-add ./tests/travis/php.ini; fi" + + # Install dependencies + - composer self-update + - composer install + + # Set Configuration + - cp tests/config.json.dist tests/config.json + - cp tests/travis/phpunit.xml phpunit.xml -# Install dependencies - - composer install --prefer-source +script: + - phpunit -c ./phpunit.xml -v -# Set Configuration - - cp tests/config.json.dist tests/config.json \ No newline at end of file +after_script: +# - php vendor/bin/coveralls -v \ No newline at end of file diff --git a/composer.json b/composer.json index 7be1414..cc2514f 100644 --- a/composer.json +++ b/composer.json @@ -27,9 +27,13 @@ "require-dev": { "predis/predis": "~1.0.0" }, + "suggest": { + "predis/predis": "Predis support" + }, "autoload": { "psr-4": { - "Desarrolla2\\Cache\\": "src/" + "Desarrolla2\\Cache\\": "src/", + "Desarrolla2\\Test\\Cache\\": "test/" } } } diff --git a/tests/Desarrolla2/Cache/Adapter/Test/AbstractCacheTest.php b/tests/Adapter/AbstractCacheTest.php similarity index 96% rename from tests/Desarrolla2/Cache/Adapter/Test/AbstractCacheTest.php rename to tests/Adapter/AbstractCacheTest.php index faa9620..9230422 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/AbstractCacheTest.php +++ b/tests/Adapter/AbstractCacheTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; /** * AbstractCacheTest @@ -30,7 +30,7 @@ abstract class AbstractCacheTest extends \PHPUnit_Framework_TestCase public function setup() { - $configurationFile = realpath(__DIR__.'/../../../../').'/config.json'; + $configurationFile = realpath(__DIR__.'/../').'/config.json'; if (!is_file($configurationFile)) { throw new \Exception(' Configuration file not found in "'.$configurationFile.'" '); diff --git a/tests/Desarrolla2/Cache/Adapter/Test/ApcuCacheTest.php b/tests/Adapter/ApcuCacheTest.php similarity index 96% rename from tests/Desarrolla2/Cache/Adapter/Test/ApcuCacheTest.php rename to tests/Adapter/ApcuCacheTest.php index afb6c22..8efd1c5 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/ApcuCacheTest.php +++ b/tests/Adapter/ApcuCacheTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Apcu; diff --git a/tests/Desarrolla2/Cache/Adapter/Test/FileTest.php b/tests/Adapter/FileTest.php similarity index 95% rename from tests/Desarrolla2/Cache/Adapter/Test/FileTest.php rename to tests/Adapter/FileTest.php index 2e06f1a..3ff36fc 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/FileTest.php +++ b/tests/Adapter/FileTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\File; diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MemcacheTest.php b/tests/Adapter/MemcacheTest.php similarity index 96% rename from tests/Desarrolla2/Cache/Adapter/Test/MemcacheTest.php rename to tests/Adapter/MemcacheTest.php index eb83f49..4b2854b 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/MemcacheTest.php +++ b/tests/Adapter/MemcacheTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Memcache; diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php b/tests/Adapter/MemcachedTest.php similarity index 96% rename from tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php rename to tests/Adapter/MemcachedTest.php index 8c0cf2e..7ca009a 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php +++ b/tests/Adapter/MemcachedTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Memcached; diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MemoryTest.php b/tests/Adapter/MemoryTest.php similarity index 96% rename from tests/Desarrolla2/Cache/Adapter/Test/MemoryTest.php rename to tests/Adapter/MemoryTest.php index bea0c6e..b6e7023 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/MemoryTest.php +++ b/tests/Adapter/MemoryTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Memory; diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MysqliTest.php b/tests/Adapter/MysqliTest.php similarity index 97% rename from tests/Desarrolla2/Cache/Adapter/Test/MysqliTest.php rename to tests/Adapter/MysqliTest.php index 2dc385f..8f11c70 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/MysqliTest.php +++ b/tests/Adapter/MysqliTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Mysqli; diff --git a/tests/Desarrolla2/Cache/Adapter/Test/NotCacheTest.php b/tests/Adapter/NotCacheTest.php similarity index 97% rename from tests/Desarrolla2/Cache/Adapter/Test/NotCacheTest.php rename to tests/Adapter/NotCacheTest.php index 81424dc..a114957 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/NotCacheTest.php +++ b/tests/Adapter/NotCacheTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\NotCache; diff --git a/tests/Desarrolla2/Cache/Adapter/Test/PredisTest.php b/tests/Adapter/PredisTest.php similarity index 96% rename from tests/Desarrolla2/Cache/Adapter/Test/PredisTest.php rename to tests/Adapter/PredisTest.php index 9a124ad..be9f3f5 100644 --- a/tests/Desarrolla2/Cache/Adapter/Test/PredisTest.php +++ b/tests/Adapter/PredisTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter\Test; +namespace Desarrolla2\Test\Cache\Adapter; use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Predis; diff --git a/tests/Desarrolla2/Cache/Test/CacheTest.php b/tests/CacheTest.php similarity index 95% rename from tests/Desarrolla2/Cache/Test/CacheTest.php rename to tests/CacheTest.php index 0fb09eb..f679030 100644 --- a/tests/Desarrolla2/Cache/Test/CacheTest.php +++ b/tests/CacheTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Test; +namespace Desarrolla2\Test\Cache; use Desarrolla2\Cache\Cache; diff --git a/tests/travis/php.ini b/tests/travis/php.ini new file mode 100644 index 0000000..105af36 --- /dev/null +++ b/tests/travis/php.ini @@ -0,0 +1,7 @@ +extension="mongo.so" +extension="memcache.so" +extension="memcached.so" +extension="redis.so" + +apc.enabled=1 +apc.enable_cli=1 diff --git a/tests/travis/phpunit.xml b/tests/travis/phpunit.xml new file mode 100644 index 0000000..cd175d4 --- /dev/null +++ b/tests/travis/phpunit.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + ./tests + + + + + + ./Test + + + /usr/share/pear/ + ./vendor + ./build + + + \ No newline at end of file From 9e407f923e797a08d684881c93b6e4049bf8917a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 22 Oct 2015 23:27:09 +0200 Subject: [PATCH 08/51] coveralls --- .coveralls.yml | 3 +++ .travis.yml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .coveralls.yml diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..2bccc3b --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +service_name: travis-ci +src_dir: lib +coverage_clover: clover.xml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index bb70862..f3ab555 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,4 +45,4 @@ script: - phpunit -c ./phpunit.xml -v after_script: -# - php vendor/bin/coveralls -v \ No newline at end of file + - php vendor/bin/coveralls -v \ No newline at end of file From 8e7ab3ddce641250261ffc76367ca76de2405b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 22 Oct 2015 23:32:26 +0200 Subject: [PATCH 09/51] coveralls --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cc2514f..f1ed4d7 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ "php": ">=5.4.0" }, "require-dev": { - "predis/predis": "~1.0.0" + "predis/predis": "~1.0.0", + "satooshi/php-coveralls": "~0.6" }, "suggest": { "predis/predis": "Predis support" From 40ce627d64d0bbcb3677903be9fc1139daad0a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Tue, 27 Oct 2015 10:58:14 +0100 Subject: [PATCH 10/51] first release for 2.0 --- .coveralls.yml | 4 +- .travis.yml | 4 +- README.md | 129 ++++++++++++++++++++----------- build.xml | 14 ++++ composer.json | 3 +- src/Adapter/AbstractAdapter.php | 4 +- src/Adapter/AdapterInterface.php | 4 +- src/Adapter/Apcu.php | 5 +- src/Adapter/File.php | 7 +- src/Adapter/Memcache.php | 2 +- src/Adapter/Memcached.php | 2 +- src/Adapter/Memory.php | 6 +- src/Adapter/Mysqli.php | 15 ++-- src/Adapter/Predis.php | 1 - tests/travis/phpunit.xml | 2 +- 15 files changed, 124 insertions(+), 78 deletions(-) diff --git a/.coveralls.yml b/.coveralls.yml index 2bccc3b..ac64b59 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,3 +1,3 @@ service_name: travis-ci -src_dir: lib -coverage_clover: clover.xml \ No newline at end of file +src_dir: src +coverage_clover: build/logs/clover.xml \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index f3ab555..c55ef1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,8 @@ before_script: # Install dependencies - composer self-update - - composer install + - composer require --dev "satooshi/php-coveralls:~0.6" + - mkdir -p build/logs # Set Configuration - cp tests/config.json.dist tests/config.json @@ -45,4 +46,5 @@ script: - phpunit -c ./phpunit.xml -v after_script: + - ls - php vendor/bin/coveralls -v \ No newline at end of file diff --git a/README.md b/README.md index 3e7b45b..269ef89 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,14 @@ A simple cache library. Implements different adapters that you can use and change easily by a manager or similar. -[![Build Status](https://secure.travis-ci.org/desarrolla2/Cache.png)](http://travis-ci.org/desarrolla2/Cache) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/desarrolla2/Cache/badges/quality-score.png?s=940939c8d0bf2056188455594f4332a002a968c2)](https://scrutinizer-ci.com/g/desarrolla2/Cache/) [![Code Coverage](https://scrutinizer-ci.com/g/desarrolla2/Cache/badges/coverage.png?s=16037142f461dcfdfd6ad57561e231881252197b)](https://scrutinizer-ci.com/g/desarrolla2/Cache/) - -[![Latest Stable Version](https://poser.pugx.org/desarrolla2/cache/v/stable.png)](https://packagist.org/packages/desarrolla2/cache) [![Total Downloads](https://poser.pugx.org/desarrolla2/cache/downloads.png)](https://packagist.org/packages/desarrolla2/cache) - +[![Latest version][ico-version]][link-packagist] +[![Software License][ico-license]][link-license] +[![Build Status][ico-travis]][link-travis] +[![Coverage Status][ico-coveralls]][link-coveralls] +[![Quality Score][ico-code-quality]][link-code-quality] +[![Total Downloads][ico-downloads]][link-downloads] +[![Gitter][ico-gitter]][link-gitter] ## Installation @@ -19,7 +22,7 @@ by including `desarrolla2/cache` in your project composer.json require: ``` json "require": { // ... - "desarrolla2/cache": "dev-master" + "desarrolla2/cache": "~2.0" } ``` @@ -50,10 +53,21 @@ echo $cache->get('key'); ## Adapters -### NotCache +### Apcu -Use it if you will not implement any cache adapter is an adapter that will serve -to fool the test environments. +Use it if you will you have APC cache available in your system. + +``` php +setOption('ttl', 3600); +$cache = new Cache($adapter); + +``` ### File @@ -73,22 +87,41 @@ $cache = new Cache($adapter); ``` -### Apcu +### Memcache -Use it if you will you have APC cache available in your system. +Use it if you will you have mencache available in your system. ``` php setOption('ttl', 3600); +$adapter = new Memcache(); $cache = new Cache($adapter); ``` +You can config your connection before + + +``` php +setOption('ttl', 3600); $cache = new Cache($adapter); ``` -### Mysqli - -Use it if you will you have mysqlnd available in your system. ``` php setOption('ttl', 3600); -$cache = new Cache($adapter); +$backend = new Backend(); +// configure it here +$cache = new Cache(new Mysqli($backend)); ``` +### NotCache + +Use it if you will not implement any cache adapter is an adapter that will serve +to fool the test environments. + ### Predis Use it if you will you have redis available in your system. @@ -165,7 +201,6 @@ use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Predis; $adapter = new Predis(); -$adapter->setOption('ttl', 3600); $cache = new Cache($adapter); ``` @@ -177,25 +212,9 @@ If you need to configure your predis client, you will instantiate it and pass it use Desarrolla2\Cache\Cache; use Desarrolla2\Cache\Adapter\Predis; -use Predis\Client; - -$adapter = new Predis(new Client($options)); -$cache = new Cache($adapter); - -``` - -### Memcache +use Predis\Client as Backend -Use it if you will you have memcache available in your system. - -``` php -setOption('ttl', 3600); +$adapter = new Predis(new Backend($options)); $cache = new Cache($adapter); ``` @@ -213,4 +232,20 @@ This can be a list of pending tasks. ## Contact -You can contact with me on [@desarrolla2](https://twitter.com/desarrolla2). \ No newline at end of file +You can contact with me on [@desarrolla2](https://twitter.com/desarrolla2). + +[ico-version]: https://img.shields.io/packagist/v/desarrolla2/cache.svg?style=flat-square +[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square +[ico-travis]: https://img.shields.io/travis/desarrolla2/cache/master.svg?style=flat-square +[ico-coveralls]: https://img.shields.io/coveralls/desarrolla2/cache/master.svg?style=flat-square +[ico-code-quality]: https://img.shields.io/scrutinizer/g/desarrolla2/cache.svg?style=flat-square +[ico-downloads]: https://img.shields.io/packagist/dt/desarrolla2/cache.svg?style=flat-square +[ico-gitter]: https://img.shields.io/badge/GITTER-JOIN%20CHAT%20%E2%86%92-brightgreen.svg?style=flat-square + +[link-packagist]: https://packagist.org/packages/desarrolla2/cache +[link-license]: http://hassankhan.mit-license.org +[link-travis]: https://travis-ci.org/desarrolla2/cache +[link-coveralls]: https://coveralls.io/github/desarrolla2/Cache +[link-code-quality]: https://scrutinizer-ci.com/g/desarrolla2/cache +[link-downloads]: https://packagist.org/packages/desarrolla2/cache +[link-gitter]: https://gitter.im/desarrolla2/Cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge \ No newline at end of file diff --git a/build.xml b/build.xml index 18ff20b..8911202 100644 --- a/build.xml +++ b/build.xml @@ -83,4 +83,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/composer.json b/composer.json index f1ed4d7..cc2514f 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,7 @@ "php": ">=5.4.0" }, "require-dev": { - "predis/predis": "~1.0.0", - "satooshi/php-coveralls": "~0.6" + "predis/predis": "~1.0.0" }, "suggest": { "predis/predis": "Predis support" diff --git a/src/Adapter/AbstractAdapter.php b/src/Adapter/AbstractAdapter.php index 34638f0..e5d1d82 100644 --- a/src/Adapter/AbstractAdapter.php +++ b/src/Adapter/AbstractAdapter.php @@ -37,14 +37,14 @@ public function setOption($key, $value) { switch ($key) { case 'ttl': - $value = (int)$value; + $value = (int) $value; if ($value < 1) { throw new CacheException('ttl cant be lower than 1'); } $this->ttl = $value; break; case 'prefix': - $this->prefix = (string)$value; + $this->prefix = (string) $value; break; default: throw new CacheException('option not valid '.$key); diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php index e8b82c7..9ae9a49 100644 --- a/src/Adapter/AdapterInterface.php +++ b/src/Adapter/AdapterInterface.php @@ -36,7 +36,7 @@ public function del($key); /** * Retrieve the value corresponding to a provided key * - * @param string $key + * @param string $key * * @return mixed */ @@ -45,7 +45,7 @@ public function get($key); /** * Retrieve the if value corresponding to a provided key exist * - * @param string $key + * @param string $key * * @return bool */ diff --git a/src/Adapter/Apcu.php b/src/Adapter/Apcu.php index 6edaba8..6d3e1f1 100644 --- a/src/Adapter/Apcu.php +++ b/src/Adapter/Apcu.php @@ -62,7 +62,7 @@ public function set($key, $value, $ttl = null) $this->pack( [ 'value' => $value, - 'ttl' => (int)$ttl + time(), + 'ttl' => (int) $ttl + time(), ] ), $ttl @@ -79,7 +79,7 @@ public function setOption($key, $value) { switch ($key) { case 'ttl': - $value = (int)$value; + $value = (int) $value; if ($value < 1) { throw new CacheException('ttl cant be lower than 1'); } @@ -123,7 +123,6 @@ protected function validateDataFromCache($data) return true; } - protected function ttlHasExpired($ttl) { return (time() > $ttl); diff --git a/src/Adapter/File.php b/src/Adapter/File.php index 4e1e507..647249d 100644 --- a/src/Adapter/File.php +++ b/src/Adapter/File.php @@ -40,7 +40,7 @@ public function __construct($cacheDir = null) $cacheDir = realpath(sys_get_temp_dir()).'/cache'; } - $this->cacheDir = (string)$cacheDir; + $this->cacheDir = (string) $cacheDir; $this->createCacheDirectory($cacheDir); } @@ -83,7 +83,7 @@ public function set($key, $value, $ttl = null) $item = $this->pack( [ 'value' => $value, - 'ttl' => (int)$ttl + time(), + 'ttl' => (int) $ttl + time(), ] ); if (!file_put_contents($cacheFile, $item)) { @@ -98,7 +98,7 @@ public function setOption($key, $value) { switch ($key) { case 'ttl': - $value = (int)$value; + $value = (int) $value; if ($value < 1) { throw new CacheException('ttl cant be lower than 1'); } @@ -124,7 +124,6 @@ protected function createCacheDirectory($path) } } - protected function deleteFile($cacheFile) { if (is_file($cacheFile)) { diff --git a/src/Adapter/Memcache.php b/src/Adapter/Memcache.php index b0051f7..17df457 100644 --- a/src/Adapter/Memcache.php +++ b/src/Adapter/Memcache.php @@ -13,7 +13,7 @@ namespace Desarrolla2\Cache\Adapter; -use \Memcache as BaseMemcache; +use Memcache as BaseMemcache; /** * Memcache diff --git a/src/Adapter/Memcached.php b/src/Adapter/Memcached.php index ef3257d..e5fd1c0 100644 --- a/src/Adapter/Memcached.php +++ b/src/Adapter/Memcached.php @@ -13,7 +13,7 @@ namespace Desarrolla2\Cache\Adapter; -use \Memcached as BaseMemcached; +use Memcached as BaseMemcached; /** * Memcached diff --git a/src/Adapter/Memory.php b/src/Adapter/Memory.php index 869777b..3e6786b 100644 --- a/src/Adapter/Memory.php +++ b/src/Adapter/Memory.php @@ -1,4 +1,5 @@ cache[$this->getKey($key)] = [ 'value' => serialize($value), - 'ttl' => (int)$ttl + time(), + 'ttl' => (int) $ttl + time(), ]; } @@ -92,7 +92,7 @@ public function setOption($key, $value) { switch ($key) { case 'limit': - $value = (int)$value; + $value = (int) $value; $this->limit = $value; return true; diff --git a/src/Adapter/Mysqli.php b/src/Adapter/Mysqli.php index e4d01f1..d5ea4c3 100644 --- a/src/Adapter/Mysqli.php +++ b/src/Adapter/Mysqli.php @@ -13,7 +13,7 @@ namespace Desarrolla2\Cache\Adapter; -use \mysqli as Server; +use mysqli as Server; /** * Mysqli @@ -116,7 +116,7 @@ public function set($key, $value, $ttl = null) if (!($ttl)) { $ttl = $this->ttl; } - $tTtl = (int)$ttl + time(); + $tTtl = (int) $ttl + time(); $this->query( sprintf( 'INSERT INTO %s (k, v, t) VALUES ("%s", "%s", %d)', @@ -141,11 +141,10 @@ protected function pack($value) return $this->escape(parent::pack($value)); } - /** * - * @param string $query - * @param int|string $mode + * @param string $query + * @param int|string $mode * * @return mixed */ @@ -161,8 +160,8 @@ protected function fetchObject($query, $mode = MYSQLI_STORE_RESULT) /** * - * @param string $query - * @param int|string $mode + * @param string $query + * @param int|string $mode * * @return mixed */ @@ -178,7 +177,7 @@ protected function query($query, $mode = MYSQLI_STORE_RESULT) /** * - * @param string $key + * @param string $key * * @return string */ diff --git a/src/Adapter/Predis.php b/src/Adapter/Predis.php index da0d503..7642e15 100644 --- a/src/Adapter/Predis.php +++ b/src/Adapter/Predis.php @@ -39,7 +39,6 @@ public function __construct(Client $client = null) return; } $this->predis = new Client(); - } public function __destruct() diff --git a/tests/travis/phpunit.xml b/tests/travis/phpunit.xml index cd175d4..5b47474 100644 --- a/tests/travis/phpunit.xml +++ b/tests/travis/phpunit.xml @@ -12,7 +12,7 @@ colors="true"> - + From 030c37302b62e54c81c80a125cd8486deeb47456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez?= Date: Tue, 27 Oct 2015 12:13:43 +0100 Subject: [PATCH 11/51] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 269ef89..e3cd0ef 100644 --- a/README.md +++ b/README.md @@ -236,16 +236,16 @@ You can contact with me on [@desarrolla2](https://twitter.com/desarrolla2). [ico-version]: https://img.shields.io/packagist/v/desarrolla2/cache.svg?style=flat-square [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square -[ico-travis]: https://img.shields.io/travis/desarrolla2/cache/master.svg?style=flat-square -[ico-coveralls]: https://img.shields.io/coveralls/desarrolla2/cache/master.svg?style=flat-square +[ico-travis]: https://img.shields.io/travis/desarrolla2/Cache/master.svg?style=flat-square +[ico-coveralls]: https://img.shields.io/coveralls/desarrolla2/Cache/master.svg?style=flat-square [ico-code-quality]: https://img.shields.io/scrutinizer/g/desarrolla2/cache.svg?style=flat-square [ico-downloads]: https://img.shields.io/packagist/dt/desarrolla2/cache.svg?style=flat-square [ico-gitter]: https://img.shields.io/badge/GITTER-JOIN%20CHAT%20%E2%86%92-brightgreen.svg?style=flat-square [link-packagist]: https://packagist.org/packages/desarrolla2/cache [link-license]: http://hassankhan.mit-license.org -[link-travis]: https://travis-ci.org/desarrolla2/cache +[link-travis]: https://travis-ci.org/desarrolla2/Cache [link-coveralls]: https://coveralls.io/github/desarrolla2/Cache [link-code-quality]: https://scrutinizer-ci.com/g/desarrolla2/cache [link-downloads]: https://packagist.org/packages/desarrolla2/cache -[link-gitter]: https://gitter.im/desarrolla2/Cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge \ No newline at end of file +[link-gitter]: https://gitter.im/desarrolla2/Cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge From 5386a1927468286c01ce6ab0c5826ebb9601e55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez?= Date: Tue, 27 Oct 2015 12:25:25 +0100 Subject: [PATCH 12/51] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e3cd0ef..c5df27c 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,14 @@ easily by a manager or similar. [![Latest version][ico-version]][link-packagist] +[![Latest version][ico-pre-release]][link-packagist] [![Software License][ico-license]][link-license] [![Build Status][ico-travis]][link-travis] [![Coverage Status][ico-coveralls]][link-coveralls] [![Quality Score][ico-code-quality]][link-code-quality] +[![Sensiolabs Insight][ico-sensiolabs]][link-sensiolabs] [![Total Downloads][ico-downloads]][link-downloads] +[![Today Downloads][ico-today-downloads]][link-downloads] [![Gitter][ico-gitter]][link-gitter] ## Installation @@ -234,12 +237,15 @@ This can be a list of pending tasks. You can contact with me on [@desarrolla2](https://twitter.com/desarrolla2). -[ico-version]: https://img.shields.io/packagist/v/desarrolla2/cache.svg?style=flat-square +[ico-version]: https://img.shields.io/packagist/v/desarrolla2/Cache.svg?style=flat-square +[ico-pre-release]: https://img.shields.io/packagist/vpre/desarrolla2/Cache.svg?style=flat-square [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square [ico-travis]: https://img.shields.io/travis/desarrolla2/Cache/master.svg?style=flat-square [ico-coveralls]: https://img.shields.io/coveralls/desarrolla2/Cache/master.svg?style=flat-square [ico-code-quality]: https://img.shields.io/scrutinizer/g/desarrolla2/cache.svg?style=flat-square +[ico-sensiolabs]: https://img.shields.io/sensiolabs/i/5f139261-1ac1-4559-846a-723e09319a88.svg?style=flat-square [ico-downloads]: https://img.shields.io/packagist/dt/desarrolla2/cache.svg?style=flat-square +[ico-today-downloads]: https://img.shields.io/packagist/dd/desarrolla2/cache.svg?style=flat-square [ico-gitter]: https://img.shields.io/badge/GITTER-JOIN%20CHAT%20%E2%86%92-brightgreen.svg?style=flat-square [link-packagist]: https://packagist.org/packages/desarrolla2/cache @@ -247,5 +253,6 @@ You can contact with me on [@desarrolla2](https://twitter.com/desarrolla2). [link-travis]: https://travis-ci.org/desarrolla2/Cache [link-coveralls]: https://coveralls.io/github/desarrolla2/Cache [link-code-quality]: https://scrutinizer-ci.com/g/desarrolla2/cache +[link-sensiolabs]: https://insight.sensiolabs.com/projects/5f139261-1ac1-4559-846a-723e09319a88 [link-downloads]: https://packagist.org/packages/desarrolla2/cache [link-gitter]: https://gitter.im/desarrolla2/Cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge From 5142e6c8f3f894f34895f180237827c2747b9d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez?= Date: Thu, 19 Nov 2015 10:49:36 +0100 Subject: [PATCH 13/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5df27c..6ea86f6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Cache +# Cache [SensioLabsInsight](https://insight.sensiolabs.com/projects/5f139261-1ac1-4559-846a-723e09319a88) A simple cache library. Implements different adapters that you can use and change easily by a manager or similar. From 5c9e319dbc44f38bdabe4df459cb633da5a488ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Thu, 19 Nov 2015 10:56:02 +0100 Subject: [PATCH 14/51] [cs] --- .coveralls.yml | 2 +- .formatter.yml | 2 +- .scrutinizer.yml | 2 +- .travis.yml | 2 +- LICENSE | 2 +- build.xml | 2 +- src/Adapter/Memory.php | 1 - 7 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.coveralls.yml b/.coveralls.yml index ac64b59..e30743e 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,3 +1,3 @@ service_name: travis-ci src_dir: src -coverage_clover: build/logs/clover.xml \ No newline at end of file +coverage_clover: build/logs/clover.xml diff --git a/.formatter.yml b/.formatter.yml index 5860833..3225af2 100644 --- a/.formatter.yml +++ b/.formatter.yml @@ -15,4 +15,4 @@ header: | * file that was distributed with this source code. * * @author Daniel González - */ \ No newline at end of file + */ diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 2e881b5..3788948 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -81,4 +81,4 @@ tools: - 'vendor/*' # Security Advisory Checker - sensiolabs_security_checker: true \ No newline at end of file + sensiolabs_security_checker: true diff --git a/.travis.yml b/.travis.yml index c55ef1b..6d30c73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,4 +47,4 @@ script: after_script: - ls - - php vendor/bin/coveralls -v \ No newline at end of file + - php vendor/bin/coveralls -v diff --git a/LICENSE b/LICENSE index 5bc389e..2dd6c18 100644 --- a/LICENSE +++ b/LICENSE @@ -16,4 +16,4 @@ 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. \ No newline at end of file +THE SOFTWARE. diff --git a/build.xml b/build.xml index 8911202..4a5ff01 100644 --- a/build.xml +++ b/build.xml @@ -97,4 +97,4 @@ - \ No newline at end of file + diff --git a/src/Adapter/Memory.php b/src/Adapter/Memory.php index 3e6786b..176f1d6 100644 --- a/src/Adapter/Memory.php +++ b/src/Adapter/Memory.php @@ -13,7 +13,6 @@ namespace Desarrolla2\Cache\Adapter; - /** * Memory */ From 6b888809cc20efef2dfbeacee365718857e1ba89 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 26 Nov 2015 06:35:01 -0400 Subject: [PATCH 15/51] Added Mongo support. This was supported in 1.8, but missing in 2.0 Supports both the mongodb\mongodb library as the (legacy) mongo extension Differenes from 1.8: - Constructor takes db and/or collection as arguments - Use `_id` field rather than custom `key` field - Expired items are ignored rather than deleted (just like Mysqli) - Using upsert rather than delete + insert - Store TTL as date bson type to allow ttl index Includes unit tests. Usage guide is added to README. Fixes desarrolla2/Cache#25 --- README.md | 55 ++++++++++++++++++ src/Adapter/Mongo.php | 113 ++++++++++++++++++++++++++++++++++++ tests/Adapter/MongoTest.php | 61 +++++++++++++++++++ tests/config.json.dist | 5 +- 4 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 src/Adapter/Mongo.php create mode 100644 tests/Adapter/MongoTest.php diff --git a/README.md b/README.md index 6ea86f6..ebce4a2 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,61 @@ $cache = new Cache($adapter); ``` +### Mongo + +Use it to store the cache in a Mongo database. Requires the +[(legacy) mongo extension](http://php.net/mongo) or the +[mongodb/mongodb](https://github.com/mongodb/mongo-php-library) library. + +``` php +selectDatabase($dbname); + +$adapter = new Mongo($database); +$adapter->setOption('ttl', 3600); +$cache = new Cache($adapter); + +``` + +By default the `items` collection is used. You specify a different collection +as second argument of the constructor. + +``` +$adapter = new Mongo($database, $colname); +$cache = new Cache($adapter); +``` + +Alternatively you may pass a collection object as single argument. + +``` php +selectDatabase($dbname); +$collection = $database->selectCollection($colname); + +$adapter = new Mongo($collection); +$adapter->setOption('ttl', 3600); +$cache = new Cache($adapter); + +``` + +_Note that expired cache items aren't automatically deleted. To keep your +database clean, you should create a +[ttl index](https://docs.mongodb.org/manual/core/index-ttl/)._ + + +``` +db.items.createIndex( { "ttl": 1 }, { expireAfterSeconds: 30 } ) +``` ### Mysqli diff --git a/src/Adapter/Mongo.php b/src/Adapter/Mongo.php new file mode 100644 index 0000000..7e52c1f --- /dev/null +++ b/src/Adapter/Mongo.php @@ -0,0 +1,113 @@ + + */ + +namespace Desarrolla2\Cache\Adapter; + +use Desarrolla2\Cache\Exception\CacheException; + +/** + * Mongo + */ +class Mongo extends AbstractAdapter implements AdapterInterface +{ + /** + * @var string + */ + protected $collection; + + /** + * @param MongoDB|MongoDB\Database $database (may be omitted) + * @param MongoCollection|MongoDB\Collection|string $collection + */ + public function __construct($database, $collection = 'items') + { + if ($database instanceof \MongoCollection || $database instanceof \MongoDB\Collection) { + $collection = $database; + $database = null; + } + + if ($collection instanceof \MongoCollection || $collection instanceof \MongoDB\Collection) { + $this->collection = $collection; + } elseif ($database instanceof \MongoDB || $database instanceof \MongoDB\Database) { + $this->collection = $database->selectCollection($collection); + } else { + $type = (is_object($database) ? get_class($database) . ' ' : '') . gettype($database); + throw new CacheException("Database should be a MongoDB or MongoDB\Database, not a $type"); + } + } + + /** + * {@inheritdoc} + */ + public function del($key) + { + $tKey = $this->getKey($key); + $this->collection->remove(array('_id' => $tKey)); + } + + /** + * {@inheritdoc } + */ + public function get($key) + { + $tKey = $this->getKey($key); + $tNow = $this->getTtl(); + $data = $this->collection->findOne(array('_id' => $tKey, 'ttl' => array('$gte' => $tNow))); + if (isset($data)) { + return $this->unPack($data['value']); + } + + return false; + } + + /** + * {@inheritdoc } + */ + public function has($key) + { + $tKey = $this->getKey($key); + $tNow = $this->getTtl(); + return $this->collection->count(array('_id' => $tKey, 'ttl' => array('$gte' => $tNow))) > 0; + } + + /** + * {@inheritdoc } + */ + public function set($key, $value, $ttl = null) + { + $tKey = $this->getKey($key); + $tValue = $this->pack($value); + if (!$ttl) { + $ttl = $this->ttl; + } + $item = array( + '_id' => $tKey, + 'value' => $tValue, + 'ttl' => $this->getTtl($ttl), + ); + $this->collection->update(array('_id' => $tKey), $item, array('upsert' => true)); + } + + /** + * Get TTL as Date type BSON object + * + * @param int $ttl + * @return MongoDate|MongoDB\BSON\UTCDatetime + */ + protected function getTtl($ttl = 0) + { + return $this->collection instanceof \MongoCollection ? + new \MongoDate((int) $ttl + time()) : + new \MongoDB\BSON\UTCDatetime(((int) $ttl + time() * 1000)); + } +} diff --git a/tests/Adapter/MongoTest.php b/tests/Adapter/MongoTest.php new file mode 100644 index 0000000..75eccb1 --- /dev/null +++ b/tests/Adapter/MongoTest.php @@ -0,0 +1,61 @@ + + */ + +namespace Desarrolla2\Test\Cache\Adapter; + +use Desarrolla2\Cache\Cache; +use Desarrolla2\Cache\Adapter\Mongo; + +/** + * MongoTest + */ +class MongoTest extends AbstractCacheTest +{ + public function setUp() + { + parent::setup(); + if (!extension_loaded('mongo')) { + $this->markTestSkipped( + 'The mongo extension is not available.' + ); + } + + $client = new \MongoClient($this->config['mongo']['dsn']); + $database = $client->selectDB($this->config['mongo']['database']); + + $this->cache = new Cache( + new Mongo($database) + ); + } + + /** + * @return array + */ + public function dataProviderForOptions() + { + return [ + ['ttl', 100], + ]; + } + + /** + * @return array + */ + public function dataProviderForOptionsException() + { + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], + ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ]; + } +} diff --git a/tests/config.json.dist b/tests/config.json.dist index 07a9c90..320ac06 100644 --- a/tests/config.json.dist +++ b/tests/config.json.dist @@ -7,7 +7,8 @@ "port": "11211" }, "mongo": { - "dns": "mongodb://localhost:27017" + "dsn": "mongodb://localhost:27017", + "database": "cache" }, "mysql": { "user": "root", @@ -16,4 +17,4 @@ "port": "3306", "database": "cache" } -} \ No newline at end of file +} From 564e2a5487e5c3cc0a912598f04240d3e427cfe7 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 26 Nov 2015 07:33:16 -0400 Subject: [PATCH 16/51] Fix pecl issue on Travis APCU 5.x requires php7, therefor it pecl won't install it for version 5.x This causes Travis to fail without running any tests --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6d30c73..fed6079 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,8 +29,8 @@ before_script: - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' # Install apcu - - sh -c "pear config-set preferred_state beta" - - sh -c "yes '' | pecl install apcu" + - "if [ $TRAVIS_PHP_VERSION == '7.0' ] || [ $TRAVIS_PHP_VERSION == 'hhvm' ]; then export APCU_VERSION=5.1.0; else export APCU_VERSION=4.0.8; fi" + - sh -c "yes '' | pecl install apcu-$APCU_VERSION" - sh -c "if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-add ./tests/travis/php.ini; fi" # Install dependencies From 47bb6fd2cf2b6c7bd743fa7a005ecea9b7328a9b Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 26 Nov 2015 08:09:08 -0400 Subject: [PATCH 17/51] Removed option to pass collection name as second argument Create default backend object is nothing is supplied --- README.md | 17 +++++------------ src/Adapter/Mongo.php | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index ebce4a2..87b15a9 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,9 @@ Use it to store the cache in a Mongo database. Requires the [(legacy) mongo extension](http://php.net/mongo) or the [mongodb/mongodb](https://github.com/mongodb/mongo-php-library) library. +You may pass either a database or collection object to the constructor. If a +database object is passed, the `items` collection within that DB is used. + ``` php selectDatabase($dbname); -$collection = $database->selectCollection($colname); +$database = $client->selectDatabase($dbName); +$collection = $database->selectCollection($collectionName); $adapter = new Mongo($collection); $adapter->setOption('ttl', 3600); diff --git a/src/Adapter/Mongo.php b/src/Adapter/Mongo.php index 7e52c1f..6ee96b3 100644 --- a/src/Adapter/Mongo.php +++ b/src/Adapter/Mongo.php @@ -21,28 +21,28 @@ class Mongo extends AbstractAdapter implements AdapterInterface { /** - * @var string + * @var MongoCollection|MongoDB\Collection */ protected $collection; /** - * @param MongoDB|MongoDB\Database $database (may be omitted) - * @param MongoCollection|MongoDB\Collection|string $collection + * @param MongoDB|MongoDB\Database|MongoCollection|MongoDB\Collection $backend */ - public function __construct($database, $collection = 'items') + public function __construct($backend = null) { - if ($database instanceof \MongoCollection || $database instanceof \MongoDB\Collection) { - $collection = $database; - $database = null; + if (!isset($backend)) { + $client = class_exist('MongoCollection') ? new \MongoClient() : new \MongoDB\Client(); + $backend = $client->selectDatabase('cache'); } - - if ($collection instanceof \MongoCollection || $collection instanceof \MongoDB\Collection) { - $this->collection = $collection; - } elseif ($database instanceof \MongoDB || $database instanceof \MongoDB\Database) { - $this->collection = $database->selectCollection($collection); + + if ($backend instanceof \MongoCollection || $backend instanceof \MongoDB\Collection) { + $this->collection = $backend; + } elseif ($backend instanceof \MongoDB || $backend instanceof \MongoDB\Database) { + $this->collection = $backend->selectCollection('items'); } else { $type = (is_object($database) ? get_class($database) . ' ' : '') . gettype($database); - throw new CacheException("Database should be a MongoDB or MongoDB\Database, not a $type"); + throw new CacheException("Database should be a database (MongoDB or MongoDB\Database) or " . + " collection (MongoCollection or MongoDB\Collection) object, not a $type"); } } From a3547ddd2b69e51ac95ddcb6595caba4ecc85c5b Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 19 May 2016 22:11:03 +0200 Subject: [PATCH 18/51] Update README.md List the available methods. Fixes #29 --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 87b15a9..15e5f3d 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,32 @@ $cache = new Cache($adapter); ``` +## Methods + +A `Desarrolla2\Cache\Cache` object has the following methods: + +##### `delete(string $key)` +Delete a value from the cache + +##### `public function get(string $key)` +Retrieve the value corresponding to a provided key + +##### `public function has($key)` +Retrieve the if value corresponding to a provided key exist + +##### `set(string $key , mixed $value [, int $ttl])` +Add a value to the cache under a unique key + +##### `setOption(string $key, string $value)` +Set option for Adapter + +##### `clearCache()` +Clean all expired records from cache + +##### `dropCache()` +Clear all cache + + ## Coming soon This library implements other adapters as soon as possible, feel free to send From 99e46731f2852a88d152293c07e05a0c587b8ae1 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 1 Sep 2016 19:59:32 +0200 Subject: [PATCH 19/51] Update README.md Fixup for desarrolla2/Cache#30 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15e5f3d..eb857fe 100644 --- a/README.md +++ b/README.md @@ -277,10 +277,10 @@ A `Desarrolla2\Cache\Cache` object has the following methods: ##### `delete(string $key)` Delete a value from the cache -##### `public function get(string $key)` +##### `get(string $key)` Retrieve the value corresponding to a provided key -##### `public function has($key)` +##### `has(string $key)` Retrieve the if value corresponding to a provided key exist ##### `set(string $key , mixed $value [, int $ttl])` From ebd1f7bfd1be605a9b5fbb0a4b25a948a31fe963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez=20Cervi=C3=B1o?= Date: Wed, 18 Jan 2017 13:58:33 +0100 Subject: [PATCH 20/51] [] - change apc to apcu_ php api for ApcuAdapter --- src/Adapter/Apcu.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Adapter/Apcu.php b/src/Adapter/Apcu.php index 6d3e1f1..48c9802 100644 --- a/src/Adapter/Apcu.php +++ b/src/Adapter/Apcu.php @@ -25,7 +25,7 @@ class Apcu extends AbstractAdapter */ public function del($key) { - apc_delete($this->getKey($key)); + apcu_delete($this->getKey($key)); } /** @@ -57,7 +57,7 @@ public function set($key, $value, $ttl = null) if (!$ttl) { $ttl = $this->ttl; } - if (!apc_store( + if (!apcu_store( $this->getKey($key), $this->pack( [ @@ -94,7 +94,7 @@ public function setOption($key, $value) protected function getValueFromCache($key) { - $data = $this->unPack(apc_fetch($this->getKey($key))); + $data = $this->unPack(apcu_fetch($this->getKey($key))); if (!$this->validateDataFromCache($data, $key)) { $this->del($key); From 3c1e5f6ad5c45163ff108400b09eac01c3a14380 Mon Sep 17 00:00:00 2001 From: Evgeny Chernyavskiy Date: Sat, 1 Apr 2017 02:59:15 -0400 Subject: [PATCH 21/51] Remove duplicate invocation of getKey() in File::getValueFromCache() Calling `getKey()` before passing the key to `getFileName()` from `getValueFromCache()` is not only unnecessary, it actually breaks caching if the prefix is not empty. I discovered this from a slightly different perspective: in my application I had to inherit from `Adapter/File` and override `getKey()` so that it applies `sha1()` to it as I frequently get cache keys with colons and other garbage in them. So as with a non-empty prefix, the key gets mutated in this instance, and then gets mutated yet again by the second `getKey()` call, so the lookup key is actually completely different. --- src/Adapter/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Adapter/File.php b/src/Adapter/File.php index 647249d..68c82a9 100644 --- a/src/Adapter/File.php +++ b/src/Adapter/File.php @@ -144,7 +144,7 @@ protected function getFileName($key) protected function getValueFromCache($key) { - $path = $this->getFileName($this->getKey($key)); + $path = $this->getFileName($key); if (!file_exists($path)) { return; From a6103b37efdd06f91bbd34a03d809ee06a9d856c Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 29 Jun 2017 02:13:45 +0200 Subject: [PATCH 22/51] Rewrite cache classes to implement PSR-16 (WIP) --- README.md | 225 ++++++++------- composer.json | 5 +- src/AbstractCache.php | 261 ++++++++++++++++++ src/Adapter/AbstractAdapter.php | 94 ------- src/Adapter/AdapterInterface.php | 80 ------ src/Adapter/Apcu.php | 130 --------- src/Apcu.php | 129 +++++++++ src/Cache.php | 131 --------- src/CacheInterface.php | 63 +---- src/Exception/CacheException.php | 7 +- src/Exception/InvalidArgumentException.php | 24 ++ ...ption.php => UnexpectedValueException.php} | 7 +- src/{Adapter => }/File.php | 0 src/{Adapter => }/Memcache.php | 0 src/{Adapter => }/Memcached.php | 0 src/{Adapter => }/Memory.php | 0 src/{Adapter => }/Mongo.php | 0 src/{Adapter => }/Mysqli.php | 18 +- src/{Adapter => }/NotCache.php | 0 src/PackTtlTrait.php | 84 ++++++ src/Packer/JsonPacker.php | 48 ++++ src/Packer/NopPacker.php | 45 +++ src/Packer/PackerInterface.php | 38 +++ src/Packer/SerializePacker.php | 46 +++ src/{Adapter => }/Predis.php | 0 25 files changed, 825 insertions(+), 610 deletions(-) create mode 100644 src/AbstractCache.php delete mode 100644 src/Adapter/AbstractAdapter.php delete mode 100644 src/Adapter/AdapterInterface.php delete mode 100644 src/Adapter/Apcu.php create mode 100644 src/Apcu.php delete mode 100644 src/Cache.php create mode 100644 src/Exception/InvalidArgumentException.php rename src/Exception/{AdapterNotSetException.php => UnexpectedValueException.php} (56%) rename src/{Adapter => }/File.php (100%) rename src/{Adapter => }/Memcache.php (100%) rename src/{Adapter => }/Memcached.php (100%) rename src/{Adapter => }/Memory.php (100%) rename src/{Adapter => }/Mongo.php (100%) rename src/{Adapter => }/Mysqli.php (91%) rename src/{Adapter => }/NotCache.php (100%) create mode 100644 src/PackTtlTrait.php create mode 100644 src/Packer/JsonPacker.php create mode 100644 src/Packer/NopPacker.php create mode 100644 src/Packer/PackerInterface.php create mode 100644 src/Packer/SerializePacker.php rename src/{Adapter => }/Predis.php (100%) diff --git a/README.md b/README.md index eb857fe..c842c22 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Cache [SensioLabsInsight](https://insight.sensiolabs.com/projects/5f139261-1ac1-4559-846a-723e09319a88) -A simple cache library. Implements different adapters that you can use and change -easily by a manager or similar. +A simple cache library, implementing the [PSR-16](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) standard. [![Latest version][ico-version]][link-packagist] @@ -15,25 +14,13 @@ easily by a manager or similar. [![Today Downloads][ico-today-downloads]][link-downloads] [![Gitter][ico-gitter]][link-gitter] -## Installation - -### With Composer -It is best installed it through [packagist](http://packagist.org/packages/desarrolla2/cache) -by including `desarrolla2/cache` in your project composer.json require: +## Installation -``` json - "require": { - // ... - "desarrolla2/cache": "~2.0" - } +``` +composer require desarrolla2/cache ``` -### Without Composer - -You can also download it from [Github] (https://github.com/desarrolla2/Cache), -but no autoloader is provided so you'll need to register it with your own PSR-0 -compatible autoloader. ## Usage @@ -41,10 +28,9 @@ compatible autoloader. ``` php set('key', 'myKeyValue', 3600); @@ -54,54 +40,58 @@ echo $cache->get('key'); ``` -## Adapters +## Cache implementations ### Apcu -Use it if you will you have APC cache available in your system. +Use [APCu cache](http://php.net/manual/en/book.apcu.php) to cache to shared +memory. ``` php setOption('ttl', 3600); -$cache = new Cache($adapter); +$cache = new ApcuCache(); +$cache->setOption('ttl', 3600); +$cache->setOption('pack-ttl', true); ``` +If the `pack-ttl` option is set to false, the cache will rely on APCu's TTL and +not verify the TTL itself. + ### File -Use it if you will you have dont have other cache system available in your system -or if you like to do your code more portable. +Save the cache as file to on the filesystem ``` php setOption('ttl', 3600); -$cache = new Cache($adapter); +$cache = new FileCache($cacheDir); +$cache->setOption('ttl', 3600); +$cache->setOption('pack-ttl', true); ``` +If the `pack-ttl` option is set to false, the cache file will only contain the +cached value. The TTL is written a file suffixed with `.ttl`. + + ### Memcache -Use it if you will you have mencache available in your system. +Store cache to [Memcached](https://memcached.org/). Memcached is a high +performance distributed caching system. ``` php setOption('ttl', 3600); -$adapter->setOption('limit', 200); -$cache = new Cache($adapter); +$cache = new MemoryCache(); +$cache->setOption('ttl', 3600); +$cache->setOption('limit', 200); ``` @@ -158,31 +144,27 @@ database object is passed, the `items` collection within that DB is used. ``` php selectDatabase($dbname); -$adapter = new Mongo($database); -$adapter->setOption('ttl', 3600); -$cache = new Cache($adapter); +$cache = new MongoCache($database); +$cache->setOption('ttl', 3600); ``` ``` php selectDatabase($dbName); $collection = $database->selectCollection($collectionName); -$adapter = new Mongo($collection); -$adapter->setOption('ttl', 3600); -$cache = new Cache($adapter); +$cache = new MongoCache($collection); +$cache->setOption('ttl', 3600); ``` @@ -202,12 +184,10 @@ Use it if you will you have mysqlnd available in your system. ``` php setOption('ttl', 3600); -$cache = new Cache($adapter); +$cache = new MysqliCache(); +$cache->setOption('ttl', 3600); ``` @@ -215,20 +195,36 @@ $cache = new Cache($adapter); ``` php value pairs in the cache + +##### `deleteMultiple(array $keys)` +Deletes multiple cache items in a single operation + ##### `setOption(string $key, string $value)` -Set option for Adapter +Set option for Adapter _(Not in PSR-16)_ -##### `clearCache()` -Clean all expired records from cache +##### `getOption(string $key)` +Get an option for Adapter _(Not in PSR-16)_ -##### `dropCache()` -Clear all cache +## Packers -## Coming soon +Cache objects typically hold a `Desarrolla2\Cache\Packer\PackerInterface` +object. By default, packing is done using `serialize` and `unserialize`. -This library implements other adapters as soon as possible, feel free to send -new adapters if you think it appropriate. +Available packers are: -This can be a list of pending tasks. +* `JsonPacker` using `json_encode` and `json_decode` +* `NopPacker` does no packing +* `SerializePacker` using `serialize` and `unserialize` +* `PhpPacker` uses `var_export` and `include`/`eval` + +#### PSR-16 incompatible packers + +The `JsonPacker` does not fully comply with PSR-16, as packing and +unpacking an object will probably not result in an object of the same class. + +The `NopPacker` is intended when caching string data only (like HTML output) or +if the caching backend supports structured data. Using it when storing objects +will likely yield unexpected results. -* Cleaning cache -* MemcachedAdapter -* Other Adapters ## Contact You can contact with me on [@desarrolla2](https://twitter.com/desarrolla2). +## Contributors + +[![Daniel González](https://avatars1.githubusercontent.com/u/661529?v=3&s=80)](https://github.com/desarrolla2) +[![Arnold Daniels](https://avatars3.githubusercontent.com/u/100821?v=3&s=80)](https://github.com/jasny) + [ico-version]: https://img.shields.io/packagist/v/desarrolla2/Cache.svg?style=flat-square [ico-pre-release]: https://img.shields.io/packagist/vpre/desarrolla2/Cache.svg?style=flat-square [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square diff --git a/composer.json b/composer.json index cc2514f..6ee4c57 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "desarrolla2/cache", - "description": "Provides an cache interface for several adapters Apc, Apcu, File, Mongo, Memcache, Memcached, Mysql, Mongo, Redis is supported. New adapters is comming!", + "description": "Provides an cache interface for several adapters Apc, Apcu, File, Mongo, Memcache, Memcached, Mysql, Mongo, Redis is supported.", "keywords": [ "cache", "apc", @@ -22,7 +22,8 @@ } ], "require": { - "php": ">=5.4.0" + "php": ">=5.6.0", + "psr/simple-cache": "^1.0" }, "require-dev": { "predis/predis": "~1.0.0" diff --git a/src/AbstractCache.php b/src/AbstractCache.php new file mode 100644 index 0000000..d394241 --- /dev/null +++ b/src/AbstractCache.php @@ -0,0 +1,261 @@ + + */ + +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\CacheInterface; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\SerializePacker; +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; + +/** + * AbstractAdapter + */ +abstract class AbstractCache implements CacheInterface +{ + /** + * @var int + */ + protected $ttl = 3600; + + /** + * @var string + */ + protected $prefix = ''; + + /** + * @var PackerInterface + */ + protected $packer; + + + /** + * {@inheritdoc} + */ + public function setOption($key, $value) + { + $method = "set" . str_replace('-', '', $key) . "Option"; + + if (empty($key) || !method_exists($this, $method)) { + throw new InvalidArgumentException("unknown option '$key'"); + } + + $this->$method($value); + } + + /** + * {@inheritdoc} + */ + protected function getOption($key) + { + $method = "get" . str_replace('-', '', $key) . "Option"; + + if (empty($key) || !method_exists($this, $method)) { + throw new InvalidArgumentException("unknown option '$key'"); + } + + return $this->$method(); + } + + /** + * Set the time to live (ttl) + * + * @param int $value Seconds + * @throws InvalidArgumentException + */ + protected function setTtlOption($value) + { + $ttl = (int)$value; + if ($ttl < 1) { + throw new InvalidArgumentException('ttl cant be lower than 1'); + } + + $this->ttl = $ttl; + } + + /** + * Get the time to live (ttl) + * + * @return int + */ + protected function getTtlOption() + { + return $this->ttl; + } + + /** + * Set the key prefix + * + * @param string $value + */ + protected function setPrefixOption($value) + { + $this->prefix = (string)$value; + } + + /** + * Get the key prefix + * + * @return string + */ + protected function getPrefixOption() + { + return $this->prefix; + } + + + /** + * Set the packer + * + * @param PackerInterface $packer + */ + public function setPacker(PackerInterface $packer) + { + $this->packer = $packer; + } + + /** + * Get the packer + * + * @return PackerInterface + */ + protected function getPacker() + { + if (!isset($this->packer)) { + $this->packer = new SerializePacker(); + } + + return $this->packer; + } + + + /** + * Assert that the keys are traversable + * + * @param iterable $subject + * @param string $msg + * @throws InvalidArgumentException if subject are not iterable + */ + protected function assertIterable($subject, $msg) + { + $iterable = function_exists('is_iterable') + ? is_iterable($subject) + : is_array($subject) || $subject instanceof Traversable; + + if (~$iterable) { + throw new InvalidArgumentException($msg); + } + } + + + /** + * Get the key with prefix + * + * @param string $key + * @return string + */ + protected function getKey($key) + { + return sprintf('%s%s', $this->prefix, $key); + } + + + /** + * {@inheritdoc} + */ + public function delete($key) + { + throw new CacheException('not ready yet'); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + $this->assertTraverable($keys, 'keys not iterable'); + + $success = true; + + foreach ($keys as $key) { + $success &= $this->delete($key); + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + throw new CacheException('not ready yet'); + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + $this->assertTraverable($keys, 'keys not iterable'); + + $result = []; + + foreach ($keys as $key) { + $result[] = $this->get($key, $default); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + throw new CacheException('not ready yet'); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + throw new CacheException('not ready yet'); + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $this->assertTraverable($values, 'values not iterable'); + + $result = []; + + foreach ($values as $key => $value) { + $result[] = $this->set($key, $value, $ttl); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + throw new CacheException('not ready yet'); + } +} diff --git a/src/Adapter/AbstractAdapter.php b/src/Adapter/AbstractAdapter.php deleted file mode 100644 index e5d1d82..0000000 --- a/src/Adapter/AbstractAdapter.php +++ /dev/null @@ -1,94 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -use Desarrolla2\Cache\Exception\CacheException; - -/** - * AbstractAdapter - */ -abstract class AbstractAdapter implements AdapterInterface -{ - /** - * @var int - */ - protected $ttl = 3600; - - /** - * @var string - */ - protected $prefix = ''; - - /** - * {@inheritdoc} - */ - public function setOption($key, $value) - { - switch ($key) { - case 'ttl': - $value = (int) $value; - if ($value < 1) { - throw new CacheException('ttl cant be lower than 1'); - } - $this->ttl = $value; - break; - case 'prefix': - $this->prefix = (string) $value; - break; - default: - throw new CacheException('option not valid '.$key); - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function clearCache() - { - throw new CacheException('not ready yet'); - } - - /** - * {@inheritdoc} - */ - public function dropCache() - { - throw new CacheException('not ready yet'); - } - - /** - * {@inheritdoc} - */ - public function check() - { - throw new CacheException('not ready yet'); - } - - protected function getKey($key) - { - return sprintf('%s%s', $this->prefix, $key); - } - - protected function pack($value) - { - return serialize($value); - } - - protected function unPack($value) - { - return unserialize($value); - } -} diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php deleted file mode 100644 index 9ae9a49..0000000 --- a/src/Adapter/AdapterInterface.php +++ /dev/null @@ -1,80 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -/** - * Interface AdapterInterface - */ -interface AdapterInterface -{ - - /** - * Check if adapter is working - * - * @return boolean - */ - public function check(); - - /** - * Delete a value from the cache - * - * @param string $key - */ - public function del($key); - - /** - * Retrieve the value corresponding to a provided key - * - * @param string $key - * - * @return mixed - */ - public function get($key); - - /** - * Retrieve the if value corresponding to a provided key exist - * - * @param string $key - * - * @return bool - */ - public function has($key); - - /** - * * Add a value to the cache under a unique key - * - * @param string $key - * @param mixed $value - * @param int $ttl - */ - public function set($key, $value, $ttl = null); - - /** - * Set option for Adapter - * - * @param string $key - * @param string $value - */ - public function setOption($key, $value); - - /** - * clean all expired records from cache - */ - public function clearCache(); - - /** - * clear all cache - */ - public function dropCache(); -} diff --git a/src/Adapter/Apcu.php b/src/Adapter/Apcu.php deleted file mode 100644 index 48c9802..0000000 --- a/src/Adapter/Apcu.php +++ /dev/null @@ -1,130 +0,0 @@ - - */ - -namespace Desarrolla2\Cache\Adapter; - -use Desarrolla2\Cache\Exception\CacheException; - -/** - * Apcu - */ -class Apcu extends AbstractAdapter -{ - /** - * {@inheritdoc} - */ - public function del($key) - { - apcu_delete($this->getKey($key)); - } - - /** - * {@inheritdoc} - */ - public function get($key) - { - return $this->getValueFromCache($key); - } - - /** - * {@inheritdoc} - */ - public function has($key) - { - $value = $this->getValueFromCache($key); - if (is_null($value)) { - return false; - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function set($key, $value, $ttl = null) - { - if (!$ttl) { - $ttl = $this->ttl; - } - if (!apcu_store( - $this->getKey($key), - $this->pack( - [ - 'value' => $value, - 'ttl' => (int) $ttl + time(), - ] - ), - $ttl - ) - ) { - throw new CacheException(sprintf('Error saving data with the key "%s" to the apcu cache.', $key)); - } - } - - /** - * {@inheritdoc} - */ - public function setOption($key, $value) - { - switch ($key) { - case 'ttl': - $value = (int) $value; - if ($value < 1) { - throw new CacheException('ttl cant be lower than 1'); - } - $this->ttl = $value; - break; - default: - throw new CacheException('option not valid '.$key); - } - - return true; - } - - protected function getValueFromCache($key) - { - $data = $this->unPack(apcu_fetch($this->getKey($key))); - if (!$this->validateDataFromCache($data, $key)) { - $this->del($key); - - return; - } - if ($this->ttlHasExpired($data['ttl'])) { - $this->del($key); - - return; - } - - return $data['value']; - } - - protected function validateDataFromCache($data) - { - if (!is_array($data)) { - return false; - } - foreach (['value', 'ttl'] as $missing) { - if (!array_key_exists($missing, $data)) { - return false; - } - } - - return true; - } - - protected function ttlHasExpired($ttl) - { - return (time() > $ttl); - } -} diff --git a/src/Apcu.php b/src/Apcu.php new file mode 100644 index 0000000..7b717d0 --- /dev/null +++ b/src/Apcu.php @@ -0,0 +1,129 @@ + + */ + +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\AbstractCache; +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\PackTtlTrait; + +/** + * Apcu + */ +class Apcu extends AbstractCache +{ + use PackTtlTrait; + + + /** + * Set the `pack-ttl` setting; Include TTL in the packed data. + * + * @param boolean $value + */ + protected function setPackTtlOption($value) + { + $this->packTtl = (boolean)$value; + } + + /** + * Get the `pack-ttl` setting + * + * @return boolean + */ + protected function getPackTtlOption() + { + return $this->packTtl; + } + + + /** + * {@inheritdoc} + */ + public function delete($key) + { + apcu_delete($this->getKey($key)); + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + return $this->getValueFromCache($key); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return apcu_exists($key) && (!$this->packTtl || $this->getValueFromCache($key) !== null); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return apcu_clear_cache(); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + if (!$ttl) { + $ttl = $this->ttl; + } + + $data = $this->pack($value, $ttl); + + if (!is_string($data)) { + throw new InvalidArgumentException( + sprintf('Error saving data with the key "%s" to the apcu cache; data must be packed as string', $key) + ); + } + + $success = apcu_store($this->getKey($key), $data, $ttl); + + if (!$success) { + throw new CacheException(sprintf('Error saving data with the key "%s" to the apcu cache.', $key)); + } + } + + /** + * Get the value from cache + * + * @param string $key + * @return mixed|null + */ + protected function getValueFromCache($key) + { + $packed = apcu_fetch($this->getKey($key), $success); + + if (!$success) { + return null; + } + + try { + $value = $this->unpack($packed); + } catch (UnexpectedValueException $e) { + $this->delete($key); + return null; + } + + return $data['value']; + } +} diff --git a/src/Cache.php b/src/Cache.php deleted file mode 100644 index a0a4f3b..0000000 --- a/src/Cache.php +++ /dev/null @@ -1,131 +0,0 @@ - - */ - -namespace Desarrolla2\Cache; - -use Desarrolla2\Cache\Adapter\AdapterInterface; -use Desarrolla2\Cache\Exception\AdapterNotSetException; - -/** - * Cache - */ -class Cache implements CacheInterface -{ - /** - * - * @var Adapter\AdapterInterface - */ - protected $adapter; - - /** - * @param AdapterInterface $adapter - */ - public function __construct(AdapterInterface $adapter = null) - { - if ($adapter) { - $this->setAdapter($adapter); - } - } - - /** - * {@inheritdoc} - * - * @param string $key - */ - public function delete($key) - { - $this->getAdapter()->del($key); - } - - /** - * {@inheritdoc} - * - * @param string $key - */ - public function get($key) - { - return $this->getAdapter()->get($key); - } - - /** - * {@inheritdoc} - */ - public function getAdapter() - { - if (!$this->adapter) { - throw new AdapterNotSetException('Required Adapter'); - } - - return $this->adapter; - } - - /** - * {@inheritdoc} - * - * @param string $key - */ - public function has($key) - { - return $this->getAdapter()->has($key); - } - - /** - * {@inheritdoc} - * - * @param string $key - * @param mixed $value - * @param null $ttl - */ - public function set($key, $value, $ttl = null) - { - $this->getAdapter()->set($key, $value, $ttl); - } - - /** - * {@inheritdoc} - * - * @param Adapter\AdapterInterface $adapter - */ - public function setAdapter(AdapterInterface $adapter) - { - $this->adapter = $adapter; - } - - /** - * {@inheritdoc} - * - * @param string $key - * @param string $value - * @return mixed - */ - public function setOption($key, $value) - { - return $this->adapter->setOption($key, $value); - } - - /** - * {@inheritdoc} - */ - public function clearCache() - { - $this->adapter->clearCache(); - } - - /** - * {@inheritdoc} - */ - public function dropCache() - { - $this->adapter->dropCache(); - } -} diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 2f5208d..c0241bb 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -13,70 +13,25 @@ namespace Desarrolla2\Cache; +use Psr\SimpleCache\CacheInterface as PsrCacheInterface; + /** * CacheInterface */ -interface CacheInterface +interface CacheInterface extends PsrCacheInterface { /** - * Delete a value from the cache - * - * @param string $key - */ - public function delete($key); - - /** - * Retrieve the value corresponding to a provided key - * - * @param string $key - */ - public function get($key); - - /** - * - * @return \Desarrolla2\Cache\Adapter\AdapterInterface $adapter - * @throws Exception - */ - public function getAdapter(); - - /** - * Retrieve the if value corresponding to a provided key exist - * - * @param string $key - */ - public function has($key); - - /** - * * Add a value to the cache under a unique key - * - * @param string $key - * @param mixed $value - * @param int $ttl - */ - public function set($key, $value, $ttl = null); - - /** - * Set Adapter interface - * - * @param \Desarrolla2\Cache\Adapter\AdapterInterface $adapter - */ - public function setAdapter(\Desarrolla2\Cache\Adapter\AdapterInterface $adapter); - - /** - * Set option for Adapter + * Set option for cache * * @param string $key * @param string $value */ public function setOption($key, $value); - - /** - * clean all expired records from cache - */ - public function clearCache(); - + /** - * clear all cache + * Get option for cache + * + * @param string $key */ - public function dropCache(); + public function getOption($key); } diff --git a/src/Exception/CacheException.php b/src/Exception/CacheException.php index b2d43d1..e197869 100644 --- a/src/Exception/CacheException.php +++ b/src/Exception/CacheException.php @@ -9,13 +9,16 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ namespace Desarrolla2\Cache\Exception; +use Psr\SimpleCache\CacheException as PsrCacheException; + /** - * CacheException + * Interface used for all types of exceptions thrown by the implementing library. */ -class CacheException extends \RuntimeException +class CacheException extends \RuntimeException implements PsrCacheException { } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..dc08e3e --- /dev/null +++ b/src/Exception/InvalidArgumentException.php @@ -0,0 +1,24 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Exception; + +use Psr\SimpleCache\InvalidArgumentException as PsrInvalidArgumentException; + +/** + * Exception for invalid cache arguments. + */ +class InvalidArgumentException extends \InvalidArgumentException implements PsrInvalidArgumentException +{ +} diff --git a/src/Exception/AdapterNotSetException.php b/src/Exception/UnexpectedValueException.php similarity index 56% rename from src/Exception/AdapterNotSetException.php rename to src/Exception/UnexpectedValueException.php index abf2673..c7841c6 100644 --- a/src/Exception/AdapterNotSetException.php +++ b/src/Exception/UnexpectedValueException.php @@ -9,13 +9,16 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ namespace Desarrolla2\Cache\Exception; +use Psr\SimpleCache\CacheException as PsrCacheException; + /** - * AdapterNotSetException + * Exception for unexpected values when reading from cache. */ -class AdapterNotSetException extends CacheException +class UnexpectedValueException extends \UnexpectedValueException implements PsrCacheException { } diff --git a/src/Adapter/File.php b/src/File.php similarity index 100% rename from src/Adapter/File.php rename to src/File.php diff --git a/src/Adapter/Memcache.php b/src/Memcache.php similarity index 100% rename from src/Adapter/Memcache.php rename to src/Memcache.php diff --git a/src/Adapter/Memcached.php b/src/Memcached.php similarity index 100% rename from src/Adapter/Memcached.php rename to src/Memcached.php diff --git a/src/Adapter/Memory.php b/src/Memory.php similarity index 100% rename from src/Adapter/Memory.php rename to src/Memory.php diff --git a/src/Adapter/Mongo.php b/src/Mongo.php similarity index 100% rename from src/Adapter/Mongo.php rename to src/Mongo.php diff --git a/src/Adapter/Mysqli.php b/src/Mysqli.php similarity index 91% rename from src/Adapter/Mysqli.php rename to src/Mysqli.php index d5ea4c3..64ebbcc 100644 --- a/src/Adapter/Mysqli.php +++ b/src/Mysqli.php @@ -28,7 +28,7 @@ class Mysqli extends AbstractAdapter implements AdapterInterface /** * @var string */ - protected $database = 'cache'; + protected $table = 'cache'; /** * @param Server|null $server @@ -43,22 +43,16 @@ public function __construct(Server $server = null) $this->server = new server(); } - public function __destruct() - { - $this->server->close(); - } - /** * {@inheritdoc} */ - public function del($key) + public function delete($key) { $this->query( sprintf( 'DELETE FROM %s WHERE k = "%s" OR t < %d', - $this->database, + $this->table, $this->getKey($key), - $this->database, time() ) ); @@ -72,7 +66,7 @@ public function get($key) $res = $this->fetchObject( sprintf( 'SELECT v FROM %s WHERE k = "%s" AND t >= %d LIMIT 1;', - $this->database, + $this->table, $this->getKey($key), time() ) @@ -92,7 +86,7 @@ public function has($key) $res = $this->fetchObject( sprintf( 'SELECT COUNT(*) AS n FROM %s WHERE k = "%s" AND t >= %d;', - $this->database, + $this->table, $this->getKey($key), time() ) @@ -120,7 +114,7 @@ public function set($key, $value, $ttl = null) $this->query( sprintf( 'INSERT INTO %s (k, v, t) VALUES ("%s", "%s", %d)', - $this->database, + $this->table, $this->getKey($key), $this->pack($value), $tTtl diff --git a/src/Adapter/NotCache.php b/src/NotCache.php similarity index 100% rename from src/Adapter/NotCache.php rename to src/NotCache.php diff --git a/src/PackTtlTrait.php b/src/PackTtlTrait.php new file mode 100644 index 0000000..c60f530 --- /dev/null +++ b/src/PackTtlTrait.php @@ -0,0 +1,84 @@ +packTtl ? ['value' => $value, 'ttl' => time() + $ttl] : $value; + + return $this->getPacker()->pack($data); + } + + /** + * Unpack the data to retreive the value and optionally the ttl + * + * @param string|mixed $packed + * @return mixed + * @throws UnexpectedValueException + */ + protected function unpack($packed) + { + $data = $this->getPacker()->unpack($packed); + + if (!$this->validateDataFromCache($data)) { + throw new UnexpectedValueException("unexpected data from cache"); + } + + if ($this->packTtl && $this->ttlHasExpired($data['ttl'])) { + throw new UnexpectedValueException("ttl has expired"); + } + + return $this->packTtl ? $data['value'] : $data; + } + + /** + * Validate that the data from cache is as expected + * + * @param array|mixed $data + * @return boolean + */ + protected function validateDataFromCache($data) + { + return !$this->packTtl || + (is_array($data) && array_key_exists('value', $data) && array_key_exists('ttl', $data)); + } + + /** + * Check if TTL has expired + * + * @param type $ttl + * @return type + */ + protected function ttlHasExpired($ttl) + { + return (time() > $ttl); + } +} diff --git a/src/Packer/JsonPacker.php b/src/Packer/JsonPacker.php new file mode 100644 index 0000000..012ddaa --- /dev/null +++ b/src/Packer/JsonPacker.php @@ -0,0 +1,48 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Packer; + +use Desarrolla2\Cache\Packer\PackerInterface; + +/** + * Pack value through serialization + */ +class JsonPacker implements PackerInterface +{ + /** + * Pack the value + * + * @param mixed $value + * @return string + */ + public function pack($value) + { + return json_encode($value); + } + + /** + * Unpack the value + * + * @param string $packed + * @return string + * @throws \UnexpectedValueException if he + */ + public function unpack($packed) + { + $value = json_decode($packed); + + return json_serialize($packed); + } +} diff --git a/src/Packer/NopPacker.php b/src/Packer/NopPacker.php new file mode 100644 index 0000000..763611d --- /dev/null +++ b/src/Packer/NopPacker.php @@ -0,0 +1,45 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Packer; + +use Desarrolla2\Cache\Packer\PackerInterface; + +/** + * Don't pack, just straight passthrough + */ +class NopPacker implements PackerInterface +{ + /** + * Pack the value + * + * @param mixed $value + * @return mixed + */ + public function pack($value) + { + return serialize($value); + } + + /** + * Unpack the value + * + * @param mixed $packed + * @return mixed + */ + public function unpack($packed) + { + return unserialize($packed); + } +} diff --git a/src/Packer/PackerInterface.php b/src/Packer/PackerInterface.php new file mode 100644 index 0000000..1f7ee14 --- /dev/null +++ b/src/Packer/PackerInterface.php @@ -0,0 +1,38 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Packer; + +/** + * Interface for packer / unpacker + */ +interface PackerInterface +{ + /** + * Pack the value + * + * @param mixed $value + * @return string|mixed + */ + public function pack($value); + + /** + * Unpack the value + * + * @param string|mixed $packed + * @return string + * @throws \UnexpectedValueException if the value can't be unpacked + */ + public function unpack($packed); +} diff --git a/src/Packer/SerializePacker.php b/src/Packer/SerializePacker.php new file mode 100644 index 0000000..9d9e702 --- /dev/null +++ b/src/Packer/SerializePacker.php @@ -0,0 +1,46 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Packer; + +use Desarrolla2\Cache\Packer\PackerInterface; + +/** + * Pack value through serialization + */ +class SerializePacker implements PackerInterface +{ + /** + * Pack the value + * + * @param mixed $value + * @return string + */ + public function pack($value) + { + return serialize($value); + } + + /** + * Unpack the value + * + * @param string $packed + * @return string + * @throws \UnexpectedValueException if he value can't be unpacked + */ + public function unpack($packed) + { + return unserialize($packed); + } +} diff --git a/src/Adapter/Predis.php b/src/Predis.php similarity index 100% rename from src/Adapter/Predis.php rename to src/Predis.php From 41d1a9df1f32077d6e093e8ed97a384e0b3d019e Mon Sep 17 00:00:00 2001 From: Andrii Cherytsya Date: Thu, 6 Jul 2017 15:17:31 +0300 Subject: [PATCH 23/51] Tests are runned --- src/AbstractCache.php | 2 +- src/Apcu.php | 13 ++++------ src/File.php | 17 +++++++----- src/Memcache.php | 13 +++++++--- src/Memcached.php | 12 ++++++--- src/Memory.php | 10 +++++--- src/Mongo.php | 6 +++-- src/Mysqli.php | 11 +++++--- src/NotCache.php | 10 +++++--- src/Predis.php | 9 ++++--- tests/Adapter/AbstractCacheTest.php | 2 +- tests/Adapter/ApcuCacheTest.php | 9 +++---- tests/Adapter/FileTest.php | 7 +++-- tests/Adapter/MemcacheTest.php | 10 ++++---- tests/Adapter/MemcachedTest.php | 10 ++++---- tests/Adapter/MemoryTest.php | 7 +++-- tests/Adapter/MongoTest.php | 9 +++---- tests/Adapter/MysqliTest.php | 22 ++++++++-------- tests/Adapter/NotCacheTest.php | 9 +++---- tests/Adapter/PredisTest.php | 10 +++----- tests/CacheTest.php | 40 ----------------------------- 21 files changed, 106 insertions(+), 132 deletions(-) delete mode 100644 tests/CacheTest.php diff --git a/src/AbstractCache.php b/src/AbstractCache.php index d394241..e7bbd91 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -57,7 +57,7 @@ public function setOption($key, $value) /** * {@inheritdoc} */ - protected function getOption($key) + public function getOption($key) { $method = "get" . str_replace('-', '', $key) . "Option"; diff --git a/src/Apcu.php b/src/Apcu.php index 7b717d0..7d68783 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -13,10 +13,8 @@ namespace Desarrolla2\Cache; -use Desarrolla2\Cache\AbstractCache; use Desarrolla2\Cache\Exception\CacheException; use Desarrolla2\Cache\Exception\InvalidArgumentException; -use Desarrolla2\Cache\PackTtlTrait; /** * Apcu @@ -24,7 +22,6 @@ class Apcu extends AbstractCache { use PackTtlTrait; - /** * Set the `pack-ttl` setting; Include TTL in the packed data. @@ -58,9 +55,9 @@ public function delete($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $default = null) { - return $this->getValueFromCache($key); + return $this->getValueFromCache($key, $default); } /** @@ -109,19 +106,19 @@ public function set($key, $value, $ttl = null) * @param string $key * @return mixed|null */ - protected function getValueFromCache($key) + protected function getValueFromCache($key, $default = null) { $packed = apcu_fetch($this->getKey($key), $success); if (!$success) { - return null; + return $default; } try { $value = $this->unpack($packed); } catch (UnexpectedValueException $e) { $this->delete($key); - return null; + return $default; } return $data['value']; diff --git a/src/File.php b/src/File.php index 68c82a9..d14bc52 100644 --- a/src/File.php +++ b/src/File.php @@ -11,15 +11,18 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; /** * File */ -class File extends AbstractAdapter +class File extends AbstractCache { + use PackTtlTrait; + const CACHE_FILE_PREFIX = '__'; const CACHE_FILE_SUBFIX = '.php.cache'; @@ -58,9 +61,9 @@ public function del($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $default = null) { - return $this->getValueFromCache($key); + return $this->getValueFromCache($key, $default); } /** @@ -142,17 +145,17 @@ protected function getFileName($key) self::CACHE_FILE_SUBFIX; } - protected function getValueFromCache($key) + protected function getValueFromCache($key, $default = null) { $path = $this->getFileName($key); if (!file_exists($path)) { - return; + return $default; } $data = $this->unPack(file_get_contents($path)); if (!$data || !$this->validateDataFromCache($data) || $this->ttlHasExpired($data['ttl'])) { - return; + return $default; } return $data['value']; diff --git a/src/Memcache.php b/src/Memcache.php index 17df457..f13bd54 100644 --- a/src/Memcache.php +++ b/src/Memcache.php @@ -11,15 +11,20 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; use Memcache as BaseMemcache; /** * Memcache */ -class Memcache extends AbstractAdapter +class Memcache extends AbstractCache { + use PackTtlTrait; + /** * * @var BaseMemcache @@ -51,11 +56,11 @@ public function del($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $default = null) { $data = $this->server->get($this->getKey($key)); if (!$data) { - return; + return $default; } return $this->unPack($data); diff --git a/src/Memcached.php b/src/Memcached.php index e5fd1c0..ffa35f5 100644 --- a/src/Memcached.php +++ b/src/Memcached.php @@ -11,15 +11,19 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; use Memcached as BaseMemcached; /** * Memcached */ -class Memcached extends AbstractAdapter +class Memcached extends AbstractCache { + use PackTtlTrait; /** * @var BaseMemcached */ @@ -50,11 +54,11 @@ public function del($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $default = null) { $data = $this->server->get($this->getKey($key)); if (!$data) { - return; + return $default; } return $this->unPack($data); diff --git a/src/Memory.php b/src/Memory.php index 176f1d6..33cac40 100644 --- a/src/Memory.php +++ b/src/Memory.php @@ -11,13 +11,17 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; /** * Memory */ -class Memory extends AbstractAdapter +class Memory extends AbstractCache { + use PackTtlTrait; /** * @var int */ @@ -39,7 +43,7 @@ public function del($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $default = null) { if ($this->has($key)) { $tKey = $this->getKey($key); diff --git a/src/Mongo.php b/src/Mongo.php index 6ee96b3..2261b9c 100644 --- a/src/Mongo.php +++ b/src/Mongo.php @@ -11,15 +11,17 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; /** * Mongo */ -class Mongo extends AbstractAdapter implements AdapterInterface +class Mongo extends AbstractCache { + use PackTtlTrait; /** * @var MongoCollection|MongoDB\Collection */ diff --git a/src/Mysqli.php b/src/Mysqli.php index 64ebbcc..ab99c4e 100644 --- a/src/Mysqli.php +++ b/src/Mysqli.php @@ -11,15 +11,18 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; use mysqli as Server; /** * Mysqli */ -class Mysqli extends AbstractAdapter implements AdapterInterface +class Mysqli extends AbstractCache { + use PackTtlTrait; /** * @var \mysqli */ @@ -61,7 +64,7 @@ public function delete($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $default = null) { $res = $this->fetchObject( sprintf( @@ -75,7 +78,7 @@ public function get($key) return $this->unPack($res->v); } - return false; + return $default; } /** diff --git a/src/NotCache.php b/src/NotCache.php index ff7c5b7..e595b00 100644 --- a/src/NotCache.php +++ b/src/NotCache.php @@ -11,13 +11,17 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; /** * NotCache */ -class NotCache extends AbstractAdapter +class NotCache extends AbstractCache { + use PackTtlTrait; /** * Delete a value from the cache * @@ -30,7 +34,7 @@ public function del($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $dafault = null) { return false; } diff --git a/src/Predis.php b/src/Predis.php index 7642e15..241a8ae 100644 --- a/src/Predis.php +++ b/src/Predis.php @@ -11,15 +11,18 @@ * @author Daniel González */ -namespace Desarrolla2\Cache\Adapter; +namespace Desarrolla2\Cache; +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; use Predis\Client; /** * Predis */ -class Predis extends AbstractAdapter +class Predis extends AbstractCache { + use PackTtlTrait; /** * @var Client */ @@ -60,7 +63,7 @@ public function del($key) /** * {@inheritdoc} */ - public function get($key) + public function get($key, $default = null) { return $this->unPack($this->predis->get($key)); } diff --git a/tests/Adapter/AbstractCacheTest.php b/tests/Adapter/AbstractCacheTest.php index 9230422..a6de775 100644 --- a/tests/Adapter/AbstractCacheTest.php +++ b/tests/Adapter/AbstractCacheTest.php @@ -11,7 +11,7 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; /** * AbstractCacheTest diff --git a/tests/Adapter/ApcuCacheTest.php b/tests/Adapter/ApcuCacheTest.php index 8efd1c5..ed8f797 100644 --- a/tests/Adapter/ApcuCacheTest.php +++ b/tests/Adapter/ApcuCacheTest.php @@ -11,10 +11,9 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Apcu; +use Desarrolla2\Cache\Apcu as ApcuCache; /** * ApcuCacheTest @@ -33,8 +32,8 @@ public function setUp() 'You need to enable apc.enable_cli' ); } - - $this->cache = new Cache(new Apcu()); + + $this->cache = new ApcuCache(); } /** diff --git a/tests/Adapter/FileTest.php b/tests/Adapter/FileTest.php index 3ff36fc..0046fa7 100644 --- a/tests/Adapter/FileTest.php +++ b/tests/Adapter/FileTest.php @@ -11,10 +11,9 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\File; +use Desarrolla2\Cache\File as FileCache; /** * FileTest @@ -24,7 +23,7 @@ class FileTest extends AbstractCacheTest public function setUp() { parent::setup(); - $this->cache = new Cache(new File($this->config['file']['dir'])); + $this->cache = new FileCache($this->config['file']['dir']); } /** diff --git a/tests/Adapter/MemcacheTest.php b/tests/Adapter/MemcacheTest.php index 4b2854b..baf2d94 100644 --- a/tests/Adapter/MemcacheTest.php +++ b/tests/Adapter/MemcacheTest.php @@ -11,10 +11,10 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Memcache; +use Desarrolla2\Cache\Memcache as MemcacheCache; +use Memcache as BaseMemcache; /** * MemcacheTest @@ -30,8 +30,8 @@ public function setUp() ); } - $adapter = new Memcache(); - $this->cache = new Cache($adapter); + $adapter = new BaseMemcache(); + $this->cache = new MemcacheCache($adapter); } /** diff --git a/tests/Adapter/MemcachedTest.php b/tests/Adapter/MemcachedTest.php index 7ca009a..899a9f9 100644 --- a/tests/Adapter/MemcachedTest.php +++ b/tests/Adapter/MemcachedTest.php @@ -11,10 +11,10 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Memcached; +use Desarrolla2\Cache\Memcached as MemcachedCache; +use Memcached as BaseMemcached; /** * MemcachedTest @@ -30,8 +30,8 @@ public function setUp() ); } - $adapter = new Memcached(); - $this->cache = new Cache($adapter); + $adapter = new BaseMemcached(); + $this->cache = new MemcachedCache($adapter); } /** diff --git a/tests/Adapter/MemoryTest.php b/tests/Adapter/MemoryTest.php index b6e7023..6adcd7c 100644 --- a/tests/Adapter/MemoryTest.php +++ b/tests/Adapter/MemoryTest.php @@ -11,10 +11,9 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Memory; +use Desarrolla2\Cache\Memory as MemoryCache; /** * MemoryTest @@ -23,7 +22,7 @@ class MemoryTest extends AbstractCacheTest { public function setUp() { - $this->cache = new Cache(new Memory()); + $this->cache = new MemoryCache(); } /** diff --git a/tests/Adapter/MongoTest.php b/tests/Adapter/MongoTest.php index 75eccb1..58e3cc1 100644 --- a/tests/Adapter/MongoTest.php +++ b/tests/Adapter/MongoTest.php @@ -11,10 +11,9 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Mongo; +use Desarrolla2\Cache\Mongo as MongoCache; /** * MongoTest @@ -33,9 +32,7 @@ public function setUp() $client = new \MongoClient($this->config['mongo']['dsn']); $database = $client->selectDB($this->config['mongo']['database']); - $this->cache = new Cache( - new Mongo($database) - ); + $this->cache =MongoCache($database); } /** diff --git a/tests/Adapter/MysqliTest.php b/tests/Adapter/MysqliTest.php index 8f11c70..40163ed 100644 --- a/tests/Adapter/MysqliTest.php +++ b/tests/Adapter/MysqliTest.php @@ -11,10 +11,9 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Mysqli; +use Desarrolla2\Cache\Mysqli as MysqliCache; /** * MysqliTest @@ -30,16 +29,15 @@ public function setUp() ); } - $this->cache = new Cache( - new Mysqli( - new \mysqli( - $this->config['mysql']['host'], - $this->config['mysql']['user'], - $this->config['mysql']['password'], - $this->config['mysql']['database'], - $this->config['mysql']['port'] - ) + $this->cache = new MysqliCache( + new \mysqli( + $this->config['mysql']['host'], + $this->config['mysql']['user'], + $this->config['mysql']['password'], + $this->config['mysql']['database'], + $this->config['mysql']['port'] ) + ); } diff --git a/tests/Adapter/NotCacheTest.php b/tests/Adapter/NotCacheTest.php index a114957..17f147b 100644 --- a/tests/Adapter/NotCacheTest.php +++ b/tests/Adapter/NotCacheTest.php @@ -11,15 +11,14 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\NotCache; +use Desarrolla2\Cache\NotCache as NotCache; /** * NotCacheTest */ -class NotCacheTest extends \PHPUnit_Framework_TestCase +class NoCacheTest extends \PHPUnit_Framework_TestCase { /** * @var \Desarrolla2\Cache\Cache @@ -28,7 +27,7 @@ class NotCacheTest extends \PHPUnit_Framework_TestCase public function setUp() { - $this->cache = new Cache(new NotCache()); + $this->cache = new NotCache(); } /** diff --git a/tests/Adapter/PredisTest.php b/tests/Adapter/PredisTest.php index be9f3f5..39e18df 100644 --- a/tests/Adapter/PredisTest.php +++ b/tests/Adapter/PredisTest.php @@ -11,16 +11,16 @@ * @author Daniel González */ -namespace Desarrolla2\Test\Cache\Adapter; +namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Predis; +use Desarrolla2\Cache\Predis as PredisCache; /** * PredisTest */ class PredisTest extends AbstractCacheTest { + public function setUp() { parent::setup(); @@ -29,9 +29,7 @@ public function setUp() 'The predis library is not available.' ); } - $this->cache = new Cache( - new Predis() - ); + $this->cache = new PredisCache(); } diff --git a/tests/CacheTest.php b/tests/CacheTest.php deleted file mode 100644 index f679030..0000000 --- a/tests/CacheTest.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ - -namespace Desarrolla2\Test\Cache; - -use Desarrolla2\Cache\Cache; - -/** - * CacheTest - */ -class CacheTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Desarrolla2\Cache\Cache - */ - protected $cache; - - public function setUp() - { - $this->cache = new Cache(); - } - - /** - * @expectedException \Desarrolla2\Cache\Exception\AdapterNotSetException - */ - public function testGetAdapterThrowsException() - { - $this->cache->getAdapter(); - } -} From b78eb206a47856de1bb301de5f473b240a28dad4 Mon Sep 17 00:00:00 2001 From: Andrii Cherytsya Date: Fri, 7 Jul 2017 15:07:10 +0300 Subject: [PATCH 24/51] Problem with trowing Exeptions --- src/AbstractCache.php | 1 + src/Apcu.php | 7 ++++--- tests/Adapter/ApcuCacheTest.php | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/AbstractCache.php b/src/AbstractCache.php index e7bbd91..227dc9b 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -52,6 +52,7 @@ public function setOption($key, $value) } $this->$method($value); + return true; } /** diff --git a/src/Apcu.php b/src/Apcu.php index 7d68783..03b37d4 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -113,14 +113,15 @@ protected function getValueFromCache($key, $default = null) if (!$success) { return $default; } - + var_dump($packed); try { $value = $this->unpack($packed); } catch (UnexpectedValueException $e) { + var_dump('cached exeption'); $this->delete($key); return $default; } - - return $data['value']; + + return $value; } } diff --git a/tests/Adapter/ApcuCacheTest.php b/tests/Adapter/ApcuCacheTest.php index ed8f797..7c39653 100644 --- a/tests/Adapter/ApcuCacheTest.php +++ b/tests/Adapter/ApcuCacheTest.php @@ -42,8 +42,8 @@ public function setUp() public function dataProviderForOptionsException() { return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], - ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], ]; } } From 95ea7178cab270100b69d5fde2f6f33c390f482f Mon Sep 17 00:00:00 2001 From: Andrii Cherytsya Date: Fri, 14 Jul 2017 15:21:10 +0300 Subject: [PATCH 25/51] Tests are ready. Expired ttl test give Ecxeption --- src/Apcu.php | 3 +-- src/File.php | 21 +++++++++------------ src/Memcache.php | 4 ++-- src/Memcached.php | 4 ++-- src/Memory.php | 18 ++++++------------ src/Mongo.php | 9 +++++---- src/Mysqli.php | 10 ++++++---- src/NotCache.php | 2 +- src/Predis.php | 4 ++-- tests/Adapter/AbstractCacheTest.php | 3 +++ tests/Adapter/FileTest.php | 9 +++++++++ tests/Adapter/MemcacheTest.php | 5 +++-- tests/Adapter/MemcachedTest.php | 5 +++-- tests/Adapter/MemoryTest.php | 6 +++--- tests/Adapter/MongoTest.php | 6 +++--- tests/Adapter/MysqliTest.php | 16 +++++++--------- tests/Adapter/NotCacheTest.php | 4 ++-- tests/Adapter/PredisTest.php | 4 ++-- 18 files changed, 69 insertions(+), 64 deletions(-) diff --git a/src/Apcu.php b/src/Apcu.php index 03b37d4..f2e1c4b 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -113,11 +113,10 @@ protected function getValueFromCache($key, $default = null) if (!$success) { return $default; } - var_dump($packed); + try { $value = $this->unpack($packed); } catch (UnexpectedValueException $e) { - var_dump('cached exeption'); $this->delete($key); return $default; } diff --git a/src/File.php b/src/File.php index d14bc52..68c7bab 100644 --- a/src/File.php +++ b/src/File.php @@ -51,7 +51,7 @@ public function __construct($cacheDir = null) /** * {@inheritdoc} */ - public function del($key) + public function delete($key) { $tKey = $this->getKey($key); $cacheFile = $this->getFileName($tKey); @@ -83,12 +83,9 @@ public function set($key, $value, $ttl = null) if (!$ttl) { $ttl = $this->ttl; } - $item = $this->pack( - [ - 'value' => $value, - 'ttl' => (int) $ttl + time(), - ] - ); + + $item = $this->pack($value, $ttl); + if (!file_put_contents($cacheFile, $item)) { throw new CacheException(sprintf('Error saving data with the key "%s" to the cache file.', $key)); } @@ -152,13 +149,13 @@ protected function getValueFromCache($key, $default = null) if (!file_exists($path)) { return $default; } - - $data = $this->unPack(file_get_contents($path)); - if (!$data || !$this->validateDataFromCache($data) || $this->ttlHasExpired($data['ttl'])) { + try { + $data = $this->unPack(file_get_contents($path)); + } catch( Exception $e ){ return $default; } - - return $data['value']; + + return $data; } protected function validateDataFromCache($data) diff --git a/src/Memcache.php b/src/Memcache.php index f13bd54..3d44efa 100644 --- a/src/Memcache.php +++ b/src/Memcache.php @@ -48,7 +48,7 @@ public function __construct(BaseMemcache $server = null) /** * {@inheritdoc} */ - public function del($key) + public function delete($key) { $this->server->delete($this->getKey($key)); } @@ -87,6 +87,6 @@ public function set($key, $value, $ttl = null) if (!$ttl) { $ttl = $this->ttl; } - $this->server->set($this->getKey($key), $this->pack($value), false, time() + $ttl); + $this->server->set($this->getKey($key), $this->pack($value, $ttl), false, time() + $ttl); } } diff --git a/src/Memcached.php b/src/Memcached.php index ffa35f5..bef996d 100644 --- a/src/Memcached.php +++ b/src/Memcached.php @@ -46,7 +46,7 @@ public function __construct(BaseMemcached $server = null) /** * {@inheritdoc} */ - public function del($key) + public function delete($key) { $this->server->delete($this->getKey($key)); } @@ -85,6 +85,6 @@ public function set($key, $value, $ttl = null) if (!$ttl) { $ttl = $this->ttl; } - $this->server->set($this->getKey($key), $this->pack($value), time() + $ttl); + $this->server->set($this->getKey($key), $this->pack($value, $ttl), false, time() + $ttl); } } diff --git a/src/Memory.php b/src/Memory.php index 33cac40..d8d1b1d 100644 --- a/src/Memory.php +++ b/src/Memory.php @@ -35,7 +35,7 @@ class Memory extends AbstractCache /** * {@inheritdoc} */ - public function del($key) + public function delete($key) { unset($this->cache[$this->getKey($key)]); } @@ -48,7 +48,7 @@ public function get($key, $default = null) if ($this->has($key)) { $tKey = $this->getKey($key); - return $this->unPack($this->cache[$tKey]['value']); + return $this->unPack($this->cache[$tKey]); } return false; @@ -61,13 +61,10 @@ public function has($key) { $tKey = $this->getKey($key); if (isset($this->cache[$tKey])) { - $data = $this->cache[$tKey]; - if (time() < $data['ttl']) { - return true; - } - $this->del($key); + return true; } - + + $this->delete($key); return false; } @@ -82,10 +79,7 @@ public function set($key, $value, $ttl = null) if (!$ttl) { $ttl = $this->ttl; } - $this->cache[$this->getKey($key)] = [ - 'value' => serialize($value), - 'ttl' => (int) $ttl + time(), - ]; + $this->cache[$this->getKey($key)] = $this->pack($value, $ttl); } /** diff --git a/src/Mongo.php b/src/Mongo.php index 2261b9c..adeb2fd 100644 --- a/src/Mongo.php +++ b/src/Mongo.php @@ -32,6 +32,7 @@ class Mongo extends AbstractCache */ public function __construct($backend = null) { + $this->packTtl = false; if (!isset($backend)) { $client = class_exist('MongoCollection') ? new \MongoClient() : new \MongoDB\Client(); $backend = $client->selectDatabase('cache'); @@ -51,7 +52,7 @@ public function __construct($backend = null) /** * {@inheritdoc} */ - public function del($key) + public function delete($key) { $tKey = $this->getKey($key); $this->collection->remove(array('_id' => $tKey)); @@ -60,7 +61,7 @@ public function del($key) /** * {@inheritdoc } */ - public function get($key) + public function get($key, $default = null) { $tKey = $this->getKey($key); $tNow = $this->getTtl(); @@ -69,7 +70,7 @@ public function get($key) return $this->unPack($data['value']); } - return false; + return $default; } /** @@ -88,10 +89,10 @@ public function has($key) public function set($key, $value, $ttl = null) { $tKey = $this->getKey($key); - $tValue = $this->pack($value); if (!$ttl) { $ttl = $this->ttl; } + $tValue = $this->pack($value, $ttl); $item = array( '_id' => $tKey, 'value' => $tValue, diff --git a/src/Mysqli.php b/src/Mysqli.php index ab99c4e..e134e9f 100644 --- a/src/Mysqli.php +++ b/src/Mysqli.php @@ -22,7 +22,9 @@ */ class Mysqli extends AbstractCache { - use PackTtlTrait; + use PackTtlTrait { + pack as protected traitpack; + } /** * @var \mysqli */ @@ -31,7 +33,7 @@ class Mysqli extends AbstractCache /** * @var string */ - protected $table = 'cache'; + protected $table = 'cache'; /** * @param Server|null $server @@ -109,7 +111,7 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $this->del($key); + $this->delete($key); if (!($ttl)) { $ttl = $this->ttl; } @@ -135,7 +137,7 @@ protected function getKey($key) protected function pack($value) { - return $this->escape(parent::pack($value)); + return $this->escape($this->traitpack($value, false)); } /** diff --git a/src/NotCache.php b/src/NotCache.php index e595b00..abc5449 100644 --- a/src/NotCache.php +++ b/src/NotCache.php @@ -27,7 +27,7 @@ class NotCache extends AbstractCache * * @param string $key */ - public function del($key) + public function delete($key) { } diff --git a/src/Predis.php b/src/Predis.php index 241a8ae..46488c3 100644 --- a/src/Predis.php +++ b/src/Predis.php @@ -52,7 +52,7 @@ public function __destruct() /** * {@inheritdoc} */ - public function del($key) + public function delete($key) { $cmd = $this->predis->createCommand('DEL'); $cmd->setArguments([$key]); @@ -84,10 +84,10 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $this->predis->set($key, $this->pack($value)); if (!$ttl) { $ttl = $this->ttl; } + $this->predis->set($key, $this->pack($value, $ttl)); $cmd = $this->predis->createCommand('EXPIRE'); $cmd->setArguments([$key, $ttl]); $this->predis->executeCommand($cmd); diff --git a/tests/Adapter/AbstractCacheTest.php b/tests/Adapter/AbstractCacheTest.php index a6de775..71b4504 100644 --- a/tests/Adapter/AbstractCacheTest.php +++ b/tests/Adapter/AbstractCacheTest.php @@ -135,6 +135,9 @@ public function testSetOptionException($key, $value, $expectedException) public function testHasWithTtlExpired() { + $this->markTestSkipped( + 'Ttl trait trowing Exception. Test are fall.' + ); $this->cache->set('key1', 'value1', 1); sleep(2); $this->assertFalse($this->cache->has('key1')); diff --git a/tests/Adapter/FileTest.php b/tests/Adapter/FileTest.php index 0046fa7..b183c77 100644 --- a/tests/Adapter/FileTest.php +++ b/tests/Adapter/FileTest.php @@ -36,4 +36,13 @@ public function dataProviderForOptionsException() array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), ); } + + /** + * Remove all temp dir with cache files + */ + public function tearDown() + { + array_map('unlink', glob($this->config['file']['dir']."/*.*")); + rmdir($this->config['file']['dir']); + } } diff --git a/tests/Adapter/MemcacheTest.php b/tests/Adapter/MemcacheTest.php index baf2d94..a9193ac 100644 --- a/tests/Adapter/MemcacheTest.php +++ b/tests/Adapter/MemcacheTest.php @@ -31,6 +31,7 @@ public function setUp() } $adapter = new BaseMemcache(); + $adapter->addServer($this->config['memcache']['host'], $this->config['memcache']['port']); $this->cache = new MemcacheCache($adapter); } @@ -40,8 +41,8 @@ public function setUp() public function dataProviderForOptionsException() { return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], - ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], ]; } } diff --git a/tests/Adapter/MemcachedTest.php b/tests/Adapter/MemcachedTest.php index 899a9f9..adba858 100644 --- a/tests/Adapter/MemcachedTest.php +++ b/tests/Adapter/MemcachedTest.php @@ -31,6 +31,7 @@ public function setUp() } $adapter = new BaseMemcached(); + $adapter->addServer($this->config['memcached']['host'], $this->config['memcached']['port']); $this->cache = new MemcachedCache($adapter); } @@ -40,8 +41,8 @@ public function setUp() public function dataProviderForOptionsException() { return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], - ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], ]; } } diff --git a/tests/Adapter/MemoryTest.php b/tests/Adapter/MemoryTest.php index 6adcd7c..af8784e 100644 --- a/tests/Adapter/MemoryTest.php +++ b/tests/Adapter/MemoryTest.php @@ -41,9 +41,9 @@ public function dataProviderForOptions() */ public function dataProviderForOptionsException() { - return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], - ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + return [ + ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], ]; } diff --git a/tests/Adapter/MongoTest.php b/tests/Adapter/MongoTest.php index 58e3cc1..6c429a0 100644 --- a/tests/Adapter/MongoTest.php +++ b/tests/Adapter/MongoTest.php @@ -32,7 +32,7 @@ public function setUp() $client = new \MongoClient($this->config['mongo']['dsn']); $database = $client->selectDB($this->config['mongo']['database']); - $this->cache =MongoCache($database); + $this->cache = new MongoCache($database); } /** @@ -51,8 +51,8 @@ public function dataProviderForOptions() public function dataProviderForOptionsException() { return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], - ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], ]; } } diff --git a/tests/Adapter/MysqliTest.php b/tests/Adapter/MysqliTest.php index 40163ed..f9dab63 100644 --- a/tests/Adapter/MysqliTest.php +++ b/tests/Adapter/MysqliTest.php @@ -28,17 +28,15 @@ public function setUp() 'The mysqli extension is not available.' ); } - - $this->cache = new MysqliCache( - new \mysqli( + $mysqli = new \mysqli( $this->config['mysql']['host'], $this->config['mysql']['user'], $this->config['mysql']['password'], $this->config['mysql']['database'], $this->config['mysql']['port'] - ) - - ); + ); + $mysqli->query('CREATE TABLE IF NOT EXISTS `'.$this->config['mysql']['table'].'`( `k` VARCHAR(255), `v` TEXT, `t` BIGINT );'); + $this->cache = new MysqliCache($mysqli); } /** @@ -47,7 +45,7 @@ public function setUp() public function dataProviderForOptions() { return [ - ['ttl', 100], + ['ttl', 100] ]; } @@ -57,8 +55,8 @@ public function dataProviderForOptions() public function dataProviderForOptionsException() { return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], - ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], ]; } } diff --git a/tests/Adapter/NotCacheTest.php b/tests/Adapter/NotCacheTest.php index 17f147b..a3a541b 100644 --- a/tests/Adapter/NotCacheTest.php +++ b/tests/Adapter/NotCacheTest.php @@ -55,7 +55,7 @@ public function testHas() public function testGet() { $this->cache->set('key', 'value'); - $this->assertFalse($this->cache->get('key')); + $this->assertFalse($this->cache->get('key', false)); } /** @@ -63,7 +63,7 @@ public function testGet() */ public function testSet() { - $this->assertNull($this->cache->set('key', 'value')); + $this->assertFalse($this->cache->set('key', 'value')); } /** diff --git a/tests/Adapter/PredisTest.php b/tests/Adapter/PredisTest.php index 39e18df..95bcf60 100644 --- a/tests/Adapter/PredisTest.php +++ b/tests/Adapter/PredisTest.php @@ -49,8 +49,8 @@ public function dataProviderForOptions() public function dataProviderForOptionsException() { return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'], - ['file', 100, '\Desarrolla2\Cache\Exception\CacheException'], + ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], ]; } } From e7e970f8c7c67c857a30dd76225d3a712673c257 Mon Sep 17 00:00:00 2001 From: Andrii Cherytsya Date: Sun, 16 Jul 2017 15:43:40 +0300 Subject: [PATCH 26/51] Added CacheExpiriedException --- src/AbstractCache.php | 1 + src/Apcu.php | 2 +- src/Exception/CacheExpiredException.php | 24 ++++++++++++++++++++++++ src/PackTtlTrait.php | 3 ++- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 src/Exception/CacheExpiredException.php diff --git a/src/AbstractCache.php b/src/AbstractCache.php index 227dc9b..b38e63a 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -18,6 +18,7 @@ use Desarrolla2\Cache\Packer\SerializePacker; use Desarrolla2\Cache\Exception\CacheException; use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\Exception\CacheExpiredException; /** * AbstractAdapter diff --git a/src/Apcu.php b/src/Apcu.php index f2e1c4b..5908748 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -116,7 +116,7 @@ protected function getValueFromCache($key, $default = null) try { $value = $this->unpack($packed); - } catch (UnexpectedValueException $e) { + } catch (CacheExpiredException $e) { $this->delete($key); return $default; } diff --git a/src/Exception/CacheExpiredException.php b/src/Exception/CacheExpiredException.php new file mode 100644 index 0000000..bb856d0 --- /dev/null +++ b/src/Exception/CacheExpiredException.php @@ -0,0 +1,24 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Exception; + +use Psr\SimpleCache\CacheException as PsrCacheException; + +/** + * Exception for unexpected values when reading from cache. + */ +class CacheExpiredException extends \RuntimeException implements PsrCacheException +{ +} diff --git a/src/PackTtlTrait.php b/src/PackTtlTrait.php index c60f530..988626e 100644 --- a/src/PackTtlTrait.php +++ b/src/PackTtlTrait.php @@ -4,6 +4,7 @@ use Desarrolla2\Cache\Packer\PackerInterface; use Desarrolla2\Cache\Exception\UnexpectedValueException; +use Desarrolla2\Cache\Exception\CacheExpiredException; /** * Methods for including the TTL in the @@ -53,7 +54,7 @@ protected function unpack($packed) } if ($this->packTtl && $this->ttlHasExpired($data['ttl'])) { - throw new UnexpectedValueException("ttl has expired"); + throw new CacheExpiredException("ttl has expired"); } return $this->packTtl ? $data['value'] : $data; From a0652aa9112b7de64587341f709058b83a0f9652 Mon Sep 17 00:00:00 2001 From: Andrii Cherytsya Date: Sun, 16 Jul 2017 17:22:46 +0300 Subject: [PATCH 27/51] Fixed get function to return default value --- src/Apcu.php | 5 +++++ src/Exception/CacheExpiredException.php | 3 ++- src/File.php | 6 +++++- src/Memcache.php | 2 ++ src/Memcached.php | 2 ++ src/Memory.php | 9 +++++++++ src/Mongo.php | 2 ++ src/Mysqli.php | 2 ++ src/NotCache.php | 2 ++ src/Predis.php | 13 ++++++++++++- tests/Adapter/AbstractCacheTest.php | 12 +++++++++--- tests/Adapter/MemcacheTest.php | 6 ++++++ 12 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/Apcu.php b/src/Apcu.php index 5908748..89e2ef5 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -14,6 +14,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; /** @@ -116,6 +118,9 @@ protected function getValueFromCache($key, $default = null) try { $value = $this->unpack($packed); + } catch (UnexpectedValueException $e) { + $this->delete($key); + return $default; } catch (CacheExpiredException $e) { $this->delete($key); return $default; diff --git a/src/Exception/CacheExpiredException.php b/src/Exception/CacheExpiredException.php index bb856d0..eb55c2a 100644 --- a/src/Exception/CacheExpiredException.php +++ b/src/Exception/CacheExpiredException.php @@ -19,6 +19,7 @@ /** * Exception for unexpected values when reading from cache. */ -class CacheExpiredException extends \RuntimeException implements PsrCacheException +class CacheExpiredException extends \Exception implements PsrCacheException { + var $code = E_USER_NOTICE; } diff --git a/src/File.php b/src/File.php index 68c7bab..1e1c88c 100644 --- a/src/File.php +++ b/src/File.php @@ -14,6 +14,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; /** @@ -151,7 +153,9 @@ protected function getValueFromCache($key, $default = null) } try { $data = $this->unPack(file_get_contents($path)); - } catch( Exception $e ){ + } catch( UnexpectedValueException $e ){ + return $default; + } catch( CacheExpiredException $e ){ return $default; } diff --git a/src/Memcache.php b/src/Memcache.php index 3d44efa..23e93b5 100644 --- a/src/Memcache.php +++ b/src/Memcache.php @@ -15,6 +15,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; use Memcache as BaseMemcache; diff --git a/src/Memcached.php b/src/Memcached.php index bef996d..05a9771 100644 --- a/src/Memcached.php +++ b/src/Memcached.php @@ -15,6 +15,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; use Memcached as BaseMemcached; diff --git a/src/Memory.php b/src/Memory.php index d8d1b1d..5779bee 100644 --- a/src/Memory.php +++ b/src/Memory.php @@ -14,6 +14,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; /** @@ -61,6 +63,13 @@ public function has($key) { $tKey = $this->getKey($key); if (isset($this->cache[$tKey])) { + try { + $this->unPack($this->cache[$tKey]); + } catch( UnexpectedValueException $e ){ + return false; + } catch( CacheExpiredException $e ){ + return false; + } return true; } diff --git a/src/Mongo.php b/src/Mongo.php index adeb2fd..ff88da0 100644 --- a/src/Mongo.php +++ b/src/Mongo.php @@ -14,6 +14,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; /** diff --git a/src/Mysqli.php b/src/Mysqli.php index e134e9f..e9a41d5 100644 --- a/src/Mysqli.php +++ b/src/Mysqli.php @@ -14,6 +14,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; use mysqli as Server; diff --git a/src/NotCache.php b/src/NotCache.php index abc5449..6d22506 100644 --- a/src/NotCache.php +++ b/src/NotCache.php @@ -14,6 +14,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; /** diff --git a/src/Predis.php b/src/Predis.php index 46488c3..b753f8f 100644 --- a/src/Predis.php +++ b/src/Predis.php @@ -14,6 +14,8 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; use Predis\Client; @@ -65,7 +67,16 @@ public function delete($key) */ public function get($key, $default = null) { - return $this->unPack($this->predis->get($key)); + + try { + $data = $this->unpack($this->predis->get($key)); + } catch( UnexpectedValueException $e ){ + return $default; + } catch( CacheExpiredException $e ){ + return $default; + } + + return $data; } /** diff --git a/tests/Adapter/AbstractCacheTest.php b/tests/Adapter/AbstractCacheTest.php index 71b4504..4e24925 100644 --- a/tests/Adapter/AbstractCacheTest.php +++ b/tests/Adapter/AbstractCacheTest.php @@ -133,13 +133,19 @@ public function testSetOptionException($key, $value, $expectedException) $this->cache->setOption($key, $value); } + public function testHasWithTtlExpired() { - $this->markTestSkipped( - 'Ttl trait trowing Exception. Test are fall.' - ); $this->cache->set('key1', 'value1', 1); sleep(2); $this->assertFalse($this->cache->has('key1')); } + + + public function testReturnDefaultValue() + { + $this->cache->set('key1', 'value1', 1); + sleep(2); + $this->assertFalse($this->cache->get('key1', false)); + } } diff --git a/tests/Adapter/MemcacheTest.php b/tests/Adapter/MemcacheTest.php index a9193ac..aec2ff0 100644 --- a/tests/Adapter/MemcacheTest.php +++ b/tests/Adapter/MemcacheTest.php @@ -32,6 +32,12 @@ public function setUp() $adapter = new BaseMemcache(); $adapter->addServer($this->config['memcache']['host'], $this->config['memcache']['port']); + if( !$adapter->getstats()) { + $this->markTestSkipped( + 'The Memcache server not started.' + ); + } + $this->cache = new MemcacheCache($adapter); } From c1bd3bcc58518757069685e765593ac09ac78728 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 7 Sep 2017 23:45:50 +0200 Subject: [PATCH 28/51] Fix running tests --- composer.json | 11 ++++++++--- phpunit.xml | 20 -------------------- phpunit.xml.dist | 4 ++-- tests/{Adapter => }/AbstractCacheTest.php | 2 +- tests/{Adapter => }/ApcuCacheTest.php | 0 tests/{Adapter => }/FileTest.php | 0 tests/{Adapter => }/MemcacheTest.php | 0 tests/{Adapter => }/MemcachedTest.php | 0 tests/{Adapter => }/MemoryTest.php | 0 tests/{Adapter => }/MongoTest.php | 0 tests/{Adapter => }/MysqliTest.php | 0 tests/{Adapter => }/NotCacheTest.php | 0 tests/{Adapter => }/PredisTest.php | 3 ++- tests/bootstrap.php | 14 -------------- 14 files changed, 13 insertions(+), 41 deletions(-) delete mode 100644 phpunit.xml rename tests/{Adapter => }/AbstractCacheTest.php (98%) rename tests/{Adapter => }/ApcuCacheTest.php (100%) rename tests/{Adapter => }/FileTest.php (100%) rename tests/{Adapter => }/MemcacheTest.php (100%) rename tests/{Adapter => }/MemcachedTest.php (100%) rename tests/{Adapter => }/MemoryTest.php (100%) rename tests/{Adapter => }/MongoTest.php (100%) rename tests/{Adapter => }/MysqliTest.php (100%) rename tests/{Adapter => }/NotCacheTest.php (100%) rename tests/{Adapter => }/PredisTest.php (97%) delete mode 100644 tests/bootstrap.php diff --git a/composer.json b/composer.json index 6ee4c57..3096835 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ ], "require": { "php": ">=5.6.0", - "psr/simple-cache": "^1.0" + "psr/simple-cache": "^1.0", + "phpunit/phpunit": "^5.0" }, "require-dev": { "predis/predis": "~1.0.0" @@ -33,8 +34,12 @@ }, "autoload": { "psr-4": { - "Desarrolla2\\Cache\\": "src/", - "Desarrolla2\\Test\\Cache\\": "test/" + "Desarrolla2\\Cache\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Desarrolla2\\Test\\Cache\\": "tests/" } } } diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 020c2f9..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - ./tests - - - - - - ./Test - - - /usr/share/pear/ - ./vendor - ./build - - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 020c2f9..38b4909 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,6 @@ - + ./tests @@ -9,7 +9,7 @@ - ./Test + ./tests /usr/share/pear/ diff --git a/tests/Adapter/AbstractCacheTest.php b/tests/AbstractCacheTest.php similarity index 98% rename from tests/Adapter/AbstractCacheTest.php rename to tests/AbstractCacheTest.php index 4e24925..e2d47b7 100644 --- a/tests/Adapter/AbstractCacheTest.php +++ b/tests/AbstractCacheTest.php @@ -30,7 +30,7 @@ abstract class AbstractCacheTest extends \PHPUnit_Framework_TestCase public function setup() { - $configurationFile = realpath(__DIR__.'/../').'/config.json'; + $configurationFile = __DIR__.'/config.json'; if (!is_file($configurationFile)) { throw new \Exception(' Configuration file not found in "'.$configurationFile.'" '); diff --git a/tests/Adapter/ApcuCacheTest.php b/tests/ApcuCacheTest.php similarity index 100% rename from tests/Adapter/ApcuCacheTest.php rename to tests/ApcuCacheTest.php diff --git a/tests/Adapter/FileTest.php b/tests/FileTest.php similarity index 100% rename from tests/Adapter/FileTest.php rename to tests/FileTest.php diff --git a/tests/Adapter/MemcacheTest.php b/tests/MemcacheTest.php similarity index 100% rename from tests/Adapter/MemcacheTest.php rename to tests/MemcacheTest.php diff --git a/tests/Adapter/MemcachedTest.php b/tests/MemcachedTest.php similarity index 100% rename from tests/Adapter/MemcachedTest.php rename to tests/MemcachedTest.php diff --git a/tests/Adapter/MemoryTest.php b/tests/MemoryTest.php similarity index 100% rename from tests/Adapter/MemoryTest.php rename to tests/MemoryTest.php diff --git a/tests/Adapter/MongoTest.php b/tests/MongoTest.php similarity index 100% rename from tests/Adapter/MongoTest.php rename to tests/MongoTest.php diff --git a/tests/Adapter/MysqliTest.php b/tests/MysqliTest.php similarity index 100% rename from tests/Adapter/MysqliTest.php rename to tests/MysqliTest.php diff --git a/tests/Adapter/NotCacheTest.php b/tests/NotCacheTest.php similarity index 100% rename from tests/Adapter/NotCacheTest.php rename to tests/NotCacheTest.php diff --git a/tests/Adapter/PredisTest.php b/tests/PredisTest.php similarity index 97% rename from tests/Adapter/PredisTest.php rename to tests/PredisTest.php index 95bcf60..0b33d9a 100644 --- a/tests/Adapter/PredisTest.php +++ b/tests/PredisTest.php @@ -14,6 +14,7 @@ namespace Desarrolla2\Test\Cache; use Desarrolla2\Cache\Predis as PredisCache; +use Predis\Client; /** * PredisTest @@ -29,8 +30,8 @@ public function setUp() 'The predis library is not available.' ); } + $this->cache = new PredisCache(); - } /** diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 6cfb6fe..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,14 +0,0 @@ - - */ - -$loader = require_once __DIR__.'/../vendor/autoload.php'; From f53e26f0555f94a5bf552ef1cda3c396238b646c Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Wed, 30 May 2018 10:53:55 +0200 Subject: [PATCH 29/51] PSR-16 WIP All cache adapters follow PSR-16 Removed Memcache (only support memcached ext) Multi key get and set for supported adapters Rely on service TTL where possible, no additional checks Added FlatFile adapter for flat files. TTL is written in differnt file. Removed the PackTtlTrait Fixed NopPacker Added PhpPacker, only to be used with files Require all libs and extensions for development --- composer.json | 17 +- src/AbstractCache.php | 129 ++++++++++---- src/AbstractFile.php | 141 +++++++++++++++ src/Apcu.php | 90 +++------- src/Exception/BadMethodCallException.php | 24 +++ src/File.php | 154 ++++------------ src/FlatFile.php | 212 +++++++++++++++++++++++ src/KeyMaker/HashKeyMaker.php | 52 ++++++ src/KeyMaker/KeyMakerInterface.php | 24 +++ src/KeyMaker/PlainKeyMaker.php | 46 +++++ src/Memcache.php | 94 ---------- src/Memcached.php | 78 ++++++--- src/Memory.php | 95 ++++++---- src/Mongo.php | 161 ++++++++++++----- src/Mysqli.php | 210 +++++++++++++--------- src/NotCache.php | 28 ++- src/PackTtlTrait.php | 85 --------- src/Packer/JsonPacker.php | 29 +++- src/Packer/NopPacker.php | 14 +- src/Packer/PackerInterface.php | 7 + src/Packer/PhpPacker.php | 67 +++++++ src/Packer/SerializePacker.php | 32 +++- src/Predis.php | 49 +++--- tests/AbstractCacheTest.php | 49 ++++-- tests/ApcuCacheTest.php | 3 + tests/FileTest.php | 15 +- tests/MemcacheTest.php | 54 ------ tests/MemcachedTest.php | 5 + tests/MemoryTest.php | 8 + tests/MongoTest.php | 8 + tests/MysqliTest.php | 42 +++-- tests/NotCacheTest.php | 6 +- tests/PhpFileTest.php | 58 +++++++ tests/PredisTest.php | 12 +- 34 files changed, 1377 insertions(+), 721 deletions(-) create mode 100644 src/AbstractFile.php create mode 100644 src/Exception/BadMethodCallException.php create mode 100644 src/FlatFile.php create mode 100644 src/KeyMaker/HashKeyMaker.php create mode 100644 src/KeyMaker/KeyMakerInterface.php create mode 100644 src/KeyMaker/PlainKeyMaker.php delete mode 100644 src/Memcache.php delete mode 100644 src/PackTtlTrait.php create mode 100644 src/Packer/PhpPacker.php delete mode 100644 tests/MemcacheTest.php create mode 100644 tests/PhpFileTest.php diff --git a/composer.json b/composer.json index 3096835..42fda1d 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,8 @@ "description": "Provides an cache interface for several adapters Apc, Apcu, File, Mongo, Memcache, Memcached, Mysql, Mongo, Redis is supported.", "keywords": [ "cache", + "simple-cache", + "psr-16", "apc", "apcu", "file", @@ -23,14 +25,17 @@ ], "require": { "php": ">=5.6.0", - "psr/simple-cache": "^1.0", - "phpunit/phpunit": "^5.0" + "psr/simple-cache": "^1.0" }, "require-dev": { - "predis/predis": "~1.0.0" - }, - "suggest": { - "predis/predis": "Predis support" + "php": ">=7.1.0", + "ext-apcu": "*", + "ext-mysqli": "*", + "ext-memcached": "*", + "phpunit/phpunit": "^7.0", + "predis/predis": "~1.0.0", + "nesbot/carbon": "^1.29", + "mongodb/mongodb": "^1.3" }, "autoload": { "psr-4": { diff --git a/src/AbstractCache.php b/src/AbstractCache.php index b38e63a..2d17e4d 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -16,9 +16,12 @@ use Desarrolla2\Cache\CacheInterface; use Desarrolla2\Cache\Packer\PackerInterface; use Desarrolla2\Cache\Packer\SerializePacker; +use Desarrolla2\Cache\KeyMaker\KeyMakerInterface; +use Desarrolla2\Cache\KeyMaker\PlainKeyMaker; use Desarrolla2\Cache\Exception\CacheException; use Desarrolla2\Cache\Exception\InvalidArgumentException; use Desarrolla2\Cache\Exception\CacheExpiredException; +use Carbon\Carbon; /** * AbstractAdapter @@ -31,16 +34,16 @@ abstract class AbstractCache implements CacheInterface protected $ttl = 3600; /** - * @var string + * @var PackerInterface */ - protected $prefix = ''; + protected $packer; /** - * @var PackerInterface + * @var KeyMakerInterface */ - protected $packer; - - + protected $keyMaker; + + /** * {@inheritdoc} */ @@ -97,23 +100,25 @@ protected function getTtlOption() } /** - * Set the key prefix + * Set the key prefix. + * @deprecated * * @param string $value */ protected function setPrefixOption($value) { - $this->prefix = (string)$value; + $this->keyMaker = new PlainKeyMaker($value); } /** * Get the key prefix - * + * @deprecated + * * @return string */ protected function getPrefixOption() { - return $this->prefix; + return $this->keyMaker()->getPrefix(); } @@ -140,10 +145,69 @@ protected function getPacker() return $this->packer; } - - + + /** + * Pack the value, optionally include the ttl + * + * @param mixed $value + * @return string|mixed $data + */ + protected function pack($value) + { + return $this->getPacker()->pack($value); + } + + /** + * Unpack the data to retrieve the value + * + * @param string|mixed $packed + * @return mixed + * @throws UnexpectedValueException + */ + protected function unpack($packed) + { + return $this->getPacker()->unpack($packed); + } + + + /** + * Set the key maker + * + * @param KeyMakerInterface $keyMaker + */ + public function setKeyMaker(KeyMakerInterface $keyMaker) + { + $this->keyMaker = $keyMaker; + } + + /** + * Get the key maker + * + * @return KeyMakerInterface + */ + protected function getKeyMaker() + { + if (!isset($this->keyMaker)) { + $this->keyMaker = new PlainKeyMaker(); + } + + return $this->keyMaker; + } + /** - * Assert that the keys are traversable + * Get the key with prefix + * + * @param string $key + * @return string + */ + protected function getKey($key) + { + return $this->getKeyMaker()->make($key); + } + + + /** + * Assert that the keys are an array or traversable * * @param iterable $subject * @param string $msg @@ -160,19 +224,6 @@ protected function assertIterable($subject, $msg) } } - - /** - * Get the key with prefix - * - * @param string $key - * @return string - */ - protected function getKey($key) - { - return sprintf('%s%s', $this->prefix, $key); - } - - /** * {@inheritdoc} */ @@ -186,7 +237,7 @@ public function delete($key) */ public function deleteMultiple($keys) { - $this->assertTraverable($keys, 'keys not iterable'); + $this->assertIterable($keys, 'keys not iterable'); $success = true; @@ -210,12 +261,12 @@ public function get($key, $default = null) */ public function getMultiple($keys, $default = null) { - $this->assertTraverable($keys, 'keys not iterable'); + $this->assertIterable($keys, 'keys not iterable'); $result = []; foreach ($keys as $key) { - $result[] = $this->get($key, $default); + $result[$key] = $this->get($key, $default); } return $result; @@ -242,15 +293,15 @@ public function set($key, $value, $ttl = null) */ public function setMultiple($values, $ttl = null) { - $this->assertTraverable($values, 'values not iterable'); - - $result = []; + $this->assertIterable($values, 'values not iterable'); + + $success = true; foreach ($values as $key => $value) { - $result[] = $this->set($key, $value, $ttl); + $success = $this->set($key, $value, $ttl) && $success; } - return $result; + return $success; } /** @@ -260,4 +311,14 @@ public function clear() { throw new CacheException('not ready yet'); } + + /** + * Get the current time + * + * @return int + */ + protected static function time() + { + return class_exists('Carbon\\Carbon') ? Carbon::now()->timestamp : time(); + } } diff --git a/src/AbstractFile.php b/src/AbstractFile.php new file mode 100644 index 0000000..45be8e2 --- /dev/null +++ b/src/AbstractFile.php @@ -0,0 +1,141 @@ + + */ + +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\CacheExpiredException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\Packer\PhpPacker; + +/** + * FlatFile. + */ +abstract class AbstractFile extends AbstractCache +{ + /** + * @var string + */ + protected $cacheDir; + + /** + * @var string + */ + protected $filePrefix = '__'; + + /** + * @var string|null + */ + protected $fileSuffix; + + /** + * @param string $cacheDir + * @throws CacheException + */ + public function __construct($cacheDir = null) + { + if (!$cacheDir) { + $cacheDir = realcacheFile(sys_get_temp_dir()) . '/cache'; + } + + $this->cacheDir = (string)$cacheDir; + $this->createCacheDirectory($cacheDir); + } + + /** + * Set the file prefix + * + * @param string $filePrefix + */ + public function setFilePrefixOption($filePrefix) + { + $this->filePrefix = $filePrefix; + } + + /** + * Get the file prefix + * + * @return string + */ + public function getFilePrefixOption() + { + return $this->filePrefix; + } + + /** + * Set the file extension + * + * @param string $fileSuffix + */ + public function setFileSuffixOption($fileSuffix) + { + $this->fileSuffix = $fileSuffix; + } + + /** + * Get the file extension + * + * @return string + */ + public function getFileSuffixOption() + { + return isset($this->fileSuffix) ? $this->fileSuffix : ('.' . $this->getPacker()->getType()); + } + + + /** + * Create the cache directory + * + * @param string $cacheFile + */ + protected function createCacheDirectory($path) + { + if (!is_dir($path)) { + if (!mkdir($path, 0777, true)) { + throw new CacheException($path.' is not writable'); + } + } + + if (!is_writable($path)) { + throw new CacheException($path.' is not writable'); + } + } + + /** + * Delete a cache file + * + * @param string $file + * @return bool + */ + protected function deleteFile($file) + { + return is_file($file) && unlink($file); + } + + /** + * Create a filename based on the key + * + * @param $key + * @return string + */ + protected function getFileName($key) + { + return $this->cacheDir. + DIRECTORY_SEPARATOR. + $this->getFilePrefixOption(). + $this->getKey($key). + $this->getFileSuffixOption(); + } + +} diff --git a/src/Apcu.php b/src/Apcu.php index 89e2ef5..4c219e7 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -14,44 +14,35 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\Packer\NopPacker; +use Desarrolla2\Cache\Packer\PackerInterface; /** * Apcu */ class Apcu extends AbstractCache { - use PackTtlTrait; - /** - * Set the `pack-ttl` setting; Include TTL in the packed data. - * - * @param boolean $value + * Get the packer + * + * @return PackerInterface */ - protected function setPackTtlOption($value) + protected function getPacker() { - $this->packTtl = (boolean)$value; - } + if (!isset($this->packer)) { + $this->packer = new NopPacker(); + } - /** - * Get the `pack-ttl` setting - * - * @return boolean - */ - protected function getPackTtlOption() - { - return $this->packTtl; + return $this->packer; } - - + + /** * {@inheritdoc} */ public function delete($key) { - apcu_delete($this->getKey($key)); + return apcu_delete($this->getKey($key)); } /** @@ -59,7 +50,13 @@ public function delete($key) */ public function get($key, $default = null) { - return $this->getValueFromCache($key, $default); + $packed = apcu_fetch($this->getKey($key), $success); + + if (!$success) { + return $default; + } + + return $this->unpack($packed); } /** @@ -67,7 +64,7 @@ public function get($key, $default = null) */ public function has($key) { - return apcu_exists($key) && (!$this->packTtl || $this->getValueFromCache($key) !== null); + return apcu_exists($key); } /** @@ -83,49 +80,6 @@ public function clear() */ public function set($key, $value, $ttl = null) { - if (!$ttl) { - $ttl = $this->ttl; - } - - $data = $this->pack($value, $ttl); - - if (!is_string($data)) { - throw new InvalidArgumentException( - sprintf('Error saving data with the key "%s" to the apcu cache; data must be packed as string', $key) - ); - } - - $success = apcu_store($this->getKey($key), $data, $ttl); - - if (!$success) { - throw new CacheException(sprintf('Error saving data with the key "%s" to the apcu cache.', $key)); - } - } - - /** - * Get the value from cache - * - * @param string $key - * @return mixed|null - */ - protected function getValueFromCache($key, $default = null) - { - $packed = apcu_fetch($this->getKey($key), $success); - - if (!$success) { - return $default; - } - - try { - $value = $this->unpack($packed); - } catch (UnexpectedValueException $e) { - $this->delete($key); - return $default; - } catch (CacheExpiredException $e) { - $this->delete($key); - return $default; - } - - return $value; + return apcu_store($this->getKey($key), $this->pack($value), $ttl ?: $this->ttl); } } diff --git a/src/Exception/BadMethodCallException.php b/src/Exception/BadMethodCallException.php new file mode 100644 index 0000000..640fd35 --- /dev/null +++ b/src/Exception/BadMethodCallException.php @@ -0,0 +1,24 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Exception; + +use Psr\SimpleCache\CacheException as PsrCacheException; + +/** + * Exception bad method calls + */ +class BadMethodCallException extends \BadMethodCallException implements PsrCacheException +{ +} diff --git a/src/File.php b/src/File.php index 1e1c88c..88bdf0c 100644 --- a/src/File.php +++ b/src/File.php @@ -17,167 +17,73 @@ use Desarrolla2\Cache\Exception\CacheExpiredException; use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\Packer\PhpPacker; /** - * File + * Cache file. + * Data contains both value and ttl */ -class File extends AbstractCache +class File extends AbstractFile { - use PackTtlTrait; - - const CACHE_FILE_PREFIX = '__'; - - const CACHE_FILE_SUBFIX = '.php.cache'; - - /** - * @var string - */ - protected $cacheDir; - - /** - * @param string $cacheDir - * - * @throws CacheException - */ - public function __construct($cacheDir = null) - { - if (!$cacheDir) { - $cacheDir = realpath(sys_get_temp_dir()).'/cache'; - } - - $this->cacheDir = (string) $cacheDir; - - $this->createCacheDirectory($cacheDir); - } - /** * {@inheritdoc} */ public function delete($key) { - $tKey = $this->getKey($key); - $cacheFile = $this->getFileName($tKey); - $this->deleteFile($cacheFile); - } - - /** - * {@inheritdoc} - */ - public function get($key, $default = null) - { - return $this->getValueFromCache($key, $default); - } + $cacheFile = $this->getFileName($key); - /** - * {@inheritdoc} - */ - public function has($key) - { - return !is_null($this->getValueFromCache($key)); + return $this->deleteFile($cacheFile); } /** * {@inheritdoc} */ - public function set($key, $value, $ttl = null) + public function get($key, $default = null) { $cacheFile = $this->getFileName($key); - if (!$ttl) { - $ttl = $this->ttl; + + if (!file_exists($cacheFile)) { + return false; } - $item = $this->pack($value, $ttl); + $contents = $this->read($cacheFile); - if (!file_put_contents($cacheFile, $item)) { - throw new CacheException(sprintf('Error saving data with the key "%s" to the cache file.', $key)); + if ($contents['ttl'] < self::time()) { + return false; } + + return $contents['value']; } /** * {@inheritdoc} */ - public function setOption($key, $value) - { - switch ($key) { - case 'ttl': - $value = (int) $value; - if ($value < 1) { - throw new CacheException('ttl cant be lower than 1'); - } - $this->ttl = $value; - break; - default: - throw new CacheException('option not valid '.$key); - } - - return true; - } - - protected function createCacheDirectory($path) + public function has($key) { - if (!is_dir($path)) { - if (!mkdir($path, 0777, true)) { - throw new CacheException($path.' is not writable'); - } - } + $cacheFile = $this->getFileName($key); - if (!is_writable($path)) { - throw new CacheException($path.' is not writable'); + if (!file_exists($cacheFile)) { + return false; } - } - protected function deleteFile($cacheFile) - { - if (is_file($cacheFile)) { - return unlink($cacheFile); - } + $contents = $this->read($cacheFile); - return false; + return $contents['ttl'] < self::time(); } - protected function getFileName($key) - { - return $this->cacheDir. - DIRECTORY_SEPARATOR. - self::CACHE_FILE_PREFIX. - $this->getKey($key). - self::CACHE_FILE_SUBFIX; - } - - protected function getValueFromCache($key, $default = null) + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) { - $path = $this->getFileName($key); + $cacheFile = $this->getFileName($key); - if (!file_exists($path)) { - return $default; - } - try { - $data = $this->unPack(file_get_contents($path)); - } catch( UnexpectedValueException $e ){ - return $default; - } catch( CacheExpiredException $e ){ - return $default; - } - - return $data; - } + $packed = $this->pack(compact('value', 'ttl')); - protected function validateDataFromCache($data) - { - if (!is_array($data)) { - return false; - } - foreach (['value', 'ttl'] as $missing) { - if (!array_key_exists($missing, $data)) { - return false; - } + if (!is_string($packed)) { + throw new UnexpectedValueException("Packer must create a string for the data to be cached to file"); } - return true; - } - - protected function ttlHasExpired($ttl) - { - return (time() > $ttl); + return file_put_contents($cacheFile, $packed); } } diff --git a/src/FlatFile.php b/src/FlatFile.php new file mode 100644 index 0000000..2d9917e --- /dev/null +++ b/src/FlatFile.php @@ -0,0 +1,212 @@ + + */ + +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\UnexpectedValueException; +use Desarrolla2\Cache\Packer\PhpPacker; + +/** + * Flat file. + * TTL will place in it's own file. + */ +class FlatFile extends AbstractCache +{ + /** + * @var string + */ + protected $cacheDir; + + /** + * @var string + */ + protected $filePrefix = '__'; + + /** + * @var string|null + */ + protected $fileSuffix; + + /** + * @param string $cacheDir + * @throws CacheException + */ + public function __construct($cacheDir = null) + { + if (!$cacheDir) { + $cacheDir = realcacheFile(sys_get_temp_dir()) . '/cache'; + } + + $this->cacheDir = (string)$cacheDir; + $this->createCacheDirectory($cacheDir); + } + + /** + * Set the file prefix + * + * @param string $filePrefix + */ + public function setFilePrefixOption($filePrefix) + { + $this->filePrefix = $filePrefix; + } + + /** + * Get the file prefix + * + * @return string + */ + public function getFilePrefixOption() + { + return $this->filePrefix; + } + + /** + * Set the file extension + * + * @param string $fileSuffix + */ + public function setFileSuffixOption($fileSuffix) + { + $this->fileSuffix = $fileSuffix; + } + + /** + * Get the file extension + * + * @return string + */ + public function getFileSuffixOption() + { + return isset($this->fileSuffix) ? $this->fileSuffix : ('.' . $this->getPacker()->getType()); + } + + + /** + * {@inheritdoc} + */ + public function delete($key) + { + $cacheFile = $this->getFileName($key); + + return $this->deleteFile($cacheFile); + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + $cacheFile = $this->getFileName($key); + + if (!$this->has($key)) { + return $default; + } + + return $this->read($cacheFile); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + $ttlFile = $this->getFileName($key) . '.ttl'; + + if (!file_exists($ttlFile)) { + return false; + } + + $ttl = $this->read($ttlFile); + + return self::time() < $ttl; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $cacheFile = $this->getFileName($key); + $ttlFile = $this->getFileName($key) . '.ttl'; + + $packed = $this->pack($value); + $packedTtl = $this->pack(self::time() + ($ttl ?: $this->ttl)); + + if (!is_string($packed)) { + throw new UnexpectedValueException("Packer must create a string for the data to be cached to file"); + } + + return file_put_contents($cacheFile, $packed) && file_put_contents($ttlFile, $packedTtl); + } + + /** + * Create the cache directory + * + * @param string $cacheFile + */ + protected function createCacheDirectory($path) + { + if (!is_dir($path)) { + if (!mkdir($path, 0777, true)) { + throw new CacheException($path.' is not writable'); + } + } + + if (!is_writable($path)) { + throw new CacheException($path.' is not writable'); + } + } + + /** + * Delete a cache file + * + * @param string $cacheFile + * @return bool + */ + protected function deleteFile($cacheFile) + { + return + (!is_file($cacheFile) || unlink($cacheFile)) && + (!is_file($cacheFile . '.ttl') || unlink($cacheFile . '.ttl')); + } + + /** + * Create a filename based on the key + * + * @param $key + * @return string + */ + protected function getFileName($key) + { + return $this->cacheDir. + DIRECTORY_SEPARATOR. + $this->getFilePrefixOption(). + $this->getKey($key). + $this->getFileSuffixOption(); + } + + /** + * Read the cache file + * + * @param string $cacheFile + * @return mixed + */ + protected function read($cacheFile) + { + return $this->packer instanceof PhpPacker + ? include $cacheFile + : $this->unpack(file_get_contents($cacheFile)); + } +} diff --git a/src/KeyMaker/HashKeyMaker.php b/src/KeyMaker/HashKeyMaker.php new file mode 100644 index 0000000..b8c3753 --- /dev/null +++ b/src/KeyMaker/HashKeyMaker.php @@ -0,0 +1,52 @@ +prefix = $prefix; + $this->algo = $algo; + } + + /** + * Get the key prefix + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * Get the key with prefix + * + * @param string $key + * @return string + */ + public function make($key) + { + return hash($this->algo, sprintf('%s%s', $this->prefix, $key)); + } +} \ No newline at end of file diff --git a/src/KeyMaker/KeyMakerInterface.php b/src/KeyMaker/KeyMakerInterface.php new file mode 100644 index 0000000..e0638f4 --- /dev/null +++ b/src/KeyMaker/KeyMakerInterface.php @@ -0,0 +1,24 @@ +prefix = $prefix; + } + + /** + * Get the key prefix + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * Get the key with prefix + * + * @param string $key + * @return string + */ + public function make($key) + { + return sprintf('%s%s', $this->prefix, $key); + } +} \ No newline at end of file diff --git a/src/Memcache.php b/src/Memcache.php deleted file mode 100644 index 23e93b5..0000000 --- a/src/Memcache.php +++ /dev/null @@ -1,94 +0,0 @@ - - */ - - -namespace Desarrolla2\Cache; - -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; -use Memcache as BaseMemcache; - -/** - * Memcache - */ -class Memcache extends AbstractCache -{ - use PackTtlTrait; - - /** - * - * @var BaseMemcache - */ - protected $server; - - /** - * @param BaseMemCache|null $server - */ - public function __construct(BaseMemcache $server = null) - { - if ($server) { - $this->server = $server; - - return; - } - $this->server = new BaseMemcache(); - $this->server->addServer('localhost', 11211); - } - - /** - * {@inheritdoc} - */ - public function delete($key) - { - $this->server->delete($this->getKey($key)); - } - - /** - * {@inheritdoc} - */ - public function get($key, $default = null) - { - $data = $this->server->get($this->getKey($key)); - if (!$data) { - return $default; - } - - return $this->unPack($data); - } - - /** - * {@inheritdoc} - */ - public function has($key) - { - $data = $this->server->get($this->getKey($key)); - if (!$data) { - return false; - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function set($key, $value, $ttl = null) - { - if (!$ttl) { - $ttl = $this->ttl; - } - $this->server->set($this->getKey($key), $this->pack($value, $ttl), false, time() + $ttl); - } -} diff --git a/src/Memcached.php b/src/Memcached.php index 05a9771..7032c52 100644 --- a/src/Memcached.php +++ b/src/Memcached.php @@ -25,7 +25,6 @@ */ class Memcached extends AbstractCache { - use PackTtlTrait; /** * @var BaseMemcached */ @@ -36,13 +35,12 @@ class Memcached extends AbstractCache */ public function __construct(BaseMemcached $server = null) { - if ($server) { - $this->server = $server; - - return; + if (!$server) { + $server = new BaseMemcached(); + $server->addServer('localhost', 11211); } - $this->server = new BaseMemcached(); - $this->server->addServer('localhost', 11211); + + $this->server = $server; } /** @@ -50,7 +48,7 @@ public function __construct(BaseMemcached $server = null) */ public function delete($key) { - $this->server->delete($this->getKey($key)); + return $this->server->delete($this->getKey($key)); } /** @@ -59,24 +57,38 @@ public function delete($key) public function get($key, $default = null) { $data = $this->server->get($this->getKey($key)); - if (!$data) { + + if ($data === false) { return $default; } - return $this->unPack($data); + return $this->unpack($data); } /** * {@inheritdoc} */ - public function has($key) + public function getMultiple($keys, $default = null) { - $data = $this->server->get($this->getKey($key)); - if (!$data) { - return false; - } + $this->assertIterable($keys, 'keys not iterable'); + + $cacheKeys = array_map([$this, 'getKey'], $keys); + + $items = $this->server->getMulti($cacheKeys); + + $result = array_map(function($item) { + return $this->unpack($item); + }, $items); - return true; + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return $this->server->get($this->getKey($key)) !== false; } /** @@ -84,9 +96,35 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - if (!$ttl) { - $ttl = $this->ttl; - } - $this->server->set($this->getKey($key), $this->pack($value, $ttl), false, time() + $ttl); + $packed = $this->pack($value, $ttl); + $ttlTime = static::time() + ($ttl ?: $this->ttl); + + return $this->server->set($this->getKey($key), $packed, $ttlTime); + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $this->assertIterable($values, 'values not iterable'); + + $cacheKeys = array_map([$this, 'getKey'], array_keys($values)); + + $packed = array_map(function($value) { + $this->pack($value); + }, $values); + + $ttlTime = static::time() + ($ttl ?: $this->ttl); + + return $this->server->setMulti(array_combine($cacheKeys, $packed), $ttlTime); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->server->flush(); } } diff --git a/src/Memory.php b/src/Memory.php index 5779bee..96d6c00 100644 --- a/src/Memory.php +++ b/src/Memory.php @@ -23,23 +23,52 @@ */ class Memory extends AbstractCache { - use PackTtlTrait; /** * @var int */ - protected $limit = false; + protected $limit = PHP_INT_MAX; /** * @var array */ protected $cache = []; + /** + * @var array + */ + protected $cacheTtl = []; + + + /** + * Set the max number of items + * + * @param int $limit + */ + public function setLimitOption($value) + { + $this->limit = (int)$value ?: PHP_INT_MAX; + } + + /** + * Get the max number of items + * + * @return int + */ + public function getLimitOption() + { + return $this->limit; + } + + /** * {@inheritdoc} */ public function delete($key) { - unset($this->cache[$this->getKey($key)]); + $cacheKey = $this->getKey($key); + unset($this->cache[$cacheKey], $this->cacheTtl[$cacheKey]); + + return true; } /** @@ -47,13 +76,13 @@ public function delete($key) */ public function get($key, $default = null) { - if ($this->has($key)) { - $tKey = $this->getKey($key); - - return $this->unPack($this->cache[$tKey]); + if (!$this->has($key)) { + return $default; } - return false; + $cacheKey = $this->getKey($key); + + return $this->unpack($this->cache[$cacheKey]); } /** @@ -61,20 +90,18 @@ public function get($key, $default = null) */ public function has($key) { - $tKey = $this->getKey($key); - if (isset($this->cache[$tKey])) { - try { - $this->unPack($this->cache[$tKey]); - } catch( UnexpectedValueException $e ){ - return false; - } catch( CacheExpiredException $e ){ - return false; - } - return true; + $cacheKey = $this->getKey($key); + + if (!isset($this->cacheTtl[$cacheKey])) { + return false; + } + + if ($this->cacheTtl[$cacheKey] < self::time()) { + unset($this->cache[$cacheKey], $this->cacheTtl[$cacheKey]); + return false; } - - $this->delete($key); - return false; + + return true; } /** @@ -82,28 +109,24 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - if ($this->limit && count($this->cache) > $this->limit) { + if (count($this->cache) > $this->limit) { array_shift($this->cache); } - if (!$ttl) { - $ttl = $this->ttl; - } - $this->cache[$this->getKey($key)] = $this->pack($value, $ttl); + + $cacheKey = $this->getKey($key); + + $this->cache[$cacheKey] = $this->pack($value); + $this->cacheTtl[$cacheKey] = self::time() + ($ttl ?: $this->ttl); + + return true; } /** * {@inheritdoc} */ - public function setOption($key, $value) + public function clear() { - switch ($key) { - case 'limit': - $value = (int) $value; - $this->limit = $value; - - return true; - } - - return parent::setOption($key, $value); + $this->cache = []; + $this->cacheTtl = []; } } diff --git a/src/Mongo.php b/src/Mongo.php index ff88da0..800ae0d 100644 --- a/src/Mongo.php +++ b/src/Mongo.php @@ -13,51 +13,74 @@ namespace Desarrolla2\Cache; -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; +use MongoDB; +use MongoClient; +use MongoCollection; +use MongoDate; +use MongoConnectionException; /** * Mongo */ class Mongo extends AbstractCache { - use PackTtlTrait; /** - * @var MongoCollection|MongoDB\Collection + * @var MongoCollection|MongoDb\Collection */ protected $collection; /** - * @param MongoDB|MongoDB\Database|MongoCollection|MongoDB\Collection $backend + * Class constructor + * + * @param mixed $backend + * @throws UnexpectedValueException + * @throws MongoConnectionException + * @throws MongoDB\Driver\Exception */ public function __construct($backend = null) { - $this->packTtl = false; if (!isset($backend)) { - $client = class_exist('MongoCollection') ? new \MongoClient() : new \MongoDB\Client(); - $backend = $client->selectDatabase('cache'); + $backend = class_exist('MongoClient') ? new MongoClient() : new MongoDB\Client(); + } + + if ($backend instanceof MongoClient || $backend instanceof MongoDB\Client) { + $backend = $backend->selectDatabase('cache'); } - - if ($backend instanceof \MongoCollection || $backend instanceof \MongoDB\Collection) { - $this->collection = $backend; - } elseif ($backend instanceof \MongoDB || $backend instanceof \MongoDB\Database) { - $this->collection = $backend->selectCollection('items'); - } else { - $type = (is_object($database) ? get_class($database) . ' ' : '') . gettype($database); - throw new CacheException("Database should be a database (MongoDB or MongoDB\Database) or " . + + if ($backend instanceof MongoCollection || $backend instanceof MongoDB\Collection) { + $backend = $backend->selectCollection('items'); + } + + if (!$backend instanceof MongoDB && !$backend instanceof MongoDB\Database) { + $type = (is_object($backend) ? get_class($backend) . ' ' : '') . gettype($backend); + throw new UnexpectedValueException("Database should be a database (MongoDB or MongoDB\Database) or " . " collection (MongoCollection or MongoDB\Collection) object, not a $type"); } + + $this->collection = $backend; + + $this->initCollection(); + } + + /** + * Initialize the DB collecition + */ + protected function initCollection() + { + // TTL index + $this->collection->createIndex(['ttl' => 1], ['expireAfterSeconds' => 0]); } + /** * {@inheritdoc} */ public function delete($key) { - $tKey = $this->getKey($key); - $this->collection->remove(array('_id' => $tKey)); + $cacheKey = $this->getKey($key); + + $this->collection->remove(['_id' => $cacheKey]); } /** @@ -65,14 +88,32 @@ public function delete($key) */ public function get($key, $default = null) { - $tKey = $this->getKey($key); - $tNow = $this->getTtl(); - $data = $this->collection->findOne(array('_id' => $tKey, 'ttl' => array('$gte' => $tNow))); - if (isset($data)) { - return $this->unPack($data['value']); + $data = $this->collection->findOne(['_id' => $this->getKey($key)]); + + return isset($data) ? $this->unpack($data['value']) : $default; + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + $this->assertIterable($keys, 'keys not iterable'); + + if (empty($keys)) { + return []; } - return $default; + $cacheKeys = array_map([$this, 'getKey'], $keys); + $items = array_fill_keys($cacheKeys, $default); + + $rows = $this->collection->find(['_id' => ['$in' => $cacheKeys]]); + + foreach ($rows as $row) { + $items[$row['_id']] = $this->unpack($row['value']); + } + + return $keys === $cacheKeys ? $items : array_merge($keys, array_values($items)); } /** @@ -80,9 +121,7 @@ public function get($key, $default = null) */ public function has($key) { - $tKey = $this->getKey($key); - $tNow = $this->getTtl(); - return $this->collection->count(array('_id' => $tKey, 'ttl' => array('$gte' => $tNow))) > 0; + return $this->collection->count(['_id' => $this->getKey($key)]) > 0; } /** @@ -90,19 +129,57 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $tKey = $this->getKey($key); - if (!$ttl) { - $ttl = $this->ttl; + $cacheKey = $this->getKey($key); + + $item = [ + '_id' => $cacheKey, + 'ttl' => $this->getTtl($ttl ?: $this->ttl), + 'value' => $this->pack($value), + ]; + + $this->collection->update(['_id' => $cacheKey], $item, ['upsert' => true]); + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $this->assertIterable($values, 'values not iterable'); + + if (empty($values)) { + return true; } - $tValue = $this->pack($value, $ttl); - $item = array( - '_id' => $tKey, - 'value' => $tValue, - 'ttl' => $this->getTtl($ttl), - ); - $this->collection->update(array('_id' => $tKey), $item, array('upsert' => true)); + + $filters = []; + $items = []; + + foreach ($values as $key => $value) { + $cacheKey = $this->getKey($key); + + $filters[] = ['_id' => $cacheKey]; + + $items[] = array( + '_id' => $cacheKey, + 'ttl' => $this->getTtl($ttl ?: $this->ttl), + 'value' => $this->pack($value), + ); + } + + $this->collection->updateMany($filters, $values, ['upsert' => true]); + + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->collection->dropCollection(); + + $this->initCollection(); } - + /** * Get TTL as Date type BSON object * @@ -111,8 +188,8 @@ public function set($key, $value, $ttl = null) */ protected function getTtl($ttl = 0) { - return $this->collection instanceof \MongoCollection ? - new \MongoDate((int) $ttl + time()) : - new \MongoDB\BSON\UTCDatetime(((int) $ttl + time() * 1000)); + return $this->collection instanceof MongoCollection ? + new MongoDate(self::time() + (int)$ttl) : + new MongoDB\BSON\UTCDatetime((self::time() + (int)$ttl) * 1000); } } diff --git a/src/Mysqli.php b/src/Mysqli.php index e9a41d5..827bc15 100644 --- a/src/Mysqli.php +++ b/src/Mysqli.php @@ -13,10 +13,6 @@ namespace Desarrolla2\Cache; -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; use mysqli as Server; /** @@ -24,11 +20,8 @@ */ class Mysqli extends AbstractCache { - use PackTtlTrait { - pack as protected traitpack; - } /** - * @var \mysqli + * @var Server */ protected $server; @@ -42,27 +35,36 @@ class Mysqli extends AbstractCache */ public function __construct(Server $server = null) { - if ($server) { - $this->server = $server; + $this->server = $server ?: new Server(); + } - return; - } - $this->server = new server(); + /** + * Set the table name + * + * @param string $table + */ + public function setTableOption($table) + { + $this->table = (string)$table; } + /** + * Get the table name + * + * @return string + */ + public function getTableOption() + { + return $this->table; + } + + /** * {@inheritdoc} */ public function delete($key) { - $this->query( - sprintf( - 'DELETE FROM %s WHERE k = "%s" OR t < %d', - $this->table, - $this->getKey($key), - time() - ) - ); + return $this->query('DELETE FROM {table} WHERE `key` = %s', $this->getKey($key)) !== false; } /** @@ -70,19 +72,40 @@ public function delete($key) */ public function get($key, $default = null) { - $res = $this->fetchObject( - sprintf( - 'SELECT v FROM %s WHERE k = "%s" AND t >= %d LIMIT 1;', - $this->table, - $this->getKey($key), - time() - ) + $row = $this->fetchRow( + 'SELECT `value` FROM {table} WHERE `key` = %s AND `ttl` >= %d LIMIT 1', + $this->getKey($key), + self::time() ); - if ($res) { - return $this->unPack($res->v); + + return $row ? $this->unpack($row[0]) : $default; + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + $this->assertIterable($keys, 'keys not iterable'); + + if (empty($keys)) { + return []; } - return $default; + $cacheKeys = array_map([$this, 'getKey'], $keys); + $items = array_fill_keys($cacheKeys, $default); + + $ret = $this->query( + 'SELECT `key`, `value` FROM {table} WHERE `key` IN `%s` AND `ttl` >= %d', + $cacheKeys, + self::time() + ); + + while ((list($key, $value) = $ret->fetch_assoc())) { + $items[$key] = $this->unpack($value); + } + + return $keys === $cacheKeys ? $items : array_merge($keys, array_values($items)); } /** @@ -90,22 +113,13 @@ public function get($key, $default = null) */ public function has($key) { - $res = $this->fetchObject( - sprintf( - 'SELECT COUNT(*) AS n FROM %s WHERE k = "%s" AND t >= %d;', - $this->table, - $this->getKey($key), - time() - ) + $row = $this->fetchRow( + 'SELECT `key` FROM {table} WHERE `key` = %s AND `ttl` >= %d LIMIT 1', + $this->getKey($key), + self::time() ); - if (!$res) { - return false; - } - if ($res->n == '0') { - return false; - } - return true; + return !empty($row); } /** @@ -113,77 +127,105 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $this->delete($key); - if (!($ttl)) { - $ttl = $this->ttl; - } - $tTtl = (int) $ttl + time(); - $this->query( - sprintf( - 'INSERT INTO %s (k, v, t) VALUES ("%s", "%s", %d)', - $this->table, - $this->getKey($key), - $this->pack($value), - $tTtl - ) + $res = $this->query( + 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES (%s, %s, %d)', + $this->getKey($key), + $this->pack($value), + self::time() + ($ttl ?: $this->ttl) ); + + return $res !== false; } /** * {@inheritdoc} */ - protected function getKey($key) + public function setMultiple($values, $ttl = null) { - return $this->escape(parent::getKey($key)); + $this->assertIterable($values, 'values not iterable'); + + if (empty($values)) { + return true; + } + + $timeTtl = self::time() + ($ttl ?: $this->ttl); + $query = 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES'; + + foreach ($values as $key => $value) { + $query .= sprintf( + '(%s, %s, %d),', + $this->quote($this->getKey($key)), + $this->quote($this->pack($value)), + $this->quote($timeTtl) + ); + } + + return $this->query(rtrim($query, ',')) !== false; } - protected function pack($value) + /** + * {@inheritdoc} + */ + public function clear() { - return $this->escape($this->traitpack($value, false)); + $this->query('TRUNCATE {table}'); } /** + * Fetch a row from a MySQL table * * @param string $query - * @param int|string $mode - * - * @return mixed + * @param string[] $params + * @return array|false */ - protected function fetchObject($query, $mode = MYSQLI_STORE_RESULT) + protected function fetchRow($query, ...$params) { - $res = $this->query($query, $mode); - if ($res) { - return $res->fetch_object(); + $res = $this->query($query, ...$params); + + if ($res === false) { + return false; } - return false; + return $res->fetch_row(); } /** + * Query the MySQL server * - * @param string $query - * @param int|string $mode - * - * @return mixed + * @param string $query + * @param mixed[] $params + * @return \mysqli_result|false; */ - protected function query($query, $mode = MYSQLI_STORE_RESULT) + protected function query($query, ...$params) { - $res = $this->server->query($query, $mode); - if ($res) { - return $res; + $saveParams = array_map([$this, 'quote'], $params); + + $baseSql = str_replace('{table}', $this->table, $query); + $sql = vsprintf($baseSql, $saveParams); + + $ret = $this->server->query($sql); + + if ($ret === false) { + trigger_error($this->server->error, E_USER_NOTICE); } - return false; + return $ret; } /** + * Quote a value to be used in an array * - * @param string $key - * - * @return string + * @param mixed $value + * @return mixed */ - private function escape($key) + protected function quote($value) { - return $this->server->real_escape_string($key); + if (is_array($value)) { + return join(', ', array_map([$this, 'quote'], $value)); + } + + return is_string($value) + ? ('"' . $this->server->real_escape_string($value) . '"') + : (is_float($value) ? (float)$value : (int)$value); } } diff --git a/src/NotCache.php b/src/NotCache.php index 6d22506..959e927 100644 --- a/src/NotCache.php +++ b/src/NotCache.php @@ -19,24 +19,30 @@ use Desarrolla2\Cache\Exception\InvalidArgumentException; /** - * NotCache + * Dummy cache handler */ class NotCache extends AbstractCache { - use PackTtlTrait; /** - * Delete a value from the cache - * - * @param string $key + * {@inheritdoc} */ public function delete($key) { + return true; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + return false; } /** * {@inheritdoc} */ - public function get($key, $dafault = null) + public function getMultiple($keys, $default = null) { return false; } @@ -60,8 +66,16 @@ public function set($key, $value, $ttl = null) /** * {@inheritdoc} */ - public function setOption($key, $value) + public function setMultiple($values, $ttl = null) { return false; } + + /** + * {@inheritdoc} + */ + public function clear() + { + return true; + } } diff --git a/src/PackTtlTrait.php b/src/PackTtlTrait.php deleted file mode 100644 index 988626e..0000000 --- a/src/PackTtlTrait.php +++ /dev/null @@ -1,85 +0,0 @@ -packTtl ? ['value' => $value, 'ttl' => time() + $ttl] : $value; - - return $this->getPacker()->pack($data); - } - - /** - * Unpack the data to retreive the value and optionally the ttl - * - * @param string|mixed $packed - * @return mixed - * @throws UnexpectedValueException - */ - protected function unpack($packed) - { - $data = $this->getPacker()->unpack($packed); - - if (!$this->validateDataFromCache($data)) { - throw new UnexpectedValueException("unexpected data from cache"); - } - - if ($this->packTtl && $this->ttlHasExpired($data['ttl'])) { - throw new CacheExpiredException("ttl has expired"); - } - - return $this->packTtl ? $data['value'] : $data; - } - - /** - * Validate that the data from cache is as expected - * - * @param array|mixed $data - * @return boolean - */ - protected function validateDataFromCache($data) - { - return !$this->packTtl || - (is_array($data) && array_key_exists('value', $data) && array_key_exists('ttl', $data)); - } - - /** - * Check if TTL has expired - * - * @param type $ttl - * @return type - */ - protected function ttlHasExpired($ttl) - { - return (time() > $ttl); - } -} diff --git a/src/Packer/JsonPacker.php b/src/Packer/JsonPacker.php index 012ddaa..b6fab0c 100644 --- a/src/Packer/JsonPacker.php +++ b/src/Packer/JsonPacker.php @@ -15,12 +15,23 @@ namespace Desarrolla2\Cache\Packer; use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Exception\InvalidArgumentException; /** * Pack value through serialization */ class JsonPacker implements PackerInterface { + /** + * Get cache type (might be used as file extension) + * + * @return string + */ + public function getType() + { + return 'json'; + } + /** * Pack the value * @@ -36,13 +47,21 @@ public function pack($value) * Unpack the value * * @param string $packed - * @return string - * @throws \UnexpectedValueException if he + * @return mixed + * @throws InvalidArgumentException */ public function unpack($packed) { - $value = json_decode($packed); - - return json_serialize($packed); + if (is_string($packed)) { + throw new InvalidArgumentException("packed value should be a string"); + } + + $ret = json_decode($packed); + + if (!isset($ret) && json_last_error()) { + throw new UnexpectedValueException("packed value is not a valid JSON string"); + } + + return $ret; } } diff --git a/src/Packer/NopPacker.php b/src/Packer/NopPacker.php index 763611d..2094e90 100644 --- a/src/Packer/NopPacker.php +++ b/src/Packer/NopPacker.php @@ -21,6 +21,16 @@ */ class NopPacker implements PackerInterface { + /** + * Get cache type (might be used as file extension) + * + * @return string + */ + public function getType() + { + return 'data'; + } + /** * Pack the value * @@ -29,7 +39,7 @@ class NopPacker implements PackerInterface */ public function pack($value) { - return serialize($value); + return $value; } /** @@ -40,6 +50,6 @@ public function pack($value) */ public function unpack($packed) { - return unserialize($packed); + return $packed; } } diff --git a/src/Packer/PackerInterface.php b/src/Packer/PackerInterface.php index 1f7ee14..3dfe483 100644 --- a/src/Packer/PackerInterface.php +++ b/src/Packer/PackerInterface.php @@ -19,6 +19,13 @@ */ interface PackerInterface { + /** + * Get cache type (might be used as file extension) + * + * @return string + */ + public function getType(); + /** * Pack the value * diff --git a/src/Packer/PhpPacker.php b/src/Packer/PhpPacker.php new file mode 100644 index 0000000..48efa9c --- /dev/null +++ b/src/Packer/PhpPacker.php @@ -0,0 +1,67 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Packer; + +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Exception\BadMethodCallException; + +/** + * Export to code that can be evaluated by PHP. + * + * This packer only works for file caching. + * It can be expected to behave much faster to normal caching, as opcode cache is utilized. + */ +class PhpPacker implements PackerInterface +{ + /** + * Get cache type (might be used as file extension) + * + * @return string + */ + public function getType() + { + return 'php'; + } + + /** + * Pack the value + * + * @param mixed $value + * @return string + */ + public function pack($value) + { + $macro = var_export($value, true); + + if (strpos($macro, 'stdClass::__set_state') !== false) { + $macro = preg_replace_callback("/('([^'\\\\]++|''\\.)')|stdClass::__set_state/", $macro, function($match) { + return empty($match[1]) ? '(object)' : $match[1]; + }); + } + + return ' true]) + { + $this->options = $options; + } + + /** + * Get cache type (might be used as file extension) + * + * @return string + */ + public function getType() + { + return 'php.cache'; + } + /** * Pack the value * @@ -41,6 +67,10 @@ public function pack($value) */ public function unpack($packed) { - return unserialize($packed); + if (!is_string($packed)) { + throw new InvalidArgumentException("packed value should be a string"); + } + + return unserialize($packed, $this->options); } } diff --git a/src/Predis.php b/src/Predis.php index b753f8f..d15e22c 100644 --- a/src/Predis.php +++ b/src/Predis.php @@ -24,28 +24,29 @@ */ class Predis extends AbstractCache { - use PackTtlTrait; /** * @var Client */ protected $predis; /** - * @param Client $client + * Class constructor + * @see predis documentation about how know your configuration https://github.com/nrk/predis * - * @see predis documentation about how know your configuration - * https://github.com/nrk/predis + * @param Client $client */ public function __construct(Client $client = null) { - if ($client) { - $this->predis = $client; - - return; + if (!$client) { + $client = new Client(); } - $this->predis = new Client(); + + $this->predis = $client; } + /** + * Class destructor + */ public function __destruct() { $this->predis->disconnect(); @@ -57,7 +58,7 @@ public function __destruct() public function delete($key) { $cmd = $this->predis->createCommand('DEL'); - $cmd->setArguments([$key]); + $cmd->setArguments([$this->getKey($key)]); $this->predis->executeCommand($cmd); } @@ -67,16 +68,9 @@ public function delete($key) */ public function get($key, $default = null) { + $packed = $this->predis->get($this->getKey($key)); - try { - $data = $this->unpack($this->predis->get($key)); - } catch( UnexpectedValueException $e ){ - return $default; - } catch( CacheExpiredException $e ){ - return $default; - } - - return $data; + return $this->unpack($packed); } /** @@ -85,7 +79,7 @@ public function get($key, $default = null) public function has($key) { $cmd = $this->predis->createCommand('EXISTS'); - $cmd->setArguments([$key]); + $cmd->setArguments([$this->getKey($key)]); return $this->predis->executeCommand($cmd); } @@ -95,12 +89,19 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - if (!$ttl) { - $ttl = $this->ttl; + $cacheKey = $this->getKey($key); + + $set = $this->predis->set($cacheKey, $this->pack($value)); + + if (!$set) { + return false; } - $this->predis->set($key, $this->pack($value, $ttl)); + $cmd = $this->predis->createCommand('EXPIRE'); - $cmd->setArguments([$key, $ttl]); + $cmd->setArguments([$cacheKey, $ttl]); + $this->predis->executeCommand($cmd); + + return true; } } diff --git a/tests/AbstractCacheTest.php b/tests/AbstractCacheTest.php index e2d47b7..55a394b 100644 --- a/tests/AbstractCacheTest.php +++ b/tests/AbstractCacheTest.php @@ -13,10 +13,13 @@ namespace Desarrolla2\Test\Cache; +use PHPUnit\Framework\TestCase; +use Carbon\Carbon; + /** * AbstractCacheTest */ -abstract class AbstractCacheTest extends \PHPUnit_Framework_TestCase +abstract class AbstractCacheTest extends TestCase { /** * @var \Desarrolla2\Cache\Cache @@ -74,9 +77,10 @@ public function dataProviderForOptions() */ public function testHas($key, $value, $ttl) { - $this->assertNull($this->cache->delete($key)); + $this->cache->delete($key); $this->assertFalse($this->cache->has($key)); - $this->assertNull($this->cache->set($key, $value, $ttl)); + + $this->assertTrue($this->cache->set($key, $value, $ttl)); $this->assertTrue($this->cache->has($key)); } @@ -105,10 +109,15 @@ public function testGet($key, $value, $ttl) public function testDelete($key, $value, $ttl) { $this->cache->set($key, $value, $ttl); - $this->assertNull($this->cache->delete($key)); + $this->assertTrue($this->cache->delete($key)); $this->assertFalse($this->cache->has($key)); } + public function testDeleteNonExisting() + { + $this->assertFalse($this->cache->delete('key0')); + } + /** * @dataProvider dataProviderForOptions * @@ -123,29 +132,43 @@ public function testSetOption($key, $value) /** * @dataProvider dataProviderForOptionsException * - * @param string $key - * @param mixed $value - * @param \Exception $expectedException + * @param string $key + * @param mixed $value + * @param string $expectedException */ public function testSetOptionException($key, $value, $expectedException) { - $this->setExpectedException($expectedException); + $this->expectException($expectedException); $this->cache->setOption($key, $value); } public function testHasWithTtlExpired() { - $this->cache->set('key1', 'value1', 1); - sleep(2); + Carbon::setTestNow(Carbon::now()->subRealSecond(10)); // Pretend it's 10 seconds ago + + $this->cache->set('key1', 'value1', 1); // TTL of 1 second + + Carbon::setTestNow(); // Back to actual time, cache is now timed out + static::sleep(2); + $this->assertFalse($this->cache->has('key1')); } public function testReturnDefaultValue() { - $this->cache->set('key1', 'value1', 1); - sleep(2); - $this->assertFalse($this->cache->get('key1', false)); + $this->assertEquals($this->cache->get('key0', 'foo'), 'foo'); + } + + + /** + * Tests can overwrite if they don't need to sleep and can work with carbon + * + * @param int $seconds + */ + protected static function sleep($seconds) + { + sleep($seconds); } } diff --git a/tests/ApcuCacheTest.php b/tests/ApcuCacheTest.php index 7c39653..365431d 100644 --- a/tests/ApcuCacheTest.php +++ b/tests/ApcuCacheTest.php @@ -22,6 +22,9 @@ class ApcuCacheTest extends AbstractCacheTest { public function setUp() { + // Required to check the TTL for new entries + ini_set('apc.use_request_time', false); + if (!extension_loaded('apcu')) { $this->markTestSkipped( 'The APCu extension is not available.' diff --git a/tests/FileTest.php b/tests/FileTest.php index b183c77..9e62149 100644 --- a/tests/FileTest.php +++ b/tests/FileTest.php @@ -13,7 +13,7 @@ namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\File as FileCache; +use Desarrolla2\Cache\FlatFile as FileCache; /** * FileTest @@ -26,14 +26,21 @@ public function setUp() $this->cache = new FileCache($this->config['file']['dir']); } + /** + * No sleep + */ + protected static function sleep($seconds) + { + return; + } + /** * @return array */ public function dataProviderForOptionsException() { return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), + array('ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException') ); } @@ -42,7 +49,7 @@ public function dataProviderForOptionsException() */ public function tearDown() { - array_map('unlink', glob($this->config['file']['dir']."/*.*")); + array_map('unlink', glob($this->config['file']['dir']."/*")); rmdir($this->config['file']['dir']); } } diff --git a/tests/MemcacheTest.php b/tests/MemcacheTest.php deleted file mode 100644 index aec2ff0..0000000 --- a/tests/MemcacheTest.php +++ /dev/null @@ -1,54 +0,0 @@ - - */ - -namespace Desarrolla2\Test\Cache; - -use Desarrolla2\Cache\Memcache as MemcacheCache; -use Memcache as BaseMemcache; - -/** - * MemcacheTest - */ -class MemcacheTest extends AbstractCacheTest -{ - public function setUp() - { - parent::setup(); - if (!extension_loaded('memcache') || !class_exists('\Memcache')) { - $this->markTestSkipped( - 'The Memcache extension is not available.' - ); - } - - $adapter = new BaseMemcache(); - $adapter->addServer($this->config['memcache']['host'], $this->config['memcache']['port']); - if( !$adapter->getstats()) { - $this->markTestSkipped( - 'The Memcache server not started.' - ); - } - - $this->cache = new MemcacheCache($adapter); - } - - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ]; - } -} diff --git a/tests/MemcachedTest.php b/tests/MemcachedTest.php index adba858..6d48847 100644 --- a/tests/MemcachedTest.php +++ b/tests/MemcachedTest.php @@ -32,6 +32,11 @@ public function setUp() $adapter = new BaseMemcached(); $adapter->addServer($this->config['memcached']['host'], $this->config['memcached']['port']); + + if (!$adapter->flush()) { + return $this->markTestSkipped("Unable to flush Memcached; not running?"); + } + $this->cache = new MemcachedCache($adapter); } diff --git a/tests/MemoryTest.php b/tests/MemoryTest.php index af8784e..39c9e46 100644 --- a/tests/MemoryTest.php +++ b/tests/MemoryTest.php @@ -25,6 +25,14 @@ public function setUp() $this->cache = new MemoryCache(); } + /** + * No sleep + */ + protected static function sleep($seconds) + { + return; + } + /** * @return array */ diff --git a/tests/MongoTest.php b/tests/MongoTest.php index 6c429a0..f0c83b1 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -35,6 +35,14 @@ public function setUp() $this->cache = new MongoCache($database); } + /** + * No sleep + */ + protected static function sleep($seconds) + { + return; + } + /** * @return array */ diff --git a/tests/MysqliTest.php b/tests/MysqliTest.php index f9dab63..712466b 100644 --- a/tests/MysqliTest.php +++ b/tests/MysqliTest.php @@ -20,23 +20,37 @@ */ class MysqliTest extends AbstractCacheTest { + protected $mysqli; + public function setUp() { parent::setup(); - if (!extension_loaded('mysqli')) { - $this->markTestSkipped( - 'The mysqli extension is not available.' - ); - } - $mysqli = new \mysqli( - $this->config['mysql']['host'], - $this->config['mysql']['user'], - $this->config['mysql']['password'], - $this->config['mysql']['database'], - $this->config['mysql']['port'] - ); - $mysqli->query('CREATE TABLE IF NOT EXISTS `'.$this->config['mysql']['table'].'`( `k` VARCHAR(255), `v` TEXT, `t` BIGINT );'); - $this->cache = new MysqliCache($mysqli); + $this->mysqli = new \mysqli( + $this->config['mysql']['host'], + $this->config['mysql']['user'], + $this->config['mysql']['password'], + null, + $this->config['mysql']['port'] + ); + + $this->mysqli->query('CREATE DATABASE IF NOT EXISTS `'.$this->config['mysql']['database'].'`;'); + $this->mysqli->select_db($this->config['mysql']['database']); + + $this->mysqli->query('CREATE TEMPORARY TABLE IF NOT EXISTS `cache`( `key` VARCHAR(255), `value` TEXT, `ttl` INT UNSIGNED )'); + $this->cache = new MysqliCache($this->mysqli); + } + + public function tearDown() + { + $this->mysqli->query('DROP DATABASE IF EXISTS `'.$this->config['mysql']['database'].'`;'); + } + + /** + * No sleep + */ + protected static function sleep($seconds) + { + return; } /** diff --git a/tests/NotCacheTest.php b/tests/NotCacheTest.php index a3a541b..c2812ab 100644 --- a/tests/NotCacheTest.php +++ b/tests/NotCacheTest.php @@ -14,11 +14,12 @@ namespace Desarrolla2\Test\Cache; use Desarrolla2\Cache\NotCache as NotCache; +use PHPUnit\Framework\TestCase; /** * NotCacheTest */ -class NoCacheTest extends \PHPUnit_Framework_TestCase +class NoCacheTest extends TestCase { /** * @var \Desarrolla2\Cache\Cache @@ -71,7 +72,7 @@ public function testSet() */ public function testDelete() { - $this->assertNull($this->cache->delete('key')); + $this->assertTrue($this->cache->delete('key')); } /** @@ -80,5 +81,6 @@ public function testDelete() public function testSetOption() { $this->cache->setOption('ttl', 3600); + $this->assertSame(3600, $this->cache->getOption('ttl')); } } diff --git a/tests/PhpFileTest.php b/tests/PhpFileTest.php new file mode 100644 index 0000000..86e00c3 --- /dev/null +++ b/tests/PhpFileTest.php @@ -0,0 +1,58 @@ + + */ + +namespace Desarrolla2\Test\Cache; + +use Desarrolla2\Cache\FlatFile as FileCache; +use Desarrolla2\Cache\Packer\PhpPacker; + +/** + * FileTest with PhpPacker + */ +class PhpFileTest extends AbstractCacheTest +{ + public function setUp() + { + parent::setup(); + + $this->cache = new FileCache($this->config['file']['dir']); + $this->cache->setPacker(new PhpPacker()); + } + + /** + * No sleep + */ + protected static function sleep($seconds) + { + return; + } + + /** + * @return array + */ + public function dataProviderForOptionsException() + { + return array( + array('ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException') + ); + } + + /** + * Remove all temp dir with cache files + */ + public function tearDown() + { + array_map('unlink', glob($this->config['file']['dir']."/*")); + rmdir($this->config['file']['dir']); + } +} diff --git a/tests/PredisTest.php b/tests/PredisTest.php index 0b33d9a..3f5e848 100644 --- a/tests/PredisTest.php +++ b/tests/PredisTest.php @@ -15,6 +15,7 @@ use Desarrolla2\Cache\Predis as PredisCache; use Predis\Client; +use Predis\Connection\ConnectionException; /** * PredisTest @@ -30,8 +31,15 @@ public function setUp() 'The predis library is not available.' ); } - - $this->cache = new PredisCache(); + + try { + $predis = new Client(); + $predis->connect(); + } catch (ConnectionException $e) { + $this->markTestSkipped($e->getMessage()); + } + + $this->cache = new PredisCache($predis); } /** From f2d016d6d81623f4cc009b8cf9c3d065c992c243 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Mon, 11 Jun 2018 04:07:28 +0200 Subject: [PATCH 30/51] Getting ready for PSR-16 Immutable Move PHP from packed to PhpFile implementation Multiple strategies for File cache Use external PSR-16 test suite PHP 7 only multiple for Mongo multiple for Predis clear for File and PhpFile Lots of fixes --- README.md | 166 +++++++++++------ composer.json | 97 +++++----- src/AbstractCache.php | 229 +++++++++++++----------- src/AbstractFile.php | 82 ++++++--- src/Apcu.php | 40 +++-- src/CacheInterface.php | 34 +++- src/Exception/CacheExpiredException.php | 25 --- src/File.php | 151 +++++++++++++--- src/FlatFile.php | 212 ---------------------- src/KeyMaker/AbstractKeyMaker.php | 51 ++++++ src/KeyMaker/HashKeyMaker.php | 38 ++-- src/KeyMaker/KeyMakerInterface.php | 6 +- src/KeyMaker/PlainKeyMaker.php | 29 +-- src/Memcached.php | 25 ++- src/Memory.php | 55 ++++-- src/Mongo.php | 84 +++++---- src/Mysqli.php | 25 ++- src/NotCache.php | 20 ++- src/Packer/PhpPacker.php | 67 ------- src/PhpFile.php | 114 ++++++++++++ src/Predis.php | 89 +++++++-- tests/AbstractCacheTest.php | 124 +++---------- tests/ApcuCacheTest.php | 18 +- tests/FileTest.php | 30 +--- tests/MemcachedTest.php | 5 +- tests/MemoryHashKeyTest.php | 55 ++++++ tests/MemoryTest.php | 46 +---- tests/MongoTest.php | 5 +- tests/MysqliTest.php | 47 ++--- tests/PhpFileTest.php | 28 +-- tests/PredisTest.php | 35 +--- 31 files changed, 1086 insertions(+), 946 deletions(-) delete mode 100644 src/Exception/CacheExpiredException.php delete mode 100644 src/FlatFile.php create mode 100644 src/KeyMaker/AbstractKeyMaker.php delete mode 100644 src/Packer/PhpPacker.php create mode 100644 src/PhpFile.php create mode 100644 tests/MemoryHashKeyTest.php diff --git a/README.md b/README.md index c842c22..bbbe1f8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Cache [SensioLabsInsight](https://insight.sensiolabs.com/projects/5f139261-1ac1-4559-846a-723e09319a88) -A simple cache library, implementing the [PSR-16](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) standard. +A simple cache library, implementing the [PSR-16](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) standard using **immutable** objects. +Caching is typically used throughout an applicatiton. Immutability ensure that modifying the cache behaviour in one +location doesn't result in unexpect behaviour in unrelated code. [![Latest version][ico-version]][link-packagist] [![Latest version][ico-pre-release]][link-packagist] @@ -14,32 +16,41 @@ A simple cache library, implementing the [PSR-16](https://github.com/php-fig/fig [![Today Downloads][ico-today-downloads]][link-downloads] [![Gitter][ico-gitter]][link-gitter] - ## Installation ``` composer require desarrolla2/cache ``` - ## Usage ``` php -set('key', 'myKeyValue', 3600); // later ... echo $cache->get('key'); - ``` +### Options + +You can set options for cache using the `withOption` or `withOptions` method. +Note that all cache objects are immutable, setting an option creates a new +object. + +#### TTL + +All cache implementations support the `ttl` option. This sets the default +time (in seconds) that cache will survive. It defaults to one hour (3600 +seconds). + +Setting the TTL to 0 or a negative number, means the cache should live forever. + ## Cache implementations ### Apcu @@ -48,88 +59,139 @@ Use [APCu cache](http://php.net/manual/en/book.apcu.php) to cache to shared memory. ``` php -setOption('ttl', 3600); -$cache->setOption('pack-ttl', true); +``` + +_Note: by default APCu uses the time at the beginning of a request for ttl. In +some cases, like with a long running script, this can be a problem. You can +change this behaviour `ini_set('apc.use_request_time', false)`._ + +### CacheFile + +Save the cache as file to on the filesystem. The file contains the TTL as well +as the data. +``` php +use Desarrolla2\Cache\CacheFile as CacheFileCache; + +$cache = new CacheFileCache(); ``` -If the `pack-ttl` option is set to false, the cache will rely on APCu's TTL and -not verify the TTL itself. +You may set the following options; -### File +``` php +use Desarrolla2\Cache\CacheFile as CacheFileCache; + +$cache = (new CacheFileCache())->withOptions([ + 'dir' => '/tmp/mycache', + 'file-prefix' => '', + 'file-suffix' => '.php.cache', + 'ttl' => 3600 +]); +``` -Save the cache as file to on the filesystem +### FlatFile + +Save the cache as file to on the filesystem. The TTL is saved in a separate +file. If TTL is disabled, the TTL file is not created. + +When storing string content, consider using the `NopPacker` to store the data +as-is. This can also be use for creating (HTML) files with the intend to serve +them directly. ``` php -setOption('ttl', 3600); -$cache->setOption('pack-ttl', true); +$cache = new FlatFileCache(); +``` + +You may set the following options; +``` php +use Desarrolla2\Cache\CacheFile as FlatFileCache; + +$cache = (new FileCache())->withOptions([ + 'dir' => '/tmp/mycache', + 'file-prefix' => '', + 'file-suffix' => '.php.cache', + 'ttl' => 3600 +]); ``` -If the `pack-ttl` option is set to false, the cache file will only contain the -cached value. The TTL is written a file suffixed with `.ttl`. +### PhpFile + +Save the cache as PHP script to on the filesystem using `var_export` when +storing the cache and `include` when loading the cache. This method is +particularly fast in PHP7.2+ due to opcache optimizations. + +``` php +use Desarrolla2\Cache\PhpFile as PhpFileCache; + +$cache = new FileCache(); +``` +You may set the following options; -### Memcache +``` php +use Desarrolla2\Cache\CacheFile as CacheFileCache; + +$cache = (new FileCache())->withOptions([ + 'dir' => '/tmp/mycache', + 'file-prefix' => '', + 'ttl' => 3600 +]); +``` + +### Memcached Store cache to [Memcached](https://memcached.org/). Memcached is a high performance distributed caching system. ``` php -setOption('ttl', 3600); -$cache->setOption('limit', 200); +``` +You may set the following options; + +``` php +use Desarrolla2\Cache\Memory as MemoryCache; + +$cache = (new MemoryCache())->withOptions([ + 'ttl' => 3600, + 'limit' => 200 +]); ``` ### Mongo @@ -309,7 +371,6 @@ Available packers are: * `JsonPacker` using `json_encode` and `json_decode` * `NopPacker` does no packing * `SerializePacker` using `serialize` and `unserialize` -* `PhpPacker` uses `var_export` and `include`/`eval` #### PSR-16 incompatible packers @@ -318,17 +379,14 @@ unpacking an object will probably not result in an object of the same class. The `NopPacker` is intended when caching string data only (like HTML output) or if the caching backend supports structured data. Using it when storing objects -will likely yield unexpected results. - - -## Contact - -You can contact with me on [@desarrolla2](https://twitter.com/desarrolla2). +will might give unexpected results. ## Contributors -[![Daniel González](https://avatars1.githubusercontent.com/u/661529?v=3&s=80)](https://github.com/desarrolla2) -[![Arnold Daniels](https://avatars3.githubusercontent.com/u/100821?v=3&s=80)](https://github.com/jasny) +* [![Daniel González](https://avatars1.githubusercontent.com/u/661529?v=3&s=80)](https://github.com/desarrolla2) +/ Twitter: [@desarrolla2](https://twitter.com/desarrolla2) +* [![Arnold Daniels](https://avatars3.githubusercontent.com/u/100821?v=3&s=80)](https://github.com/jasny) +/ Twitter: [@ArnoldDaniels](https://twitter.com/ArnoldDaniels) [ico-version]: https://img.shields.io/packagist/v/desarrolla2/Cache.svg?style=flat-square [ico-pre-release]: https://img.shields.io/packagist/vpre/desarrolla2/Cache.svg?style=flat-square diff --git a/composer.json b/composer.json index 42fda1d..0e7a7dd 100644 --- a/composer.json +++ b/composer.json @@ -1,50 +1,53 @@ { - "name": "desarrolla2/cache", - "description": "Provides an cache interface for several adapters Apc, Apcu, File, Mongo, Memcache, Memcached, Mysql, Mongo, Redis is supported.", - "keywords": [ - "cache", - "simple-cache", - "psr-16", - "apc", - "apcu", - "file", - "memcached", - "memcache", - "mysql", - "mongo", - "redis" - ], - "type": "library", - "license": "MIT", - "homepage": "https://github.com/desarrolla2/Cache/", - "authors": [ - { - "name": "Daniel González", - "homepage": "http://desarrolla2.com/" + "name": "desarrolla2/cache", + "description": "Provides an cache interface for several adapters Apc, Apcu, File, Mongo, Memcache, Memcached, Mysql, Mongo, Redis is supported.", + "keywords": [ + "cache", + "simple-cache", + "psr-16", + "apc", + "apcu", + "file", + "memcached", + "memcache", + "mysql", + "mongo", + "redis" + ], + "type": "library", + "license": "MIT", + "homepage": "https://github.com/desarrolla2/Cache/", + "authors": [ + { + "name": "Daniel González", + "homepage": "http://desarrolla2.com/" + }, + { + "name": "Arnold Daniels", + "homepage": "https://jasny.net/" + } + ], + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "ext-apcu": "*", + "ext-mysqli": "*", + "ext-memcached": "*", + "predis/predis": "~1.0.0", + "mongodb/mongodb": "^1.3", + "cache/integration-tests": "dev-master", + "phpunit/phpunit": "^7.2" + }, + "autoload": { + "psr-4": { + "Desarrolla2\\Cache\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Desarrolla2\\Test\\Cache\\": "tests/" + } } - ], - "require": { - "php": ">=5.6.0", - "psr/simple-cache": "^1.0" - }, - "require-dev": { - "php": ">=7.1.0", - "ext-apcu": "*", - "ext-mysqli": "*", - "ext-memcached": "*", - "phpunit/phpunit": "^7.0", - "predis/predis": "~1.0.0", - "nesbot/carbon": "^1.29", - "mongodb/mongodb": "^1.3" - }, - "autoload": { - "psr-4": { - "Desarrolla2\\Cache\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Desarrolla2\\Test\\Cache\\": "tests/" - } - } } diff --git a/src/AbstractCache.php b/src/AbstractCache.php index 2d17e4d..b3de9ff 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -9,19 +9,22 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; use Desarrolla2\Cache\CacheInterface; use Desarrolla2\Cache\Packer\PackerInterface; -use Desarrolla2\Cache\Packer\SerializePacker; use Desarrolla2\Cache\KeyMaker\KeyMakerInterface; use Desarrolla2\Cache\KeyMaker\PlainKeyMaker; use Desarrolla2\Cache\Exception\CacheException; use Desarrolla2\Cache\Exception\InvalidArgumentException; use Desarrolla2\Cache\Exception\CacheExpiredException; -use Carbon\Carbon; +use DateTimeImmutable; +use DateInterval; /** * AbstractAdapter @@ -29,9 +32,9 @@ abstract class AbstractCache implements CacheInterface { /** - * @var int + * @var int|null */ - protected $ttl = 3600; + protected $ttl; /** * @var PackerInterface @@ -44,19 +47,42 @@ abstract class AbstractCache implements CacheInterface protected $keyMaker; + /** + * Make a clone of this object. + * + * @return static + */ + protected function cloneSelf() + { + return clone $this; + } + /** * {@inheritdoc} */ - public function setOption($key, $value) + public function withOption($key, $value) { - $method = "set" . str_replace('-', '', $key) . "Option"; - - if (empty($key) || !method_exists($this, $method)) { - throw new InvalidArgumentException("unknown option '$key'"); + return $this->withOptions([$key => $value]); + } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options) + { + $cache = $this->cloneSelf(); + + foreach ($options as $key => $value) { + $method = "set" . str_replace('-', '', $key) . "Option"; + + if (empty($key) || !method_exists($cache, $method)) { + throw new InvalidArgumentException("unknown option '$key'"); + } + + $cache->$method($value); } - - $this->$method($value); - return true; + + return $cache; } /** @@ -72,64 +98,53 @@ public function getOption($key) return $this->$method(); } - + + /** * Set the time to live (ttl) * - * @param int $value Seconds + * @param int|null $value Seconds * @throws InvalidArgumentException */ - protected function setTtlOption($value) + protected function setTtlOption(?int $value) { - $ttl = (int)$value; - if ($ttl < 1) { + if (isset($value) && $value < 1) { throw new InvalidArgumentException('ttl cant be lower than 1'); } - $this->ttl = $ttl; + $this->ttl = $value; } /** * Get the time to live (ttl) * - * @return int + * @return ?int */ - protected function getTtlOption() + protected function getTtlOption(): ?int { return $this->ttl; } - - /** - * Set the key prefix. - * @deprecated - * - * @param string $value - */ - protected function setPrefixOption($value) - { - $this->keyMaker = new PlainKeyMaker($value); - } + /** - * Get the key prefix - * @deprecated + * Create the default packer for this cache implementation * - * @return string + * @return PackerInterface */ - protected function getPrefixOption() - { - return $this->keyMaker()->getPrefix(); - } - - + abstract protected static function createDefaultPacker(): PackerInterface; + /** - * Set the packer - * + * Set a packer to pack (serialialize) and unpack (unserialize) the data. + * * @param PackerInterface $packer + * @return static */ - public function setPacker(PackerInterface $packer) + public function withPacker(PackerInterface $packer) { - $this->packer = $packer; + $cache = $this->cloneSelf(); + $cache->packer = $packer; + + return $cache; } /** @@ -137,20 +152,20 @@ public function setPacker(PackerInterface $packer) * * @return PackerInterface */ - protected function getPacker() + protected function getPacker(): PackerInterface { if (!isset($this->packer)) { - $this->packer = new SerializePacker(); + $this->packer = static::createDefaultPacker(); } return $this->packer; } /** - * Pack the value, optionally include the ttl + * Pack the value * * @param mixed $value - * @return string|mixed $data + * @return string|mixed */ protected function pack($value) { @@ -171,13 +186,14 @@ protected function unpack($packed) /** - * Set the key maker - * - * @param KeyMakerInterface $keyMaker + * {@inheritdoc} */ - public function setKeyMaker(KeyMakerInterface $keyMaker) + public function withKeyMaker(KeyMakerInterface $keyMaker) { - $this->keyMaker = $keyMaker; + $cache = clone $this; + $cache->keyMaker = $keyMaker; + + return $cache; } /** @@ -197,10 +213,10 @@ protected function getKeyMaker() /** * Get the key with prefix * - * @param string $key + * @param mixed $key * @return string */ - protected function getKey($key) + protected function getKey($key): string { return $this->getKeyMaker()->make($key); } @@ -219,7 +235,7 @@ protected function assertIterable($subject, $msg) ? is_iterable($subject) : is_array($subject) || $subject instanceof Traversable; - if (~$iterable) { + if (!$iterable) { throw new InvalidArgumentException($msg); } } @@ -232,29 +248,6 @@ public function delete($key) throw new CacheException('not ready yet'); } - /** - * {@inheritdoc} - */ - public function deleteMultiple($keys) - { - $this->assertIterable($keys, 'keys not iterable'); - - $success = true; - - foreach ($keys as $key) { - $success &= $this->delete($key); - } - - return $success; - } - - /** - * {@inheritdoc} - */ - public function get($key, $default = null) - { - throw new CacheException('not ready yet'); - } /** * {@inheritdoc} @@ -275,50 +268,82 @@ public function getMultiple($keys, $default = null) /** * {@inheritdoc} */ - public function has($key) + public function setMultiple($values, $ttl = null) { - throw new CacheException('not ready yet'); - } + $this->assertIterable($values, 'values not iterable'); - /** - * {@inheritdoc} - */ - public function set($key, $value, $ttl = null) - { - throw new CacheException('not ready yet'); + $success = true; + + foreach ($values as $key => $value) { + $success = $this->set(is_int($key) ? (string)$key : $key, $value, $ttl) && $success; + } + + return $success; } /** * {@inheritdoc} */ - public function setMultiple($values, $ttl = null) + public function deleteMultiple($keys) { - $this->assertIterable($values, 'values not iterable'); + $this->assertIterable($keys, 'keys not iterable'); $success = true; - - foreach ($values as $key => $value) { - $success = $this->set($key, $value, $ttl) && $success; + + foreach ($keys as $key) { + $success = $this->delete($key) && $success; } - + return $success; } - + + /** - * {@inheritdoc} + * Convert TTL to seconds from now + * + * @param null|int|DateInterval $ttl + * @return int|null + * @throws InvalidArgumentException */ - public function clear() + protected function ttlToSeconds($ttl): ?int { - throw new CacheException('not ready yet'); + if (!isset($ttl) || is_int($ttl)) { + return $ttl; + } + + if ($ttl instanceof DateInterval) { + $reference = new DateTimeImmutable(); + $endTime = $reference->add($ttl); + + return $endTime->getTimestamp() - $reference->getTimestamp(); + } + + $type = (is_object($ttl) ? get_class($ttl) . ' ' : '') . gettype($ttl); + throw new InvalidArgumentException("ttl should be of type int or DateInterval, not $type"); } /** - * Get the current time + * Convert TTL to epoch timestamp * - * @return int + * @param null|int|DateInterval $ttl + * @return int|null + * @throws InvalidArgumentException */ - protected static function time() + protected function ttlToTimestamp($ttl): ?int { - return class_exists('Carbon\\Carbon') ? Carbon::now()->timestamp : time(); + if (!isset($ttl)) { + return null; + } + + if (is_int($ttl)) { + return time() + $ttl; + } + + if ($ttl instanceof DateInterval) { + return (new DateTimeImmutable())->add($ttl)->getTimestamp(); + } + + $type = (is_object($ttl) ? get_class($ttl) . ' ' : '') . gettype($ttl); + throw new InvalidArgumentException("ttl should be of type int or DateInterval, not $type"); } } diff --git a/src/AbstractFile.php b/src/AbstractFile.php index 45be8e2..31020d5 100644 --- a/src/AbstractFile.php +++ b/src/AbstractFile.php @@ -9,18 +9,18 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ namespace Desarrolla2\Cache; +use Desarrolla2\Cache\AbstractCache; use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; -use Desarrolla2\Cache\Packer\PhpPacker; /** - * FlatFile. + * Abstract class for using files as cache. + * + * @package Desarrolla2\Cache */ abstract class AbstractFile extends AbstractCache { @@ -32,21 +32,22 @@ abstract class AbstractFile extends AbstractCache /** * @var string */ - protected $filePrefix = '__'; + protected $filePrefix = ''; /** - * @var string|null + * @var string */ protected $fileSuffix; + /** * @param string $cacheDir * @throws CacheException */ - public function __construct($cacheDir = null) + public function __construct(string $cacheDir = null) { if (!$cacheDir) { - $cacheDir = realcacheFile(sys_get_temp_dir()) . '/cache'; + $cacheDir = realpath(sys_get_temp_dir()) . '/cache'; } $this->cacheDir = (string)$cacheDir; @@ -57,8 +58,9 @@ public function __construct($cacheDir = null) * Set the file prefix * * @param string $filePrefix + * @return void */ - public function setFilePrefixOption($filePrefix) + protected function setFilePrefixOption(string $filePrefix): void { $this->filePrefix = $filePrefix; } @@ -68,7 +70,7 @@ public function setFilePrefixOption($filePrefix) * * @return string */ - public function getFilePrefixOption() + protected function getFilePrefixOption(): string { return $this->filePrefix; } @@ -77,8 +79,9 @@ public function getFilePrefixOption() * Set the file extension * * @param string $fileSuffix + * @return void */ - public function setFileSuffixOption($fileSuffix) + protected function setFileSuffixOption(string $fileSuffix): void { $this->fileSuffix = $fileSuffix; } @@ -88,7 +91,7 @@ public function setFileSuffixOption($fileSuffix) * * @return string */ - public function getFileSuffixOption() + protected function getFileSuffixOption(): string { return isset($this->fileSuffix) ? $this->fileSuffix : ('.' . $this->getPacker()->getType()); } @@ -98,8 +101,10 @@ public function getFileSuffixOption() * Create the cache directory * * @param string $cacheFile + * @return void + * @throws CacheException */ - protected function createCacheDirectory($path) + protected function createCacheDirectory(string $path): void { if (!is_dir($path)) { if (!mkdir($path, 0777, true)) { @@ -112,24 +117,62 @@ protected function createCacheDirectory($path) } } + /** + * Read the cache file + * + * @param $cacheFile + * @return string + */ + protected function readFile($cacheFile): string + { + return file_get_contents($cacheFile); + } + + /** + * Read the first line of the cache file + * + * @param string $cacheFile + * @return string + */ + protected function readLine(string $cacheFile): string + { + $fp = fopen($cacheFile, 'r'); + $line = fgets($fp); + fclose($fp); + + return $line; + } + + /** + * Create a cache file + * + * @param string $cacheFile + * @param string $contents + * @return bool + */ + protected function writeFile(string $cacheFile, string $contents): bool + { + return (bool)file_put_contents($cacheFile, $contents); + } + /** * Delete a cache file * * @param string $file * @return bool */ - protected function deleteFile($file) + protected function deleteFile(string $file): bool { - return is_file($file) && unlink($file); + return !is_file($file) || unlink($file); } /** * Create a filename based on the key * - * @param $key + * @param string|mixed $key * @return string */ - protected function getFileName($key) + protected function getFileName($key): string { return $this->cacheDir. DIRECTORY_SEPARATOR. @@ -137,5 +180,4 @@ protected function getFileName($key) $this->getKey($key). $this->getFileSuffixOption(); } - -} +} \ No newline at end of file diff --git a/src/Apcu.php b/src/Apcu.php index 4c219e7..cc6b2fd 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -9,13 +9,16 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Packer\NopPacker; use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\NopPacker; /** * Apcu @@ -23,26 +26,28 @@ class Apcu extends AbstractCache { /** - * Get the packer + * Create the default packer for this cache implementation * * @return PackerInterface */ - protected function getPacker() + protected static function createDefaultPacker(): PackerInterface { - if (!isset($this->packer)) { - $this->packer = new NopPacker(); - } - - return $this->packer; + return new NopPacker(); } /** * {@inheritdoc} */ - public function delete($key) + public function set($key, $value, $ttl = null) { - return apcu_delete($this->getKey($key)); + $ttlSeconds = $this->ttlToSeconds($ttl ?? $this->ttl); + + if (isset($ttlSeconds) && $ttlSeconds <= 0) { + return $this->delete($key); + } + + return apcu_store($this->getKey($key), $this->pack($value), $ttlSeconds ?? 0); } /** @@ -64,22 +69,25 @@ public function get($key, $default = null) */ public function has($key) { - return apcu_exists($key); + return apcu_exists($this->getKey($key)); } - + /** * {@inheritdoc} */ - public function clear() + public function delete($key) { - return apcu_clear_cache(); + $cacheKey = $this->getKey($key); + + return apcu_delete($cacheKey) || !apcu_exists($cacheKey); } /** * {@inheritdoc} */ - public function set($key, $value, $ttl = null) + public function clear() { - return apcu_store($this->getKey($key), $this->pack($value), $ttl ?: $this->ttl); + return apcu_clear_cache(); } + } diff --git a/src/CacheInterface.php b/src/CacheInterface.php index c0241bb..89e1a58 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -9,11 +9,14 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ namespace Desarrolla2\Cache; use Psr\SimpleCache\CacheInterface as PsrCacheInterface; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\KeyMaker\KeyMakerInterface; /** * CacheInterface @@ -25,13 +28,40 @@ interface CacheInterface extends PsrCacheInterface * * @param string $key * @param string $value + * @return static */ - public function setOption($key, $value); - + public function withOption($key, $value); + + /** + * Set multiple options for cache + * + * @param array $options + * @return static + */ + public function withOptions(array $options); + /** * Get option for cache * * @param string $key + * @return mixed */ public function getOption($key); + + /** + * Set the packer + * + * @param PackerInterface $packer + * @return static + */ + public function withPacker(PackerInterface $packer); + + /** + * Set the key maker + * + * @param KeyMakerInterface $keyMaker + * @return static + */ + public function withKeyMaker(KeyMakerInterface $keyMaker); + } diff --git a/src/Exception/CacheExpiredException.php b/src/Exception/CacheExpiredException.php deleted file mode 100644 index eb55c2a..0000000 --- a/src/Exception/CacheExpiredException.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @author Arnold Daniels - */ - -namespace Desarrolla2\Cache\Exception; - -use Psr\SimpleCache\CacheException as PsrCacheException; - -/** - * Exception for unexpected values when reading from cache. - */ -class CacheExpiredException extends \Exception implements PsrCacheException -{ - var $code = E_USER_NOTICE; -} diff --git a/src/File.php b/src/File.php index 88bdf0c..4b3b6d7 100644 --- a/src/File.php +++ b/src/File.php @@ -9,36 +9,133 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; +use Desarrolla2\Cache\AbstractFile; use Desarrolla2\Cache\Exception\InvalidArgumentException; -use Desarrolla2\Cache\Packer\PhpPacker; +use Desarrolla2\Cache\Exception\UnexpectedValueException; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\SerializePacker; /** * Cache file. - * Data contains both value and ttl */ class File extends AbstractFile { + /** + * @var string 'embed', 'file', 'mtime' + */ + protected $ttlStrategy = 'embed'; + + /** + * Create the default packer for this cache implementation + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new SerializePacker(); + } + + /** + * Set TTL strategy + * + * @param string $strategy + */ + protected function setTtlStrategyOption($strategy) + { + if (!in_array($strategy, ['embed', 'file', 'mtime'])) { + throw new InvalidArgumentException("Unknown strategry '$strategy', should be 'embed', 'file' or 'mtime'"); + } + + $this->ttlStrategy = $strategy; + } + + /** + * Get TTL strategy + * + * @return bool + */ + protected function getTtlStrategyOption() + { + return $this->useTtlFile; + } + + + /** + * Get the TTL using one of the strategies + * + * @param string $cacheFile + * @return int + */ + protected function getTtl(string $cacheFile) + { + switch ($this->ttlStrategy) { + case 'embed': + return (int)$this->readLine($cacheFile); + case 'file': + return file_exists($cacheFile . '.ttl') + ? (int)file_get_contents($cacheFile . '.ttl') + : PHP_INT_MAX; + case 'mtime': + return $this->getTtl() > 0 ? filemtime($cacheFile) + $this->ttl : PHP_INT_MAX; + } + } + + /** + * Set the TTL using one of the strategies + * + * @param int $expiration + * @param string $contents + * @param string $cacheFile + * @return string The (modified) contents + */ + protected function setTtl($expiration, $contents, $cacheFile) + { + switch ($this->ttlStrategy) { + case 'embed': + $contents = ($expiration ?: PHP_INT_MAX) . "\n" . $contents; + break; + case 'file': + file_put_contents("$cacheFile.ttl", $expiration); + break; + case 'mtime': + // nothing + break; + } + + return $contents; + } + + /** * {@inheritdoc} */ - public function delete($key) + public function get($key, $default = null) { + if (!$this->has($key)) { + return $default; + } + $cacheFile = $this->getFileName($key); + $packed = $this->readFile($cacheFile); - return $this->deleteFile($cacheFile); + if ($this->ttlStrategy === 'embed') { + $packed = substr($packed, strpos($packed, "\n") + 1); + } + + return $this->unpack($packed); } /** * {@inheritdoc} */ - public function get($key, $default = null) + public function has($key) { $cacheFile = $this->getFileName($key); @@ -46,44 +143,56 @@ public function get($key, $default = null) return false; } - $contents = $this->read($cacheFile); + $ttl = $this->getTtl($cacheFile); - if ($contents['ttl'] < self::time()) { + if ($ttl <= time()) { + $this->deleteFile($cacheFile); return false; } - return $contents['value']; + return true; } /** * {@inheritdoc} */ - public function has($key) + public function set($key, $value, $ttl = null) { $cacheFile = $this->getFileName($key); - if (!file_exists($cacheFile)) { - return false; + $packed = $this->pack($value); + + if (!is_string($packed)) { + throw new UnexpectedValueException("Packer must create a string for the data to be cached to file"); } - $contents = $this->read($cacheFile); + $contents = $this->setTtl($this->ttlToTimestamp($ttl), $packed, $cacheFile); - return $contents['ttl'] < self::time(); + return $this->writeFile($cacheFile, $contents); } /** * {@inheritdoc} */ - public function set($key, $value, $ttl = null) + public function delete($key) { $cacheFile = $this->getFileName($key); - $packed = $this->pack(compact('value', 'ttl')); + return $this->deleteFile($cacheFile); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $pattern = $this->cacheDir . DIRECTORY_SEPARATOR . + $this->getFilePrefixOption() . '*' . $this->getFileSuffixOption(); - if (!is_string($packed)) { - throw new UnexpectedValueException("Packer must create a string for the data to be cached to file"); + foreach (glob($pattern) as $file) { + $this->deleteFile($file); } - return file_put_contents($cacheFile, $packed); + return true; } } diff --git a/src/FlatFile.php b/src/FlatFile.php deleted file mode 100644 index 2d9917e..0000000 --- a/src/FlatFile.php +++ /dev/null @@ -1,212 +0,0 @@ - - */ - -namespace Desarrolla2\Cache; - -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Packer\PhpPacker; - -/** - * Flat file. - * TTL will place in it's own file. - */ -class FlatFile extends AbstractCache -{ - /** - * @var string - */ - protected $cacheDir; - - /** - * @var string - */ - protected $filePrefix = '__'; - - /** - * @var string|null - */ - protected $fileSuffix; - - /** - * @param string $cacheDir - * @throws CacheException - */ - public function __construct($cacheDir = null) - { - if (!$cacheDir) { - $cacheDir = realcacheFile(sys_get_temp_dir()) . '/cache'; - } - - $this->cacheDir = (string)$cacheDir; - $this->createCacheDirectory($cacheDir); - } - - /** - * Set the file prefix - * - * @param string $filePrefix - */ - public function setFilePrefixOption($filePrefix) - { - $this->filePrefix = $filePrefix; - } - - /** - * Get the file prefix - * - * @return string - */ - public function getFilePrefixOption() - { - return $this->filePrefix; - } - - /** - * Set the file extension - * - * @param string $fileSuffix - */ - public function setFileSuffixOption($fileSuffix) - { - $this->fileSuffix = $fileSuffix; - } - - /** - * Get the file extension - * - * @return string - */ - public function getFileSuffixOption() - { - return isset($this->fileSuffix) ? $this->fileSuffix : ('.' . $this->getPacker()->getType()); - } - - - /** - * {@inheritdoc} - */ - public function delete($key) - { - $cacheFile = $this->getFileName($key); - - return $this->deleteFile($cacheFile); - } - - /** - * {@inheritdoc} - */ - public function get($key, $default = null) - { - $cacheFile = $this->getFileName($key); - - if (!$this->has($key)) { - return $default; - } - - return $this->read($cacheFile); - } - - /** - * {@inheritdoc} - */ - public function has($key) - { - $ttlFile = $this->getFileName($key) . '.ttl'; - - if (!file_exists($ttlFile)) { - return false; - } - - $ttl = $this->read($ttlFile); - - return self::time() < $ttl; - } - - /** - * {@inheritdoc} - */ - public function set($key, $value, $ttl = null) - { - $cacheFile = $this->getFileName($key); - $ttlFile = $this->getFileName($key) . '.ttl'; - - $packed = $this->pack($value); - $packedTtl = $this->pack(self::time() + ($ttl ?: $this->ttl)); - - if (!is_string($packed)) { - throw new UnexpectedValueException("Packer must create a string for the data to be cached to file"); - } - - return file_put_contents($cacheFile, $packed) && file_put_contents($ttlFile, $packedTtl); - } - - /** - * Create the cache directory - * - * @param string $cacheFile - */ - protected function createCacheDirectory($path) - { - if (!is_dir($path)) { - if (!mkdir($path, 0777, true)) { - throw new CacheException($path.' is not writable'); - } - } - - if (!is_writable($path)) { - throw new CacheException($path.' is not writable'); - } - } - - /** - * Delete a cache file - * - * @param string $cacheFile - * @return bool - */ - protected function deleteFile($cacheFile) - { - return - (!is_file($cacheFile) || unlink($cacheFile)) && - (!is_file($cacheFile . '.ttl') || unlink($cacheFile . '.ttl')); - } - - /** - * Create a filename based on the key - * - * @param $key - * @return string - */ - protected function getFileName($key) - { - return $this->cacheDir. - DIRECTORY_SEPARATOR. - $this->getFilePrefixOption(). - $this->getKey($key). - $this->getFileSuffixOption(); - } - - /** - * Read the cache file - * - * @param string $cacheFile - * @return mixed - */ - protected function read($cacheFile) - { - return $this->packer instanceof PhpPacker - ? include $cacheFile - : $this->unpack(file_get_contents($cacheFile)); - } -} diff --git a/src/KeyMaker/AbstractKeyMaker.php b/src/KeyMaker/AbstractKeyMaker.php new file mode 100644 index 0000000..13ff080 --- /dev/null +++ b/src/KeyMaker/AbstractKeyMaker.php @@ -0,0 +1,51 @@ +prefix; + } + + + /** + * PSR-16 key validation + * + * @param string|mixed $key + * @throws InvalidArgumentException + */ + protected function validateKey($key) + { + if (!is_string($key)) { + $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); + throw new InvalidArgumentException("Expected key to be a string, not $type"); + } + + if ($key === '' || preg_match('~[{}()/\\\\@:]~', $key)) { + throw new InvalidArgumentException("Invalid key '$key'"); + } + } +} \ No newline at end of file diff --git a/src/KeyMaker/HashKeyMaker.php b/src/KeyMaker/HashKeyMaker.php index b8c3753..eab8269 100644 --- a/src/KeyMaker/HashKeyMaker.php +++ b/src/KeyMaker/HashKeyMaker.php @@ -1,52 +1,58 @@ prefix = $prefix; $this->algo = $algo; + $this->prefix = $prefix; } /** - * Get the key prefix + * Lenient key validation * - * @return string + * @param string|mixed $key + * @throws InvalidArgumentException */ - public function getPrefix() + protected function validateKey($key) { - return $this->prefix; + if (!is_scalar($key)) { + $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); + throw new InvalidArgumentException("Expected key to be a scalar, not $type"); + } } /** * Get the key with prefix * - * @param string $key + * @param string|mixed $key * @return string */ - public function make($key) + public function make($key): string { + $this->validateKey($key); + return hash($this->algo, sprintf('%s%s', $this->prefix, $key)); } -} \ No newline at end of file +} diff --git a/src/KeyMaker/KeyMakerInterface.php b/src/KeyMaker/KeyMakerInterface.php index e0638f4..b49f3ef 100644 --- a/src/KeyMaker/KeyMakerInterface.php +++ b/src/KeyMaker/KeyMakerInterface.php @@ -12,13 +12,13 @@ interface KeyMakerInterface * * @return string */ - public function getPrefix(); + public function getPrefix(): string; /** * Get the key with prefix * - * @param string $key + * @param string|mixed $key * @return string */ - public function make($key); + public function make($key): string; } \ No newline at end of file diff --git a/src/KeyMaker/PlainKeyMaker.php b/src/KeyMaker/PlainKeyMaker.php index 9954e62..b9e06df 100644 --- a/src/KeyMaker/PlainKeyMaker.php +++ b/src/KeyMaker/PlainKeyMaker.php @@ -1,18 +1,14 @@ prefix = $prefix; } - /** - * Get the key prefix - * - * @return string - */ - public function getPrefix() - { - return $this->prefix; - } - /** * Get the key with prefix * - * @param string $key + * @param string|mixed $key * @return string + * @throws InvalidArgumentException if key is not a alphanumeric string */ - public function make($key) + public function make($key): string { + $this->validateKey($key); + return sprintf('%s%s', $this->prefix, $key); } -} \ No newline at end of file +} diff --git a/src/Memcached.php b/src/Memcached.php index 7032c52..51953bb 100644 --- a/src/Memcached.php +++ b/src/Memcached.php @@ -9,15 +9,15 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); namespace Desarrolla2\Cache; -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\NopPacker; use Memcached as BaseMemcached; /** @@ -43,6 +43,17 @@ public function __construct(BaseMemcached $server = null) $this->server = $server; } + /** + * Create the default packer for this cache implementation + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new NopPacker(); + } + + /** * {@inheritdoc} */ @@ -97,7 +108,7 @@ public function has($key) public function set($key, $value, $ttl = null) { $packed = $this->pack($value, $ttl); - $ttlTime = static::time() + ($ttl ?: $this->ttl); + $ttlTime = $this->ttlToTimestamp($ttl); return $this->server->set($this->getKey($key), $packed, $ttlTime); } @@ -115,9 +126,7 @@ public function setMultiple($values, $ttl = null) $this->pack($value); }, $values); - $ttlTime = static::time() + ($ttl ?: $this->ttl); - - return $this->server->setMulti(array_combine($cacheKeys, $packed), $ttlTime); + return $this->server->setMulti(array_combine($cacheKeys, $packed), $this->ttlToTimestamp($ttl)); } /** diff --git a/src/Memory.php b/src/Memory.php index 96d6c00..bf85666 100644 --- a/src/Memory.php +++ b/src/Memory.php @@ -9,14 +9,15 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\SerializePacker; /** * Memory @@ -24,10 +25,12 @@ class Memory extends AbstractCache { /** + * Limit the amount of entries * @var int */ protected $limit = PHP_INT_MAX; + /** * @var array */ @@ -39,14 +42,41 @@ class Memory extends AbstractCache protected $cacheTtl = []; + /** + * Create the default packer for this cache implementation. + * {@internal NopPacker might fail PSR-16, as cached objects would change} + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new SerializePacker(); + } + + /** + * Make a clone of this object. + * Set by cache reference, thus using the same pool. + * + * @return static + */ + protected function cloneSelf() + { + $clone = clone $this; + + $clone->cache =& $this->cache; + $clone->cacheTtl =& $this->cacheTtl; + + return $clone; + } + /** * Set the max number of items * * @param int $limit */ - public function setLimitOption($value) + protected function setLimitOption($limit) { - $this->limit = (int)$value ?: PHP_INT_MAX; + $this->limit = (int)$limit ?: PHP_INT_MAX; } /** @@ -54,7 +84,7 @@ public function setLimitOption($value) * * @return int */ - public function getLimitOption() + protected function getLimitOption() { return $this->limit; } @@ -96,7 +126,7 @@ public function has($key) return false; } - if ($this->cacheTtl[$cacheKey] < self::time()) { + if ($this->cacheTtl[$cacheKey] <= time()) { unset($this->cache[$cacheKey], $this->cacheTtl[$cacheKey]); return false; } @@ -109,14 +139,15 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - if (count($this->cache) > $this->limit) { - array_shift($this->cache); + if (count($this->cache) >= $this->limit) { + $deleteKey = key($this->cache); + unset($this->cache[$deleteKey], $this->cacheTtl[$deleteKey]); } $cacheKey = $this->getKey($key); $this->cache[$cacheKey] = $this->pack($value); - $this->cacheTtl[$cacheKey] = self::time() + ($ttl ?: $this->ttl); + $this->cacheTtl[$cacheKey] = $this->ttlToTimestamp($ttl ?? $this->ttl) ?? PHP_INT_MAX; return true; } @@ -128,5 +159,7 @@ public function clear() { $this->cache = []; $this->cacheTtl = []; + + return true; } } diff --git a/src/Mongo.php b/src/Mongo.php index 800ae0d..533dad4 100644 --- a/src/Mongo.php +++ b/src/Mongo.php @@ -9,16 +9,18 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; use Desarrolla2\Cache\Exception\UnexpectedValueException; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\SerializePacker; use MongoDB; -use MongoClient; -use MongoCollection; -use MongoDate; -use MongoConnectionException; +use MongoDB\BSON\UTCDatetime as BSONUTCDateTime; /** * Mongo @@ -26,7 +28,7 @@ class Mongo extends AbstractCache { /** - * @var MongoCollection|MongoDb\Collection + * @var MongoDb\Collection */ protected $collection; @@ -35,27 +37,26 @@ class Mongo extends AbstractCache * * @param mixed $backend * @throws UnexpectedValueException - * @throws MongoConnectionException * @throws MongoDB\Driver\Exception */ public function __construct($backend = null) { if (!isset($backend)) { - $backend = class_exist('MongoClient') ? new MongoClient() : new MongoDB\Client(); + $backend = new MongoDB\Client(); } - if ($backend instanceof MongoClient || $backend instanceof MongoDB\Client) { + if ($backend instanceof MongoDB\Client) { $backend = $backend->selectDatabase('cache'); } - if ($backend instanceof MongoCollection || $backend instanceof MongoDB\Collection) { + if ($backend instanceof MongoDB\Collection) { $backend = $backend->selectCollection('items'); } - if (!$backend instanceof MongoDB && !$backend instanceof MongoDB\Database) { + if (!$backend instanceof MongoDB\Database) { $type = (is_object($backend) ? get_class($backend) . ' ' : '') . gettype($backend); - throw new UnexpectedValueException("Database should be a database (MongoDB or MongoDB\Database) or " . - " collection (MongoCollection or MongoDB\Collection) object, not a $type"); + throw new UnexpectedValueException("Database should be a database (MongoDB\Database) or " . + " collection (MongoDB\Collection) object, not a $type"); } $this->collection = $backend; @@ -72,6 +73,25 @@ protected function initCollection() $this->collection->createIndex(['ttl' => 1], ['expireAfterSeconds' => 0]); } + /** + * Class destructor + */ + public function __destruct() + { + $this->predis->disconnect(); + } + + + /** + * Create the default packer for this cache implementation. + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new SerializePacker(); + } + /** * {@inheritdoc} @@ -133,11 +153,11 @@ public function set($key, $value, $ttl = null) $item = [ '_id' => $cacheKey, - 'ttl' => $this->getTtl($ttl ?: $this->ttl), - 'value' => $this->pack($value), + 'ttl' => $this->getTtlBSON($ttl ?: $this->ttl), + 'value' => $this->pack($value) ]; - $this->collection->update(['_id' => $cacheKey], $item, ['upsert' => true]); + $this->collection->save(['_id' => $cacheKey], $item, ['upsert' => true]); } /** @@ -151,23 +171,26 @@ public function setMultiple($values, $ttl = null) return true; } - $filters = []; + $bsonTtl = $this->getTtlBSON($ttl ?: $this->ttl); $items = []; foreach ($values as $key => $value) { $cacheKey = $this->getKey($key); - $filters[] = ['_id' => $cacheKey]; - - $items[] = array( - '_id' => $cacheKey, - 'ttl' => $this->getTtl($ttl ?: $this->ttl), - 'value' => $this->pack($value), - ); + $items[] = [ + 'replaceOne' => [ + 'filter' => ['_id' => $cacheKey], + 'replacement' => [ + '_id' => $cacheKey, + 'ttl' => $bsonTtl, + 'value' => $this->pack($value) + ], + 'upsert' => true + ] + ]; } - $this->collection->updateMany($filters, $values, ['upsert' => true]); - + $this->collection->bulkWrite($items); } /** @@ -176,20 +199,17 @@ public function setMultiple($values, $ttl = null) public function clear() { $this->collection->dropCollection(); - $this->initCollection(); } /** * Get TTL as Date type BSON object * - * @param int $ttl - * @return MongoDate|MongoDB\BSON\UTCDatetime + * @param int|null $ttl + * @return BSONUTCDatetime|null */ - protected function getTtl($ttl = 0) + protected function getTtlBSON(?int $ttl): ?BSONUTCDatetime { - return $this->collection instanceof MongoCollection ? - new MongoDate(self::time() + (int)$ttl) : - new MongoDB\BSON\UTCDatetime((self::time() + (int)$ttl) * 1000); + return isset($ttl) ? new BSONUTCDateTime($this->ttlToSeconds($ttl) * 1000) : null; } } diff --git a/src/Mysqli.php b/src/Mysqli.php index 827bc15..fb090cc 100644 --- a/src/Mysqli.php +++ b/src/Mysqli.php @@ -9,11 +9,16 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; use mysqli as Server; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\SerializePacker; /** * Mysqli @@ -38,6 +43,16 @@ public function __construct(Server $server = null) $this->server = $server ?: new Server(); } + /** + * Create the default packer for this cache implementation. + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new SerializePacker(); + } + /** * Set the table name * @@ -75,7 +90,7 @@ public function get($key, $default = null) $row = $this->fetchRow( 'SELECT `value` FROM {table} WHERE `key` = %s AND `ttl` >= %d LIMIT 1', $this->getKey($key), - self::time() + $this->time() ); return $row ? $this->unpack($row[0]) : $default; @@ -98,7 +113,7 @@ public function getMultiple($keys, $default = null) $ret = $this->query( 'SELECT `key`, `value` FROM {table} WHERE `key` IN `%s` AND `ttl` >= %d', $cacheKeys, - self::time() + $this->time() ); while ((list($key, $value) = $ret->fetch_assoc())) { @@ -116,7 +131,7 @@ public function has($key) $row = $this->fetchRow( 'SELECT `key` FROM {table} WHERE `key` = %s AND `ttl` >= %d LIMIT 1', $this->getKey($key), - self::time() + $this->time() ); return !empty($row); @@ -131,7 +146,7 @@ public function set($key, $value, $ttl = null) 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES (%s, %s, %d)', $this->getKey($key), $this->pack($value), - self::time() + ($ttl ?: $this->ttl) + $this->time() + ($ttl ?: $this->ttl) ); return $res !== false; @@ -148,7 +163,7 @@ public function setMultiple($values, $ttl = null) return true; } - $timeTtl = self::time() + ($ttl ?: $this->ttl); + $timeTtl = $this->time() + ($ttl ?: $this->ttl); $query = 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES'; foreach ($values as $key => $value) { diff --git a/src/NotCache.php b/src/NotCache.php index 959e927..1aafc7f 100644 --- a/src/NotCache.php +++ b/src/NotCache.php @@ -9,20 +9,32 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\AbstractCache; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\NopPacker; /** * Dummy cache handler */ class NotCache extends AbstractCache { + /** + * Create the default packer for this cache implementation. + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new NopPacker(); + } + /** * {@inheritdoc} */ diff --git a/src/Packer/PhpPacker.php b/src/Packer/PhpPacker.php deleted file mode 100644 index 48efa9c..0000000 --- a/src/Packer/PhpPacker.php +++ /dev/null @@ -1,67 +0,0 @@ - - * @author Arnold Daniels - */ - -namespace Desarrolla2\Cache\Packer; - -use Desarrolla2\Cache\Packer\PackerInterface; -use Desarrolla2\Cache\Exception\BadMethodCallException; - -/** - * Export to code that can be evaluated by PHP. - * - * This packer only works for file caching. - * It can be expected to behave much faster to normal caching, as opcode cache is utilized. - */ -class PhpPacker implements PackerInterface -{ - /** - * Get cache type (might be used as file extension) - * - * @return string - */ - public function getType() - { - return 'php'; - } - - /** - * Pack the value - * - * @param mixed $value - * @return string - */ - public function pack($value) - { - $macro = var_export($value, true); - - if (strpos($macro, 'stdClass::__set_state') !== false) { - $macro = preg_replace_callback("/('([^'\\\\]++|''\\.)')|stdClass::__set_state/", $macro, function($match) { - return empty($match[1]) ? '(object)' : $match[1]; - }); - } - - return ' + * @author Arnold Daniels + */ + +declare(strict_types=1); + +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\AbstractFile; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\SerializePacker; + +/** + * Cache file as PHP script. + */ +class PhpFile extends AbstractFile +{ + /** + * @var string + */ + protected $fileSuffix = '.php'; + + /** + * Create the default packer for this cache implementation. + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new SerializePacker(); + } + + /** + * Create a PHP script returning the cached value + * + * @param mixed $value + * @param int|null $ttl + * @return string + */ + public function createScript($value, ?int $ttl): string + { + $macro = var_export($value, true); + + if (strpos($macro, 'stdClass::__set_state') !== false) { + $macro = preg_replace_callback("/('([^'\\\\]++|''\\.)')|stdClass::__set_state/", $macro, function($match) { + return empty($match[1]) ? '(object)' : $match[1]; + }); + } + + return $ttl !== null + ? "getFileName($key); + + if (!file_exists($cacheFile)) { + return $default; + } + + $packed = include $cacheFile; + + return $packed === false ? $default : $this->unpack($packed); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return $this->get($key) !== null; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $cacheFile = $this->getFileName($key); + + $packed = $this->pack($value); + $script = $this->createScript($packed, $this->ttlToTimestamp($ttl)); + + return $this->write($cacheFile, $script); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $pattern = $this->cacheDir . DIRECTORY_SEPARATOR . + $this->getFilePrefixOption() . '*' . $this->getFileSuffixOption(); + + foreach (glob($pattern) as $file) { + $this->deleteFile($file); + } + } +} diff --git a/src/Predis.php b/src/Predis.php index d15e22c..efdad03 100644 --- a/src/Predis.php +++ b/src/Predis.php @@ -9,14 +9,16 @@ * file that was distributed with this source code. * * @author Daniel González + * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache; -use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\CacheExpiredException; -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\AbstractCache; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\SerializePacker; use Predis\Client; /** @@ -52,15 +54,22 @@ public function __destruct() $this->predis->disconnect(); } + /** + * Create the default packer for this cache implementation. + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new SerializePacker(); + } + /** * {@inheritdoc} */ public function delete($key) { - $cmd = $this->predis->createCommand('DEL'); - $cmd->setArguments([$this->getKey($key)]); - - $this->predis->executeCommand($cmd); + return $this->predis->executeRaw(['DEL', $this->getKey($key)]); } /** @@ -76,12 +85,29 @@ public function get($key, $default = null) /** * {@inheritdoc} */ - public function has($key) + public function getMultiple($keys, $default = null) { - $cmd = $this->predis->createCommand('EXISTS'); - $cmd->setArguments([$this->getKey($key)]); + $this->assertIterable($keys, 'keys not iterable'); - return $this->predis->executeCommand($cmd); + $transaction = $this->predis->transaction(); + + foreach ($keys as $key) { + $transaction->get($key); + } + + $responses = $transaction->execute(); + + return array_map(function ($value) use ($default) { + return is_string($value) ? $this->unpack($value) : $default; + }, $responses); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return $this->predis->executeRaw(['EXISTS', $this->getKey($key)]); } /** @@ -93,15 +119,42 @@ public function set($key, $value, $ttl = null) $set = $this->predis->set($cacheKey, $this->pack($value)); - if (!$set) { - return false; + if ($set && isset($ttl)) { + $this->predis->executeRaw(['EXPIRE', $cacheKey, $this->ttlToSeconds($ttl)]); + } + + return $set; + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $this->assertIterable($values, 'values not iterable'); + + $transaction = $this->predis->transaction(); + $ttlSeconds = $this->ttlToSeconds($ttl); + + foreach ($values as $key => $value) { + $cacheKey = $this->getKey($key); + $transaction->set($cacheKey); + + if (isset($ttlSeconds)) { + $transaction->executeRaw(['EXPIRE', $cacheKey, $ttlSeconds]); + } } - $cmd = $this->predis->createCommand('EXPIRE'); - $cmd->setArguments([$cacheKey, $ttl]); + $responses = $transaction->execute(); - $this->predis->executeCommand($cmd); + return count(array_filter($responses)) > 0; + } - return true; + /** + * {@inheritdoc} + */ + public function clear() + { + $this->predis->executeRaw(['FLUSHDB']); } } diff --git a/tests/AbstractCacheTest.php b/tests/AbstractCacheTest.php index 55a394b..41564d3 100644 --- a/tests/AbstractCacheTest.php +++ b/tests/AbstractCacheTest.php @@ -13,13 +13,13 @@ namespace Desarrolla2\Test\Cache; -use PHPUnit\Framework\TestCase; -use Carbon\Carbon; +use Cache\IntegrationTests\SimpleCacheTest; +use Desarrolla2\Cache\Exception\InvalidArgumentException; /** * AbstractCacheTest */ -abstract class AbstractCacheTest extends TestCase +abstract class AbstractCacheTest extends SimpleCacheTest { /** * @var \Desarrolla2\Cache\Cache @@ -31,7 +31,7 @@ abstract class AbstractCacheTest extends TestCase */ protected $config = []; - public function setup() + public function setUp() { $configurationFile = __DIR__.'/config.json'; @@ -39,22 +39,8 @@ public function setup() throw new \Exception(' Configuration file not found in "'.$configurationFile.'" '); } $this->config = json_decode(file_get_contents($configurationFile), true); - } - /** - * @return array - */ - public function dataProvider() - { - return [ - ['key1', 'value1', 1], - ['key2', 'value2', 100], - ['key3', 'value3', null], - ['key4', true, null], - ['key5', false, null], - ['key6', [], null], - ['key7', new \DateTime(), null], - ]; + parent::setUp(); } /** @@ -68,65 +54,31 @@ public function dataProviderForOptions() } /** + * @dataProvider dataProviderForOptions * - * @dataProvider dataProvider - * - * @param string $key - * @param mixed $value - * @param int|null $ttl - */ - public function testHas($key, $value, $ttl) - { - $this->cache->delete($key); - $this->assertFalse($this->cache->has($key)); - - $this->assertTrue($this->cache->set($key, $value, $ttl)); - $this->assertTrue($this->cache->has($key)); - } - - /** - * - * @dataProvider dataProvider - * - * @param string $key - * @param mixed $value - * @param int|null $ttl - */ - public function testGet($key, $value, $ttl) - { - $this->cache->set($key, $value, $ttl); - $this->assertEquals($value, $this->cache->get($key)); - } - - /** - * - * @dataProvider dataProvider - * - * @param string $key - * @param mixed $value - * @param int|null $ttl + * @param string $key + * @param mixed $value */ - public function testDelete($key, $value, $ttl) + public function testWithOption($key, $value) { - $this->cache->set($key, $value, $ttl); - $this->assertTrue($this->cache->delete($key)); - $this->assertFalse($this->cache->has($key)); - } + $base = $this->createSimpleCache(); + $cache = $base->withOption($key, $value); + $this->assertEquals($value, $cache->getOption($key)); - public function testDeleteNonExisting() - { - $this->assertFalse($this->cache->delete('key0')); + // Check immutability + $this->assertNotSame($base, $cache); + $this->assertNotEquals($value, $base->getOption($key)); } /** - * @dataProvider dataProviderForOptions - * - * @param string $key - * @param mixed $value + * @return array */ - public function testSetOption($key, $value) + public function dataProviderForOptionsException() { - $this->assertTrue($this->cache->setOption($key, $value)); + return [ + ['ttl', 0, InvalidArgumentException::class], + ['file', 100, InvalidArgumentException::class] + ]; } /** @@ -136,39 +88,9 @@ public function testSetOption($key, $value) * @param mixed $value * @param string $expectedException */ - public function testSetOptionException($key, $value, $expectedException) + public function testWithOptionException($key, $value, $expectedException) { $this->expectException($expectedException); - $this->cache->setOption($key, $value); - } - - - public function testHasWithTtlExpired() - { - Carbon::setTestNow(Carbon::now()->subRealSecond(10)); // Pretend it's 10 seconds ago - - $this->cache->set('key1', 'value1', 1); // TTL of 1 second - - Carbon::setTestNow(); // Back to actual time, cache is now timed out - static::sleep(2); - - $this->assertFalse($this->cache->has('key1')); - } - - - public function testReturnDefaultValue() - { - $this->assertEquals($this->cache->get('key0', 'foo'), 'foo'); - } - - - /** - * Tests can overwrite if they don't need to sleep and can work with carbon - * - * @param int $seconds - */ - protected static function sleep($seconds) - { - sleep($seconds); + $this->createSimpleCache()->withOption($key, $value); } } diff --git a/tests/ApcuCacheTest.php b/tests/ApcuCacheTest.php index 365431d..7b96cf4 100644 --- a/tests/ApcuCacheTest.php +++ b/tests/ApcuCacheTest.php @@ -20,11 +20,14 @@ */ class ApcuCacheTest extends AbstractCacheTest { - public function setUp() + public static function setUpBeforeClass() { // Required to check the TTL for new entries ini_set('apc.use_request_time', false); + } + public function createSimpleCache() + { if (!extension_loaded('apcu')) { $this->markTestSkipped( 'The APCu extension is not available.' @@ -36,17 +39,6 @@ public function setUp() ); } - $this->cache = new ApcuCache(); - } - - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ]; + return new ApcuCache(); } } diff --git a/tests/FileTest.php b/tests/FileTest.php index 9e62149..a3648a2 100644 --- a/tests/FileTest.php +++ b/tests/FileTest.php @@ -13,35 +13,20 @@ namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\FlatFile as FileCache; +use Desarrolla2\Cache\File as FileCache; /** * FileTest */ class FileTest extends AbstractCacheTest { - public function setUp() - { - parent::setup(); - $this->cache = new FileCache($this->config['file']['dir']); - } - - /** - * No sleep - */ - protected static function sleep($seconds) - { - return; - } + protected $skippedTests = [ + 'testBasicUsageWithLongKey' => 'Only support keys up to 64 bytes' + ]; - /** - * @return array - */ - public function dataProviderForOptionsException() + public function createSimpleCache() { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException') - ); + return new FileCache($this->config['file']['dir']); } /** @@ -49,7 +34,8 @@ public function dataProviderForOptionsException() */ public function tearDown() { - array_map('unlink', glob($this->config['file']['dir']."/*")); + parent::tearDown(); + rmdir($this->config['file']['dir']); } } diff --git a/tests/MemcachedTest.php b/tests/MemcachedTest.php index 6d48847..a0a9ff2 100644 --- a/tests/MemcachedTest.php +++ b/tests/MemcachedTest.php @@ -21,9 +21,8 @@ */ class MemcachedTest extends AbstractCacheTest { - public function setUp() + public function createSimpleCache() { - parent::setup(); if (!extension_loaded('memcached') || !class_exists('\Memcached')) { $this->markTestSkipped( 'The Memcached extension is not available.' @@ -37,7 +36,7 @@ public function setUp() return $this->markTestSkipped("Unable to flush Memcached; not running?"); } - $this->cache = new MemcachedCache($adapter); + return new MemcachedCache($adapter); } /** diff --git a/tests/MemoryHashKeyTest.php b/tests/MemoryHashKeyTest.php new file mode 100644 index 0000000..43c3fb2 --- /dev/null +++ b/tests/MemoryHashKeyTest.php @@ -0,0 +1,55 @@ + + */ + +namespace Desarrolla2\Test\Cache; + +use Desarrolla2\Cache\KeyMaker\HashKeyMaker; +use Desarrolla2\Cache\Memory as MemoryCache; + +/** + * Memory with HashKeyMaker. + * Doesn't adhere to PSR-16 keys. + */ +class MemoryHashKeyTest extends AbstractCacheTest +{ + public function createSimpleCache() + { + return (new MemoryCache())->withKeyMaker(new HashKeyMaker()); + } + + /** + * Data provider for invalid keys. + * + * @return array + */ + public static function invalidKeys() + { + return [ + [null], + [new \stdClass()], + [['array']], + ]; + } + + public function testExceededLimit() + { + $cache = $this->createSimpleCache()->withOption('limit', 1); + + $cache->set('foo', 1); + $this->assertTrue($cache->has('foo')); + + $cache->set('bar', 1); + $this->assertFalse($cache->has('foo')); + $this->assertTrue($cache->has('bar')); + } +} diff --git a/tests/MemoryTest.php b/tests/MemoryTest.php index 39c9e46..f5c5a95 100644 --- a/tests/MemoryTest.php +++ b/tests/MemoryTest.php @@ -20,48 +20,20 @@ */ class MemoryTest extends AbstractCacheTest { - public function setUp() + public function createSimpleCache() { - $this->cache = new MemoryCache(); + return new MemoryCache(); } - /** - * No sleep - */ - protected static function sleep($seconds) + public function testExceededLimit() { - return; - } + $cache = $this->createSimpleCache()->withOption('limit', 1); - /** - * @return array - */ - public function dataProviderForOptions() - { - return [ - ['ttl', 100], - ['limit', 100], - ]; - } + $cache->set('foo', 1); + $this->assertTrue($cache->has('foo')); - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ]; - } - - public function testExceededLimit() - { - $limit = 1; - $this->cache->setOption('limit', $limit); - for ($i = 0; $i <= $limit; $i++) { - $this->cache->set($i, $i); - } - $this->assertFalse($this->cache->has($i)); + $cache->set('bar', 1); + $this->assertFalse($cache->has('foo')); + $this->assertTrue($cache->has('bar')); } } diff --git a/tests/MongoTest.php b/tests/MongoTest.php index f0c83b1..d990966 100644 --- a/tests/MongoTest.php +++ b/tests/MongoTest.php @@ -20,9 +20,8 @@ */ class MongoTest extends AbstractCacheTest { - public function setUp() + public function createSimpleCache() { - parent::setup(); if (!extension_loaded('mongo')) { $this->markTestSkipped( 'The mongo extension is not available.' @@ -32,7 +31,7 @@ public function setUp() $client = new \MongoClient($this->config['mongo']['dsn']); $database = $client->selectDB($this->config['mongo']['database']); - $this->cache = new MongoCache($database); + return new MongoCache($database); } /** diff --git a/tests/MysqliTest.php b/tests/MysqliTest.php index 712466b..92f2ddc 100644 --- a/tests/MysqliTest.php +++ b/tests/MysqliTest.php @@ -20,11 +20,17 @@ */ class MysqliTest extends AbstractCacheTest { + /** + * @var \mysqli + */ protected $mysqli; public function setUp() { - parent::setup(); + if (!class_exists('mysqli')) { + return $this->markTestSkipped("mysqli extension not loaded"); + } + $this->mysqli = new \mysqli( $this->config['mysql']['host'], $this->config['mysql']['user'], @@ -33,44 +39,25 @@ public function setUp() $this->config['mysql']['port'] ); - $this->mysqli->query('CREATE DATABASE IF NOT EXISTS `'.$this->config['mysql']['database'].'`;'); + if ($this->mysqli->errno) { + return $this->markTestSkipped($this->mysqli->error); + } + + $this->mysqli->query('CREATE DATABASE IF NOT EXISTS `' . $this->config['mysql']['database'] . '`;'); $this->mysqli->select_db($this->config['mysql']['database']); $this->mysqli->query('CREATE TEMPORARY TABLE IF NOT EXISTS `cache`( `key` VARCHAR(255), `value` TEXT, `ttl` INT UNSIGNED )'); - $this->cache = new MysqliCache($this->mysqli); - } - public function tearDown() - { - $this->mysqli->query('DROP DATABASE IF EXISTS `'.$this->config['mysql']['database'].'`;'); + parent::setUp(); } - /** - * No sleep - */ - protected static function sleep($seconds) + public function createSimpleCache() { - return; + return new MysqliCache($this->mysqli); } - /** - * @return array - */ - public function dataProviderForOptions() - { - return [ - ['ttl', 100] - ]; - } - - /** - * @return array - */ - public function dataProviderForOptionsException() + public function tearDown() { - return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ]; + $this->mysqli->query('DROP DATABASE IF EXISTS `'.$this->config['mysql']['database'].'`;'); } } diff --git a/tests/PhpFileTest.php b/tests/PhpFileTest.php index 86e00c3..bf0c27f 100644 --- a/tests/PhpFileTest.php +++ b/tests/PhpFileTest.php @@ -15,36 +15,16 @@ use Desarrolla2\Cache\FlatFile as FileCache; use Desarrolla2\Cache\Packer\PhpPacker; +use Desarrolla2\Cache\PhpFile; /** * FileTest with PhpPacker */ class PhpFileTest extends AbstractCacheTest { - public function setUp() + public function createSimpleCache() { - parent::setup(); - - $this->cache = new FileCache($this->config['file']['dir']); - $this->cache->setPacker(new PhpPacker()); - } - - /** - * No sleep - */ - protected static function sleep($seconds) - { - return; - } - - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException') - ); + return new PhpFile($this->config['file']['dir']); } /** @@ -52,7 +32,7 @@ public function dataProviderForOptionsException() */ public function tearDown() { - array_map('unlink', glob($this->config['file']['dir']."/*")); + array_map('unlink', glob($this->config['file']['dir'] . "/*")); rmdir($this->config['file']['dir']); } } diff --git a/tests/PredisTest.php b/tests/PredisTest.php index 3f5e848..efe74a8 100644 --- a/tests/PredisTest.php +++ b/tests/PredisTest.php @@ -22,44 +22,19 @@ */ class PredisTest extends AbstractCacheTest { - - public function setUp() + public function createSimpleCache() { - parent::setup(); - if (!class_exists('\Predis\Client')) { - $this->markTestSkipped( - 'The predis library is not available.' - ); + if (!class_exists('Predis\Client')) { + return $this->markTestSkipped('The predis library is not available'); } try { $predis = new Client(); $predis->connect(); } catch (ConnectionException $e) { - $this->markTestSkipped($e->getMessage()); + return $this->markTestSkipped($e->getMessage()); } - $this->cache = new PredisCache($predis); - } - - /** - * @return array - */ - public function dataProviderForOptions() - { - return [ - ['ttl', 100], - ]; - } - - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ]; + return new PredisCache($predis); } } From 48080b0907d0d0c6fc76006f622ebdd05da7b943 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Wed, 13 Jun 2018 23:57:31 +0200 Subject: [PATCH 31/51] Use callable to generate filename Added class to using Trie directory structure --- README.md | 115 +++++++++++++---------------- src/AbstractCache.php | 72 ++++++++++-------- src/AbstractFile.php | 98 +++++++++++++----------- src/CacheInterface.php | 13 +--- src/File.php | 31 +------- src/File/BasicFilename.php | 55 ++++++++++++++ src/File/TrieFilename.php | 108 +++++++++++++++++++++++++++ src/KeyMaker/AbstractKeyMaker.php | 51 ------------- src/KeyMaker/HashKeyMaker.php | 58 --------------- src/KeyMaker/KeyMakerInterface.php | 24 ------ src/KeyMaker/PlainKeyMaker.php | 35 --------- src/Memory.php | 2 +- src/PhpFile.php | 36 ++++----- tests/MemoryHashKeyTest.php | 55 -------------- 14 files changed, 334 insertions(+), 419 deletions(-) create mode 100644 src/File/BasicFilename.php create mode 100644 src/File/TrieFilename.php delete mode 100644 src/KeyMaker/AbstractKeyMaker.php delete mode 100644 src/KeyMaker/HashKeyMaker.php delete mode 100644 src/KeyMaker/KeyMakerInterface.php delete mode 100644 src/KeyMaker/PlainKeyMaker.php delete mode 100644 tests/MemoryHashKeyTest.php diff --git a/README.md b/README.md index bbbe1f8..0bf1a27 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,16 @@ Setting the TTL to 0 or a negative number, means the cache should live forever. ## Cache implementations +* [Apcu](#apcu) +* [File](#file) +* [Memcached](#memcached) +* [Memory](#memory) +* [Mongo](#mongo) +* [Mysqli](#mysqli) +* [NotCache](#notcache) +* [PhpFile](#phpfile) +* [Predis](#predis) + ### Apcu Use [APCu cache](http://php.net/manual/en/book.apcu.php) to cache to shared @@ -68,9 +78,11 @@ _Note: by default APCu uses the time at the beginning of a request for ttl. In some cases, like with a long running script, this can be a problem. You can change this behaviour `ini_set('apc.use_request_time', false)`._ -### CacheFile +### File + +Save the cache as file to on the filesystem. -Save the cache as file to on the filesystem. The file contains the TTL as well +The file contains the TTL as well as the data. ``` php @@ -92,58 +104,6 @@ $cache = (new CacheFileCache())->withOptions([ ]); ``` -### FlatFile - -Save the cache as file to on the filesystem. The TTL is saved in a separate -file. If TTL is disabled, the TTL file is not created. - -When storing string content, consider using the `NopPacker` to store the data -as-is. This can also be use for creating (HTML) files with the intend to serve -them directly. - -``` php -use Desarrolla2\Cache\FlatFile as FlatFileCache; - -$cache = new FlatFileCache(); -``` - -You may set the following options; - -``` php -use Desarrolla2\Cache\CacheFile as FlatFileCache; - -$cache = (new FileCache())->withOptions([ - 'dir' => '/tmp/mycache', - 'file-prefix' => '', - 'file-suffix' => '.php.cache', - 'ttl' => 3600 -]); -``` - -### PhpFile - -Save the cache as PHP script to on the filesystem using `var_export` when -storing the cache and `include` when loading the cache. This method is -particularly fast in PHP7.2+ due to opcache optimizations. - -``` php -use Desarrolla2\Cache\PhpFile as PhpFileCache; - -$cache = new FileCache(); -``` - -You may set the following options; - -``` php -use Desarrolla2\Cache\CacheFile as CacheFileCache; - -$cache = (new FileCache())->withOptions([ - 'dir' => '/tmp/mycache', - 'file-prefix' => '', - 'ttl' => 3600 -]); -``` - ### Memcached Store cache to [Memcached](https://memcached.org/). Memcached is a high @@ -288,6 +248,30 @@ $cache = new NotCache(); ``` +### PhpFile + +Save the cache as PHP script to on the filesystem using `var_export` when +storing the cache and `include` when loading the cache. This method is +particularly fast in PHP7.2+ due to opcache optimizations. + +``` php +use Desarrolla2\Cache\PhpFile as PhpFileCache; + +$cache = new FileCache(); +``` + +You may set the following options; + +``` php +use Desarrolla2\Cache\CacheFile as CacheFileCache; + +$cache = (new FileCache())->withOptions([ + 'dir' => '/tmp/mycache', + 'file-prefix' => '', + 'ttl' => 3600 +]); +``` + ### Predis Use it if you will you have redis available in your system. @@ -327,8 +311,8 @@ $cache = new PredisCache($backend); ## Methods -The `Desarrolla2\Cache\CacheInterface` extends `Psr\SimpleCache\CacheInterface` -and defines the following methods: +Each cache implementation has the following `Psr\SimpleCache\CacheInterface` +methods: ##### `get(string $key)` Retrieve the value corresponding to a provided key @@ -354,11 +338,16 @@ Persists a set of key => value pairs in the cache ##### `deleteMultiple(array $keys)` Deletes multiple cache items in a single operation -##### `setOption(string $key, string $value)` -Set option for Adapter _(Not in PSR-16)_ +The `Desarrolla2\Cache\CacheInterface` also has the following methods: + +##### `withOption(string $key, string $value)` +Set option for implementation. Creates a new instance. + +##### `withOptions(array $options)` +Set multiple options for implementation. Creates a new instance. ##### `getOption(string $key)` -Get an option for Adapter _(Not in PSR-16)_ +Get option for implementation. ## Packers @@ -383,10 +372,10 @@ will might give unexpected results. ## Contributors -* [![Daniel González](https://avatars1.githubusercontent.com/u/661529?v=3&s=80)](https://github.com/desarrolla2) -/ Twitter: [@desarrolla2](https://twitter.com/desarrolla2) -* [![Arnold Daniels](https://avatars3.githubusercontent.com/u/100821?v=3&s=80)](https://github.com/jasny) -/ Twitter: [@ArnoldDaniels](https://twitter.com/ArnoldDaniels) +[![Daniel González](https://avatars1.githubusercontent.com/u/661529?v=3&s=80)](https://github.com/desarrolla2) +Twitter: [@desarrolla2](https://twitter.com/desarrolla2)\ +[![Arnold Daniels](https://avatars3.githubusercontent.com/u/100821?v=3&s=80)](https://github.com/jasny) +Twitter: [@ArnoldDaniels](https://twitter.com/ArnoldDaniels) [ico-version]: https://img.shields.io/packagist/v/desarrolla2/Cache.svg?style=flat-square [ico-pre-release]: https://img.shields.io/packagist/vpre/desarrolla2/Cache.svg?style=flat-square diff --git a/src/AbstractCache.php b/src/AbstractCache.php index b3de9ff..25e5e3b 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -46,13 +46,18 @@ abstract class AbstractCache implements CacheInterface */ protected $keyMaker; + /** + * @var string + */ + protected $prefix; + /** * Make a clone of this object. * * @return static */ - protected function cloneSelf() + protected function cloneSelf(): self { return clone $this; } @@ -60,7 +65,7 @@ protected function cloneSelf() /** * {@inheritdoc} */ - public function withOption($key, $value) + public function withOption(string $key, $value): self { return $this->withOptions([$key => $value]); } @@ -68,7 +73,7 @@ public function withOption($key, $value) /** * {@inheritdoc} */ - public function withOptions(array $options) + public function withOptions(array $options): self { $cache = $this->cloneSelf(); @@ -106,7 +111,7 @@ public function getOption($key) * @param int|null $value Seconds * @throws InvalidArgumentException */ - protected function setTtlOption(?int $value) + protected function setTtlOption(?int $value): void { if (isset($value) && $value < 1) { throw new InvalidArgumentException('ttl cant be lower than 1'); @@ -125,6 +130,27 @@ protected function getTtlOption(): ?int return $this->ttl; } + /** + * Set the key prefix + * + * @param string $prefix + * @return void + */ + protected function setPrefixOption(string $prefix): void + { + $this->prefix = $prefix; + } + + /** + * Get the key prefix + * + * @return string + */ + protected function getPrefixOption(): string + { + return $this->prefix; + } + /** * Create the default packer for this cache implementation @@ -186,39 +212,23 @@ protected function unpack($packed) /** - * {@inheritdoc} - */ - public function withKeyMaker(KeyMakerInterface $keyMaker) - { - $cache = clone $this; - $cache->keyMaker = $keyMaker; - - return $cache; - } - - /** - * Get the key maker + * Validate and return the key with prefix * - * @return KeyMakerInterface + * @param string $key + * @return string */ - protected function getKeyMaker() + protected function getKey($key): string { - if (!isset($this->keyMaker)) { - $this->keyMaker = new PlainKeyMaker(); + if (!is_string($key)) { + $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); + throw new InvalidArgumentException("Expected key to be a string, not $type"); } - return $this->keyMaker; - } + if ($key === '' || preg_match('~[{}()/\\\\@:]~', $key)) { + throw new InvalidArgumentException("Invalid key '$key'"); + } - /** - * Get the key with prefix - * - * @param mixed $key - * @return string - */ - protected function getKey($key): string - { - return $this->getKeyMaker()->make($key); + return sprintf('%s%s', $this->prefix, $key); } diff --git a/src/AbstractFile.php b/src/AbstractFile.php index 31020d5..73e047d 100644 --- a/src/AbstractFile.php +++ b/src/AbstractFile.php @@ -16,6 +16,8 @@ use Desarrolla2\Cache\AbstractCache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; +use Desarrolla2\Cache\File\BasicFilename; /** * Abstract class for using files as cache. @@ -30,14 +32,9 @@ abstract class AbstractFile extends AbstractCache protected $cacheDir; /** - * @var string - */ - protected $filePrefix = ''; - - /** - * @var string + * @var callable */ - protected $fileSuffix; + protected $filename; /** @@ -47,7 +44,7 @@ abstract class AbstractFile extends AbstractCache public function __construct(string $cacheDir = null) { if (!$cacheDir) { - $cacheDir = realpath(sys_get_temp_dir()) . '/cache'; + $cacheDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'cache'; } $this->cacheDir = (string)$cacheDir; @@ -55,45 +52,37 @@ public function __construct(string $cacheDir = null) } /** - * Set the file prefix + * Filename format or callable. + * The filename format will be applied using sprintf, replacing `%s` with the key. * - * @param string $filePrefix + * @param string|callable $filename * @return void */ - protected function setFilePrefixOption(string $filePrefix): void + protected function setFilenameOption($filename): void { - $this->filePrefix = $filePrefix; - } + if (is_string($filename)) { + $filename = new BasicFilename($filename); + } - /** - * Get the file prefix - * - * @return string - */ - protected function getFilePrefixOption(): string - { - return $this->filePrefix; - } + if (!is_callable($filename) || !is_object($filename)) { + throw new \TypeError("Filename should be a string or callable"); + } - /** - * Set the file extension - * - * @param string $fileSuffix - * @return void - */ - protected function setFileSuffixOption(string $fileSuffix): void - { - $this->fileSuffix = $fileSuffix; + $this->filename = $filename; } /** - * Get the file extension + * Get the filename callable * - * @return string + * @return callable */ - protected function getFileSuffixOption(): string + protected function getFilenameOption(): callable { - return isset($this->fileSuffix) ? $this->fileSuffix : ('.' . $this->getPacker()->getType()); + if (!isset($this->filename)) { + $this->filename = new BasicFilename('%s.' . $this->getPacker()->getType()); + } + + return $this->filename; } @@ -172,12 +161,37 @@ protected function deleteFile(string $file): bool * @param string|mixed $key * @return string */ - protected function getFileName($key): string + protected function getFilename($key): string + { + $cacheKey = $this->getKey($key); + $generator = $this->getFilenameOption(); + + return $this->cacheDir . DIRECTORY_SEPARATOR . $generator($key); + } + + + /** + * {@inheritdoc} + */ + public function delete($key) { - return $this->cacheDir. - DIRECTORY_SEPARATOR. - $this->getFilePrefixOption(). - $this->getKey($key). - $this->getFileSuffixOption(); + $cacheFile = $this->getFilename($key); + + return $this->deleteFile($cacheFile); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $generator = $this->getFilenameOption(); + $pattern = $this->cacheDir . DIRECTORY_SEPARATOR . $generator(''); + + foreach (glob($pattern) as $file) { + $this->deleteFile($file); + } + + return true; } -} \ No newline at end of file +} diff --git a/src/CacheInterface.php b/src/CacheInterface.php index 89e1a58..00d5d46 100644 --- a/src/CacheInterface.php +++ b/src/CacheInterface.php @@ -27,10 +27,10 @@ interface CacheInterface extends PsrCacheInterface * Set option for cache * * @param string $key - * @param string $value + * @param mixed $value * @return static */ - public function withOption($key, $value); + public function withOption(string $key, $value); /** * Set multiple options for cache @@ -55,13 +55,4 @@ public function getOption($key); * @return static */ public function withPacker(PackerInterface $packer); - - /** - * Set the key maker - * - * @param KeyMakerInterface $keyMaker - * @return static - */ - public function withKeyMaker(KeyMakerInterface $keyMaker); - } diff --git a/src/File.php b/src/File.php index 4b3b6d7..68ee0ad 100644 --- a/src/File.php +++ b/src/File.php @@ -122,7 +122,7 @@ public function get($key, $default = null) return $default; } - $cacheFile = $this->getFileName($key); + $cacheFile = $this->getFilename($key); $packed = $this->readFile($cacheFile); if ($this->ttlStrategy === 'embed') { @@ -137,7 +137,7 @@ public function get($key, $default = null) */ public function has($key) { - $cacheFile = $this->getFileName($key); + $cacheFile = $this->getFilename($key); if (!file_exists($cacheFile)) { return false; @@ -158,7 +158,7 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $cacheFile = $this->getFileName($key); + $cacheFile = $this->getFilename($key); $packed = $this->pack($value); @@ -170,29 +170,4 @@ public function set($key, $value, $ttl = null) return $this->writeFile($cacheFile, $contents); } - - /** - * {@inheritdoc} - */ - public function delete($key) - { - $cacheFile = $this->getFileName($key); - - return $this->deleteFile($cacheFile); - } - - /** - * {@inheritdoc} - */ - public function clear() - { - $pattern = $this->cacheDir . DIRECTORY_SEPARATOR . - $this->getFilePrefixOption() . '*' . $this->getFileSuffixOption(); - - foreach (glob($pattern) as $file) { - $this->deleteFile($file); - } - - return true; - } } diff --git a/src/File/BasicFilename.php b/src/File/BasicFilename.php new file mode 100644 index 0000000..fb7f3ac --- /dev/null +++ b/src/File/BasicFilename.php @@ -0,0 +1,55 @@ +format = $format; + } + + /** + * Get the format + * + * @return string + */ + public function getFormat(): string + { + return $this->format; + } + + /** + * Create the path for a key + * + * @param string $key + * @return string + */ + public function __invoke(string $key): string + { + return sprintf($this->format, $key ?: '*'); + } + + /** + * Cast to string + * + * @return string + */ + public function __toString(): string + { + return $this->getFormat(); + } +} \ No newline at end of file diff --git a/src/File/TrieFilename.php b/src/File/TrieFilename.php new file mode 100644 index 0000000..d314c04 --- /dev/null +++ b/src/File/TrieFilename.php @@ -0,0 +1,108 @@ +format = $format; + $this->levels = $levels; + $this->hash = $hash; + } + + /** + * Get the format + * + * @return string + */ + public function getFormat(): string + { + return $this->format; + } + + /** + * Get the depth of the structure + * + * @return int + */ + public function getLevels(): int + { + return $this->levels; + } + + /** + * Will the key be hashed to create the trie. + * + * @return bool + */ + public function isHashed(): bool + { + return $this->hash; + } + + + /** + * Create the path for a key + * + * @param string $key + * @return string + */ + public function __invoke(string $key): string + { + if (empty($key)) { + return $this->wildcardPath(); + } + + $dirname = $this->hash ? base_convert(md5($key), 16, 36) : $key; + $filename = sprintf($this->format, $key); + + $path = ''; + + for ($length = 1; $length <= $this->levels; $length++) { + $path .= substr($dirname, 0, $length) . DIRECTORY_SEPARATOR; + } + + return $path . $filename; + } + + /** + * Get a path for all files (using glob) + * + * @return string + */ + protected function wildcardPath(): string + { + $filename = sprintf($this->format, '*'); + + return str_repeat('*' . DIRECTORY_SEPARATOR, $this->levels) . $filename; + } +} diff --git a/src/KeyMaker/AbstractKeyMaker.php b/src/KeyMaker/AbstractKeyMaker.php deleted file mode 100644 index 13ff080..0000000 --- a/src/KeyMaker/AbstractKeyMaker.php +++ /dev/null @@ -1,51 +0,0 @@ -prefix; - } - - - /** - * PSR-16 key validation - * - * @param string|mixed $key - * @throws InvalidArgumentException - */ - protected function validateKey($key) - { - if (!is_string($key)) { - $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); - throw new InvalidArgumentException("Expected key to be a string, not $type"); - } - - if ($key === '' || preg_match('~[{}()/\\\\@:]~', $key)) { - throw new InvalidArgumentException("Invalid key '$key'"); - } - } -} \ No newline at end of file diff --git a/src/KeyMaker/HashKeyMaker.php b/src/KeyMaker/HashKeyMaker.php deleted file mode 100644 index eab8269..0000000 --- a/src/KeyMaker/HashKeyMaker.php +++ /dev/null @@ -1,58 +0,0 @@ -algo = $algo; - $this->prefix = $prefix; - } - - /** - * Lenient key validation - * - * @param string|mixed $key - * @throws InvalidArgumentException - */ - protected function validateKey($key) - { - if (!is_scalar($key)) { - $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); - throw new InvalidArgumentException("Expected key to be a scalar, not $type"); - } - } - - /** - * Get the key with prefix - * - * @param string|mixed $key - * @return string - */ - public function make($key): string - { - $this->validateKey($key); - - return hash($this->algo, sprintf('%s%s', $this->prefix, $key)); - } -} diff --git a/src/KeyMaker/KeyMakerInterface.php b/src/KeyMaker/KeyMakerInterface.php deleted file mode 100644 index b49f3ef..0000000 --- a/src/KeyMaker/KeyMakerInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -prefix = $prefix; - } - - /** - * Get the key with prefix - * - * @param string|mixed $key - * @return string - * @throws InvalidArgumentException if key is not a alphanumeric string - */ - public function make($key): string - { - $this->validateKey($key); - - return sprintf('%s%s', $this->prefix, $key); - } -} diff --git a/src/Memory.php b/src/Memory.php index bf85666..7b91546 100644 --- a/src/Memory.php +++ b/src/Memory.php @@ -59,7 +59,7 @@ protected static function createDefaultPacker(): PackerInterface * * @return static */ - protected function cloneSelf() + protected function cloneSelf(): AbstractCache { $clone = clone $this; diff --git a/src/PhpFile.php b/src/PhpFile.php index 6a2a99e..6d2fa7b 100644 --- a/src/PhpFile.php +++ b/src/PhpFile.php @@ -25,11 +25,6 @@ */ class PhpFile extends AbstractFile { - /** - * @var string - */ - protected $fileSuffix = '.php'; - /** * Create the default packer for this cache implementation. * @@ -40,6 +35,20 @@ protected static function createDefaultPacker(): PackerInterface return new SerializePacker(); } + /** + * Get the filename callable + * + * @return callable + */ + protected function getFilenameOption(): callable + { + if (!isset($this->filename)) { + $this->filename = new SimplePath('%s.php'); + } + + return $this->filename; + } + /** * Create a PHP script returning the cached value * @@ -67,7 +76,7 @@ public function createScript($value, ?int $ttl): string */ public function get($key, $default = null) { - $cacheFile = $this->getFileName($key); + $cacheFile = $this->getFilename($key); if (!file_exists($cacheFile)) { return $default; @@ -91,24 +100,11 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $cacheFile = $this->getFileName($key); + $cacheFile = $this->getFilename($key); $packed = $this->pack($value); $script = $this->createScript($packed, $this->ttlToTimestamp($ttl)); return $this->write($cacheFile, $script); } - - /** - * {@inheritdoc} - */ - public function clear() - { - $pattern = $this->cacheDir . DIRECTORY_SEPARATOR . - $this->getFilePrefixOption() . '*' . $this->getFileSuffixOption(); - - foreach (glob($pattern) as $file) { - $this->deleteFile($file); - } - } } diff --git a/tests/MemoryHashKeyTest.php b/tests/MemoryHashKeyTest.php deleted file mode 100644 index 43c3fb2..0000000 --- a/tests/MemoryHashKeyTest.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ - -namespace Desarrolla2\Test\Cache; - -use Desarrolla2\Cache\KeyMaker\HashKeyMaker; -use Desarrolla2\Cache\Memory as MemoryCache; - -/** - * Memory with HashKeyMaker. - * Doesn't adhere to PSR-16 keys. - */ -class MemoryHashKeyTest extends AbstractCacheTest -{ - public function createSimpleCache() - { - return (new MemoryCache())->withKeyMaker(new HashKeyMaker()); - } - - /** - * Data provider for invalid keys. - * - * @return array - */ - public static function invalidKeys() - { - return [ - [null], - [new \stdClass()], - [['array']], - ]; - } - - public function testExceededLimit() - { - $cache = $this->createSimpleCache()->withOption('limit', 1); - - $cache->set('foo', 1); - $this->assertTrue($cache->has('foo')); - - $cache->set('bar', 1); - $this->assertFalse($cache->has('foo')); - $this->assertTrue($cache->has('bar')); - } -} From fc2069d5058c6448f2e1a13134f38df4532984bd Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Sat, 16 Jun 2018 02:03:46 +0200 Subject: [PATCH 32/51] Fixes for memcached Moved options into trait Better abstraction choises --- src/AbstractCache.php | 215 ++++++++++++----------------------- src/AbstractFile.php | 61 +--------- src/Apcu.php | 19 ++-- src/File.php | 3 +- src/Memcached.php | 156 +++++++++++++++++++++---- src/Mongo.php | 2 +- src/Option/FilenameTrait.php | 89 +++++++++++++++ src/Option/PrefixTrait.php | 47 ++++++++ src/Option/TtlTrait.php | 52 +++++++++ src/Packer/PackingTrait.php | 84 ++++++++++++++ src/PhpFile.php | 7 +- tests/MemcachedTest.php | 9 +- tests/PhpFileTest.php | 5 + 13 files changed, 504 insertions(+), 245 deletions(-) create mode 100644 src/Option/FilenameTrait.php create mode 100644 src/Option/PrefixTrait.php create mode 100644 src/Option/TtlTrait.php create mode 100644 src/Packer/PackingTrait.php diff --git a/src/AbstractCache.php b/src/AbstractCache.php index 25e5e3b..84e1479 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -18,8 +18,9 @@ use Desarrolla2\Cache\CacheInterface; use Desarrolla2\Cache\Packer\PackerInterface; -use Desarrolla2\Cache\KeyMaker\KeyMakerInterface; -use Desarrolla2\Cache\KeyMaker\PlainKeyMaker; +use Desarrolla2\Cache\Option\PrefixTrait as PrefixOption; +use Desarrolla2\Cache\Option\TtlTrait as TtlOption; +use Desarrolla2\Cache\Packer\PackingTrait as Packing; use Desarrolla2\Cache\Exception\CacheException; use Desarrolla2\Cache\Exception\InvalidArgumentException; use Desarrolla2\Cache\Exception\CacheExpiredException; @@ -31,26 +32,9 @@ */ abstract class AbstractCache implements CacheInterface { - /** - * @var int|null - */ - protected $ttl; - - /** - * @var PackerInterface - */ - protected $packer; - - /** - * @var KeyMakerInterface - */ - protected $keyMaker; - - /** - * @var string - */ - protected $prefix; - + use PrefixOption; + use TtlOption; + use Packing; /** * Make a clone of this object. @@ -106,158 +90,97 @@ public function getOption($key) /** - * Set the time to live (ttl) - * - * @param int|null $value Seconds + * Validate the key + * + * @param string $key + * @return void * @throws InvalidArgumentException */ - protected function setTtlOption(?int $value): void + protected function assertKey($key): void { - if (isset($value) && $value < 1) { - throw new InvalidArgumentException('ttl cant be lower than 1'); + if (!is_string($key)) { + $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); + throw new InvalidArgumentException("Expected key to be a string, not $type"); + } + + if ($key === '' || preg_match('~[{}()/\\\\@:]~', $key)) { + throw new InvalidArgumentException("Invalid key '$key'"); } - - $this->ttl = $value; - } - - /** - * Get the time to live (ttl) - * - * @return ?int - */ - protected function getTtlOption(): ?int - { - return $this->ttl; } /** - * Set the key prefix - * - * @param string $prefix + * Assert that the keys are an array or traversable + * + * @param iterable $subject + * @param string $msg * @return void + * @throws InvalidArgumentException if subject are not iterable */ - protected function setPrefixOption(string $prefix): void + protected function assertIterable($subject, $msg): void { - $this->prefix = $prefix; + $iterable = function_exists('is_iterable') + ? is_iterable($subject) + : is_array($subject) || $subject instanceof Traversable; + + if (!$iterable) { + throw new InvalidArgumentException($msg); + } } /** - * Get the key prefix + * Turn the key into a cache identifier * + * @param string $key * @return string + * @throws InvalidArgumentException */ - protected function getPrefixOption(): string + protected function keyToId($key): string { - return $this->prefix; - } + $this->assertKey($key); + return sprintf('%s%s', $this->prefix, $key); + } /** - * Create the default packer for this cache implementation - * - * @return PackerInterface - */ - abstract protected static function createDefaultPacker(): PackerInterface; - - /** - * Set a packer to pack (serialialize) and unpack (unserialize) the data. + * Create a map with keys and ids * - * @param PackerInterface $packer - * @return static + * @param iterable $keys + * @return array + * @throws InvalidArgumentException */ - public function withPacker(PackerInterface $packer) + protected function mapKeysToIds($keys): array { - $cache = $this->cloneSelf(); - $cache->packer = $packer; + $this->assertIterable($keys, 'keys not iterable'); - return $cache; - } - - /** - * Get the packer - * - * @return PackerInterface - */ - protected function getPacker(): PackerInterface - { - if (!isset($this->packer)) { - $this->packer = static::createDefaultPacker(); + $map = []; + + foreach ($keys as $key) { + $map[$key] = $this->keyToId($key); } - - return $this->packer; - } - /** - * Pack the value - * - * @param mixed $value - * @return string|mixed - */ - protected function pack($value) - { - return $this->getPacker()->pack($value); - } - - /** - * Unpack the data to retrieve the value - * - * @param string|mixed $packed - * @return mixed - * @throws UnexpectedValueException - */ - protected function unpack($packed) - { - return $this->getPacker()->unpack($packed); + return $map; } /** - * Validate and return the key with prefix + * Pack all values and turn keys into ids * - * @param string $key - * @return string + * @param iterable $values + * @return array */ - protected function getKey($key): string + protected function packValues(iterable $values): array { - if (!is_string($key)) { - $type = (is_object($key) ? get_class($key) . ' ' : '') . gettype($key); - throw new InvalidArgumentException("Expected key to be a string, not $type"); - } + $packed = []; - if ($key === '' || preg_match('~[{}()/\\\\@:]~', $key)) { - throw new InvalidArgumentException("Invalid key '$key'"); + foreach ($values as $key => $value) { + $id = $this->keyToId(is_int($key) ? (string)$key : $key); + $packed[$id] = $this->pack($value); } - return sprintf('%s%s', $this->prefix, $key); + return $packed; } - /** - * Assert that the keys are an array or traversable - * - * @param iterable $subject - * @param string $msg - * @throws InvalidArgumentException if subject are not iterable - */ - protected function assertIterable($subject, $msg) - { - $iterable = function_exists('is_iterable') - ? is_iterable($subject) - : is_array($subject) || $subject instanceof Traversable; - - if (!$iterable) { - throw new InvalidArgumentException($msg); - } - } - - /** - * {@inheritdoc} - */ - public function delete($key) - { - throw new CacheException('not ready yet'); - } - /** * {@inheritdoc} @@ -317,19 +240,23 @@ public function deleteMultiple($keys) */ protected function ttlToSeconds($ttl): ?int { - if (!isset($ttl) || is_int($ttl)) { - return $ttl; + if (!isset($ttl)) { + return $this->ttl; } if ($ttl instanceof DateInterval) { $reference = new DateTimeImmutable(); $endTime = $reference->add($ttl); - return $endTime->getTimestamp() - $reference->getTimestamp(); + $ttl = $endTime->getTimestamp() - $reference->getTimestamp(); } - $type = (is_object($ttl) ? get_class($ttl) . ' ' : '') . gettype($ttl); - throw new InvalidArgumentException("ttl should be of type int or DateInterval, not $type"); + if (!is_int($ttl)) { + $type = (is_object($ttl) ? get_class($ttl) . ' ' : '') . gettype($ttl); + throw new InvalidArgumentException("ttl should be of type int or DateInterval, not $type"); + } + + return isset($this->ttl) ? min($ttl, $this->ttl) : $ttl; } /** @@ -342,15 +269,17 @@ protected function ttlToSeconds($ttl): ?int protected function ttlToTimestamp($ttl): ?int { if (!isset($ttl)) { - return null; + return isset($this->ttl) ? time() + $this->ttl : null; } if (is_int($ttl)) { - return time() + $ttl; + return time() + (isset($this->ttl) ? min($ttl, $this->ttl) : $ttl); } if ($ttl instanceof DateInterval) { - return (new DateTimeImmutable())->add($ttl)->getTimestamp(); + $timestamp = (new DateTimeImmutable())->add($ttl)->getTimestamp(); + + return isset($this->ttl) ? min($timestamp, time() + $this->ttl) : $timestamp; } $type = (is_object($ttl) ? get_class($ttl) . ' ' : '') . gettype($ttl); diff --git a/src/AbstractFile.php b/src/AbstractFile.php index 73e047d..96676e0 100644 --- a/src/AbstractFile.php +++ b/src/AbstractFile.php @@ -16,8 +16,7 @@ use Desarrolla2\Cache\AbstractCache; use Desarrolla2\Cache\Exception\CacheException; -use Desarrolla2\Cache\Exception\InvalidArgumentException; -use Desarrolla2\Cache\File\BasicFilename; +use Desarrolla2\Cache\Option\FilenameTrait as FilenameOption; /** * Abstract class for using files as cache. @@ -26,16 +25,13 @@ */ abstract class AbstractFile extends AbstractCache { + use FilenameOption; + /** * @var string */ protected $cacheDir; - /** - * @var callable - */ - protected $filename; - /** * @param string $cacheDir @@ -51,40 +47,6 @@ public function __construct(string $cacheDir = null) $this->createCacheDirectory($cacheDir); } - /** - * Filename format or callable. - * The filename format will be applied using sprintf, replacing `%s` with the key. - * - * @param string|callable $filename - * @return void - */ - protected function setFilenameOption($filename): void - { - if (is_string($filename)) { - $filename = new BasicFilename($filename); - } - - if (!is_callable($filename) || !is_object($filename)) { - throw new \TypeError("Filename should be a string or callable"); - } - - $this->filename = $filename; - } - - /** - * Get the filename callable - * - * @return callable - */ - protected function getFilenameOption(): callable - { - if (!isset($this->filename)) { - $this->filename = new BasicFilename('%s.' . $this->getPacker()->getType()); - } - - return $this->filename; - } - /** * Create the cache directory @@ -155,20 +117,6 @@ protected function deleteFile(string $file): bool return !is_file($file) || unlink($file); } - /** - * Create a filename based on the key - * - * @param string|mixed $key - * @return string - */ - protected function getFilename($key): string - { - $cacheKey = $this->getKey($key); - $generator = $this->getFilenameOption(); - - return $this->cacheDir . DIRECTORY_SEPARATOR . $generator($key); - } - /** * {@inheritdoc} @@ -185,8 +133,7 @@ public function delete($key) */ public function clear() { - $generator = $this->getFilenameOption(); - $pattern = $this->cacheDir . DIRECTORY_SEPARATOR . $generator(''); + $pattern = $this->getWildcard(); foreach (glob($pattern) as $file) { $this->deleteFile($file); diff --git a/src/Apcu.php b/src/Apcu.php index cc6b2fd..9cdfab5 100644 --- a/src/Apcu.php +++ b/src/Apcu.php @@ -41,13 +41,13 @@ protected static function createDefaultPacker(): PackerInterface */ public function set($key, $value, $ttl = null) { - $ttlSeconds = $this->ttlToSeconds($ttl ?? $this->ttl); + $ttlSeconds = $this->ttlToSeconds($ttl); if (isset($ttlSeconds) && $ttlSeconds <= 0) { return $this->delete($key); } - return apcu_store($this->getKey($key), $this->pack($value), $ttlSeconds ?? 0); + return apcu_store($this->keyToId($key), $this->pack($value), $ttlSeconds ?? 0); } /** @@ -55,13 +55,9 @@ public function set($key, $value, $ttl = null) */ public function get($key, $default = null) { - $packed = apcu_fetch($this->getKey($key), $success); + $packed = apcu_fetch($this->keyToId($key), $success); - if (!$success) { - return $default; - } - - return $this->unpack($packed); + return $success ? $this->unpack($packed) : $default; } /** @@ -69,7 +65,7 @@ public function get($key, $default = null) */ public function has($key) { - return apcu_exists($this->getKey($key)); + return apcu_exists($this->keyToId($key)); } /** @@ -77,9 +73,9 @@ public function has($key) */ public function delete($key) { - $cacheKey = $this->getKey($key); + $id = $this->keyToId($key); - return apcu_delete($cacheKey) || !apcu_exists($cacheKey); + return apcu_delete($id) || !apcu_exists($id); } /** @@ -89,5 +85,4 @@ public function clear() { return apcu_clear_cache(); } - } diff --git a/src/File.php b/src/File.php index 68ee0ad..2403627 100644 --- a/src/File.php +++ b/src/File.php @@ -50,7 +50,7 @@ protected static function createDefaultPacker(): PackerInterface protected function setTtlStrategyOption($strategy) { if (!in_array($strategy, ['embed', 'file', 'mtime'])) { - throw new InvalidArgumentException("Unknown strategry '$strategy', should be 'embed', 'file' or 'mtime'"); + throw new InvalidArgumentException("Unknown strategy '$strategy', should be 'embed', 'file' or 'mtime'"); } $this->ttlStrategy = $strategy; @@ -159,7 +159,6 @@ public function has($key) public function set($key, $value, $ttl = null) { $cacheFile = $this->getFilename($key); - $packed = $this->pack($value); if (!is_string($packed)) { diff --git a/src/Memcached.php b/src/Memcached.php index 51953bb..a8cc5e8 100644 --- a/src/Memcached.php +++ b/src/Memcached.php @@ -16,6 +16,7 @@ namespace Desarrolla2\Cache; +use Desarrolla2\Cache\Exception\InvalidArgumentException; use Desarrolla2\Cache\Packer\PackerInterface; use Desarrolla2\Cache\Packer\NopPacker; use Memcached as BaseMemcached; @@ -43,6 +44,7 @@ public function __construct(BaseMemcached $server = null) $this->server = $server; } + /** * Create the default packer for this cache implementation * @@ -53,23 +55,73 @@ protected static function createDefaultPacker(): PackerInterface return new NopPacker(); } + /** + * Set the key prefix + * + * @param string $prefix + * @return void + */ + protected function setPrefixOption(string $prefix): void + { + $this->server->setOption(Memcached::OPT_PREFIX_KEY, $prefix); + } /** - * {@inheritdoc} + * Get the key prefix + * + * @return string */ - public function delete($key) + protected function getPrefixOption(): string + { + return $this->server->getOption(Memcached::OPT_PREFIX_KEY) ?? ''; + } + + + /** + * Validate the key + * + * @param string $key + * @return void + * @throws InvalidArgumentException + */ + protected function assertKey($key): void + { + parent::assertKey($key); + + if (strlen($key) > 250) { + throw new InvalidArgumentException("Key to long, max 250 characters"); + } + } + + /** + * Pack all values and turn keys into ids + * + * @param iterable $values + * @return array + */ + protected function packValues(iterable $values): array { - return $this->server->delete($this->getKey($key)); + $packed = []; + + foreach ($values as $key => $value) { + $this->assertKey(is_int($key) ? (string)$key : $key); + $packed[$key] = $this->pack($value); + } + + return $packed; } + /** * {@inheritdoc} */ public function get($key, $default = null) { - $data = $this->server->get($this->getKey($key)); + $this->assertKey($key); - if ($data === false) { + $data = $this->server->get($key); + + if ($this->server->getResultCode() !== BaseMemcached::RES_SUCCESS) { return $default; } @@ -79,38 +131,69 @@ public function get($key, $default = null) /** * {@inheritdoc} */ - public function getMultiple($keys, $default = null) + public function has($key) { - $this->assertIterable($keys, 'keys not iterable'); + $this->assertKey($key); + $this->server->get($key); - $cacheKeys = array_map([$this, 'getKey'], $keys); + $result = $this->server->getResultCode(); - $items = $this->server->getMulti($cacheKeys); + return $result === BaseMemcached::RES_SUCCESS; + } - $result = array_map(function($item) { - return $this->unpack($item); - }, $items); + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $this->assertKey($key); + + $packed = $this->pack($value); + $ttlTime = $this->ttlToMemcachedTime($ttl); - return $result; + if ($ttlTime === false) { + return $this->delete($key); + } + + $success = $this->server->set($key, $packed, $ttlTime); + + return $success; } /** * {@inheritdoc} */ - public function has($key) + public function delete($key) { - return $this->server->get($this->getKey($key)) !== false; + $this->server->delete($this->keyToId($key)); + + $result = $this->server->getResultCode(); + + return $result === BaseMemcached::RES_SUCCESS || $result === BaseMemcached::RES_NOTFOUND; } /** * {@inheritdoc} */ - public function set($key, $value, $ttl = null) + public function getMultiple($keys, $default = null) { - $packed = $this->pack($value, $ttl); - $ttlTime = $this->ttlToTimestamp($ttl); + $this->assertIterable($keys, 'keys not iterable'); + $keysArr = is_array($keys) ? $keys : iterator_to_array($keys, false); + array_walk($keysArr, [$this, 'assertKey']); - return $this->server->set($this->getKey($key), $packed, $ttlTime); + $result = $this->server->getMulti($keysArr); + + if ($result === false) { + return false; + } + + $items = array_fill_keys($keysArr, $default); + + foreach ($result as $key => $value) { + $items[$key] = $this->unpack($value); + } + + return $items; } /** @@ -120,13 +203,14 @@ public function setMultiple($values, $ttl = null) { $this->assertIterable($values, 'values not iterable'); - $cacheKeys = array_map([$this, 'getKey'], array_keys($values)); + $packed = $this->packValues($values); + $ttlTime = $this->ttlToMemcachedTime($ttl); - $packed = array_map(function($value) { - $this->pack($value); - }, $values); + if ($ttlTime === false) { + return $this->server->deleteMulti(array_keys($packed)); + } - return $this->server->setMulti(array_combine($cacheKeys, $packed), $this->ttlToTimestamp($ttl)); + return $this->server->setMulti($packed, $ttlTime); } /** @@ -134,6 +218,28 @@ public function setMultiple($values, $ttl = null) */ public function clear() { - $this->server->flush(); + return $this->server->flush(); + } + + + /** + * Convert ttl to timestamp or seconds. + * + * @see http://php.net/manual/en/memcached.expiration.php + * + * @param null|int|DateInterval $ttl + * @return int|null + * @throws InvalidArgumentException + */ + protected function ttlToMemcachedTime($ttl) + { + $seconds = $this->ttlToSeconds($ttl); + + if ($seconds <= 0) { + return isset($seconds) ? false : 0; + } + + /* 2592000 seconds = 30 days */ + return $seconds <= 2592000 ? $seconds : $this->ttlToTimestamp($ttl); } } diff --git a/src/Mongo.php b/src/Mongo.php index 533dad4..411fcfa 100644 --- a/src/Mongo.php +++ b/src/Mongo.php @@ -124,7 +124,7 @@ public function getMultiple($keys, $default = null) return []; } - $cacheKeys = array_map([$this, 'getKey'], $keys); + $cacheKeys = array_map([$this, 'getKey'], is_array($keys) ? $keys : iterator_to_array($keys)); $items = array_fill_keys($cacheKeys, $default); $rows = $this->collection->find(['_id' => ['$in' => $cacheKeys]]); diff --git a/src/Option/FilenameTrait.php b/src/Option/FilenameTrait.php new file mode 100644 index 0000000..184039e --- /dev/null +++ b/src/Option/FilenameTrait.php @@ -0,0 +1,89 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Option; + +use TypeError; +use Desarrolla2\Cache\File\BasicFilename; + +/** + * Use filename generator + */ +trait FilenameTrait +{ + /** + * @var callable + */ + protected $filename; + + + /** + * Filename format or callable. + * The filename format will be applied using sprintf, replacing `%s` with the key. + * + * @param string|callable $filename + * @return void + */ + protected function setFilenameOption($filename): void + { + if (is_string($filename)) { + $filename = new BasicFilename($filename); + } + + if (!is_callable($filename)) { + throw new TypeError("Filename should be a string or callable"); + } + + $this->filename = $filename; + } + + /** + * Get the filename callable + * + * @return callable + */ + protected function getFilenameOption(): callable + { + if (!isset($this->filename)) { + $this->filename = new BasicFilename('%s.' . $this->getPacker()->getType()); + } + + return $this->filename; + } + + /** + * Create a filename based on the key + * + * @param string|mixed $key + * @return string + */ + protected function getFilename($key): string + { + $id = $this->keyToId($key); + $generator = $this->getFilenameOption(); + + return $this->cacheDir . DIRECTORY_SEPARATOR . $generator($id); + } + + /** + * Get a wildcard for all files + * + * @return string + */ + protected function getWildcard(): string + { + $generator = $this->getFilenameOption(); + + return $this->cacheDir . DIRECTORY_SEPARATOR . $generator(''); + } +} diff --git a/src/Option/PrefixTrait.php b/src/Option/PrefixTrait.php new file mode 100644 index 0000000..52fbaca --- /dev/null +++ b/src/Option/PrefixTrait.php @@ -0,0 +1,47 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Option; + +/** + * Prefix option + */ +trait PrefixTrait +{ + /** + * @var string + */ + protected $prefix = ''; + + + /** + * Set the key prefix + * + * @param string $prefix + * @return void + */ + protected function setPrefixOption(string $prefix): void + { + $this->prefix = $prefix; + } + + /** + * Get the key prefix + * + * @return string + */ + protected function getPrefixOption(): string + { + return $this->prefix; + } +} diff --git a/src/Option/TtlTrait.php b/src/Option/TtlTrait.php new file mode 100644 index 0000000..3f1e110 --- /dev/null +++ b/src/Option/TtlTrait.php @@ -0,0 +1,52 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Option; + +use Desarrolla2\Cache\Exception\InvalidArgumentException; + +/** + * TTL option + */ +trait TtlTrait +{ + /** + * @var int|null + */ + protected $ttl = null; + + /** + * Set the maximum time to live (ttl) + * + * @param int|null $value Seconds or null to live forever + * @throws InvalidArgumentException + */ + protected function setTtlOption(?int $value): void + { + if (isset($value) && $value < 1) { + throw new InvalidArgumentException('ttl cant be lower than 1'); + } + + $this->ttl = $value; + } + + /** + * Get the maximum time to live (ttl) + * + * @return int|null + */ + protected function getTtlOption(): ?int + { + return $this->ttl; + } +} \ No newline at end of file diff --git a/src/Packer/PackingTrait.php b/src/Packer/PackingTrait.php new file mode 100644 index 0000000..b833e8e --- /dev/null +++ b/src/Packer/PackingTrait.php @@ -0,0 +1,84 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Packer; + +/** + * Support packing for Caching adapter + */ +trait PackingTrait +{ + /** + * @var PackerInterface + */ + protected $packer; + + + /** + * Create the default packer for this cache implementation + * + * @return PackerInterface + */ + abstract protected static function createDefaultPacker(): PackerInterface; + + /** + * Set a packer to pack (serialialize) and unpack (unserialize) the data. + * + * @param PackerInterface $packer + * @return static + */ + public function withPacker(PackerInterface $packer) + { + $cache = $this->cloneSelf(); + $cache->packer = $packer; + + return $cache; + } + + /** + * Get the packer + * + * @return PackerInterface + */ + protected function getPacker(): PackerInterface + { + if (!isset($this->packer)) { + $this->packer = static::createDefaultPacker(); + } + + return $this->packer; + } + + /** + * Pack the value + * + * @param mixed $value + * @return string|mixed + */ + protected function pack($value) + { + return $this->getPacker()->pack($value); + } + + /** + * Unpack the data to retrieve the value + * + * @param string|mixed $packed + * @return mixed + * @throws UnexpectedValueException + */ + protected function unpack($packed) + { + return $this->getPacker()->unpack($packed); + } +} diff --git a/src/PhpFile.php b/src/PhpFile.php index 6d2fa7b..a8774e3 100644 --- a/src/PhpFile.php +++ b/src/PhpFile.php @@ -19,6 +19,7 @@ use Desarrolla2\Cache\AbstractFile; use Desarrolla2\Cache\Packer\PackerInterface; use Desarrolla2\Cache\Packer\SerializePacker; +use Desarrolla2\Cache\File\BasicFilename; /** * Cache file as PHP script. @@ -43,7 +44,7 @@ protected static function createDefaultPacker(): PackerInterface protected function getFilenameOption(): callable { if (!isset($this->filename)) { - $this->filename = new SimplePath('%s.php'); + $this->filename = new BasicFilename('%s.php'); } return $this->filename; @@ -67,7 +68,7 @@ public function createScript($value, ?int $ttl): string } return $ttl !== null - ? "pack($value); $script = $this->createScript($packed, $this->ttlToTimestamp($ttl)); - return $this->write($cacheFile, $script); + return $this->writeFile($cacheFile, $script); } } diff --git a/tests/MemcachedTest.php b/tests/MemcachedTest.php index a0a9ff2..75e88ad 100644 --- a/tests/MemcachedTest.php +++ b/tests/MemcachedTest.php @@ -15,12 +15,17 @@ use Desarrolla2\Cache\Memcached as MemcachedCache; use Memcached as BaseMemcached; +use Desarrolla2\Cache\Exception\InvalidArgumentException; /** * MemcachedTest */ class MemcachedTest extends AbstractCacheTest { + protected $skippedTests = [ + 'testBasicUsageWithLongKey' => 'Only support keys up to 250 bytes' + ]; + public function createSimpleCache() { if (!extension_loaded('memcached') || !class_exists('\Memcached')) { @@ -45,8 +50,8 @@ public function createSimpleCache() public function dataProviderForOptionsException() { return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], + ['ttl', 0, InvalidArgumentException::class], + ['file', 100, InvalidArgumentException::class], ]; } } diff --git a/tests/PhpFileTest.php b/tests/PhpFileTest.php index bf0c27f..48098b0 100644 --- a/tests/PhpFileTest.php +++ b/tests/PhpFileTest.php @@ -22,6 +22,11 @@ */ class PhpFileTest extends AbstractCacheTest { + protected $skippedTests = [ + 'testBasicUsageWithLongKey' => 'Only support keys up to 64 bytes' + ]; + + public function createSimpleCache() { return new PhpFile($this->config['file']['dir']); From 56b2d82ff1bb82b8e8f3f1740ba142854df8eca6 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Sat, 16 Jun 2018 02:08:17 +0200 Subject: [PATCH 33/51] Travis fixes --- .travis.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index fed6079..069f530 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,8 @@ language: php #sudo: false php: - - 5.4 - - 5.5 - - 5.6 - - 7.0 + - 7.1 + - 7.2 - hhvm matrix: @@ -23,13 +21,12 @@ notifications: - daniel.gonzalez@freelancemadrid.es before_script: - # Create Mysql Database - mysql -e 'CREATE DATABASE IF NOT EXISTS `cache`;' - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' # Install apcu - - "if [ $TRAVIS_PHP_VERSION == '7.0' ] || [ $TRAVIS_PHP_VERSION == 'hhvm' ]; then export APCU_VERSION=5.1.0; else export APCU_VERSION=4.0.8; fi" + - "if [ $TRAVIS_PHP_VERSION == 'hhvm' ]; then export APCU_VERSION=5.1.0; else export APCU_VERSION=4.0.8; fi" - sh -c "yes '' | pecl install apcu-$APCU_VERSION" - sh -c "if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-add ./tests/travis/php.ini; fi" From fb480cd0085fc1ac32c7a27fedd403c1dd370744 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Mon, 18 Jun 2018 04:14:20 +0200 Subject: [PATCH 34/51] Fixed MongoDB and MySQLi --- composer.json | 6 +- phpunit.xml.dist | 17 +- src/AbstractCache.php | 13 +- src/AbstractFile.php | 88 +++++-- src/Exception/BadMethodCallException.php | 5 +- src/Exception/CacheException.php | 5 +- src/Exception/InvalidArgumentException.php | 5 +- src/Exception/UnexpectedValueException.php | 5 +- src/File/BasicFilename.php | 13 + src/File/TrieFilename.php | 15 +- src/Memory.php | 40 +-- src/Mongo.php | 215 ---------------- src/MongoDB.php | 273 +++++++++++++++++++++ src/Mysqli.php | 169 ++++++++----- src/Option/FilenameTrait.php | 2 + src/Option/InitializeTrait.php | 65 +++++ src/Option/PrefixTrait.php | 2 + src/Option/TtlTrait.php | 2 + src/Packer/JsonPacker.php | 2 + src/Packer/MongoDBBinaryPacker.php | 77 ++++++ src/Packer/NopPacker.php | 2 + src/Packer/PackerInterface.php | 2 + src/Packer/PackingTrait.php | 2 + src/Packer/SerializePacker.php | 2 + tests/AbstractCacheTest.php | 49 ++-- tests/FileTest.php | 26 +- tests/FileTrieTest.php | 54 ++++ tests/MemcachedTest.php | 19 +- tests/MongoDBTest.php | 57 +++++ tests/MongoTest.php | 65 ----- tests/MysqliTest.php | 32 +-- tests/NotCacheTest.php | 8 +- tests/config.json.dist | 20 -- 33 files changed, 876 insertions(+), 481 deletions(-) delete mode 100644 src/Mongo.php create mode 100644 src/MongoDB.php create mode 100644 src/Option/InitializeTrait.php create mode 100644 src/Packer/MongoDBBinaryPacker.php create mode 100644 tests/FileTrieTest.php create mode 100644 tests/MongoDBTest.php delete mode 100644 tests/MongoTest.php delete mode 100644 tests/config.json.dist diff --git a/composer.json b/composer.json index 0e7a7dd..275e0ea 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ ], "require": { "php": ">=7.1.0", - "psr/simple-cache": "^1.0" + "psr/simple-cache": "^1.0", + "webmozart/glob": "^4.1" }, "require-dev": { "ext-apcu": "*", @@ -38,7 +39,8 @@ "predis/predis": "~1.0.0", "mongodb/mongodb": "^1.3", "cache/integration-tests": "dev-master", - "phpunit/phpunit": "^7.2" + "phpunit/phpunit": "^7.2", + "mikey179/vfsStream": "^1.6" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 38b4909..32498e3 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,10 +11,17 @@ ./tests - - /usr/share/pear/ - ./vendor - ./build - + + + + + + + + + + + + diff --git a/src/AbstractCache.php b/src/AbstractCache.php index 84e1479..65179ee 100644 --- a/src/AbstractCache.php +++ b/src/AbstractCache.php @@ -155,7 +155,8 @@ protected function mapKeysToIds($keys): array $map = []; foreach ($keys as $key) { - $map[$key] = $this->keyToId($key); + $id = $this->keyToId($key); + $map[$id] = $key; } return $map; @@ -231,6 +232,16 @@ public function deleteMultiple($keys) } + /** + * Get the current time. + * + * @return int + */ + protected function currentTimestamp(): int + { + return time(); + } + /** * Convert TTL to seconds from now * diff --git a/src/AbstractFile.php b/src/AbstractFile.php index 96676e0..4132868 100644 --- a/src/AbstractFile.php +++ b/src/AbstractFile.php @@ -16,7 +16,9 @@ use Desarrolla2\Cache\AbstractCache; use Desarrolla2\Cache\Exception\CacheException; +use Desarrolla2\Cache\Exception\InvalidArgumentException; use Desarrolla2\Cache\Option\FilenameTrait as FilenameOption; +use Webmozart\Glob\Iterator\GlobIterator; /** * Abstract class for using files as cache. @@ -34,40 +36,37 @@ abstract class AbstractFile extends AbstractCache /** - * @param string $cacheDir + * Class constructor + * + * @param string|null $cacheDir * @throws CacheException */ - public function __construct(string $cacheDir = null) + public function __construct(?string $cacheDir = null) { if (!$cacheDir) { $cacheDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'cache'; } - $this->cacheDir = (string)$cacheDir; - $this->createCacheDirectory($cacheDir); + $this->cacheDir = rtrim($cacheDir, '/'); } - /** - * Create the cache directory + * Validate the key * - * @param string $cacheFile + * @param string $key * @return void - * @throws CacheException + * @throws InvalidArgumentException */ - protected function createCacheDirectory(string $path): void + protected function assertKey($key): void { - if (!is_dir($path)) { - if (!mkdir($path, 0777, true)) { - throw new CacheException($path.' is not writable'); - } - } + parent::assertKey($key); - if (!is_writable($path)) { - throw new CacheException($path.' is not writable'); + if (strpos($key, '*')) { + throw new InvalidArgumentException("Key may not contain the character '*'"); } } + /** * Read the cache file * @@ -103,6 +102,12 @@ protected function readLine(string $cacheFile): string */ protected function writeFile(string $cacheFile, string $contents): bool { + $dir = dirname($cacheFile); + + if ($dir !== $this->cacheDir && !is_dir($dir)) { + mkdir($dir, 0775, true); + } + return (bool)file_put_contents($cacheFile, $contents); } @@ -117,6 +122,47 @@ protected function deleteFile(string $file): bool return !is_file($file) || unlink($file); } + /** + * Recursive delete an empty directory. + * + * @return bool + */ + protected function removeFiles() + { + $generator = $this->getFilenameOption(); + $pattern = $this->cacheDir . DIRECTORY_SEPARATOR . $generator('*'); + + $objects = new GlobIterator($pattern); + + foreach ($objects as $object) { + unlink($object); + } + } + + /** + * Recursive delete an empty directory. + * + * @param string $dir + * @return bool + */ + protected function removeChildDirecotries(string $dir = null) + { + if (empty($dir)) { + $dir = $this->cacheDir; + } + + $success = true; + $objects = new GlobIterator($dir); + + foreach ($objects as $object) { + if (!is_dir("$dir/$object") && is_link("$dir/$object")) { + $success = $this->recursiveRemove("$dir/$object") && rmdir($dir) && $success; + } + } + + return $success; + } + /** * {@inheritdoc} @@ -129,16 +175,14 @@ public function delete($key) } /** + * Delete cache directory. + * * {@inheritdoc} */ public function clear() { - $pattern = $this->getWildcard(); - - foreach (glob($pattern) as $file) { - $this->deleteFile($file); - } + $this->removeFiles(); - return true; + return $this->removeChildDirecotries(); } } diff --git a/src/Exception/BadMethodCallException.php b/src/Exception/BadMethodCallException.php index 640fd35..079f2c1 100644 --- a/src/Exception/BadMethodCallException.php +++ b/src/Exception/BadMethodCallException.php @@ -1,6 +1,5 @@ */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Exception; use Psr\SimpleCache\CacheException as PsrCacheException; diff --git a/src/Exception/CacheException.php b/src/Exception/CacheException.php index e197869..079df45 100644 --- a/src/Exception/CacheException.php +++ b/src/Exception/CacheException.php @@ -1,6 +1,5 @@ */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Exception; use Psr\SimpleCache\CacheException as PsrCacheException; diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index dc08e3e..fd155e5 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -1,6 +1,5 @@ */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Exception; use Psr\SimpleCache\InvalidArgumentException as PsrInvalidArgumentException; diff --git a/src/Exception/UnexpectedValueException.php b/src/Exception/UnexpectedValueException.php index c7841c6..2c05cc4 100644 --- a/src/Exception/UnexpectedValueException.php +++ b/src/Exception/UnexpectedValueException.php @@ -1,6 +1,5 @@ */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Exception; use Psr\SimpleCache\CacheException as PsrCacheException; diff --git a/src/File/BasicFilename.php b/src/File/BasicFilename.php index fb7f3ac..1c7130c 100644 --- a/src/File/BasicFilename.php +++ b/src/File/BasicFilename.php @@ -1,4 +1,17 @@ + * @author Arnold Daniels + */ + +declare(strict_types=1); namespace Desarrolla2\Cache\File; diff --git a/src/File/TrieFilename.php b/src/File/TrieFilename.php index d314c04..6219009 100644 --- a/src/File/TrieFilename.php +++ b/src/File/TrieFilename.php @@ -1,4 +1,17 @@ + * @author Arnold Daniels + */ + +declare(strict_types=1); namespace Desarrolla2\Cache\File; @@ -29,7 +42,7 @@ class TrieFilename * TrieFilename constructor. * * @param string $format - * @parma int $levels The depth of the structure + * @param int $levels The depth of the structure * @param bool $hash MD5 hash the key to get a better spread */ public function __construct(string $format, int $levels = 1, bool $hash = false) diff --git a/src/Memory.php b/src/Memory.php index 7b91546..899cc79 100644 --- a/src/Memory.php +++ b/src/Memory.php @@ -90,17 +90,6 @@ protected function getLimitOption() } - /** - * {@inheritdoc} - */ - public function delete($key) - { - $cacheKey = $this->getKey($key); - unset($this->cache[$cacheKey], $this->cacheTtl[$cacheKey]); - - return true; - } - /** * {@inheritdoc} */ @@ -110,9 +99,9 @@ public function get($key, $default = null) return $default; } - $cacheKey = $this->getKey($key); + $id = $this->keyToId($key); - return $this->unpack($this->cache[$cacheKey]); + return $this->unpack($this->cache[$id]); } /** @@ -120,14 +109,14 @@ public function get($key, $default = null) */ public function has($key) { - $cacheKey = $this->getKey($key); + $id = $this->keyToId($key); - if (!isset($this->cacheTtl[$cacheKey])) { + if (!isset($this->cacheTtl[$id])) { return false; } - if ($this->cacheTtl[$cacheKey] <= time()) { - unset($this->cache[$cacheKey], $this->cacheTtl[$cacheKey]); + if ($this->cacheTtl[$id] <= time()) { + unset($this->cache[$id], $this->cacheTtl[$id]); return false; } @@ -144,10 +133,21 @@ public function set($key, $value, $ttl = null) unset($this->cache[$deleteKey], $this->cacheTtl[$deleteKey]); } - $cacheKey = $this->getKey($key); + $id = $this->keyToId($key); - $this->cache[$cacheKey] = $this->pack($value); - $this->cacheTtl[$cacheKey] = $this->ttlToTimestamp($ttl ?? $this->ttl) ?? PHP_INT_MAX; + $this->cache[$id] = $this->pack($value); + $this->cacheTtl[$id] = $this->ttlToTimestamp($ttl) ?? PHP_INT_MAX; + + return true; + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + $id = $this->keyToId($key); + unset($this->cache[$id], $this->cacheTtl[$id]); return true; } diff --git a/src/Mongo.php b/src/Mongo.php deleted file mode 100644 index 411fcfa..0000000 --- a/src/Mongo.php +++ /dev/null @@ -1,215 +0,0 @@ - - * @author Arnold Daniels - */ - -declare(strict_types=1); - -namespace Desarrolla2\Cache; - -use Desarrolla2\Cache\Exception\UnexpectedValueException; -use Desarrolla2\Cache\Packer\PackerInterface; -use Desarrolla2\Cache\Packer\SerializePacker; -use MongoDB; -use MongoDB\BSON\UTCDatetime as BSONUTCDateTime; - -/** - * Mongo - */ -class Mongo extends AbstractCache -{ - /** - * @var MongoDb\Collection - */ - protected $collection; - - /** - * Class constructor - * - * @param mixed $backend - * @throws UnexpectedValueException - * @throws MongoDB\Driver\Exception - */ - public function __construct($backend = null) - { - if (!isset($backend)) { - $backend = new MongoDB\Client(); - } - - if ($backend instanceof MongoDB\Client) { - $backend = $backend->selectDatabase('cache'); - } - - if ($backend instanceof MongoDB\Collection) { - $backend = $backend->selectCollection('items'); - } - - if (!$backend instanceof MongoDB\Database) { - $type = (is_object($backend) ? get_class($backend) . ' ' : '') . gettype($backend); - throw new UnexpectedValueException("Database should be a database (MongoDB\Database) or " . - " collection (MongoDB\Collection) object, not a $type"); - } - - $this->collection = $backend; - - $this->initCollection(); - } - - /** - * Initialize the DB collecition - */ - protected function initCollection() - { - // TTL index - $this->collection->createIndex(['ttl' => 1], ['expireAfterSeconds' => 0]); - } - - /** - * Class destructor - */ - public function __destruct() - { - $this->predis->disconnect(); - } - - - /** - * Create the default packer for this cache implementation. - * - * @return PackerInterface - */ - protected static function createDefaultPacker(): PackerInterface - { - return new SerializePacker(); - } - - - /** - * {@inheritdoc} - */ - public function delete($key) - { - $cacheKey = $this->getKey($key); - - $this->collection->remove(['_id' => $cacheKey]); - } - - /** - * {@inheritdoc } - */ - public function get($key, $default = null) - { - $data = $this->collection->findOne(['_id' => $this->getKey($key)]); - - return isset($data) ? $this->unpack($data['value']) : $default; - } - - /** - * {@inheritdoc} - */ - public function getMultiple($keys, $default = null) - { - $this->assertIterable($keys, 'keys not iterable'); - - if (empty($keys)) { - return []; - } - - $cacheKeys = array_map([$this, 'getKey'], is_array($keys) ? $keys : iterator_to_array($keys)); - $items = array_fill_keys($cacheKeys, $default); - - $rows = $this->collection->find(['_id' => ['$in' => $cacheKeys]]); - - foreach ($rows as $row) { - $items[$row['_id']] = $this->unpack($row['value']); - } - - return $keys === $cacheKeys ? $items : array_merge($keys, array_values($items)); - } - - /** - * {@inheritdoc } - */ - public function has($key) - { - return $this->collection->count(['_id' => $this->getKey($key)]) > 0; - } - - /** - * {@inheritdoc } - */ - public function set($key, $value, $ttl = null) - { - $cacheKey = $this->getKey($key); - - $item = [ - '_id' => $cacheKey, - 'ttl' => $this->getTtlBSON($ttl ?: $this->ttl), - 'value' => $this->pack($value) - ]; - - $this->collection->save(['_id' => $cacheKey], $item, ['upsert' => true]); - } - - /** - * {@inheritdoc} - */ - public function setMultiple($values, $ttl = null) - { - $this->assertIterable($values, 'values not iterable'); - - if (empty($values)) { - return true; - } - - $bsonTtl = $this->getTtlBSON($ttl ?: $this->ttl); - $items = []; - - foreach ($values as $key => $value) { - $cacheKey = $this->getKey($key); - - $items[] = [ - 'replaceOne' => [ - 'filter' => ['_id' => $cacheKey], - 'replacement' => [ - '_id' => $cacheKey, - 'ttl' => $bsonTtl, - 'value' => $this->pack($value) - ], - 'upsert' => true - ] - ]; - } - - $this->collection->bulkWrite($items); - } - - /** - * {@inheritdoc} - */ - public function clear() - { - $this->collection->dropCollection(); - $this->initCollection(); - } - - /** - * Get TTL as Date type BSON object - * - * @param int|null $ttl - * @return BSONUTCDatetime|null - */ - protected function getTtlBSON(?int $ttl): ?BSONUTCDatetime - { - return isset($ttl) ? new BSONUTCDateTime($this->ttlToSeconds($ttl) * 1000) : null; - } -} diff --git a/src/MongoDB.php b/src/MongoDB.php new file mode 100644 index 0000000..863697c --- /dev/null +++ b/src/MongoDB.php @@ -0,0 +1,273 @@ + + * @author Arnold Daniels + */ + +declare(strict_types=1); + +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Packer\MongoDBBinaryPacker; +use Desarrolla2\Cache\Option\InitializeTrait as InitializeOption; +use MongoDB\Collection; +use MongoDB\BSON\UTCDatetime as BSONUTCDateTime; +use MongoDB\Driver\Exception\RuntimeException as MongoDBRuntimeException; + +/** + * MongoDB cache implementation + */ +class MongoDB extends AbstractCache +{ + use InitializeOption; + + /** + * @var Collection + */ + protected $collection; + + /** + * Class constructor + * + * @param Collection $collection + */ + public function __construct(Collection $collection) + { + $this->collection = $collection; + } + + /** + * Initialize the DB collection. + * Set TTL index. + */ + protected function initialize() + { + $this->collection->createIndex(['ttl' => 1], ['expireAfterSeconds' => 0]); + } + + + /** + * Create the default packer for this cache implementation. + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new MongoDBBinaryPacker(); + } + + /** + * Get filter for key and ttl. + * + * @param string|iterable $key + * @return array + */ + protected function filter($key) + { + if (is_array($key)) { + $key = ['$in' => $key]; + } + + return [ + '_id' => $key, + '$or' => [ + ['ttl' => ['$gt' => new BSONUTCDateTime($this->currentTimestamp() * 1000)]], + ['ttl' => null] + ] + ]; + } + + /** + * {@inheritdoc } + */ + public function get($key, $default = null) + { + $filter = $this->filter($this->keyToId($key)); + + try { + $data = $this->collection->findOne($filter); + } catch (MongoDBRuntimeException $e) { + return $default; + } + + return isset($data) ? $this->unpack($data['value']) : $default; + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + $idKeyPairs = $this->mapKeysToIds($keys); + + if (empty($idKeyPairs)) { + return []; + } + + $filter = $this->filter(array_keys($idKeyPairs)); + $items = array_fill_keys(array_values($idKeyPairs), $default); + + try { + $rows = $this->collection->find($filter); + } catch (MongoDBRuntimeException $e) { + return $items; + } + + foreach ($rows as $row) { + $id = $row['_id']; + $key = $idKeyPairs[$id]; + + $items[$key] = $this->unpack($row['value']); + } + + return $items; + } + + /** + * {@inheritdoc } + */ + public function has($key) + { + $filter = $this->filter($this->keyToId($key)); + + try { + $count = $this->collection->count($filter); + } catch (MongoDBRuntimeException $e) { + return false; + } + + return $count > 0; + } + + /** + * {@inheritdoc } + */ + public function set($key, $value, $ttl = null) + { + $id = $this->keyToId($key); + + $item = [ + '_id' => $id, + 'ttl' => $this->getTtlBSON($ttl), + 'value' => $this->pack($value) + ]; + + try { + $this->collection->replaceOne(['_id' => $id], $item, ['upsert' => true]); + } catch (MongoDBRuntimeException $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $this->assertIterable($values, 'values not iterable'); + + if (empty($values)) { + return true; + } + + $bsonTtl = $this->getTtlBSON($ttl); + $items = []; + + foreach ($values as $key => $value) { + $id = $this->keyToId(is_int($key) ? (string)$key : $key); + + $items[] = [ + 'replaceOne' => [ + ['_id' => $id], + [ + '_id' => $id, + 'ttl' => $bsonTtl, + 'value' => $this->pack($value) + ], + [ 'upsert' => true ] + ] + ]; + } + + try { + $this->collection->bulkWrite($items); + } catch (MongoDBRuntimeException $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + $id = $this->keyToId($key); + + try { + $this->collection->deleteOne(['_id' => $id]); + } catch (MongoDBRuntimeException $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + $idKeyPairs = $this->mapKeysToIds($keys); + + try { + if (!empty($idKeyPairs)) { + $this->collection->deleteMany(['_id' => ['$in' => array_keys($idKeyPairs)]]); + } + } catch (MongoDBRuntimeException $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + try { + $this->collection->drop(); + } catch (MongoDBRuntimeException $e) { + return false; + } + + $this->requireInitialization(); + + return true; + } + + + /** + * Get TTL as Date type BSON object + * + * @param null|int|DateInterval $ttl + * @return BSONUTCDatetime|null + */ + protected function getTtlBSON($ttl): ?BSONUTCDatetime + { + return isset($ttl) ? new BSONUTCDateTime($this->ttlToTimestamp($ttl) * 1000) : null; + } +} diff --git a/src/Mysqli.php b/src/Mysqli.php index fb090cc..8f22289 100644 --- a/src/Mysqli.php +++ b/src/Mysqli.php @@ -1,6 +1,5 @@ server = $server ?: new Server(); + $this->server = $server; + } + + + /** + * Initialize table. + * Automatically delete old cache. + */ + protected function initialize() + { + if ($this->initialized !== false) { + return; + } + + $this->query( + "CREATE TABLE IF NOT EXISTS `{table}` " + . "( `key` VARCHAR(255), `value` TEXT, `ttl` INT UNSIGNED, PRIMARY KEY (`key`) )" + ); + + $this->query( + "CREATE EVENT IF NOT EXISTS `apply_ttl_{$this->table}` ON SCHEDULE EVERY 1 HOUR DO BEGIN" + . " DELETE FROM {table} WHERE ttl < NOW();" + . " END" + ); + + $this->initialized = true; } /** @@ -53,14 +84,16 @@ protected static function createDefaultPacker(): PackerInterface return new SerializePacker(); } + /** * Set the table name * * @param string $table */ - public function setTableOption($table) + public function setTableOption(string $table) { - $this->table = (string)$table; + $this->table = $table; + $this->requireInitialization(); } /** @@ -68,31 +101,27 @@ public function setTableOption($table) * * @return string */ - public function getTableOption() + public function getTableOption(): string { return $this->table; } - /** - * {@inheritdoc} - */ - public function delete($key) - { - return $this->query('DELETE FROM {table} WHERE `key` = %s', $this->getKey($key)) !== false; - } - /** * {@inheritdoc} */ public function get($key, $default = null) { - $row = $this->fetchRow( - 'SELECT `value` FROM {table} WHERE `key` = %s AND `ttl` >= %d LIMIT 1', - $this->getKey($key), - $this->time() + $this->initialize(); + + $result = $this->query( + 'SELECT `value` FROM {table} WHERE `key` = %s AND (`ttl` > %d OR `ttl` IS NULL) LIMIT 1', + $this->keyToId($key), + $this->currentTimestamp() ); + $row = $result !== false ? $result->fetch_row() : null; + return $row ? $this->unpack($row[0]) : $default; } @@ -101,26 +130,28 @@ public function get($key, $default = null) */ public function getMultiple($keys, $default = null) { - $this->assertIterable($keys, 'keys not iterable'); + $idKeyPairs = $this->mapKeysToIds($keys); - if (empty($keys)) { + if (empty($idKeyPairs)) { return []; } - $cacheKeys = array_map([$this, 'getKey'], $keys); - $items = array_fill_keys($cacheKeys, $default); + $this->initialize(); + + $values = array_fill_keys(array_values($idKeyPairs), $default); - $ret = $this->query( - 'SELECT `key`, `value` FROM {table} WHERE `key` IN `%s` AND `ttl` >= %d', - $cacheKeys, - $this->time() + $result = $this->query( + 'SELECT `key`, `value` FROM {table} WHERE `key` IN (%s) AND (`ttl` > %d OR `ttl` IS NULL)', + array_keys($idKeyPairs), + $this->currentTimestamp() ); - while ((list($key, $value) = $ret->fetch_assoc())) { - $items[$key] = $this->unpack($value); + while ((list($id, $value) = $result->fetch_row())) { + $key = $idKeyPairs[$id]; + $values[$key] = $this->unpack($value); } - return $keys === $cacheKeys ? $items : array_merge($keys, array_values($items)); + return $values; } /** @@ -128,13 +159,17 @@ public function getMultiple($keys, $default = null) */ public function has($key) { - $row = $this->fetchRow( - 'SELECT `key` FROM {table} WHERE `key` = %s AND `ttl` >= %d LIMIT 1', - $this->getKey($key), - $this->time() + $this->initialize(); + + $result = $this->query( + 'SELECT COUNT(`key`) FROM {table} WHERE `key` = %s AND (`ttl` > %d OR `ttl` IS NULL) LIMIT 1', + $this->keyToId($key), + $this->currentTimestamp() ); - return !empty($row); + list($count) = $result ? $result->fetch_row() : null; + + return isset($count) && $count > 0; } /** @@ -142,14 +177,16 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $res = $this->query( - 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES (%s, %s, %d)', - $this->getKey($key), + $this->initialize(); + + $result = $this->query( + 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES (%s, %s, %s)', + $this->keyToId($key), $this->pack($value), - $this->time() + ($ttl ?: $this->ttl) + $this->ttlToTimestamp($ttl) ); - return $res !== false; + return $result !== false; } /** @@ -163,13 +200,15 @@ public function setMultiple($values, $ttl = null) return true; } - $timeTtl = $this->time() + ($ttl ?: $this->ttl); + $this->initialize(); + + $timeTtl = $this->ttlToTimestamp($ttl); $query = 'REPLACE INTO {table} (`key`, `value`, `ttl`) VALUES'; foreach ($values as $key => $value) { $query .= sprintf( - '(%s, %s, %d),', - $this->quote($this->getKey($key)), + ' (%s, %s, %s),', + $this->quote($this->keyToId(is_int($key) ? (string)$key : $key)), $this->quote($this->pack($value)), $this->quote($timeTtl) ); @@ -181,35 +220,45 @@ public function setMultiple($values, $ttl = null) /** * {@inheritdoc} */ - public function clear() + public function delete($key) { - $this->query('TRUNCATE {table}'); + $this->initialize(); + + return (bool)$this->query('DELETE FROM {table} WHERE `key` = %s', $this->keyToId($key)); } /** - * Fetch a row from a MySQL table - * - * @param string $query - * @param string[] $params - * @return array|false + * {@inheritdoc} */ - protected function fetchRow($query, ...$params) + public function deleteMultiple($keys) { - $res = $this->query($query, ...$params); + $idKeyPairs = $this->mapKeysToIds($keys); - if ($res === false) { - return false; + if (empty($idKeyPairs)) { + return true; } - return $res->fetch_row(); + $this->initialize(); + + return (bool)$this->query('DELETE FROM {table} WHERE `key` IN (%s)', array_keys($idKeyPairs)); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->initialize(); + return (bool)$this->query('TRUNCATE {table}'); } + /** * Query the MySQL server * * @param string $query * @param mixed[] $params - * @return \mysqli_result|false; + * @return \mysqli_result|false */ protected function query($query, ...$params) { @@ -221,7 +270,7 @@ protected function query($query, ...$params) $ret = $this->server->query($sql); if ($ret === false) { - trigger_error($this->server->error, E_USER_NOTICE); + trigger_error($this->server->error . " $sql", E_USER_NOTICE); } return $ret; @@ -235,6 +284,10 @@ protected function query($query, ...$params) */ protected function quote($value) { + if ($value === null) { + return 'NULL'; + } + if (is_array($value)) { return join(', ', array_map([$this, 'quote'], $value)); } diff --git a/src/Option/FilenameTrait.php b/src/Option/FilenameTrait.php index 184039e..345607c 100644 --- a/src/Option/FilenameTrait.php +++ b/src/Option/FilenameTrait.php @@ -11,6 +11,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Option; use TypeError; diff --git a/src/Option/InitializeTrait.php b/src/Option/InitializeTrait.php new file mode 100644 index 0000000..680d507 --- /dev/null +++ b/src/Option/InitializeTrait.php @@ -0,0 +1,65 @@ + + * @author Arnold Daniels + */ + +declare(strict_types=1); + +namespace Desarrolla2\Cache\Option; + +/** + * Auto initialize the cache + */ +trait InitializeTrait +{ + /** + * Is cache initialized + * @var bool|null + */ + protected $initialized = false; + + + /** + * Enable/disable initialization + * + * @param bool $enabled + */ + public function setInitializeOption(bool $enabled) + { + $this->initialized = $enabled ? (bool)$this->initialized : null; + } + + /** + * Should initialize + * + * @return bool + */ + protected function getInitializeOption(): bool + { + return $this->initialized !== null; + } + + /** + * Mark as initialization required (if enabled) + */ + protected function requireInitialization() + { + $this->initialized = isset($this->initialized) ? false : null; + } + + + /** + * Initialize + * + * @return void + */ + abstract protected function initialize(): void; +} diff --git a/src/Option/PrefixTrait.php b/src/Option/PrefixTrait.php index 52fbaca..2befaf4 100644 --- a/src/Option/PrefixTrait.php +++ b/src/Option/PrefixTrait.php @@ -11,6 +11,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Option; /** diff --git a/src/Option/TtlTrait.php b/src/Option/TtlTrait.php index 3f1e110..330aa15 100644 --- a/src/Option/TtlTrait.php +++ b/src/Option/TtlTrait.php @@ -11,6 +11,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Option; use Desarrolla2\Cache\Exception\InvalidArgumentException; diff --git a/src/Packer/JsonPacker.php b/src/Packer/JsonPacker.php index b6fab0c..1e1eac4 100644 --- a/src/Packer/JsonPacker.php +++ b/src/Packer/JsonPacker.php @@ -12,6 +12,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Packer; use Desarrolla2\Cache\Packer\PackerInterface; diff --git a/src/Packer/MongoDBBinaryPacker.php b/src/Packer/MongoDBBinaryPacker.php new file mode 100644 index 0000000..fcd3a83 --- /dev/null +++ b/src/Packer/MongoDBBinaryPacker.php @@ -0,0 +1,77 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache\Packer; + +use Desarrolla2\Cache\Packer\PackerInterface; +use MongoDB\BSON\Binary; + +/** + * Pack as BSON binary + * + * @todo Don't use serialize when packer chain is here. + */ +class MongoDBBinaryPacker implements PackerInterface +{ + /** + * @var array + */ + protected $options; + + /** + * SerializePacker constructor + * + * @param array $options Any options to be provided to unserialize() + */ + public function __construct(array $options = ['allowed_classes' => true]) + { + $this->options = $options; + } + + /** + * Get cache type (might be used as file extension) + * + * @return string + */ + public function getType() + { + return 'bson'; + } + + /** + * Pack the value + * + * @param mixed $value + * @return string + */ + public function pack($value) + { + return new Binary(serialize($value), Binary::TYPE_GENERIC); + } + + /** + * Unpack the value + * + * @param string $packed + * @return string + * @throws \UnexpectedValueException if he value can't be unpacked + */ + public function unpack($packed) + { + if (!$packed instanceof Binary) { + throw new InvalidArgumentException("packed value should be BSON binary"); + } + + return unserialize((string)$packed, $this->options); + } +} \ No newline at end of file diff --git a/src/Packer/NopPacker.php b/src/Packer/NopPacker.php index 2094e90..3e1bd44 100644 --- a/src/Packer/NopPacker.php +++ b/src/Packer/NopPacker.php @@ -12,6 +12,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Packer; use Desarrolla2\Cache\Packer\PackerInterface; diff --git a/src/Packer/PackerInterface.php b/src/Packer/PackerInterface.php index 3dfe483..1c1539e 100644 --- a/src/Packer/PackerInterface.php +++ b/src/Packer/PackerInterface.php @@ -12,6 +12,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Packer; /** diff --git a/src/Packer/PackingTrait.php b/src/Packer/PackingTrait.php index b833e8e..3c6192f 100644 --- a/src/Packer/PackingTrait.php +++ b/src/Packer/PackingTrait.php @@ -11,6 +11,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Packer; /** diff --git a/src/Packer/SerializePacker.php b/src/Packer/SerializePacker.php index 8d116a7..8df68fd 100644 --- a/src/Packer/SerializePacker.php +++ b/src/Packer/SerializePacker.php @@ -12,6 +12,8 @@ * @author Arnold Daniels */ +declare(strict_types=1); + namespace Desarrolla2\Cache\Packer; use Desarrolla2\Cache\Packer\PackerInterface; diff --git a/tests/AbstractCacheTest.php b/tests/AbstractCacheTest.php index 41564d3..eb56d2d 100644 --- a/tests/AbstractCacheTest.php +++ b/tests/AbstractCacheTest.php @@ -1,6 +1,5 @@ config = json_decode(file_get_contents($configurationFile), true); - - parent::setUp(); - } - /** * @return array */ @@ -50,6 +27,7 @@ public function dataProviderForOptions() { return [ ['ttl', 100], + ['prefix', 'test'] ]; } @@ -70,6 +48,27 @@ public function testWithOption($key, $value) $this->assertNotEquals($value, $base->getOption($key)); } + public function testWithOptions() + { + $data = $this->dataProviderForOptions(); + $options = array_combine(array_column($data, 0), array_column($data, 1)); + + $base = $this->createSimpleCache(); + $cache = $base->withOptions($options); + + foreach ($options as $key => $value) { + $this->assertEquals($value, $cache->getOption($key)); + } + + // Check immutability + $this->assertNotSame($base, $cache); + + foreach ($options as $key => $value) { + $this->assertNotEquals($value, $base->getOption($key)); + } + } + + /** * @return array */ @@ -77,7 +76,7 @@ public function dataProviderForOptionsException() { return [ ['ttl', 0, InvalidArgumentException::class], - ['file', 100, InvalidArgumentException::class] + ['foo', 'bar', InvalidArgumentException::class] ]; } diff --git a/tests/FileTest.php b/tests/FileTest.php index a3648a2..477d0f2 100644 --- a/tests/FileTest.php +++ b/tests/FileTest.php @@ -14,28 +14,38 @@ namespace Desarrolla2\Test\Cache; use Desarrolla2\Cache\File as FileCache; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; /** * FileTest */ class FileTest extends AbstractCacheTest { + /** + * @var vfsStreamDirectory + */ + private $root; + protected $skippedTests = [ 'testBasicUsageWithLongKey' => 'Only support keys up to 64 bytes' ]; - public function createSimpleCache() + public function setUp() { - return new FileCache($this->config['file']['dir']); + $this->root = vfsStream::setup('cache'); + + parent::setUp(); } - /** - * Remove all temp dir with cache files - */ - public function tearDown() + public function createSimpleCache() { - parent::tearDown(); + return new FileCache(vfsStream::url('cache')); + } - rmdir($this->config['file']['dir']); + + public function tearDown() + { + // No need to clear all files, as the virtual filesystem is cleared after each test. } } diff --git a/tests/FileTrieTest.php b/tests/FileTrieTest.php new file mode 100644 index 0000000..2d7dc44 --- /dev/null +++ b/tests/FileTrieTest.php @@ -0,0 +1,54 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Test\Cache; + +use Desarrolla2\Cache\File as FileCache; +use Desarrolla2\Cache\File\TrieFilename; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; + +/** + * FileTest with Trie structure + */ +class FileTrieTest extends AbstractCacheTest +{ + /** + * @var vfsStreamDirectory + */ + private $root; + + protected $skippedTests = [ + 'testBasicUsageWithLongKey' => 'Only support keys up to 64 bytes' + ]; + + public function setUp() + { + $this->root = vfsStream::setup('cache'); + + parent::setUp(); + } + + public function createSimpleCache() + { + return (new FileCache(vfsStream::url('cache'))) + ->withOption('filename', new TrieFilename('%s.php.cache',4)); + } + + + public function tearDown() + { + // No need to clear all files, as the virtual filesystem is cleared after each test. + } +} diff --git a/tests/MemcachedTest.php b/tests/MemcachedTest.php index 75e88ad..95c8bcb 100644 --- a/tests/MemcachedTest.php +++ b/tests/MemcachedTest.php @@ -14,7 +14,7 @@ namespace Desarrolla2\Test\Cache; use Desarrolla2\Cache\Memcached as MemcachedCache; -use Memcached as BaseMemcached; +use Memcached; use Desarrolla2\Cache\Exception\InvalidArgumentException; /** @@ -34,8 +34,10 @@ public function createSimpleCache() ); } - $adapter = new BaseMemcached(); - $adapter->addServer($this->config['memcached']['host'], $this->config['memcached']['port']); + list($host, $port) = explode(':', CACHE_TESTS_MEMCACHED_SERVER) + [1 => 11211]; + + $adapter = new Memcached(); + $adapter->addServer($host, (int)$port); if (!$adapter->flush()) { return $this->markTestSkipped("Unable to flush Memcached; not running?"); @@ -43,15 +45,4 @@ public function createSimpleCache() return new MemcachedCache($adapter); } - - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return [ - ['ttl', 0, InvalidArgumentException::class], - ['file', 100, InvalidArgumentException::class], - ]; - } } diff --git a/tests/MongoDBTest.php b/tests/MongoDBTest.php new file mode 100644 index 0000000..5336d81 --- /dev/null +++ b/tests/MongoDBTest.php @@ -0,0 +1,57 @@ + + */ + +namespace Desarrolla2\Test\Cache; + +use Desarrolla2\Cache\MongoDB as MongoDBCache; +use MongoDB\Client; + +/** + * MongoDBTest + */ +class MongoDBTest extends AbstractCacheTest +{ + /** + * @var Client + */ + protected static $client; + + /** + * Use one client per test, as the MongoDB extension leaves connections open + */ + public static function setUpBeforeClass() + { + if (!extension_loaded('mongodb')) { + return; + } + + self::$client = new Client(CACHE_TESTS_MONGO_DSN); + } + + public function setUp() + { + if (!isset(self::$client)) { + $this->markTestSkipped('The mongodb extension is not available'); + } + + parent::setUp(); + } + + public function createSimpleCache() + { + $collection = self::$client->selectCollection(CACHE_TESTS_MONGO_DATABASE, 'cache'); + + return (new MongoDBCache($collection)) + ->withOption('initialize', false); + } +} diff --git a/tests/MongoTest.php b/tests/MongoTest.php deleted file mode 100644 index d990966..0000000 --- a/tests/MongoTest.php +++ /dev/null @@ -1,65 +0,0 @@ - - */ - -namespace Desarrolla2\Test\Cache; - -use Desarrolla2\Cache\Mongo as MongoCache; - -/** - * MongoTest - */ -class MongoTest extends AbstractCacheTest -{ - public function createSimpleCache() - { - if (!extension_loaded('mongo')) { - $this->markTestSkipped( - 'The mongo extension is not available.' - ); - } - - $client = new \MongoClient($this->config['mongo']['dsn']); - $database = $client->selectDB($this->config['mongo']['database']); - - return new MongoCache($database); - } - - /** - * No sleep - */ - protected static function sleep($seconds) - { - return; - } - - /** - * @return array - */ - public function dataProviderForOptions() - { - return [ - ['ttl', 100], - ]; - } - - /** - * @return array - */ - public function dataProviderForOptionsException() - { - return [ - ['ttl', 0, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ['file', 100, '\Desarrolla2\Cache\Exception\InvalidArgumentException'], - ]; - } -} diff --git a/tests/MysqliTest.php b/tests/MysqliTest.php index 92f2ddc..12896b3 100644 --- a/tests/MysqliTest.php +++ b/tests/MysqliTest.php @@ -1,6 +1,5 @@ 'Only support keys up to 255 bytes' + ]; + public function setUp() { if (!class_exists('mysqli')) { return $this->markTestSkipped("mysqli extension not loaded"); } - $this->mysqli = new \mysqli( - $this->config['mysql']['host'], - $this->config['mysql']['user'], - $this->config['mysql']['password'], - null, - $this->config['mysql']['port'] - ); + $this->mysqli = new \mysqli(ini_get('mysqli.default_host')); if ($this->mysqli->errno) { return $this->markTestSkipped($this->mysqli->error); } - $this->mysqli->query('CREATE DATABASE IF NOT EXISTS `' . $this->config['mysql']['database'] . '`;'); - $this->mysqli->select_db($this->config['mysql']['database']); + $this->mysqli->query('CREATE DATABASE IF NOT EXISTS `' . CACHE_TESTS_MYSQLI_DATABASE . '`'); + $this->mysqli->select_db(CACHE_TESTS_MYSQLI_DATABASE); - $this->mysqli->query('CREATE TEMPORARY TABLE IF NOT EXISTS `cache`( `key` VARCHAR(255), `value` TEXT, `ttl` INT UNSIGNED )'); + $this->mysqli->query("CREATE TABLE IF NOT EXISTS `cache` " + ."( `key` VARCHAR(255), `value` TEXT, `ttl` INT UNSIGNED, PRIMARY KEY (`key`) )"); + + if ($this->mysqli->error) { + $this->markTestSkipped($this->mysqli->error); + } parent::setUp(); } public function createSimpleCache() { - return new MysqliCache($this->mysqli); + return (new MysqliCache($this->mysqli)) + ->withOption('initialize', false); } public function tearDown() { - $this->mysqli->query('DROP DATABASE IF EXISTS `'.$this->config['mysql']['database'].'`;'); + $this->mysqli->query('DROP DATABASE IF EXISTS `' . CACHE_TESTS_MYSQLI_DATABASE . '`'); + $this->mysqli->close(); } } diff --git a/tests/NotCacheTest.php b/tests/NotCacheTest.php index c2812ab..375c59f 100644 --- a/tests/NotCacheTest.php +++ b/tests/NotCacheTest.php @@ -78,9 +78,11 @@ public function testDelete() /** * @dataProvider dataProvider */ - public function testSetOption() + public function testWithOption() { - $this->cache->setOption('ttl', 3600); - $this->assertSame(3600, $this->cache->getOption('ttl')); + $cache = $this->cache->withOption('ttl', 3600); + $this->assertSame(3600, $cache->getOption('ttl')); + + $this->assertNotSame($this->cache, $cache); } } diff --git a/tests/config.json.dist b/tests/config.json.dist deleted file mode 100644 index 320ac06..0000000 --- a/tests/config.json.dist +++ /dev/null @@ -1,20 +0,0 @@ -{ - "file": { - "dir": "" - }, - "memcached": { - "host": "127.0.0.1", - "port": "11211" - }, - "mongo": { - "dsn": "mongodb://localhost:27017", - "database": "cache" - }, - "mysql": { - "user": "root", - "password": "", - "host": "127.0.0.1", - "port": "3306", - "database": "cache" - } -} From 3e30ee262922d0b503c89c2d089b4a836e877ba7 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Mon, 18 Jun 2018 04:24:37 +0200 Subject: [PATCH 35/51] Fixed Memcached --- src/Memcached.php | 41 +++++++---------------------------------- tests/MemcachedTest.php | 7 ++++++- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/src/Memcached.php b/src/Memcached.php index a8cc5e8..3b34974 100644 --- a/src/Memcached.php +++ b/src/Memcached.php @@ -19,7 +19,7 @@ use Desarrolla2\Cache\Exception\InvalidArgumentException; use Desarrolla2\Cache\Packer\PackerInterface; use Desarrolla2\Cache\Packer\NopPacker; -use Memcached as BaseMemcached; +use Memcached as MemcachedServer; /** * Memcached @@ -27,20 +27,15 @@ class Memcached extends AbstractCache { /** - * @var BaseMemcached + * @var Server */ protected $server; /** - * @param BaseMemcached|null $server + * @param MemcachedServer|null $server */ - public function __construct(BaseMemcached $server = null) + public function __construct(MemcachedServer $server) { - if (!$server) { - $server = new BaseMemcached(); - $server->addServer('localhost', 11211); - } - $this->server = $server; } @@ -55,28 +50,6 @@ protected static function createDefaultPacker(): PackerInterface return new NopPacker(); } - /** - * Set the key prefix - * - * @param string $prefix - * @return void - */ - protected function setPrefixOption(string $prefix): void - { - $this->server->setOption(Memcached::OPT_PREFIX_KEY, $prefix); - } - - /** - * Get the key prefix - * - * @return string - */ - protected function getPrefixOption(): string - { - return $this->server->getOption(Memcached::OPT_PREFIX_KEY) ?? ''; - } - - /** * Validate the key * @@ -121,7 +94,7 @@ public function get($key, $default = null) $data = $this->server->get($key); - if ($this->server->getResultCode() !== BaseMemcached::RES_SUCCESS) { + if ($this->server->getResultCode() !== MemcachedServer::RES_SUCCESS) { return $default; } @@ -138,7 +111,7 @@ public function has($key) $result = $this->server->getResultCode(); - return $result === BaseMemcached::RES_SUCCESS; + return $result === MemcachedServer::RES_SUCCESS; } /** @@ -169,7 +142,7 @@ public function delete($key) $result = $this->server->getResultCode(); - return $result === BaseMemcached::RES_SUCCESS || $result === BaseMemcached::RES_NOTFOUND; + return $result === MemcachedServer::RES_SUCCESS || $result === MemcachedServer::RES_NOTFOUND; } /** diff --git a/tests/MemcachedTest.php b/tests/MemcachedTest.php index 95c8bcb..ec1cac2 100644 --- a/tests/MemcachedTest.php +++ b/tests/MemcachedTest.php @@ -26,7 +26,7 @@ class MemcachedTest extends AbstractCacheTest 'testBasicUsageWithLongKey' => 'Only support keys up to 250 bytes' ]; - public function createSimpleCache() + public function setUp() { if (!extension_loaded('memcached') || !class_exists('\Memcached')) { $this->markTestSkipped( @@ -34,6 +34,11 @@ public function createSimpleCache() ); } + parent::setUp(); + } + + public function createSimpleCache() + { list($host, $port) = explode(':', CACHE_TESTS_MEMCACHED_SERVER) + [1 => 11211]; $adapter = new Memcached(); From 40e0e80f55d5038d1f41a9223209d5b642637a3d Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 19 Jun 2018 03:38:26 +0200 Subject: [PATCH 36/51] Fixed Predis implementation Run travis on PHP 7.3 --- .travis.yml | 3 +- phpunit.xml.dist | 2 + src/File.php | 10 ++- src/Mysqli.php | 7 +- src/Predis.php | 167 +++++++++++++++++++++++++++--------- tests/AbstractCacheTest.php | 14 ++- tests/FileTtlFileTest.php | 53 ++++++++++++ tests/PredisTest.php | 25 +++++- 8 files changed, 221 insertions(+), 60 deletions(-) create mode 100644 tests/FileTtlFileTest.php diff --git a/.travis.yml b/.travis.yml index 069f530..8090ea4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,15 @@ language: php php: - 7.1 - 7.2 + - 7.3 - hhvm matrix: allow_failures: - - php: 7.0 - php: hhvm services: + - mysql - mongodb - redis-server - memcached diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 32498e3..6f8a0a4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,5 +23,7 @@ + + diff --git a/src/File.php b/src/File.php index 2403627..88524d2 100644 --- a/src/File.php +++ b/src/File.php @@ -79,8 +79,8 @@ protected function getTtl(string $cacheFile) case 'embed': return (int)$this->readLine($cacheFile); case 'file': - return file_exists($cacheFile . '.ttl') - ? (int)file_get_contents($cacheFile . '.ttl') + return file_exists("$cacheFile.ttl") + ? (int)file_get_contents("$cacheFile.ttl") : PHP_INT_MAX; case 'mtime': return $this->getTtl() > 0 ? filemtime($cacheFile) + $this->ttl : PHP_INT_MAX; @@ -99,10 +99,12 @@ protected function setTtl($expiration, $contents, $cacheFile) { switch ($this->ttlStrategy) { case 'embed': - $contents = ($expiration ?: PHP_INT_MAX) . "\n" . $contents; + $contents = ($expiration ?? PHP_INT_MAX) . "\n" . $contents; break; case 'file': - file_put_contents("$cacheFile.ttl", $expiration); + if (isset($expiration)) { + file_put_contents("$cacheFile.ttl", $expiration); + } break; case 'mtime': // nothing diff --git a/src/Mysqli.php b/src/Mysqli.php index 8f22289..b2b724f 100644 --- a/src/Mysqli.php +++ b/src/Mysqli.php @@ -22,7 +22,10 @@ use Desarrolla2\Cache\Packer\SerializePacker; /** - * Mysqli + * Mysqli cache adapter. + * + * Errors are silently ignored but exceptions are **not** caught. Beware when using `mysqli_report()` to throw a + * `mysqli_sql_exception` on error. */ class Mysqli extends AbstractCache { @@ -67,7 +70,7 @@ protected function initialize() $this->query( "CREATE EVENT IF NOT EXISTS `apply_ttl_{$this->table}` ON SCHEDULE EVERY 1 HOUR DO BEGIN" - . " DELETE FROM {table} WHERE ttl < NOW();" + . " DELETE FROM {table} WHERE `ttl` < NOW();" . " END" ); diff --git a/src/Predis.php b/src/Predis.php index efdad03..d5c9b9d 100644 --- a/src/Predis.php +++ b/src/Predis.php @@ -17,12 +17,19 @@ namespace Desarrolla2\Cache; use Desarrolla2\Cache\AbstractCache; +use Desarrolla2\Cache\Exception\UnexpectedValueException; use Desarrolla2\Cache\Packer\PackerInterface; use Desarrolla2\Cache\Packer\SerializePacker; use Predis\Client; +use Predis\Response\ServerException; +use Predis\Response\Status; +use Predis\Response\ErrorInterface; /** - * Predis + * Predis cache adapter. + * + * Errors are silently ignored but ServerExceptions are **not** caught. To PSR-16 compliant disable the `exception` + * option. */ class Predis extends AbstractCache { @@ -37,49 +44,91 @@ class Predis extends AbstractCache * * @param Client $client */ - public function __construct(Client $client = null) + public function __construct(Client $client) { - if (!$client) { - $client = new Client(); - } - $this->predis = $client; } /** - * Class destructor + * Create the default packer for this cache implementation. + * + * @return PackerInterface */ - public function __destruct() + protected static function createDefaultPacker(): PackerInterface { - $this->predis->disconnect(); + return new SerializePacker(); } + /** - * Create the default packer for this cache implementation. + * Run a predis command. * - * @return PackerInterface + * @param string $cmd + * @param mixed + * @return mixed|bool */ - protected static function createDefaultPacker(): PackerInterface + protected function execCommand(string $cmd, ...$args) { - return new SerializePacker(); + $command = $this->predis->createCommand($cmd, $args); + $response = $this->predis->executeCommand($command); + + if ($response instanceof ErrorInterface) { + return false; + } + + if ($response instanceof Status) { + return $response->getPayload() === 'OK'; + } + + return $response; } /** - * {@inheritdoc} + * Set multiple (mset) with expire + * + * @param array $dictionary + * @param int|null $ttlSeconds + * @return bool */ - public function delete($key) + protected function msetExpire(array $dictionary, ?int $ttlSeconds): bool { - return $this->predis->executeRaw(['DEL', $this->getKey($key)]); + if (empty($dictionary)) { + return true; + } + + if (!isset($ttlSeconds)) { + return $this->execCommand('MSET', $dictionary); + } + + $transaction = $this->predis->transaction(); + + foreach ($dictionary as $key => $value) { + $transaction->set($key, $value, 'EX', $ttlSeconds); + } + + try { + $responses = $transaction->execute(); + } catch (ServerException $e) { + return false; + } + + $ok = array_reduce($responses, function($ok, $response) { + return $ok && $response instanceof Status && $response->getPayload() === 'OK'; + }, true); + + return $ok; } + /** * {@inheritdoc} */ public function get($key, $default = null) { - $packed = $this->predis->get($this->getKey($key)); + $id = $this->keyToId($key); + $response = $this->execCommand('GET', $id); - return $this->unpack($packed); + return !empty($response) ? $this->unpack($response) : $default; } /** @@ -87,19 +136,23 @@ public function get($key, $default = null) */ public function getMultiple($keys, $default = null) { - $this->assertIterable($keys, 'keys not iterable'); + $idKeyPairs = $this->mapKeysToIds($keys); + $ids = array_keys($idKeyPairs); - $transaction = $this->predis->transaction(); + $response = $this->execCommand('MGET', $ids); - foreach ($keys as $key) { - $transaction->get($key); + if ($response === false) { + return false; } - $responses = $transaction->execute(); + $items = []; + $packedItems = array_combine(array_values($idKeyPairs), $response); - return array_map(function ($value) use ($default) { - return is_string($value) ? $this->unpack($value) : $default; - }, $responses); + foreach ($packedItems as $key => $packed) { + $items[$key] = isset($packed) ? $this->unpack($packed) : $default; + } + + return $items; } /** @@ -107,7 +160,7 @@ public function getMultiple($keys, $default = null) */ public function has($key) { - return $this->predis->executeRaw(['EXISTS', $this->getKey($key)]); + return $this->execCommand('EXISTS', $this->keyToId($key)); } /** @@ -115,15 +168,22 @@ public function has($key) */ public function set($key, $value, $ttl = null) { - $cacheKey = $this->getKey($key); + $id = $this->keyToId($key); + $packed = $this->pack($value); - $set = $this->predis->set($cacheKey, $this->pack($value)); + if (!is_string($packed)) { + throw new UnexpectedValueException("Packer must create a string for the data"); + } - if ($set && isset($ttl)) { - $this->predis->executeRaw(['EXPIRE', $cacheKey, $this->ttlToSeconds($ttl)]); + $ttlSeconds = $this->ttlToSeconds($ttl); + + if (isset($ttlSeconds) && $ttlSeconds <= 0) { + return $this->execCommand('DEL', [$id]); } - return $set; + return !isset($ttlSeconds) + ? $this->execCommand('SET', $id, $packed) + : $this->execCommand('SETEX', $id, $ttlSeconds, $packed); } /** @@ -133,21 +193,46 @@ public function setMultiple($values, $ttl = null) { $this->assertIterable($values, 'values not iterable'); - $transaction = $this->predis->transaction(); - $ttlSeconds = $this->ttlToSeconds($ttl); + $dictionary = []; foreach ($values as $key => $value) { - $cacheKey = $this->getKey($key); - $transaction->set($cacheKey); + $id = $this->keyToId(is_int($key) ? (string)$key : $key); + $packed = $this->pack($value); - if (isset($ttlSeconds)) { - $transaction->executeRaw(['EXPIRE', $cacheKey, $ttlSeconds]); + if (!is_string($packed)) { + throw new UnexpectedValueException("Packer must create a string for the data"); } + + $dictionary[$id] = $packed; + } + + $ttlSeconds = $this->ttlToSeconds($ttl); + + if (isset($ttlSeconds) && $ttlSeconds <= 0) { + return $this->execCommand('DEL', array_keys($dictionary)); } - $responses = $transaction->execute(); + return $this->msetExpire($dictionary, $ttlSeconds); + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + $id = $this->keyToId($key); + + return $this->execCommand('DEL', [$id]) !== false; + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + $ids = array_keys($this->mapKeysToIds($keys)); - return count(array_filter($responses)) > 0; + return empty($ids) || $this->execCommand('DEL', $ids) !== false; } /** @@ -155,6 +240,6 @@ public function setMultiple($values, $ttl = null) */ public function clear() { - $this->predis->executeRaw(['FLUSHDB']); + return $this->execCommand('FLUSHDB'); } } diff --git a/tests/AbstractCacheTest.php b/tests/AbstractCacheTest.php index eb56d2d..8c16a7a 100644 --- a/tests/AbstractCacheTest.php +++ b/tests/AbstractCacheTest.php @@ -39,13 +39,12 @@ public function dataProviderForOptions() */ public function testWithOption($key, $value) { - $base = $this->createSimpleCache(); - $cache = $base->withOption($key, $value); + $cache = $this->cache->withOption($key, $value); $this->assertEquals($value, $cache->getOption($key)); // Check immutability - $this->assertNotSame($base, $cache); - $this->assertNotEquals($value, $base->getOption($key)); + $this->assertNotSame($this->cache, $cache); + $this->assertNotEquals($value, $this->cache->getOption($key)); } public function testWithOptions() @@ -53,18 +52,17 @@ public function testWithOptions() $data = $this->dataProviderForOptions(); $options = array_combine(array_column($data, 0), array_column($data, 1)); - $base = $this->createSimpleCache(); - $cache = $base->withOptions($options); + $cache = $this->cache->withOptions($options); foreach ($options as $key => $value) { $this->assertEquals($value, $cache->getOption($key)); } // Check immutability - $this->assertNotSame($base, $cache); + $this->assertNotSame($this->cache, $cache); foreach ($options as $key => $value) { - $this->assertNotEquals($value, $base->getOption($key)); + $this->assertNotEquals($value, $this->cache->getOption($key)); } } diff --git a/tests/FileTtlFileTest.php b/tests/FileTtlFileTest.php new file mode 100644 index 0000000..8df80ec --- /dev/null +++ b/tests/FileTtlFileTest.php @@ -0,0 +1,53 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Test\Cache; + +use Desarrolla2\Cache\File as FileCache; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; + +/** + * FileTest + */ +class FileTtlFileTest extends AbstractCacheTest +{ + /** + * @var vfsStreamDirectory + */ + private $root; + + protected $skippedTests = [ + 'testBasicUsageWithLongKey' => 'Only support keys up to 64 bytes' + ]; + + public function setUp() + { + $this->root = vfsStream::setup('cache'); + + parent::setUp(); + } + + public function createSimpleCache() + { + return (new FileCache(vfsStream::url('cache'))) + ->withOption('ttl-strategy', 'file'); + } + + + public function tearDown() + { + // No need to clear all files, as the virtual filesystem is cleared after each test. + } +} diff --git a/tests/PredisTest.php b/tests/PredisTest.php index efe74a8..80f8b68 100644 --- a/tests/PredisTest.php +++ b/tests/PredisTest.php @@ -22,19 +22,36 @@ */ class PredisTest extends AbstractCacheTest { - public function createSimpleCache() + /** + * @var Client + */ + protected $client; + + public function setUp() { if (!class_exists('Predis\Client')) { return $this->markTestSkipped('The predis library is not available'); } try { - $predis = new Client(); - $predis->connect(); + $this->client = new Client(CACHE_TESTS_PREDIS_DSN, ['exceptions' => false]); + $this->client->connect(); } catch (ConnectionException $e) { return $this->markTestSkipped($e->getMessage()); } - return new PredisCache($predis); + parent::setUp(); + } + + public function tearDown() + { + parent::tearDown(); + + $this->client->disconnect(); + } + + public function createSimpleCache() + { + return new PredisCache($this->client); } } From 796b33f2a48d4d8f4aa2914352dba58970e26774 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 19 Jun 2018 03:39:00 +0200 Subject: [PATCH 37/51] Updating docs Removing this from README.md [skip ci] --- README.md | 295 ++++-------------------------- docs/implementations/acpu.md | 25 +++ docs/implementations/file.md | 82 +++++++++ docs/implementations/memcached.md | 28 +++ docs/implementations/memory.md | 23 +++ docs/implementations/mongodb.md | 45 +++++ docs/implementations/mysqli.md | 47 +++++ docs/implementations/notcache.md | 13 ++ docs/implementations/phpfile.md | 74 ++++++++ docs/implementations/predis.md | 31 ++++ {doc => docs}/performance.md | 0 11 files changed, 399 insertions(+), 264 deletions(-) create mode 100644 docs/implementations/acpu.md create mode 100644 docs/implementations/file.md create mode 100644 docs/implementations/memcached.md create mode 100644 docs/implementations/memory.md create mode 100644 docs/implementations/mongodb.md create mode 100644 docs/implementations/mysqli.md create mode 100644 docs/implementations/notcache.md create mode 100644 docs/implementations/phpfile.md create mode 100644 docs/implementations/predis.md rename {doc => docs}/performance.md (100%) diff --git a/README.md b/README.md index 0bf1a27..be06d95 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ -# Cache [SensioLabsInsight](https://insight.sensiolabs.com/projects/5f139261-1ac1-4559-846a-723e09319a88) +# Desarollo2 Cache A simple cache library, implementing the [PSR-16](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) standard using **immutable** objects. +![life-is-hard-cache-is](https://user-images.githubusercontent.com/100821/41566888-ecd60cde-735d-11e8-893f-da42b2cd65e7.jpg) + Caching is typically used throughout an applicatiton. Immutability ensure that modifying the cache behaviour in one -location doesn't result in unexpect behaviour in unrelated code. +location doesn't result in unexpected behaviour due to changes in unrelated code. + +_Desarollo2 Cache aims to be the most complete, correct and best performing PSR-16 implementation available._ [![Latest version][ico-version]][link-packagist] [![Latest version][ico-pre-release]][link-packagist] @@ -16,6 +20,7 @@ location doesn't result in unexpect behaviour in unrelated code. [![Today Downloads][ico-today-downloads]][link-downloads] [![Gitter][ico-gitter]][link-gitter] + ## Installation ``` @@ -30,13 +35,33 @@ use Desarrolla2\Cache\Memory as Cache; $cache = new Cache(); -$cache->set('key', 'myKeyValue', 3600); +$value = $cache->get('key'); -// later ... +if (!isset($value)) { + $value = do_something(); + $cache->set('key', $value, 3600); +} -echo $cache->get('key'); +echo $value; ``` +## Cache implementations + +* [Apcu](docs/implementations/apcu.md) +* [File](docs/implementations/file.md) +* [Memcached](docs/implementations/memcached.md) +* [Memory](docs/implementations/memory.md) +* [MongoDB](docs/implementations/mongodb.md) +* [Mysqli](docs/implementations/mysqli.md) +* [NotCache](docs/implementations/notcache.md) +* [PhpFile](docs/implementations/phpfile.md) +* [Predis](docs/implementations/predis.md) + +[Other implementations][todo-implementations] are planned. Please vote or +provide a PR to speed up the process of adding the to this library. + +[todo-implementations]: https://github.com/desarrolla2/Cache/issues?q=is%3Aissue+is%3Aopen+label%3Aadapter + ### Options You can set options for cache using the `withOption` or `withOptions` method. @@ -51,270 +76,12 @@ seconds). Setting the TTL to 0 or a negative number, means the cache should live forever. -## Cache implementations - -* [Apcu](#apcu) -* [File](#file) -* [Memcached](#memcached) -* [Memory](#memory) -* [Mongo](#mongo) -* [Mysqli](#mysqli) -* [NotCache](#notcache) -* [PhpFile](#phpfile) -* [Predis](#predis) - -### Apcu - -Use [APCu cache](http://php.net/manual/en/book.apcu.php) to cache to shared -memory. - -``` php -use Desarrolla2\Cache\Apcu as ApcuCache; - -$cache = new ApcuCache(); -``` - -_Note: by default APCu uses the time at the beginning of a request for ttl. In -some cases, like with a long running script, this can be a problem. You can -change this behaviour `ini_set('apc.use_request_time', false)`._ - -### File - -Save the cache as file to on the filesystem. - -The file contains the TTL as well -as the data. - -``` php -use Desarrolla2\Cache\CacheFile as CacheFileCache; - -$cache = new CacheFileCache(); -``` - -You may set the following options; - -``` php -use Desarrolla2\Cache\CacheFile as CacheFileCache; - -$cache = (new CacheFileCache())->withOptions([ - 'dir' => '/tmp/mycache', - 'file-prefix' => '', - 'file-suffix' => '.php.cache', - 'ttl' => 3600 -]); -``` - -### Memcached - -Store cache to [Memcached](https://memcached.org/). Memcached is a high -performance distributed caching system. - -``` php -use Desarrolla2\Cache\Memcached as MemcacheCache; - -$cache = new MemcacheCache(); -``` - -You can config your connection before - -``` php -use Desarrolla2\Cache\Memcached as MemcachedCache; -use Memcached; - -$server = new Memcached(); -// configure it here - -$cache = new MemcachedCache($server); -``` - -This implementation uses the [memcached](https://php.net/memcached) php -extension. The (alternative) memcache extension is not supported. - -### Memory - -Store the cache in process memory. Cache Memory is removed when the PHP process -exist. Also it is not shared between different processes. - -Memory cache have a option `limit`, that limit the max items in cache. - -``` php -use Desarrolla2\Cache\Memory as MemoryCache; - -$cache = new MemoryCache(); -``` - -You may set the following options; - -``` php -use Desarrolla2\Cache\Memory as MemoryCache; - -$cache = (new MemoryCache())->withOptions([ - 'ttl' => 3600, - 'limit' => 200 -]); -``` - -### Mongo - -Use it to store the cache in a Mongo database. Requires the -[(legacy) mongo extension](http://php.net/mongo) or the -[mongodb/mongodb](https://github.com/mongodb/mongo-php-library) library. - -You may pass either a database or collection object to the constructor. If a -database object is passed, the `items` collection within that DB is used. - -``` php -selectDatabase($dbname); - -$cache = new MongoCache($database); -$cache->setOption('ttl', 3600); - -``` - -``` php -selectDatabase($dbName); -$collection = $database->selectCollection($collectionName); - -$cache = new MongoCache($collection); -$cache->setOption('ttl', 3600); - -``` - -_Note that expired cache items aren't automatically deleted. To keep your -database clean, you should create a -[ttl index](https://docs.mongodb.org/manual/core/index-ttl/)._ - - -``` -db.items.createIndex( { "ttl": 1 }, { expireAfterSeconds: 30 } ) -``` - -### Mysqli - -Use it if you will you have mysqlnd available in your system. - -``` php -setOption('ttl', 3600); - -``` - - -``` php -withOptions([ - 'dir' => '/tmp/mycache', - 'file-prefix' => '', - 'ttl' => 3600 -]); -``` - -### Predis - -Use it if you will you have redis available in your system. - -You need to add predis as dependency in your composer file. - -``` json -"require": { - //... - "predis/predis": "~1.0.0" -} -``` - -other version will have compatibility issues. - -``` php -withOption('filename', $callback); +``` + +In this case, adding an item with key `foobar` would be create a file at + + /tmp/cache/f/fo/foobar.php.cache + +### Packer + +By default the [`SerializePacker`](../packers/serialize.md) is used. The +[`NopPacker`](../packers/nop.md) can be used if the values are strings. +Other packers, like the [`JsonPacker`](../packers/json.md) are also +useful with file cache. diff --git a/docs/implementations/memcached.md b/docs/implementations/memcached.md new file mode 100644 index 0000000..8e00eb0 --- /dev/null +++ b/docs/implementations/memcached.md @@ -0,0 +1,28 @@ +# Memcached + +Store cache to [Memcached](https://memcached.org/). Memcached is a high +performance distributed caching system. + +``` php +use Desarrolla2\Cache\Memcached as MemcachedCache; +use Memcached; + +$server = new Memcached(); +// configure it here + +$cache = new MemcachedCache($server); +``` + +This implementation uses the [memcached](https://php.net/memcached) php +extension. The (alternative) memcache extension is not supported. + +### Options + +| name | type | default | | +| --------- | ---- | ------- | ------------------------------------- | +| ttl | int | null | Maximum time to live in seconds | +| prefix | string | "" | Key prefix | + +### Packer + +By default the [`NopPacker`](../packers/nop.md) is used. diff --git a/docs/implementations/memory.md b/docs/implementations/memory.md new file mode 100644 index 0000000..03d1262 --- /dev/null +++ b/docs/implementations/memory.md @@ -0,0 +1,23 @@ +# Memory + +Store the cache in process memory _(in other words in an array)_. Cache Memory +is removed when the PHP process exist. Also it is not shared between different +processes. + +``` php +use Desarrolla2\Cache\Memory as MemoryCache; + +$cache = new MemoryCache(); +``` + +### Options + +| name | type | default | | +| --------- | ---- | ------- | ------------------------------------- | +| ttl | int | null | Maximum time to live in seconds | +| limit | int | null | Maximum items in cache | +| prefix | string | "" | Key prefix | + +### Packer + +By default the [`SerializePacker`](../packers/serialize.md) is used. diff --git a/docs/implementations/mongodb.md b/docs/implementations/mongodb.md new file mode 100644 index 0000000..3bad03d --- /dev/null +++ b/docs/implementations/mongodb.md @@ -0,0 +1,45 @@ +# Mongo + +Use it to store the cache in a Mongo database. Requires the mongodb extension +and the [mongodb/mongodb](https://github.com/mongodb/mongo-php-library) +library. + +You must pass a `MongoDB\Collection` object to the cache constructor. + +``` php +selectDatabase('mycache'); +$collection = $database->selectCollection('cache'); + +$cache = new MongoCache($collection); +``` + +MonoDB will always automatically create the database and collection if needed. + +### Options + +| name | type | default | | +| --------- | ---- | ------- | ------------------------------------- | +| initialize | bool | true | Enable auto-initialize | +| ttl | int | null | Maximum time to live in seconds | +| prefix | string | "" | Key prefix | + +#### Initialize option + +If `initialize` is enabled, the cache implementation will automatically create +a [ttl index](https://docs.mongodb.com/manual/core/index-ttl/). In production +it's better to disable auto-initialization and create the ttl index explicitly +when setting up the database. This prevents a `createIndex()` call on each +request. + +### Packer + +By default the [`MongoDBBinaryPacker`](../packers/mongodbbinary.md) is used. It +serializes the data and stores it in a [Binary BSON variable](http://php.net/manual/en/class.mongodb-bson-binary.php). +If the data is a UTF-8 string of simple array or stdClass object, it may be +useful to use the [`NopPacker`](../packers/nop.md) instead. diff --git a/docs/implementations/mysqli.md b/docs/implementations/mysqli.md new file mode 100644 index 0000000..df676e3 --- /dev/null +++ b/docs/implementations/mysqli.md @@ -0,0 +1,47 @@ +# Mysqli + +Cache to a [MySQL database](https://www.mysql.com/) using the +[mysqli](http://php.net/manual/en/book.mysqli.php) PHP extension. + +You must pass a `mysqli` connection object to the constructor. + +``` php +withOption('filename', $callback); +``` + +In this case, adding an item with key `foobar` would be create a file at + + /tmp/cache/f/fo/foobar.php + +### Packer + +By default the [`NopPacker`](../packers/nop.md) is used. Other packers should +not be used. + +[read more]: https://medium.com/@dylanwenzlau/500x-faster-caching-than-redis-memcache-apc-in-php-hhvm-dcd26e8447ad diff --git a/docs/implementations/predis.md b/docs/implementations/predis.md new file mode 100644 index 0000000..d29fa96 --- /dev/null +++ b/docs/implementations/predis.md @@ -0,0 +1,31 @@ +# Predis + +Cache using a [redis server](https://redis.io/). Redis is an open source, +in-memory data structure store, used as a database, cache and message broker. + +You must provide a `Predis\Client` object to the constructor. + +```php +use Desarrolla2\Cache\Predis as PredisCache; +use Predis\Client as PredisClient; + +$client = new PredisClient('tcp://localhost:6379'); +$cache = new PredisCache($client); +``` + +### Installation + +Requires the [`predis`](https://github.com/nrk/predis/wiki) library. + + composer require predis/predis + +### Options + +| name | type | default | | +| --------- | ---- | ------- | ------------------------------------- | +| ttl | int | null | Maximum time to live in seconds | +| prefix | string | "" | Key prefix | + +### Packer + +By default the [`SerializePacker`](../packers/serialize.md) is used. diff --git a/doc/performance.md b/docs/performance.md similarity index 100% rename from doc/performance.md rename to docs/performance.md From 573e620dec782eed20e7affbd20aaf6e5b3bb039 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 19 Jun 2018 03:41:22 +0200 Subject: [PATCH 38/51] typo file acpu --- docs/implementations/{acpu.md => apcu.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/implementations/{acpu.md => apcu.md} (100%) diff --git a/docs/implementations/acpu.md b/docs/implementations/apcu.md similarity index 100% rename from docs/implementations/acpu.md rename to docs/implementations/apcu.md From 774bf0de8837d39b5ddc224fe1c058a7d4ee4d47 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 19 Jun 2018 22:08:32 +0200 Subject: [PATCH 39/51] Fix travis PHP extensions Fix tests for PHPFile cache --- .travis.yml | 5 ++--- tests/PhpFileTest.php | 28 ++++++++++++++++++---------- tests/travis/php.ini | 4 ++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8090ea4..3683294 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,9 +26,8 @@ before_script: - mysql -e 'CREATE DATABASE IF NOT EXISTS `cache`;' - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' - # Install apcu - - "if [ $TRAVIS_PHP_VERSION == 'hhvm' ]; then export APCU_VERSION=5.1.0; else export APCU_VERSION=4.0.8; fi" - - sh -c "yes '' | pecl install apcu-$APCU_VERSION" + # Enable extensions + - sh -c "yes '' | pecl install mongodb" - sh -c "if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-add ./tests/travis/php.ini; fi" # Install dependencies diff --git a/tests/PhpFileTest.php b/tests/PhpFileTest.php index 48098b0..6f8c6d9 100644 --- a/tests/PhpFileTest.php +++ b/tests/PhpFileTest.php @@ -13,31 +13,39 @@ namespace Desarrolla2\Test\Cache; -use Desarrolla2\Cache\FlatFile as FileCache; -use Desarrolla2\Cache\Packer\PhpPacker; -use Desarrolla2\Cache\PhpFile; +use Desarrolla2\Cache\PhpFile as PhpFileCache; +use org\bovigo\vfs\vfsStream; +use org\bovigo\vfs\vfsStreamDirectory; /** * FileTest with PhpPacker */ class PhpFileTest extends AbstractCacheTest { + /** + * @var vfsStreamDirectory + */ + private $root; + protected $skippedTests = [ 'testBasicUsageWithLongKey' => 'Only support keys up to 64 bytes' ]; + public function setUp() + { + $this->root = vfsStream::setup('cache'); + + parent::setUp(); + } public function createSimpleCache() { - return new PhpFile($this->config['file']['dir']); + return new PhpFileCache(vfsStream::url('cache')); } - /** - * Remove all temp dir with cache files - */ - public function tearDown() + + public function tearDown() { - array_map('unlink', glob($this->config['file']['dir'] . "/*")); - rmdir($this->config['file']['dir']); + // No need to clear all files, as the virtual filesystem is cleared after each test. } } diff --git a/tests/travis/php.ini b/tests/travis/php.ini index 105af36..0754169 100644 --- a/tests/travis/php.ini +++ b/tests/travis/php.ini @@ -1,5 +1,5 @@ -extension="mongo.so" -extension="memcache.so" +extension="apcu.so" +extension="mongodb.so" extension="memcached.so" extension="redis.so" From 098b70242b0a024d23b2628f9cad4c64b4fb05b7 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 19 Jun 2018 22:29:09 +0200 Subject: [PATCH 40/51] More fixes for Travis --- .travis.yml | 17 +++++------- tests/travis/php.ini => .travis/apcu.ini | 1 - .travis/memcached.ini | 1 + .travis/mongodb.ini | 1 + .travis/redis.ini | 1 + phpunit.xml.dist | 13 ++++++++- tests/travis/phpunit.xml | 34 ------------------------ 7 files changed, 22 insertions(+), 46 deletions(-) rename tests/travis/php.ini => .travis/apcu.ini (80%) create mode 100644 .travis/memcached.ini create mode 100644 .travis/mongodb.ini create mode 100644 .travis/redis.ini delete mode 100644 tests/travis/phpunit.xml diff --git a/.travis.yml b/.travis.yml index 3683294..b117f69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,11 @@ language: php php: - 7.1 - 7.2 - - 7.3 - - hhvm + - nightly matrix: allow_failures: - - php: hhvm + - php: nightly services: - mysql @@ -27,21 +26,19 @@ before_script: - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' # Enable extensions - - sh -c "yes '' | pecl install mongodb" - - sh -c "if [ $TRAVIS_PHP_VERSION != 'hhvm' ]; then phpenv config-add ./tests/travis/php.ini; fi" + - sh -c "ls .travis/*.ini | xargs phpenv config-add" + - sh -c "yes '' | pecl install --soft mongodb && phpenv config-rm mongodb" + - sh -c "if [ $TRAVIS_PHP_VERSION == 'nightly' ]; then yes '' | pecl install --soft memcached && phpenv config-rm memcached.ini; fi" # Install dependencies - composer self-update - composer require --dev "satooshi/php-coveralls:~0.6" - mkdir -p build/logs - # Set Configuration - - cp tests/config.json.dist tests/config.json - - cp tests/travis/phpunit.xml phpunit.xml - script: - - phpunit -c ./phpunit.xml -v + - phpunit -v --coverage-text --coverage-xml=./build/logs/clover.xml after_script: - ls - php vendor/bin/coveralls -v + diff --git a/tests/travis/php.ini b/.travis/apcu.ini similarity index 80% rename from tests/travis/php.ini rename to .travis/apcu.ini index 0754169..782fd5f 100644 --- a/tests/travis/php.ini +++ b/.travis/apcu.ini @@ -1,5 +1,4 @@ extension="apcu.so" -extension="mongodb.so" extension="memcached.so" extension="redis.so" diff --git a/.travis/memcached.ini b/.travis/memcached.ini new file mode 100644 index 0000000..6b9b24f --- /dev/null +++ b/.travis/memcached.ini @@ -0,0 +1 @@ +extension="memcached.so" diff --git a/.travis/mongodb.ini b/.travis/mongodb.ini new file mode 100644 index 0000000..0d77e9f --- /dev/null +++ b/.travis/mongodb.ini @@ -0,0 +1 @@ +extension="mongodb.so" diff --git a/.travis/redis.ini b/.travis/redis.ini new file mode 100644 index 0000000..61c6d5c --- /dev/null +++ b/.travis/redis.ini @@ -0,0 +1 @@ +extension="redis.so" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 6f8a0a4..d8196d7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,17 @@ - + ./tests diff --git a/tests/travis/phpunit.xml b/tests/travis/phpunit.xml deleted file mode 100644 index 5b47474..0000000 --- a/tests/travis/phpunit.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - ./tests - - - - - - ./Test - - - /usr/share/pear/ - ./vendor - ./build - - - \ No newline at end of file From bfd61ca1a859896b8644d14d4a7fc640efb71912 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 19 Jun 2018 23:14:47 +0200 Subject: [PATCH 41/51] Fixed whitelist in phpunit.xml --- .travis.yml | 4 ++-- phpunit.xml.dist | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b117f69..82dbb7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,9 +26,9 @@ before_script: - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' # Enable extensions + - sh -c "yes '' | pecl install --soft mongodb" + - sh -c "if [ $TRAVIS_PHP_VERSION == 'nightly' ]; then yes '' | pecl install --soft memcached; fi" - sh -c "ls .travis/*.ini | xargs phpenv config-add" - - sh -c "yes '' | pecl install --soft mongodb && phpenv config-rm mongodb" - - sh -c "if [ $TRAVIS_PHP_VERSION == 'nightly' ]; then yes '' | pecl install --soft memcached && phpenv config-rm memcached.ini; fi" # Install dependencies - composer self-update diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d8196d7..4795d1f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,7 +20,7 @@ - ./tests + ./src From 981c98452c891ac10170911162742e9b55d70ea6 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Wed, 20 Jun 2018 05:13:24 +0200 Subject: [PATCH 42/51] Skip mysqli tests and use root as default user --- phpunit.xml.dist | 1 - tests/MysqliTest.php | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4795d1f..c49dbde 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,7 +9,6 @@ processIsolation="false" stopOnFailure="false" backupGlobals="false" - syntaxCheck="false" colors="true" > diff --git a/tests/MysqliTest.php b/tests/MysqliTest.php index 12896b3..35bfdf5 100644 --- a/tests/MysqliTest.php +++ b/tests/MysqliTest.php @@ -34,10 +34,13 @@ public function setUp() return $this->markTestSkipped("mysqli extension not loaded"); } - $this->mysqli = new \mysqli(ini_get('mysqli.default_host')); - - if ($this->mysqli->errno) { - return $this->markTestSkipped($this->mysqli->error); + try { + $this->mysqli = new \mysqli( + ini_get('mysqli.default_host'), + ini_get('mysqli.default_user') ?: 'root' + ); + } catch (\Exception $e) { + return $this->markTestSkipped("skipping mysqli test; " . mysqli_connect_error()); } $this->mysqli->query('CREATE DATABASE IF NOT EXISTS `' . CACHE_TESTS_MYSQLI_DATABASE . '`'); From b78ad5b9c517f0f6e8755e712892e5dcaba7c28e Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Wed, 20 Jun 2018 05:17:28 +0200 Subject: [PATCH 43/51] Fix travis for nightly --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 82dbb7c..34c0f73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,9 +26,9 @@ before_script: - mysql -e 'USE `cache`; CREATE TABLE `cache` (`k` varchar(255) NOT NULL, `v` text NOT NULL, `t` int(11) NOT NULL, PRIMARY KEY (`k`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;' # Enable extensions - - sh -c "yes '' | pecl install --soft mongodb" - - sh -c "if [ $TRAVIS_PHP_VERSION == 'nightly' ]; then yes '' | pecl install --soft memcached; fi" - - sh -c "ls .travis/*.ini | xargs phpenv config-add" + - yes '' | pecl install --soft mongodb + - test "$TRAVIS_PHP_VERSION" != "nightly" || sh -c "cd /tmp && pecl download memcached-1.0.2 && tar zxvf memcached-1.0.2.tgz && cd memcached-1.0.2 && phpize && ./configure --with-php-config=/home/travis/.phpenv/versions/master/bin/php-config --with-libmemcached-dir=no --disable-memcached-sasl && make && make install" + - ls .travis/*.ini | xargs phpenv config-add # Install dependencies - composer self-update From 165983cd612c32e1d4781dee3fce5ef1bfb3a8fb Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Wed, 20 Jun 2018 13:44:56 +0200 Subject: [PATCH 44/51] Travis once more for nightly --- .travis.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 34c0f73..8b3305f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,18 @@ before_script: # Enable extensions - yes '' | pecl install --soft mongodb - - test "$TRAVIS_PHP_VERSION" != "nightly" || sh -c "cd /tmp && pecl download memcached-1.0.2 && tar zxvf memcached-1.0.2.tgz && cd memcached-1.0.2 && phpize && ./configure --with-php-config=/home/travis/.phpenv/versions/master/bin/php-config --with-libmemcached-dir=no --disable-memcached-sasl && make && make install" + - > + test "$TRAVIS_PHP_VERSION" != "nightly" || + sh -c " + cd /tmp && + pecl download memcached-3.0.4 && + tar zxvf memcached-3.0.4.tgz && + cd memcached-3.0.4 && + phpize && + ./configure --with-php-config=/home/travis/.phpenv/versions/master/bin/php-config --with-libmemcached-dir=no --disable-memcached-sasl && + make && + make install + " - ls .travis/*.ini | xargs phpenv config-add # Install dependencies From 760efd6aa16eadeefff26559474bed49fbf23ef3 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Wed, 20 Jun 2018 13:50:04 +0200 Subject: [PATCH 45/51] memcached ext op nightly is broken --- .coveralls.yml | 1 - .travis.yml | 16 +++------------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.coveralls.yml b/.coveralls.yml index e30743e..90ae313 100644 --- a/.coveralls.yml +++ b/.coveralls.yml @@ -1,3 +1,2 @@ service_name: travis-ci -src_dir: src coverage_clover: build/logs/clover.xml diff --git a/.travis.yml b/.travis.yml index 8b3305f..f052dff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,23 +27,13 @@ before_script: # Enable extensions - yes '' | pecl install --soft mongodb - - > - test "$TRAVIS_PHP_VERSION" != "nightly" || - sh -c " - cd /tmp && - pecl download memcached-3.0.4 && - tar zxvf memcached-3.0.4.tgz && - cd memcached-3.0.4 && - phpize && - ./configure --with-php-config=/home/travis/.phpenv/versions/master/bin/php-config --with-libmemcached-dir=no --disable-memcached-sasl && - make && - make install - " - ls .travis/*.ini | xargs phpenv config-add + - test "$TRAVIS_PHP_VERSION" != "nightly" || phpenv config-rm memcached.ini || true # Install dependencies - composer self-update - - composer require --dev "satooshi/php-coveralls:~0.6" + - composer install --ignore-platform-reqs + - composer require --dev "satooshi/php-coveralls:~0.6" --ignore-platform-reqs - mkdir -p build/logs script: From 3725b9c64dff89bafdd50d12ddee20c4f8a01540 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Sat, 23 Jun 2018 05:28:37 +0200 Subject: [PATCH 46/51] Added Chain cache Closes #66 --- README.md | 8 +- docs/implementations/chain.md | 37 ++++++ src/Chain.php | 190 ++++++++++++++++++++++++++++++ tests/ChainTest.php | 216 ++++++++++++++++++++++++++++++++++ tests/MemoryTest.php | 5 + 5 files changed, 455 insertions(+), 1 deletion(-) create mode 100644 docs/implementations/chain.md create mode 100644 src/Chain.php create mode 100644 tests/ChainTest.php diff --git a/README.md b/README.md index be06d95..b7343e6 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ if (!isset($value)) { echo $value; ``` -## Cache implementations +## Adapters * [Apcu](docs/implementations/apcu.md) * [File](docs/implementations/file.md) @@ -57,6 +57,10 @@ echo $value; * [PhpFile](docs/implementations/phpfile.md) * [Predis](docs/implementations/predis.md) +The following implementation allows you to combine cache adapters. + +* [Chain](docs/implementations/chain.md) + [Other implementations][todo-implementations] are planned. Please vote or provide a PR to speed up the process of adding the to this library. @@ -105,6 +109,8 @@ Persists a set of key => value pairs in the cache ##### `deleteMultiple(array $keys)` Deletes multiple cache items in a single operation +. + The `Desarrolla2\Cache\CacheInterface` also has the following methods: ##### `withOption(string $key, string $value)` diff --git a/docs/implementations/chain.md b/docs/implementations/chain.md new file mode 100644 index 0000000..4aa5c11 --- /dev/null +++ b/docs/implementations/chain.md @@ -0,0 +1,37 @@ +# Chain + +The Cache chain allows you to use multiple implementations to store cache. For +instance, you can use both fast volatile (in-memory) storage and slower +non-volatile (disk) storage. Alternatively you can have a local storage +as well as a shared storage service. + +``` php +use Desarrolla2\Cache\Chain as CacheChain; +use Desarrolla2\Cache\Memory as MemoryCache; +use Desarrolla2\Cache\Predis as PredisCache; + +$cache = new CacheChain([ + (new MemoryCache())->withOption('ttl', 3600), + (new PredisCache())->withOption('ttl', 10800) +]); +``` + +The Chain cache implementation doesn't use any option. It uses the `Nop` packer +by default. + +Typically it's useful to specify a maximum `ttl` for each implementation. This +means that the volatile memory only holds items that are used often. + +The following actions propogate to all cache adapters in the chain + +* `set` +* `setMultiple` +* `delete` +* `deleteMultiple` +* `clear` + +For the following actions all nodes are tried in sequence + +* `has` +* `get` +* `getMultiple` \ No newline at end of file diff --git a/src/Chain.php b/src/Chain.php new file mode 100644 index 0000000..cc9ea2c --- /dev/null +++ b/src/Chain.php @@ -0,0 +1,190 @@ + + * @author Arnold Daniels + */ + +namespace Desarrolla2\Cache; + +use Desarrolla2\Cache\AbstractCache; +use Desarrolla2\Cache\CacheInterface; +use Desarrolla2\Cache\Packer\PackerInterface; +use Desarrolla2\Cache\Exception\InvalidArgumentException; + +/** + * Use multiple cache adapters. + */ +class Chain extends AbstractCache +{ + /** + * @var CacheInterface[] + */ + protected $adapters; + + /** + * Create the default packer for this cache implementation + * + * @return PackerInterface + */ + protected static function createDefaultPacker(): PackerInterface + { + return new NopPacker(); + } + + + /** + * Chain constructor. + * + * @param CacheInterface[] $adapters Fastest to slowest + */ + public function __construct(array $adapters) + { + foreach ($adapters as $adapter) { + if (!$adapter instanceof CacheInterface) { + throw new InvalidArgumentException("All adapters should be a cache implementation"); + } + } + + $this->adapters = $adapters; + } + + /** + * {@inheritdoc} + */ + public function set($key, $value, $ttl = null) + { + $success = true; + + foreach ($this->adapters as $adapter) { + $success = $adapter->set($key, $value, $ttl) && $success; + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function setMultiple($values, $ttl = null) + { + $success = true; + + foreach ($this->adapters as $adapter) { + $success = $adapter->setMultiple($values, $ttl) && $success; + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function get($key, $default = null) + { + foreach ($this->adapters as $adapter) { + $result = $adapter->get($key); // Not using $default as we want to get null if the adapter doesn't have it + + if (isset($result)) { + return $result; + } + } + + return $default; + } + + /** + * {@inheritdoc} + */ + public function getMultiple($keys, $default = null) + { + $this->assertIterable($keys, 'keys are not iterable'); + + $missing = []; + $values = []; + + foreach ($keys as $key) { + $this->assertKey($key); + + $missing[] = $key; + $values[$key] = $default; + } + + foreach ($this->adapters as $adapter) { + if (empty($missing)) { + break; + } + + $found = array_filter($adapter->getMultiple($missing), function($value) { + return isset($value); + }); + + $values = array_merge($values, $found); + $missing = array_diff($missing, array_keys($found)); + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + foreach ($this->adapters as $adapter) { + if ($adapter->has($key)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function delete($key) + { + $success = true; + + foreach ($this->adapters as $adapter) { + $success = $adapter->delete($key) && $success; + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple($keys) + { + $success = true; + + foreach ($this->adapters as $adapter) { + $success = $adapter->deleteMultiple($keys) && $success; + } + + return $success; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $success = true; + + foreach ($this->adapters as $adapter) { + $success = $adapter->clear() && $success; + } + + return $success; + } +} diff --git a/tests/ChainTest.php b/tests/ChainTest.php new file mode 100644 index 0000000..9397a1c --- /dev/null +++ b/tests/ChainTest.php @@ -0,0 +1,216 @@ + + */ + +namespace Desarrolla2\Test\Cache; + +use Desarrolla2\Cache\Chain as CacheChain; +use Desarrolla2\Cache\Memory as MemoryCache; + +/** + * ChainTest + */ +class ChainTest extends AbstractCacheTest +{ + public function createSimpleCache() + { + $adapters = [new MemoryCache()]; // For the general PSR-16 tests, we don't need more than 1 adapter + + return new CacheChain($adapters); + } + + + public function tearDown() + { + // No need to clear cache, as the adapters don't persist between tests. + } + + + public function testChainSet() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('set')->with("foo", "bar", 300); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('set')->with("foo", "bar", 300); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $cache->set("foo", "bar", 300); + } + + public function testChainSetMultiple() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('setMultiple')->with(["foo" => 1, "bar" => 2], 300); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('setMultiple')->with(["foo" => 1, "bar" => 2], 300); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $cache->setMultiple(["foo" => 1, "bar" => 2], 300); + } + + + public function testChainGetFirst() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('get')->with("foo")->willReturn("bar"); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->never())->method('get'); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $this->assertEquals("bar", $cache->get("foo", 42)); + } + + public function testChainGetSecond() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('get')->with("foo")->willReturn(null); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('get')->with("foo")->willReturn("car"); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $this->assertEquals("car", $cache->get("foo", 42)); + } + + public function testChainGetNone() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('get')->with("foo")->willReturn(null); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('get')->with("foo")->willReturn(null); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $this->assertEquals(42, $cache->get("foo", 42)); + } + + + public function testChainGetMultipleFirst() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('getMultiple')->with(["foo", "bar"]) + ->willReturn(["foo" => 1, "bar" => 2]); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->never())->method('getMultiple'); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $this->assertEquals(["foo" => 1, "bar" => 2], $cache->getMultiple(["foo", "bar"], 42)); + } + + public function testChainGetMultipleMixed() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('getMultiple') + ->with($this->equalTo(["foo", "bar", "wux", "lot"], 0.0, 10, true)) + ->willReturn(["foo" => null, "bar" => 2, "wux" => null, "lot" => null]); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('getMultiple') + ->with($this->equalTo(["foo", "wux", "lot"], 0.0, 10, true)) + ->willReturn(["foo" => 11, "wux" => 15, "lot" => null]); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $expected = ["foo" => 11, "bar" => 2, "wux" => 15, "lot" => 42]; + $this->assertEquals($expected, $cache->getMultiple(["foo", "bar", "wux", "lot"], 42)); + } + + + public function testChainHasFirst() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('has')->with("foo")->willReturn(true); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->never())->method('has'); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $this->assertTrue($cache->has("foo")); + } + + public function testChainHasSecond() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('has')->with("foo")->willReturn(false); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('has')->with("foo")->willReturn(true); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $this->assertTrue($cache->has("foo")); + } + + public function testChainHasNone() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('has')->with("foo")->willReturn(false); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('has')->with("foo")->willReturn(false); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $this->assertFalse($cache->has("foo")); + } + + + public function testChainDelete() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('delete')->with("foo"); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('delete')->with("foo"); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $cache->delete("foo"); + } + + public function testChainDeleteMultiple() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('deleteMultiple')->with(["foo", "bar"]); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('deleteMultiple')->with(["foo", "bar"]); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $cache->deleteMultiple(["foo", "bar"]); + } + + public function testChainClear() + { + $adapter1 = $this->createMock(MemoryCache::class); + $adapter1->expects($this->once())->method('clear'); + + $adapter2 = $this->createMock(MemoryCache::class); + $adapter2->expects($this->once())->method('clear'); + + $cache = new CacheChain([$adapter1, $adapter2]); + + $cache->clear(); + } +} diff --git a/tests/MemoryTest.php b/tests/MemoryTest.php index f5c5a95..fb12e76 100644 --- a/tests/MemoryTest.php +++ b/tests/MemoryTest.php @@ -25,6 +25,11 @@ public function createSimpleCache() return new MemoryCache(); } + public function tearDown() + { + // No need to clear cache, as the adapters don't persist between tests. + } + public function testExceededLimit() { $cache = $this->createSimpleCache()->withOption('limit', 1); From ab7ad23ae5d56f2642b8a8697cc472e5f0bde38f Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 26 Jun 2018 08:11:37 +0200 Subject: [PATCH 47/51] Update README.md Fixed up name Added MongoDBBinaryPacker [skip ci] --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b7343e6..cb6bea9 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# Desarollo2 Cache +# Desarolla2 Cache -A simple cache library, implementing the [PSR-16](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) standard using **immutable** objects. +A **simple cache** library, implementing the [PSR-16](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) standard using **immutable** objects. ![life-is-hard-cache-is](https://user-images.githubusercontent.com/100821/41566888-ecd60cde-735d-11e8-893f-da42b2cd65e7.jpg) Caching is typically used throughout an applicatiton. Immutability ensure that modifying the cache behaviour in one location doesn't result in unexpected behaviour due to changes in unrelated code. -_Desarollo2 Cache aims to be the most complete, correct and best performing PSR-16 implementation available._ +_Desarolla2 Cache aims to be the most complete, correct and best performing PSR-16 implementation available._ [![Latest version][ico-version]][link-packagist] [![Latest version][ico-pre-release]][link-packagist] [![Software License][ico-license]][link-license] [![Build Status][ico-travis]][link-travis] -[![Coverage Status][ico-coveralls]][link-coveralls] +[![Coverage Status][icoe-coveralls]][link-coveralls] [![Quality Score][ico-code-quality]][link-code-quality] [![Sensiolabs Insight][ico-sensiolabs]][link-sensiolabs] [![Total Downloads][ico-downloads]][link-downloads] @@ -130,9 +130,10 @@ object. By default, packing is done using `serialize` and `unserialize`. Available packers are: +* `SerializePacker` using `serialize` and `unserialize` * `JsonPacker` using `json_encode` and `json_decode` * `NopPacker` does no packing -* `SerializePacker` using `serialize` and `unserialize` +* `MongoDBBinaryPacker` using `serialize` and `unserialize` to store as [BSON Binary](http://php.net/manual/en/class.mongodb-bson-binary.php) #### PSR-16 incompatible packers From 012b3310e3ea4a4879ae28b7255d55898c10898d Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Tue, 26 Jun 2018 08:29:13 +0200 Subject: [PATCH 48/51] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb6bea9..d6203cb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ _Desarolla2 Cache aims to be the most complete, correct and best performing PSR- [![Latest version][ico-pre-release]][link-packagist] [![Software License][ico-license]][link-license] [![Build Status][ico-travis]][link-travis] -[![Coverage Status][icoe-coveralls]][link-coveralls] +[![Coverage Status][ico-coveralls]][link-coveralls] [![Quality Score][ico-code-quality]][link-code-quality] [![Sensiolabs Insight][ico-sensiolabs]][link-sensiolabs] [![Total Downloads][ico-downloads]][link-downloads] From 00d537442633121236ef8038aa780039f298a128 Mon Sep 17 00:00:00 2001 From: Pranav Phlagun Date: Thu, 30 Aug 2018 12:16:36 +0800 Subject: [PATCH 49/51] Fixes bug in JsonPacker --- src/Packer/JsonPacker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Packer/JsonPacker.php b/src/Packer/JsonPacker.php index 1e1eac4..1fa58e8 100644 --- a/src/Packer/JsonPacker.php +++ b/src/Packer/JsonPacker.php @@ -54,7 +54,7 @@ public function pack($value) */ public function unpack($packed) { - if (is_string($packed)) { + if (!is_string($packed)) { throw new InvalidArgumentException("packed value should be a string"); } From e25b51fe0f9b386161c9c35e1d591f587617fcfa Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 27 Sep 2018 08:10:24 +0200 Subject: [PATCH 50/51] composer.json provides psr/simple-cache-implementation --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 275e0ea..88731af 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,9 @@ "homepage": "https://jasny.net/" } ], + "provide": { + "psr/simple-cache-implementation": "1.0" + }, "require": { "php": ">=7.1.0", "psr/simple-cache": "^1.0", From e1b2c63f7bdf62e9d3cfaa0bf496c9a99aedb450 Mon Sep 17 00:00:00 2001 From: Dmitry Maltsev Date: Fri, 28 Feb 2020 10:33:25 +0200 Subject: [PATCH 51/51] Revert fixes --- src/Adapter/Memcached.php | 6 ++- .../Cache/Adapter/Test/MemcachedTest.php | 51 ------------------- 2 files changed, 4 insertions(+), 53 deletions(-) delete mode 100644 tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php diff --git a/src/Adapter/Memcached.php b/src/Adapter/Memcached.php index 50daadb..ff44e56 100644 --- a/src/Adapter/Memcached.php +++ b/src/Adapter/Memcached.php @@ -37,7 +37,7 @@ class Memcached extends AbstractAdapter * @param mixed $data * */ - public function __construct($data = null) + public function __construct($data = null, $options = []) { if ($data instanceof BaseMemcached) { $this->adapter = $data; @@ -48,9 +48,11 @@ public function __construct($data = null) if (is_array($data)) { /* if array, then the user supplied an array of servers */ foreach ($data as $s) { - $this->addServer($s['host'], $s['port'], $s['weight']); + $this->adapter->addServer($s['host'], $s['port'], $s['weight']); } } + + $this->setOption($options); } /** diff --git a/tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php b/tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php deleted file mode 100644 index 7c2aa05..0000000 --- a/tests/Desarrolla2/Cache/Adapter/Test/MemcachedTest.php +++ /dev/null @@ -1,51 +0,0 @@ - - */ -namespace Desarrolla2\Cache\Adapter\Test; - -use Desarrolla2\Cache\Cache; -use Desarrolla2\Cache\Adapter\Memcached; - -/** - * MemcachedTest - */ -class MemcachedTest extends AbstractCacheTest { - - public function setUp() { - parent::setup(); - if (!extension_loaded('memcached') || !class_exists('\Memcached')) { - $this->markTestSkipped( - 'The Memcached extension is not available.' - ); - } - - $data = [ - [ - 'host' => 'localhost', - 'port' => 11211, - 'weight' => 0 - ] - ]; - - $adapter = new Memcached($data); - $this->cache = new Cache($adapter); - } - - /** - * @return array - */ - public function dataProviderForOptionsException() { - return array( - array('ttl', 0, '\Desarrolla2\Cache\Exception\CacheException'), - array('file', 100, '\Desarrolla2\Cache\Exception\CacheException'), - ); - } -}