From f31fe826b0351e954abbba5192335590fe3cb356 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Thu, 7 May 2020 17:17:03 -0400 Subject: [PATCH] Implement PSR-16 --- CHANGELOG.md | 5 + RoboFile.php | 49 ++--- src/Driver/AbstractDriver.php | 9 +- src/Driver/ApcuDriver.php | 60 +++--- src/Driver/DriverInterface.php | 6 +- src/Driver/MemcachedDriver.php | 42 ++-- src/Driver/NullDriver.php | 18 +- src/Driver/RedisDriver.php | 21 +- src/Exception/CacheException.php | 7 +- src/Exception/InvalidArgumentException.php | 9 +- src/Item.php | 25 ++- src/ItemCollection.php | 2 +- src/LoggerTrait.php | 2 +- src/Pool.php | 19 +- src/Teller.php | 240 +++++++++++++++++++++ tests/Driver/ApcuDriverTest.php | 2 +- tests/Driver/DriverTestBase.php | 10 +- tests/Driver/MemcachedDriverTest.php | 2 +- tests/Driver/NullDriverTest.php | 2 +- tests/Driver/RedisDriverTest.php | 2 +- tests/Friend.php | 2 +- tests/ItemCollectionTest.php | 2 +- tests/ItemTest.php | 2 +- tests/PoolTest.php | 12 +- tests/TellerTest.php | 164 ++++++++++++++ tests/bootstrap.php | 2 +- 26 files changed, 562 insertions(+), 154 deletions(-) create mode 100644 src/Teller.php create mode 100644 tests/TellerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bdad32..efb7eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 3.0.0 +* Updated dependencies +* Increased required PHP version to 7.4 +* Added SimpleCache (PSR-16) implementation in `Teller` class + ## 2.0.0 * Removed `Memcache` integration, as the extension does not seem to be maintained * Increased required PHP version to 7.1 \ No newline at end of file diff --git a/RoboFile.php b/RoboFile.php index 355c6df..5015bb5 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -51,7 +51,7 @@ class RoboFile extends \Robo\Tasks { /** * Do static analysis tasks */ - public function analyze() + public function analyze(): void { $this->prepare(); $this->lint(); @@ -64,7 +64,7 @@ class RoboFile extends \Robo\Tasks { /** * Run all tests, generate coverage, generate docs, generate code statistics */ - public function build() + public function build(): void { $this->analyze(); $this->coverage(); @@ -74,22 +74,22 @@ class RoboFile extends \Robo\Tasks { /** * Cleanup temporary files */ - public function clean() + public function clean(): void { $cleanFiles = [ 'build/humbug.json', 'build/humbug-log.txt', ]; - array_map(function ($file) { + array_map(static function ($file) { @unlink($file); }, $cleanFiles); // So the task doesn't complain, // make any 'missing' dirs to cleanup - array_map(function ($dir) { + array_map(static function ($dir) { if ( ! is_dir($dir)) { - `mkdir -p {$dir}`; + shell_exec("mkdir -p {$dir}"); } }, $this->cleanDirs); @@ -100,18 +100,15 @@ class RoboFile extends \Robo\Tasks { /** * Run unit tests and generate coverage reports */ - public function coverage() + public function coverage(): void { - $this->taskPhpUnit() - ->configFile('build/phpunit.xml') - ->printed(true) - ->run(); + $this->_run(['phpdbg -qrr -- vendor/bin/phpunit -c build']); } /** * Generate documentation with phpdox */ - public function docs() + public function docs(): void { $cmd_parts = [ 'cd build', @@ -124,7 +121,7 @@ class RoboFile extends \Robo\Tasks { /** * Verify that source files are valid */ - public function lint() + public function lint(): void { $files = $this->getAllSourceFiles(); @@ -142,7 +139,7 @@ class RoboFile extends \Robo\Tasks { * * @param bool $stats - if true, generates stats rather than running mutation tests */ - public function mutate($stats = FALSE) + public function mutate($stats = FALSE): void { $test_parts = [ 'vendor/bin/humbug' @@ -164,7 +161,7 @@ class RoboFile extends \Robo\Tasks { * * @param bool $report - if true, generates reports instead of direct output */ - public function phpcs($report = FALSE) + public function phpcs($report = FALSE): void { $report_cmd_parts = [ 'vendor/bin/phpcs', @@ -187,7 +184,7 @@ class RoboFile extends \Robo\Tasks { * * @param bool $report - if true, generates reports instead of direct output */ - public function phploc($report = FALSE) + public function phploc($report = FALSE): void { // Command for generating reports $report_cmd_parts = [ @@ -215,7 +212,7 @@ class RoboFile extends \Robo\Tasks { /** * Create temporary directories */ - public function prepare() + public function prepare(): void { array_map([$this, '_mkdir'], $this->taskDirs); } @@ -223,7 +220,7 @@ class RoboFile extends \Robo\Tasks { /** * Lint php files and run unit tests */ - public function test() + public function test(): void { $this->lint(); $this->taskPHPUnit() @@ -235,7 +232,7 @@ class RoboFile extends \Robo\Tasks { /** * Watches for file updates, and automatically runs appropriate actions */ - public function watch() + public function watch(): void { $this->taskWatch() ->monitor('composer.json', function() { @@ -253,7 +250,7 @@ class RoboFile extends \Robo\Tasks { /** * Create pdepend reports */ - protected function dependencyReport() + protected function dependencyReport(): void { $cmd_parts = [ 'vendor/bin/pdepend', @@ -270,14 +267,14 @@ class RoboFile extends \Robo\Tasks { * * @return array */ - protected function getAllSourceFiles() + protected function getAllSourceFiles(): array { - $files = array_merge( + $files = array_unique(array_merge( glob_recursive('build/*.php'), glob_recursive('src/*.php'), glob_recursive('tests/*.php'), glob('*.php') - ); + )); sort($files); @@ -289,7 +286,7 @@ class RoboFile extends \Robo\Tasks { * * @param array $chunk */ - protected function parallelLint(array $chunk) + protected function parallelLint(array $chunk): void { $task = $this->taskParallelExec() ->timeout(5) @@ -306,7 +303,7 @@ class RoboFile extends \Robo\Tasks { /** * Generate copy paste detector report */ - protected function phpcpdReport() + protected function phpcpdReport(): void { $cmd_parts = [ 'vendor/bin/phpcpd', @@ -323,7 +320,7 @@ class RoboFile extends \Robo\Tasks { * @param array $cmd_parts - command arguments * @param string $join_on - what to join the command arguments with */ - protected function _run(array $cmd_parts, $join_on = ' ') + protected function _run(array $cmd_parts, $join_on = ' '): void { $this->taskExec(implode($join_on, $cmd_parts))->run(); } diff --git a/src/Driver/AbstractDriver.php b/src/Driver/AbstractDriver.php index cc166bc..a99fd1a 100644 --- a/src/Driver/AbstractDriver.php +++ b/src/Driver/AbstractDriver.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -30,7 +30,7 @@ abstract class AbstractDriver implements DriverInterface, LoggerAwareInterface { * * @var array */ - protected $deferred = []; + protected array $deferred = []; /** * Common constructor interface for driver classes @@ -40,11 +40,6 @@ abstract class AbstractDriver implements DriverInterface, LoggerAwareInterface { */ abstract public function __construct(array $config = [], array $options = []); - /** - * Common destructor - */ - abstract public function __destruct(); - /** * Retrieve a set of values by their cache key * diff --git a/src/Driver/ApcuDriver.php b/src/Driver/ApcuDriver.php index d9ba427..9d9e903 100644 --- a/src/Driver/ApcuDriver.php +++ b/src/Driver/ApcuDriver.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -17,6 +17,11 @@ namespace Aviat\Banker\Driver; use Aviat\Banker\Exception\CacheException; +use function apcu_clear_cache; +use function apcu_delete; +use function apcu_exists; +use function apcu_fetch; +use function apcu_store; /** * Memcached cache backend @@ -29,6 +34,7 @@ class ApcuDriver extends AbstractDriver { * @param array $config - Not used by this driver * @param array $options - Not used by this driver * @throws CacheException + * @codeCoverageIgnore */ public function __construct(array $config = [], array $options = []) { @@ -38,14 +44,6 @@ class ApcuDriver extends AbstractDriver { } } - /** - * Destructor - */ - public function __destruct() - { - // noop - } - /** * See if a key currently exists in the cache * @@ -54,7 +52,7 @@ class ApcuDriver extends AbstractDriver { */ public function exists(string $key): bool { - return \apcu_exists($key) !== FALSE; + return apcu_exists($key) !== FALSE; } /** @@ -65,7 +63,7 @@ class ApcuDriver extends AbstractDriver { */ public function get(string $key) { - return \apcu_fetch($key); + return apcu_fetch($key); } /** @@ -77,7 +75,7 @@ class ApcuDriver extends AbstractDriver { public function getMultiple(array $keys = []): array { $status = FALSE; - return \apcu_fetch($keys, $status); + return apcu_fetch($keys, $status); } /** @@ -86,20 +84,13 @@ class ApcuDriver extends AbstractDriver { * @param string $key * @param mixed $value * @param int $expires - * @return DriverInterface + * @return bool */ - public function set(string $key, $value, int $expires = 0): DriverInterface + public function set(string $key, $value, int $expires = 0): bool { - if ( ! \apcu_exists($key)) - { - \apcu_add($key, $value, $expires); - } - else - { - \apcu_store($key, $value, $expires); - } + $ttl = $this->getTTLFromExpiration($expires); - return $this; + return apcu_store($key, $value, $ttl); } /** @@ -110,7 +101,7 @@ class ApcuDriver extends AbstractDriver { */ public function delete(string $key): bool { - return (bool) \apcu_delete($key); + return apcu_delete($key); } /** @@ -121,7 +112,8 @@ class ApcuDriver extends AbstractDriver { */ public function deleteMultiple(array $keys = []): bool { - return (bool) \apcu_delete($keys); + $deleted = apcu_delete($keys); + return ($keys <=> $deleted) === 0; } /** @@ -131,7 +123,7 @@ class ApcuDriver extends AbstractDriver { */ public function flush(): bool { - return \apcu_clear_cache(); + return apcu_clear_cache(); } /** @@ -146,11 +138,25 @@ class ApcuDriver extends AbstractDriver { if ($this->exists($key)) { $value = $this->get($key); - return \apcu_store($key, $value, $expires); + $ttl = $this->getTTLFromExpiration($expires); + return apcu_store($key, $value, $ttl); } $this->getLogger()->log('warning', 'Tried to set expiration on a key that does not exist'); return FALSE; } + + /** + * Convert expiration date argument into TTL argument + * + * @param int $expires + * @return int + */ + protected function getTTLFromExpiration(int $expires): int + { + $ttl = $expires - time(); + + return ($ttl < 0) ? 0 : $ttl; + } } diff --git a/src/Driver/DriverInterface.php b/src/Driver/DriverInterface.php index 34777fc..57a0c7b 100644 --- a/src/Driver/DriverInterface.php +++ b/src/Driver/DriverInterface.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -34,9 +34,9 @@ interface DriverInterface { * @param string $key * @param mixed $value * @param int $expires - * @return DriverInterface + * @return bool */ - public function set(string $key, $value, int $expires = 0): DriverInterface; + public function set(string $key, $value, int $expires = 0): bool; /** * Get the value for the selected cache key diff --git a/src/Driver/MemcachedDriver.php b/src/Driver/MemcachedDriver.php index dbbfd21..b1ebda9 100644 --- a/src/Driver/MemcachedDriver.php +++ b/src/Driver/MemcachedDriver.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -17,15 +17,18 @@ namespace Aviat\Banker\Driver; use Aviat\Banker\Exception\CacheException; +use Memcached; +use MemcachedException; + /** * Memcached cache backend */ class MemcachedDriver extends AbstractDriver { /** - * @var \Memcached + * @var Memcached */ - private $conn; + private ?Memcached $conn; /** * Driver for PHP Memcache extension @@ -47,8 +50,8 @@ class MemcachedDriver extends AbstractDriver { try { - $this->conn = new \Memcached(); - $this->conn->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->conn = new Memcached(); + $this->conn->setOption(Memcached::OPT_BINARY_PROTOCOL, true); $this->conn->addServer($config['host'], (int) $config['port']); if ( ! empty($options)) @@ -56,7 +59,7 @@ class MemcachedDriver extends AbstractDriver { $this->conn->setOptions($options); } } - catch (\MemcachedException $e) + catch (MemcachedException $e) { // Rewrite MemcachedException as a CacheException to // match the requirements of the interface @@ -66,6 +69,7 @@ class MemcachedDriver extends AbstractDriver { /** * Disconnect from memcached server + * @codeCoverageIgnore */ public function __destruct() { @@ -80,7 +84,10 @@ class MemcachedDriver extends AbstractDriver { */ public function exists(string $key): bool { - return $this->conn->get($key) !== FALSE; + $this->conn->get($key); + $resultFlag = $this->conn->getResultCode(); + + return ($resultFlag !== Memcached::RES_NOTFOUND); } /** @@ -102,8 +109,8 @@ class MemcachedDriver extends AbstractDriver { */ public function getMultiple(array $keys = []): array { - $return = $this->conn->getMulti($keys); - return ($return === FALSE) ? [] : $return; + $response = $this->conn->getMulti($keys); + return (is_array($response)) ? $response : []; } /** @@ -112,20 +119,11 @@ class MemcachedDriver extends AbstractDriver { * @param string $key * @param mixed $value * @param int $expires - * @return DriverInterface + * @return bool */ - public function set(string $key, $value, int $expires = 0): DriverInterface + public function set(string $key, $value, int $expires = 0): bool { - if ( ! $this->exists($key)) - { - $this->conn->add($key, $value, $expires); - } - else - { - $this->conn->replace($key, $value, $expires); - } - - return $this; + return $this->conn->set($key, $value, $expires); } /** @@ -136,7 +134,7 @@ class MemcachedDriver extends AbstractDriver { */ public function delete(string $key): bool { - return (bool) $this->conn->delete($key); + return $this->conn->delete($key); } /** diff --git a/src/Driver/NullDriver.php b/src/Driver/NullDriver.php index a0274de..874d67e 100644 --- a/src/Driver/NullDriver.php +++ b/src/Driver/NullDriver.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -27,7 +27,7 @@ class NullDriver extends AbstractDriver { * * @var array */ - protected $store = []; + protected array $store = []; /** * NullDriver constructor. @@ -40,14 +40,6 @@ class NullDriver extends AbstractDriver { $this->store = []; } - /** - * Clean up nothing - */ - public function __destruct() - { - //noop - } - /** * See if a key currently exists in the cache * @@ -78,12 +70,12 @@ class NullDriver extends AbstractDriver { * @param string $key * @param mixed $value * @param int $expires - * @return DriverInterface + * @return bool */ - public function set(string $key, $value, int $expires = 0): DriverInterface + public function set(string $key, $value, int $expires = 0): bool { $this->store[$key] = $value; - return $this; + return $this->store[$key] === $value; } /** diff --git a/src/Driver/RedisDriver.php b/src/Driver/RedisDriver.php index 5e968fe..ae01116 100644 --- a/src/Driver/RedisDriver.php +++ b/src/Driver/RedisDriver.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -28,7 +28,7 @@ class RedisDriver extends AbstractDriver { * * @var Client */ - protected $conn; + protected ?Client $conn; /** * RedisDriver constructor. @@ -49,7 +49,8 @@ class RedisDriver extends AbstractDriver { } /** - * Disconnect from memcached server + * Disconnect from redis server + * @codeCoverageIgnore */ public function __destruct() { @@ -85,22 +86,18 @@ class RedisDriver extends AbstractDriver { * @param string $key * @param mixed $value * @param int $expires - * @return DriverInterface + * @return bool */ - public function set(string $key, $value, int $expires = 0): DriverInterface + public function set(string $key, $value, int $expires = 0): bool { $value = serialize($value); if ($expires !== 0) { - $this->conn->set($key, $value, 'EX', $expires); - } - else - { - $this->conn->set($key, $value); + return (bool) $this->conn->set($key, $value, 'EX', $expires); } - return $this; + return (bool)$this->conn->set($key, $value); } /** @@ -123,7 +120,7 @@ class RedisDriver extends AbstractDriver { public function deleteMultiple(array $keys = []): bool { $res = $this->conn->del(...$keys); - return $res === \count($keys); + return $res === count($keys); } /** diff --git a/src/Exception/CacheException.php b/src/Exception/CacheException.php index 1b9e7d1..db68059 100644 --- a/src/Exception/CacheException.php +++ b/src/Exception/CacheException.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -16,10 +16,11 @@ namespace Aviat\Banker\Exception; use Psr\Cache\CacheException as CacheExceptionInterface; +use Psr\SimpleCache\CacheException as SimpleCacheExceptionInterface; /** * Exception interface for all exceptions thrown by an Implementing Library. */ -class CacheException extends \Exception implements CacheExceptionInterface { - +class CacheException extends \Exception implements CacheExceptionInterface, SimpleCacheExceptionInterface { + } \ No newline at end of file diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 80902e3..2ef7e4d 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -15,7 +15,8 @@ */ namespace Aviat\Banker\Exception; -use Psr\Cache\InvalidArgumentException as InvalidArgumentExceptionInterface; +use Psr\Cache\InvalidArgumentException as IAEInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleIAEInterface; /** * Exception interface for invalid cache arguments. @@ -23,7 +24,7 @@ use Psr\Cache\InvalidArgumentException as InvalidArgumentExceptionInterface; * Any time an invalid argument is passed into a method it must throw an * exception class which implements Psr\Cache\InvalidArgumentException. */ -class InvalidArgumentException extends CacheException implements InvalidArgumentExceptionInterface { +class InvalidArgumentException extends CacheException implements IAEInterface, SimpleIAEInterface { /** * Constructor @@ -32,7 +33,7 @@ class InvalidArgumentException extends CacheException implements InvalidArgument * @param int $code * @param \Exception $previous */ - public function __construct(string $message = "Cache key must be a string.", int $code = 0, \Exception $previous = NULL) + public function __construct(string $message = 'Cache key must be a string.', int $code = 0, \Exception $previous = NULL) { parent::__construct($message, $code, $previous); } diff --git a/src/Item.php b/src/Item.php index 3eaf922..4ede1ff 100644 --- a/src/Item.php +++ b/src/Item.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -15,6 +15,8 @@ */ namespace Aviat\Banker; +use DateInterval; +use DateTimeInterface; use Psr\Cache\CacheItemInterface; use Aviat\Banker\Driver\DriverInterface; @@ -29,28 +31,28 @@ class Item implements CacheItemInterface { * * @var DriverInterface */ - protected $driver; + protected DriverInterface $driver; /** * The key of the cache item * * @var string */ - protected $key; + protected string $key; /** * The expiration time * * @var int | null */ - protected $expiresAt; + protected ?int $expiresAt; /** * The time to live * * @var int | null */ - protected $ttl; + protected ?int $ttl; /** * The value to save in the cache @@ -65,10 +67,13 @@ class Item implements CacheItemInterface { * @param DriverInterface $driver * @param string $key */ - public function __construct(DriverInterface $driver, $key) + public function __construct(DriverInterface $driver, string $key) { $this->driver = $driver; $this->key = $key; + + $this->expiresAt = NULL; + $this->ttl = NULL; } /** @@ -143,7 +148,7 @@ class Item implements CacheItemInterface { /** * Sets the expiration time for this cache item. * - * @param \DateTimeInterface|null $expiration + * @param DateTimeInterface|null $expiration * The point in time after which the item MUST be considered expired. * If null is passed explicitly, a default value MAY be used. If none is set, * the value should be stored permanently or for as long as the @@ -154,7 +159,7 @@ class Item implements CacheItemInterface { */ public function expiresAt($expiration = NULL): Item { - if ($expiration instanceof \DateTimeInterface) + if ($expiration instanceof DateTimeInterface) { $expiration = $expiration->getTimestamp(); } @@ -167,7 +172,7 @@ class Item implements CacheItemInterface { /** * Sets the expiration time for this cache item. * - * @param int|\DateInterval|null $time + * @param int|DateInterval|null $time * The period of time from the present after which the item MUST be considered * expired. An integer parameter is understood to be the time in seconds until * expiration. If null is passed explicitly, a default value MAY be used. @@ -179,7 +184,7 @@ class Item implements CacheItemInterface { */ public function expiresAfter($time = NULL): Item { - if ($time instanceof \DateInterval) + if ($time instanceof DateInterval) { $time = $time->format("%s"); } diff --git a/src/ItemCollection.php b/src/ItemCollection.php index 6417409..d10fe89 100644 --- a/src/ItemCollection.php +++ b/src/ItemCollection.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/src/LoggerTrait.php b/src/LoggerTrait.php index 68a3703..a83cec9 100644 --- a/src/LoggerTrait.php +++ b/src/LoggerTrait.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/src/Pool.php b/src/Pool.php index 2365008..b1b5539 100644 --- a/src/Pool.php +++ b/src/Pool.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -17,10 +17,11 @@ namespace Aviat\Banker; use Aviat\Banker\Driver\DriverInterface; use Aviat\Banker\Exception\InvalidArgumentException; -use Aviat\Banker\{Item, ItemCollection}; use Psr\Cache\{CacheItemInterface, CacheItemPoolInterface}; use Psr\Log\{LoggerAwareInterface, LoggerInterface}; +use function is_string; + /** * The main cache manager */ @@ -33,14 +34,14 @@ final class Pool implements CacheItemPoolInterface, LoggerAwareInterface { * * @var DriverInterface */ - protected $driver; + protected DriverInterface $driver; /** * Cache Items to be saved * * @var array */ - protected $deferred = []; + protected array $deferred = []; /** * Set up the cache backend @@ -76,7 +77,7 @@ final class Pool implements CacheItemPoolInterface, LoggerAwareInterface { */ public function getItem($key): CacheItemInterface { - if ( ! \is_string($key)) + if ( ! is_string($key)) { throw new InvalidArgumentException(); } @@ -117,7 +118,7 @@ final class Pool implements CacheItemPoolInterface, LoggerAwareInterface { $items = []; foreach($keys as $key) { - if ( ! \is_string($key)) + if ( ! is_string($key)) { throw new InvalidArgumentException(); } @@ -149,7 +150,7 @@ final class Pool implements CacheItemPoolInterface, LoggerAwareInterface { */ public function hasItem($key): bool { - if ( ! \is_string($key)) + if ( ! is_string($key)) { throw new InvalidArgumentException(); } @@ -189,7 +190,7 @@ final class Pool implements CacheItemPoolInterface, LoggerAwareInterface { */ public function deleteItem($key): bool { - if ( ! \is_string($key)) + if ( ! is_string($key)) { throw new InvalidArgumentException(); } @@ -219,7 +220,7 @@ final class Pool implements CacheItemPoolInterface, LoggerAwareInterface { { foreach ($keys as $key) { - if ( ! \is_string($key)) + if ( ! is_string($key)) { throw new InvalidArgumentException(); } diff --git a/src/Teller.php b/src/Teller.php new file mode 100644 index 0000000..8720e90 --- /dev/null +++ b/src/Teller.php @@ -0,0 +1,240 @@ + + * @copyright 2016 - 2019 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 3.0.0 + * @link https://git.timshomepage.net/timw4mail/banker + */ +namespace Aviat\Banker; + +use Aviat\Banker\Exception\InvalidArgumentException; +use Psr\SimpleCache; +use Psr\Log\LoggerInterface; + +class Teller implements SimpleCache\CacheInterface { + private Pool $pool; + + /** + * Set up the cache backend + * + * @param array $config + * @param LoggerInterface $logger + */ + public function __construct(array $config, ?LoggerInterface $logger = NULL) + { + $this->pool = new Pool($config, $logger); + } + + /** + * Fetches a value from the cache. + * + * @param string $key The unique key of this item in the cache. + * @param mixed $default Default value to return if the key does not exist. + * + * @return mixed The value of the item from the cache, or $default in case of cache miss. + * + * @throws SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function get($key, $default = null) + { + $this->validateKey($key); + + $item = $this->pool->getItem($key); + return ($item->isHit()) ? $item->get() : $default; + } + + /** + * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. + * + * @param string $key The key of the item to store. + * @param mixed $value The value of the item to store, must be serializable. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function set($key, $value, $ttl = null): bool + { + $this->validateKey($key); + + $item = $this->pool->getItem($key); + $item->set($value); + + if ($ttl !== NULL) + { + $item->expiresAfter($ttl); + } + + return $this->pool->save($item); + } + + /** + * Delete an item from the cache by its unique key. + * + * @param string $key The unique cache key of the item to delete. + * + * @return bool True if the item was successfully removed. False if there was an error. + * + * @throws SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function delete($key): bool + { + $this->validateKey($key); + + return $this->pool->deleteItem($key); + } + + /** + * Wipes clean the entire cache's keys. + * + * @return bool True on success and false on failure. + */ + public function clear(): bool + { + return $this->pool->clear(); + } + + /** + * Obtains multiple cache items by their unique keys. + * + * @param iterable $keys A list of keys that can obtained in a single operation. + * @param mixed $default Default value to return for keys that do not exist. + * + * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null) + { + $this->validateKeys($keys); + + $output = []; + + foreach ($keys as $k) + { + $output[$k] = $this->get($k, $default); + } + + return $output; + } + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null): bool + { + $this->validateKeys($values, TRUE); + + $setResults = []; + foreach ($values as $k => $v) + { + $setResults[] = $this->set($k, $v, $ttl); + } + + // Only return true if all the results are true + return array_reduce($setResults, fn ($carry, $item) => $item && $carry, TRUE); + } + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys): bool + { + $this->validateKeys($keys); + + return $this->pool->deleteItems((array)$keys); + } + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key): bool + { + $this->validateKey($key); + + return $this->pool->hasItem($key); + } + + /** + * @param $keys + * @param bool $hash + * @throws InvalidArgumentException + */ + private function validateKeys($keys, bool $hash = FALSE): void + { + // Check type of keys + if ( ! is_iterable($keys)) + { + throw new InvalidArgumentException('Keys must be an array or a traversable object'); + } + + $keys = ($hash) ? array_keys((array)$keys) : (array)$keys; + + // Check each key + array_walk($keys, fn($key) => $this->validateKey($key)); + } + + /** + * @param string $key + * @throws InvalidArgumentException + */ + private function validateKey($key): void + { + if ( ! is_string($key)) + { + throw new InvalidArgumentException('Cache key must be a string.'); + } + + if (is_string($key) && preg_match("`[{}()/@:\\\]`", $key) === 1) + { + throw new InvalidArgumentException('Invalid characters in cache key'); + } + } +} \ No newline at end of file diff --git a/tests/Driver/ApcuDriverTest.php b/tests/Driver/ApcuDriverTest.php index 13e2b15..3feb21a 100644 --- a/tests/Driver/ApcuDriverTest.php +++ b/tests/Driver/ApcuDriverTest.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/tests/Driver/DriverTestBase.php b/tests/Driver/DriverTestBase.php index 8c872fc..27b194f 100644 --- a/tests/Driver/DriverTestBase.php +++ b/tests/Driver/DriverTestBase.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -15,16 +15,14 @@ */ namespace Aviat\Banker\Tests\Driver; +use Aviat\Banker\Driver\DriverInterface; use PHPUnit\Framework\TestCase; class DriverTestBase extends TestCase { - protected $driver; + protected DriverInterface $driver; + - public function tearDown(): void - { - $this->driver->__destruct(); - } public function testGetSet(): void { diff --git a/tests/Driver/MemcachedDriverTest.php b/tests/Driver/MemcachedDriverTest.php index 5a7adbb..6e2b187 100644 --- a/tests/Driver/MemcachedDriverTest.php +++ b/tests/Driver/MemcachedDriverTest.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/tests/Driver/NullDriverTest.php b/tests/Driver/NullDriverTest.php index 5363663..d1e35e9 100644 --- a/tests/Driver/NullDriverTest.php +++ b/tests/Driver/NullDriverTest.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/tests/Driver/RedisDriverTest.php b/tests/Driver/RedisDriverTest.php index 5cef999..d34b343 100644 --- a/tests/Driver/RedisDriverTest.php +++ b/tests/Driver/RedisDriverTest.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/tests/Friend.php b/tests/Friend.php index 1be94b8..9f5f34b 100644 --- a/tests/Friend.php +++ b/tests/Friend.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/tests/ItemCollectionTest.php b/tests/ItemCollectionTest.php index e1e4845..c57a85e 100644 --- a/tests/ItemCollectionTest.php +++ b/tests/ItemCollectionTest.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/tests/ItemTest.php b/tests/ItemTest.php index 3793723..69d748b 100644 --- a/tests/ItemTest.php +++ b/tests/ItemTest.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * diff --git a/tests/PoolTest.php b/tests/PoolTest.php index e322b68..b82a574 100644 --- a/tests/PoolTest.php +++ b/tests/PoolTest.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 * @@ -214,11 +214,19 @@ class PoolTest extends TestCase { public function testDeleteItemBadKey(): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("Cache key must be a string."); + $this->expectExceptionMessage('Cache key must be a string.'); $this->pool->deleteItem(34); } + public function testDeleteItemsBadKey(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cache key must be a string.'); + + $this->pool->deleteItems([34]); + } + public function testDeleteItemThatDoesNotExist(): void { $this->pool->clear(); diff --git a/tests/TellerTest.php b/tests/TellerTest.php new file mode 100644 index 0000000..237b1f8 --- /dev/null +++ b/tests/TellerTest.php @@ -0,0 +1,164 @@ + + * @copyright 2016 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 2.0.0 + * @link https://git.timshomepage.net/timw4mail/banker + */ +namespace Aviat\Banker\Tests; + +use Aviat\Banker\Teller; +use Aviat\Banker\Exception\InvalidArgumentException; +use PHPUnit\Framework\TestCase; + +class TellerTest extends TestCase { + + protected Teller $teller; + + private array $testValues = [ + 'foo' => 24, + 'bar' => '87', + 'baz' => [1, 2, 3], + 'a' => TRUE, + 'b' => 1, + 'c' => FALSE, + 'd' => 0, + 'e' => NULL, + 'f' => [ + 'a' => [ + 'b' => 'c', + 'd' => [1, 2, 3] + ] + ] + ]; + + public function setUp(): void + { + $this->teller = new Teller([ + 'driver' => 'null', + 'connection' => [] + ]); + + // Call clear to make sure we are working from a clean slate to start + $this->teller->clear(); + } + + public function testGetSet(): void + { + foreach ($this->testValues as $key => $value) + { + $this->assertTrue($this->teller->set($key, $value, 0), "Failed to set value for key: {$key}"); + + $received = $this->teller->get($key); + + $this->assertEquals($value, $received, "Invalid value returned for key: {$key}"); + } + } + + public function testGetSetMultiple(): void + { + $this->assertTrue($this->teller->setMultiple($this->testValues)); + + $received = $this->teller->getMultiple(array_keys($this->testValues)); + $this->assertEquals($this->testValues, $received); + } + + public function testClear(): void + { + $data = [ + 'foo' => 'bar', + 'bar' => 'baz', + 'foobar' => 'foobarbaz' + ]; + + // Set up some data + $this->teller->setMultiple($data); + + foreach($data as $key => $val) + { + $this->assertTrue($this->teller->has($key)); + $this->assertEquals($val, $this->teller->get($key)); + } + + // Now we clear it all! + $this->teller->clear(); + + foreach($data as $key => $val) + { + $this->assertFalse($this->teller->has($key)); + $this->assertNull($this->teller->get($key)); + } + } + + public function testDelete(): void + { + $this->teller->setMultiple($this->testValues); + + $this->assertTrue($this->teller->delete('foo')); + $this->assertFalse($this->teller->has('foo')); + + // Make sure we get the default value for the key + $this->assertEquals('Q', $this->teller->get('foo', 'Q')); + } + + public function testDeleteMultiple(): void + { + $this->teller->setMultiple($this->testValues); + + $deleteKeys = ['foo', 'bar', 'baz']; + $hasKeys = ['a', 'b', 'c', 'd', 'e', 'f']; + + $this->assertTrue($this->teller->deleteMultiple($deleteKeys)); + + array_walk($deleteKeys, fn ($key) => $this->assertFalse($this->teller->has($key))); + array_walk($hasKeys, fn ($key) => $this->assertTrue($this->teller->has($key))); + } + + public function testBadKeyType(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Cache key must be a string.'); + + $this->teller->get(546567); + } + + public function testBadKeysType (): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Keys must be an array or a traversable object'); + + $keys = (object)[]; + $this->teller->getMultiple($keys); + } + + /** + * @dataProvider keyValidationTests + */ + public function testKeyValidation($key): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid characters in cache key'); + + $this->teller->get($key); + } + + public function keyValidationTests(): array + { + // {}()/@:\\\ + return [ + ['key' => '{}()/@:\\'], + ['key' => 'a: b'], + ['key' => 'a/b'], + ['key' => '{'], + ['key' => 'a@b'], + ]; + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 84bfffd..d5647fc 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,7 +2,7 @@ /** * Banker * - * A Caching library implementing psr/cache + * A Caching library implementing psr/cache (PSR 6) * * PHP version 7.2 *