From 03bf558d62ba734cb48cbe594d841dfa095f7242 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Tue, 6 Sep 2016 17:03:43 -0400 Subject: [PATCH] Lots of fixes and tests --- composer.json | 4 +- src/Driver/{Driver.php => AbstractDriver.php} | 11 +- src/Driver/DriverInterface.php | 9 + src/Driver/MemcacheDriver.php | 27 +- src/Driver/MemcachedDriver.php | 46 ++- src/Driver/NullDriver.php | 27 +- src/Driver/RedisDriver.php | 14 +- src/Driver/SQLDriver.php | 24 -- src/Exception/InvalidArgumentException.php | 11 + src/Item.php | 43 ++- src/ItemCollection.php | 1 + src/LoggerTrait.php | 68 ++++ src/Pool.php | 74 ++++- tests/Driver/DriverTestBase.php | 5 + tests/Driver/NullDriverTest.php | 14 + tests/Driver/RedisDriverTest.php | 10 +- tests/Friend.php | 120 +++++++ tests/ItemCollectionTest.php | 23 ++ tests/ItemTest.php | 39 +++ tests/PoolTest.php | 314 ++++++++++++++++++ 20 files changed, 829 insertions(+), 55 deletions(-) rename src/Driver/{Driver.php => AbstractDriver.php} (78%) delete mode 100644 src/Driver/SQLDriver.php create mode 100644 src/LoggerTrait.php create mode 100644 tests/Driver/NullDriverTest.php create mode 100644 tests/Friend.php create mode 100644 tests/ItemCollectionTest.php create mode 100644 tests/ItemTest.php create mode 100644 tests/PoolTest.php diff --git a/composer.json b/composer.json index f5dee7e..a75b255 100644 --- a/composer.json +++ b/composer.json @@ -24,8 +24,9 @@ } }, "require": { + "predis/predis": "^1.1", "psr/log": "^1.0", - "psr/cache": "^1.0" + "psr/cache": "^1.0.1" }, "require-dev": { "consolidation/robo": "1.0.0-RC2", @@ -36,7 +37,6 @@ "phploc/phploc": "^3.0", "phpmd/phpmd": "^2.4", "phpunit/phpunit": "^5.5", - "predis/predis": "^1.1", "sebastian/phpcpd": "^2.0", "squizlabs/php_codesniffer": "^3.0.0@alpha", "theseer/phpdox": "^0.9.0" diff --git a/src/Driver/Driver.php b/src/Driver/AbstractDriver.php similarity index 78% rename from src/Driver/Driver.php rename to src/Driver/AbstractDriver.php index ad8c187..582d9d1 100644 --- a/src/Driver/Driver.php +++ b/src/Driver/AbstractDriver.php @@ -16,10 +16,14 @@ namespace Aviat\Banker\Driver; +use Psr\Log\LoggerAwareInterface; + /** * Base class for cache backends */ -abstract class Driver implements DriverInterface { +abstract class AbstractDriver implements DriverInterface, LoggerAwareInterface { + + use \Aviat\Banker\LoggerTrait; /** * The object encapsulating the connection to the cache backend @@ -41,4 +45,9 @@ abstract class Driver implements DriverInterface { * @param array $config - Connection parameters for the specified backend */ abstract public function __construct(array $config = []); + + /** + * Common destructor + */ + abstract public function __destruct(); } \ No newline at end of file diff --git a/src/Driver/DriverInterface.php b/src/Driver/DriverInterface.php index 0235f32..0defc5d 100644 --- a/src/Driver/DriverInterface.php +++ b/src/Driver/DriverInterface.php @@ -77,4 +77,13 @@ interface DriverInterface { * @return boolean */ public function flush(); + + /** + * Set the specified key to expire at the given time + * + * @param string $key + * @param int $expires + * @return boolean + */ + public function expiresAt($key, $expires); } \ No newline at end of file diff --git a/src/Driver/MemcacheDriver.php b/src/Driver/MemcacheDriver.php index eea8ad3..e437db2 100644 --- a/src/Driver/MemcacheDriver.php +++ b/src/Driver/MemcacheDriver.php @@ -21,7 +21,7 @@ use Aviat\Banker\Exception\CacheException; /** * Redis cache backend */ -class MemcacheDriver extends Driver { +class MemcacheDriver extends AbstractDriver { /** * Driver for PHP Memcache extension @@ -94,7 +94,15 @@ class MemcacheDriver extends Driver { */ public function set($key, $value, $expires = 0) { - $this->conn->set($key, $value, 0, $expires); + if ($this->exists($key)) + { + $this->conn->replace($key, $value, 0, $expires); + } + else + { + $this->conn->set($key, $value, 0, $expires); + } + return $this; } @@ -133,4 +141,19 @@ class MemcacheDriver extends Driver { { return $this->conn->flush(); } + + /** + * Set the specified key to expire at the given time + * + * @param string $key + * @param int $expires + * @return DriverInterface + */ + public function expiresAt($key, $expires) + { + $value = $this->get($key); + $timediff = $expires - time(); + + return $this->set($key, $value, $timediff); + } } \ No newline at end of file diff --git a/src/Driver/MemcachedDriver.php b/src/Driver/MemcachedDriver.php index 4d7af38..f53e0a5 100644 --- a/src/Driver/MemcachedDriver.php +++ b/src/Driver/MemcachedDriver.php @@ -24,7 +24,7 @@ use MemcachedException; /** * Memcached cache backend */ -class MemcachedDriver extends Driver { +class MemcachedDriver extends AbstractDriver { /** * Driver for PHP Memcache extension @@ -39,9 +39,18 @@ class MemcachedDriver extends Driver { throw new CacheException("Memcached driver requires memcached extensions"); } - $this->conn = new Memcached(); - - $this->conn->addServer($config['host'], $config['port']); + try + { + $this->conn = new Memcached(); + $this->conn->setOption(Memcached::OPT_BINARY_PROTOCOL, true); + $this->conn->addServer($config['host'], $config['port']); + } + catch (MemcachedException $e) + { + // Rewrite MemcachedException as a CacheException to + // match the requirements of the interface + throw new CacheException($e->getMessage(), $e->getCode(), $e); + } } /** @@ -95,7 +104,15 @@ class MemcachedDriver extends Driver { */ public function set($key, $value, $expires = 0) { - $this->conn->set($key, $value, $expires); + if ( ! $this->exists($key)) + { + $this->conn->add($key, $value, $expires); + } + else + { + $this->conn->replace($key, $value, $expires); + } + return $this; } @@ -130,4 +147,23 @@ class MemcachedDriver extends Driver { { return $this->conn->flush(); } + + /** + * Set the specified key to expire at the given time + * + * @param string $key + * @param int $expires + * @return boolean + */ + public function expiresAt($key, $expires) + { + if ($this->exists($key)) + { + return $this->conn->touch($key, $expires); + } + + $this->getLogger()->warn("Tried to set expiration on a key that does not exist"); + + return FALSE; + } } diff --git a/src/Driver/NullDriver.php b/src/Driver/NullDriver.php index 8268693..12902d9 100644 --- a/src/Driver/NullDriver.php +++ b/src/Driver/NullDriver.php @@ -21,7 +21,7 @@ namespace Aviat\Banker\Driver; * Cache backend for use without a cache server. Only does transient * in-memory caching */ -class NullDriver extends Driver { +class NullDriver extends AbstractDriver { /** * In memory store @@ -39,6 +39,14 @@ class NullDriver extends Driver { { $this->store = []; } + + /** + * Clean up nothing + */ + public function __destruct() + { + //noop + } /** * See if a key currently exists in the cache @@ -59,7 +67,9 @@ class NullDriver extends Driver { */ public function get($key) { - return $this->store[$key]; + return ($this->exists($key)) + ? $this->store[$key] + : NULL; } /** @@ -134,4 +144,17 @@ class NullDriver extends Driver { $this->store = []; return TRUE; } + + /** + * Set the specified key to expire at the given time + * + * @param string $key + * @param int $expires + * @return boolean + */ + public function expiresAt($key, $expires) + { + //noop + return TRUE; + } } \ No newline at end of file diff --git a/src/Driver/RedisDriver.php b/src/Driver/RedisDriver.php index 4fafd81..891bf86 100644 --- a/src/Driver/RedisDriver.php +++ b/src/Driver/RedisDriver.php @@ -22,7 +22,7 @@ use Predis\Client; /** * Redis cache backend */ -class RedisDriver extends Driver { +class RedisDriver extends AbstractDriver { /** * The object encapsulating the connection to the Redis server @@ -107,8 +107,16 @@ class RedisDriver extends Driver { public function set($key, $value, $expires = 0) { $value = \serialize($value); - - $this->conn->set($key, $value, "EX {$expires}"); + + if ($expires !== 0) + { + $this->conn->set($key, $value, "EX {$expires}"); + } + else + { + $this->conn->set($key, $value); + } + return $this; } diff --git a/src/Driver/SQLDriver.php b/src/Driver/SQLDriver.php deleted file mode 100644 index 7e7c678..0000000 --- a/src/Driver/SQLDriver.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @copyright 2016 Timothy J. Warren - * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @version 1.0.0 - * @link https://git.timshomepage.net/timw4mail/banker - */ - -namespace Aviat\Banker\Driver; - -/** - * SQL cache backend - */ -class SQLDriver extends Driver { - -} \ No newline at end of file diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 8a50405..2795300 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -26,4 +26,15 @@ use Psr\Cache\InvalidArgumentException as InvalidArgumentExceptionInterface; */ class InvalidArgumentException extends CacheException implements InvalidArgumentExceptionInterface { + /** + * Constructor + * + * @param string $message + * @param int $code + * @param \Exception $previous + */ + public function __construct($message = "Cache key must be a string.", $code = 0, \Exception $previous = NULL) + { + parent::__construct($message, $code, $previous); + } } \ No newline at end of file diff --git a/src/Item.php b/src/Item.php index 7b865e7..da648be 100644 --- a/src/Item.php +++ b/src/Item.php @@ -100,7 +100,12 @@ class Item implements CacheItemInterface { */ public function get() { - return $this->driver->get($this->key); + if ($this->isHit()) + { + return $this->driver->get($this->key); + } + + return NULL; } /** @@ -150,6 +155,13 @@ class Item implements CacheItemInterface { */ public function expiresAt($expiration = NULL) { + if ($time instanceof \DateTimeInterface) + { + $time = $time->getTimestamp(); + } + + $this->expiresAt = (int) $time; + return $this; } @@ -168,7 +180,34 @@ class Item implements CacheItemInterface { */ public function expiresAfter($time = 0) { - $this->ttl = $time; + if ($time instanceof \DateInterval) + { + $time = $time->format("%s"); + } + + $this->ttl = (int) $time; return $this; } + + /** + * Save the current value to the cache + * + * @return bool + */ + public function save() + { + if ($this->expiresAt !== NULL && $this->expiresAt !== 0) + { + $setResult = $this->driver->set($this->key, $this->value); + $expResult = $this->driver->expiresAt($this->key, $this->expiresAt); + + return $setResult && $expResult; + } + else if ($this->ttl !== NULL && $this->ttl !== 0) + { + return $this->driver->set($this->key, $this->value, $this->ttl); + } + + return $this->driver->set($this->key, $this->value); + } } \ No newline at end of file diff --git a/src/ItemCollection.php b/src/ItemCollection.php index daa6e75..f8cc644 100644 --- a/src/ItemCollection.php +++ b/src/ItemCollection.php @@ -22,6 +22,7 @@ use Psr\Cache\CacheItemInterface; * Collection of Psr\Cache\CacheItemIterface objects to be returned by getItems * * @see http://php.net/manual/en/class.arrayiterator.php + * @see http://php.net/manual/en/class.jsonserializable.php */ class ItemCollection extends \ArrayIterator implements \JsonSerializable { diff --git a/src/LoggerTrait.php b/src/LoggerTrait.php new file mode 100644 index 0000000..098bd91 --- /dev/null +++ b/src/LoggerTrait.php @@ -0,0 +1,68 @@ + + * @copyright 2016 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 1.0.0 + * @link https://git.timshomepage.net/timw4mail/banker + */ + +namespace Aviat\Banker; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Psr\Log\NullLogger; + +/** + * Trait for keeping track of logger objects + */ +trait LoggerTrait { + + /** + * Logger instance to use + * + * @var LoggerInterface + */ + protected $logger = NULL; + + /** + * Return the existing logger instance or + * a NullLogger, if no instance set + * + * @return LoggerInterface + */ + protected function getLogger() + { + if ($this->logger === NULL) + { + $this->logger = new NullLogger(); + } + return $this->logger; + } + + /** + * Set a logger to keep track of errors + * + * @param LoggerInterface $logger + * @return self + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + + // Set the logger for the current driver too + if (isset($this->driver)) + { + $this->driver->setLogger($logger); + } + + return $this; + } +} \ No newline at end of file diff --git a/src/Pool.php b/src/Pool.php index 19329b0..94419ae 100644 --- a/src/Pool.php +++ b/src/Pool.php @@ -18,16 +18,20 @@ namespace Aviat\Banker; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; -use Aviat\Banker\Driver; use Aviat\Banker\Driver\DriverInterface; use Aviat\Banker\Exception\InvalidArgumentException; use Aviat\Banker\Item; +use Aviat\Banker\ItemCollection; /** * The main cache manager */ -class Pool implements CacheItemPoolInterface { +class Pool implements CacheItemPoolInterface, LoggerAwareInterface { + + use LoggerTrait; /** * Driver class for handling the chosen caching backend @@ -48,9 +52,14 @@ class Pool implements CacheItemPoolInterface { * * @param array $config */ - public function __construct(array $config) + public function __construct(array $config, LoggerInterface $logger = NULL) { $this->driver = $this->loadDriver($config); + + if ( ! is_null($logger)) + { + $this->setLogger($logger); + } } /** @@ -73,7 +82,13 @@ class Pool implements CacheItemPoolInterface { { if ( ! is_string($key)) { - throw new InvalidArgumentException("Cache key must be a string."); + throw new InvalidArgumentException(); + } + + // If a deferred item exists, return that + if (array_key_exists($key, $this->deferred)) + { + return $this->deferred[$key]; } $item = new Item($this->driver, $key); @@ -98,11 +113,32 @@ class Pool implements CacheItemPoolInterface { */ public function getItems(array $keys = []) { - // Get the set of items selected - $items = []; + if (empty($keys)) + { + return new ItemCollection([]); + } + foreach($keys as $key) { - $items[$key] = $this->getItem($key); + if ( ! is_string($key)) + { + throw new InvalidArgumentException(); + } + } + + // Get the set of items selected + $items = []; + $rawItems = $this->driver->getMultiple($keys); + foreach($rawItems as $key => $val) + { + if (array_key_exists($key, $this->deferred)) + { + $items[$key] = $this->deferred[$key]; + } + else + { + $items[$key] = new Item($this->driver, $key); + } } return new ItemCollection($items); @@ -129,7 +165,13 @@ class Pool implements CacheItemPoolInterface { { if ( ! is_string($key)) { - throw new InvalidArgumentException("Cache item must be a string"); + throw new InvalidArgumentException(); + } + + // See if there are any deferred items + if (array_key_exists($key, $this->deferred)) + { + return TRUE; } return $this->driver->exists($key); @@ -163,7 +205,7 @@ class Pool implements CacheItemPoolInterface { { if ( ! is_string($key)) { - throw new InvalidArgumentException("Cache item must be a string"); + throw new InvalidArgumentException(); } if ( ! $this->hasItem($key)) @@ -195,7 +237,7 @@ class Pool implements CacheItemPoolInterface { { if ( ! is_string($key)) { - throw new InvalidArgumentException("Cache item must be a string"); + throw new InvalidArgumentException(); } } @@ -213,7 +255,7 @@ class Pool implements CacheItemPoolInterface { */ public function save(CacheItemInterface $item) { - + return $item->save(); } /** @@ -227,7 +269,8 @@ class Pool implements CacheItemPoolInterface { */ public function saveDeferred(CacheItemInterface $item) { - $this->deferred[] = $item; + $this->deferred[$item->getKey()] = $item; + return TRUE; } /** @@ -249,6 +292,11 @@ class Pool implements CacheItemPoolInterface { { $result = $result && $this->save($item); } + + if ($result === TRUE) + { + $this->deferred = []; + } return $result; } @@ -262,7 +310,7 @@ class Pool implements CacheItemPoolInterface { protected function loadDriver(array $driverConfig) { $driver = ucfirst(strtolower($driverConfig['driver'])); - $class = "Driver\\${driver}Driver"; + $class = __NAMESPACE__ . "\\Driver\\${driver}Driver"; return new $class($driverConfig['connection']); } diff --git a/tests/Driver/DriverTestBase.php b/tests/Driver/DriverTestBase.php index 987cd41..ee08485 100644 --- a/tests/Driver/DriverTestBase.php +++ b/tests/Driver/DriverTestBase.php @@ -7,6 +7,11 @@ use PHPUnit\Framework\TestCase; class DriverTestBase extends TestCase { protected $driver; + + public function tearDown() + { + $this->driver->__destruct(); + } public function testGetSet() { diff --git a/tests/Driver/NullDriverTest.php b/tests/Driver/NullDriverTest.php new file mode 100644 index 0000000..9e2b158 --- /dev/null +++ b/tests/Driver/NullDriverTest.php @@ -0,0 +1,14 @@ +driver = new NullDriver(); + $this->driver->flush(); + } +} diff --git a/tests/Driver/RedisDriverTest.php b/tests/Driver/RedisDriverTest.php index 4007fe7..2558fff 100644 --- a/tests/Driver/RedisDriverTest.php +++ b/tests/Driver/RedisDriverTest.php @@ -2,6 +2,14 @@ namespace Aviat\Banker\Tests\Driver; -class RedisDriverTest extends DriverTestBase { +use Aviat\Banker\Driver\RedisDriver; +class RedisDriverTest extends DriverTestBase { + + public function setup() + { + $this->driver = new RedisDriver(); + $this->driver->flush(); + } + } \ No newline at end of file diff --git a/tests/Friend.php b/tests/Friend.php new file mode 100644 index 0000000..de1485e --- /dev/null +++ b/tests/Friend.php @@ -0,0 +1,120 @@ +_friend_ = $obj; + $this->_reflect_ = new ReflectionClass($obj); + } + + /** + * Retrieve a friend's property + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if ($this->_reflect_->hasProperty($key)) + { + $property = $this->_get_property($key); + return $property->getValue($this->_friend_); + } + + return NULL; + } + + /** + * Set a friend's property + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + if ($this->_reflect_->hasProperty($key)) + { + $property = $this->_get_property($key); + $property->setValue($this->_friend_, $value); + } + } + + /** + * Calls a protected or private method on the friend + * + * @param string $method + * @param array $args + * @return mixed + * @throws BadMethodCallException + */ + public function __call($method, $args) + { + if ( ! $this->_reflect_->hasMethod($method)) + { + throw new BadMethodCallException("Method '{$method}' does not exist"); + } + + $friendMethod = new ReflectionMethod($this->_friend_, $method); + $friendMethod->setAccessible(TRUE); + return $friendMethod->invokeArgs($this->_friend_, $args); + } + + /** + * Iterates over parent classes to get a ReflectionProperty + * + * @codeCoverageIgnore + * @param string $name + * @return ReflectionProperty|null + */ + private function _get_property($name) + { + try + { + $property = $this->_reflect_->getProperty($name); + $property->setAccessible(TRUE); + return $property; + } + // Return NULL on any exception, so no further logic needed + // in the catch block + catch (\Exception $e) + { + return NULL; + } + } +} \ No newline at end of file diff --git a/tests/ItemCollectionTest.php b/tests/ItemCollectionTest.php new file mode 100644 index 0000000..2e8c657 --- /dev/null +++ b/tests/ItemCollectionTest.php @@ -0,0 +1,23 @@ +collection = new ItemCollection([]); + } + + public function testJsonSerialize() + { + $this->assertEquals([], $this->collection->jsonSerialize()); + + $json = json_encode($this->collection); + $result = json_decode($json); + $this->assertEquals([], $result); + } +} \ No newline at end of file diff --git a/tests/ItemTest.php b/tests/ItemTest.php new file mode 100644 index 0000000..65957e4 --- /dev/null +++ b/tests/ItemTest.php @@ -0,0 +1,39 @@ +driver = new NullDriver(); + $this->item = new Item($this->driver, $this->key); + } + + public function testGetKey() + { + $this->assertEquals($this->key, $this->item->getKey()); + } + + public function testGet() + { + // No value set yet + $this->assertNull($this->item->get()); + + // Set a value + $this->item->set('bar') + ->save(); + + $this->assertEquals('bar', $this->item->get()); + } +} \ No newline at end of file diff --git a/tests/PoolTest.php b/tests/PoolTest.php new file mode 100644 index 0000000..dc51e21 --- /dev/null +++ b/tests/PoolTest.php @@ -0,0 +1,314 @@ +pool = new Pool([ + 'driver' => 'null', + 'connection' => [] + ]); + } + + public function testGetDefaultLogger() + { + $friend = new Friend($this->pool); + $driverFriend = new Friend($friend->driver); + + // Check that a valid logger is set + $this->assertInstanceOf(LoggerInterface::class, $friend->getLogger(), "Logger exists after being set"); + $this->assertInstanceOf(LoggerInterface::class, $driverFriend->getLogger(), "Logger exists on driver after being set"); + + // Make sure we get the default Null logger + $this->assertTrue(is_a($friend->getLogger(), NullLogger::class)); + $this->assertTrue(is_a($driverFriend->getLogger(), NullLogger::class)); + } + + public function testSetLoggerInConstructor() + { + $logger = new Logger('test'); + $logger->pushHandler(new SyslogHandler(Logger::WARNING)); + + $pool = new Pool([ + 'driver' => 'null', + 'connection' => [], + ], $logger); + + $friend = new Friend($pool); + $driverFriend = new Friend($friend->driver); + + // Check that a valid logger is set + $this->assertInstanceOf(LoggerInterface::class, $friend->getLogger(), "Logger exists after being set"); + $this->assertInstanceOf(LoggerInterface::class, $driverFriend->getLogger(), "Logger exists on driver after being set"); + + // Make sure we aren't just getting the default Null logger + $this->assertFalse(is_a($friend->getLogger(), NullLogger::class)); + $this->assertFalse(is_a($driverFriend->getLogger(), NullLogger::class)); + } + + public function testGetSetLogger() + { + $logger = new Logger('test'); + $logger->pushHandler(new SyslogHandler(Logger::WARNING)); + + $this->pool->setLogger($logger); + + $friend = new Friend($this->pool); + $driverFriend = new Friend($friend->driver); + + // Check that a valid logger is set + $this->assertInstanceOf(LoggerInterface::class, $friend->getLogger(), "Logger exists after being set"); + $this->assertInstanceOf(LoggerInterface::class, $driverFriend->getLogger(), "Logger exists on driver after being set"); + + // Make sure we aren't just getting the default Null logger + $this->assertFalse(is_a($friend->getLogger(), NullLogger::class)); + $this->assertFalse(is_a($driverFriend->getLogger(), NullLogger::class)); + } + + public function testGetItem() + { + $item = $this->pool->getItem('foo'); + $this->assertInstanceOf(Item::class, $item); + } + + public function testItemBadKey() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Cache key must be a string."); + + $this->pool->getItem([]); + } + + public function testGetItems() + { + $collection = $this->pool->getItems(['foo', 'bar', 'baz']); + $this->assertInstanceOf(ItemCollection::class, $collection); + + foreach($collection as $item) + { + $this->assertInstanceOf(Item::class, $item); + } + } + + public function testEmptyGetItems() + { + $collection = $this->pool->getItems(); + + $this->assertInstanceOf(ItemCollection::class, $collection); + $this->assertEquals(0, count($collection)); + } + + public function testHasItem() + { + // The key doesn't exist yet + $this->assertFalse($this->pool->hasItem('foobar')); + + // Create the item + $item = $this->pool->getItem('foobar') + ->set('baz') + ->save(); + + // The item exists now + $this->assertTrue($this->pool->hasItem('foobar')); + } + + public function testHasItemBadKey() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Cache key must be a string."); + + $this->pool->hasItem(34); + } + + public function testClear() + { + // Call clear to make sure we are working from a clean slate to start + $this->pool->clear(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz', + 'foobar' => 'foobarbaz' + ]; + + // Set up some data + $this->setupDataInCache($data); + + foreach($data as $key => $val) + { + $this->assertTrue($this->pool->hasItem($key)); + $item = $this->pool->getItem($key); + $this->assertEquals($val, $item->get()); + } + + // Now we clear it all! + $this->pool->clear(); + + foreach($data as $key => $val) + { + $this->assertFalse($this->pool->hasItem($key)); + $item = $this->pool->getItem($key); + $this->assertNull($item->get()); + } + } + + public function testDeleteItemBadKey() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Cache key must be a string."); + + $this->pool->deleteItem(34); + } + + public function testDeleteItemThatDoesNotExist() + { + $this->pool->clear(); + $this->assertFalse($this->pool->deleteItem('foo')); + } + + public function testDeleteItem() + { + // Start with a clean slate + $this->pool->clear(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz', + 'foobar' => 'foobarbaz' + ]; + + $this->setupDataInCache($data); + + $this->pool->deleteItem('foo'); + + // The item no longer exists + $this->assertFalse($this->pool->hasItem('foo')); + $item = $this->pool->getItem('foo'); + $this->assertNull($item->get()); + + // The other items still exist + foreach(['bar', 'foobar'] as $key) + { + $this->assertTrue($this->pool->hasItem($key)); + $item = $this->pool->getItem($key); + $this->assertFalse(is_null($item->get())); + } + } + + public function testDeleteItems() + { + $this->pool->clear(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz', + 'foobar' => 'foobarbaz' + ]; + + $this->setupDataInCache($data); + + $this->pool->deleteItems(['foo', 'bar']); + + foreach(['foo', 'bar'] as $key) + { + $this->assertFalse($this->pool->hasItem($key)); + $item = $this->pool->getItem($key); + $this->assertNull($item->get()); + } + } + + public function testSaveDeferred() + { + $this->pool->clear(); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz', + 'foobar' => 'foobarbaz' + ]; + + $this->setupDeferredData($data); + + // See that the data is returned by the pool + foreach($data as $key => $val) + { + $this->assertTrue($this->pool->hasItem($key)); + $item = $this->pool->getItem($key); + + // The cache hasn't been updated yet, even + // though the pool data has + $this->assertNotEquals($data[$key], $item->get()); + } + } + + public function testCommit() + { + $this->pool->clear(); + + // If there are no deferred items, this will return true + $this->assertTrue($this->pool->commit()); + + $data = [ + 'foo' => 'bar', + 'bar' => 'baz', + 'foobar' => 'foobarbaz' + ]; + + $this->setupDeferredData($data); + + // See that the data is returned by the pool + foreach($this->pool->getItems(array_keys($data)) as $key => $item) + { + $this->assertTrue($this->pool->hasItem($key)); + + // The cache hasn't been updated yet, even + // though the pool data has + $this->assertNotEquals($data[$key], $item->get()); + } + + $this->pool->commit(); + + // See that the data is saved in the cache backend + foreach($this->pool->getItems(array_keys($data)) as $key => $item) + { + $this->assertTrue($this->pool->hasItem($key)); + $this->assertEquals($data[$key], $item->get()); + } + } + + protected function setupDeferredData($data) + { + foreach($data as $key => $val) + { + $item = $this->pool->getItem($key) + ->set($val); + + $this->assertTrue($this->pool->saveDeferred($item)); + } + } + + protected function setupDataInCache($data) + { + foreach($data as $key => $val) + { + $item = $this->pool->getItem($key) + ->set($val); + + $this->pool->save($item); + } + } +} \ No newline at end of file