diff --git a/CHANGELOG.md b/CHANGELOG.md index 42481ec3..d8fcb764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ To get the diff between two versions, go to https://github.com/SonsOfPHP/sonsofp ## [Unreleased] +* [PR #227](https://github.com/SonsOfPHP/sonsofphp/pull/227) [Cache] FilesystemAdapter, Marshallers, and other updates * [PR #226](https://github.com/SonsOfPHP/sonsofphp/pull/226) [Registry] New Component and Contract * [PR #225](https://github.com/SonsOfPHP/sonsofphp/pull/225) Maintenance * [PR #222](https://github.com/SonsOfPHP/sonsofphp/pull/222) [Assert] New Component diff --git a/composer.json b/composer.json index 552ef0a1..ef7944b4 100644 --- a/composer.json +++ b/composer.json @@ -88,8 +88,7 @@ "psr/http-server-handler": "^1.0", "psr/http-server-middleware": "^1.0", "aws/aws-sdk-php": "^3.0", - "liip/imagine-bundle": "^2.0", - "sonsofphp/registry-contract": "0.3.x-dev" + "liip/imagine-bundle": "^2.0" }, "replace": { "sonsofphp/bard": "self.version", @@ -247,4 +246,4 @@ "SonsOfPHP\\Bridge\\Symfony\\Cqrs\\Tests\\": "src/SonsOfPHP/Bridge/Symfony/Cqrs/Tests" } } -} \ No newline at end of file +} diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 0f3d1625..8e6529fe 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -16,6 +16,7 @@ * [Assert](components/assert.md) * [Cache](components/cache/README.md) * [Adapters](components/cache/adapters.md) + * [Marshallers](components/cache/marshallers.md) * [Clock](components/clock.md) * [Container](components/container.md) * [Cookie](components/cookie.md) diff --git a/docs/components/cache/README.md b/docs/components/cache/README.md index ad8f34d1..f9343102 100644 --- a/docs/components/cache/README.md +++ b/docs/components/cache/README.md @@ -2,8 +2,6 @@ title: Cache - Overview --- -# Cache - ## Installation ```shell @@ -59,6 +57,8 @@ $pool = new ChainAdapter([ ]); ``` -The `ChainAdapter` will read from all pools and return the first result it finds. So in the above example, if the cache item is not found in `ArrayAdapter`, it will look for it in the `ApcuAdapter` pool. +The `ChainAdapter` will read from all pools and return the first result it +finds. So in the above example, if the cache item is not found in +`ArrayAdapter`, it will look for it in the `ApcuAdapter` pool. The `ChainAdapter` will also write to and delete from all pools. diff --git a/docs/components/cache/adapters.md b/docs/components/cache/adapters.md index cbcd0f3e..0f2875aa 100644 --- a/docs/components/cache/adapters.md +++ b/docs/components/cache/adapters.md @@ -1,18 +1,82 @@ +--- +title: Cache Adapters +--- + # Adapters +All adapters implement the PSR-6 `\Psr\Cache\CacheItemPoolInterface` and can be used as +standalone cache pools. + ## ApcuAdapter +Requires the APCu extension is loaded and enabled. + +```php + + */ +abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface +{ + use LoggerAwareTrait; + + protected array $deferred = []; + + public function __construct( + protected int $defaultTTL = 0, + protected ?MarshallerInterface $marshaller = null, + ) { + if (!$this->marshaller instanceof MarshallerInterface) { + $this->marshaller = new SerializableMarshaller(); + } + } + + public function __destruct() + { + $this->commit(); + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = []): iterable + { + foreach ($keys as $key) { + yield $key => $this->getItem($key); + } + } + + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys): bool + { + $isOk = true; + foreach ($keys as $key) { + if (!$this->deleteItem($key)) { + $this->logger?->debug(sprintf('Unable to delete key "%s".', $key)); + $isOk = false; + } + } + + return $isOk; + } + + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item): bool + { + $this->deferred[$item->getKey()] = $item; + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit(): bool + { + $isOk = true; + + foreach ($this->deferred as $item) { + if (!$this->save($item)) { + $this->logger?->debug(sprintf('Unable to save key "%s".', $item->getKey())); + $isOk = false; + } + } + + $this->deferred = []; + + return $isOk; + } +} diff --git a/src/SonsOfPHP/Component/Cache/Adapter/ApcuAdapter.php b/src/SonsOfPHP/Component/Cache/Adapter/ApcuAdapter.php index fe2bc45c..edb89c84 100644 --- a/src/SonsOfPHP/Component/Cache/Adapter/ApcuAdapter.php +++ b/src/SonsOfPHP/Component/Cache/Adapter/ApcuAdapter.php @@ -7,19 +7,25 @@ use Psr\Cache\CacheItemInterface; use SonsOfPHP\Component\Cache\CacheItem; use SonsOfPHP\Component\Cache\Exception\CacheException; +use SonsOfPHP\Component\Cache\Marshaller\MarshallerInterface; /** * @author Joshua Estes */ -class ApcuAdapter implements AdapterInterface +final class ApcuAdapter extends AbstractAdapter { - private array $deferred = []; - - public function __construct() - { + public function __construct( + int $defaultTTL = 0, + ?MarshallerInterface $marshaller = null, + ) { if (!extension_loaded('apcu') || !filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOL) || false === apcu_enabled()) { throw new CacheException('APCu extension is required.'); } + + parent::__construct( + defaultTTL: $defaultTTL, + marshaller: $marshaller, + ); } /** @@ -28,22 +34,14 @@ public function __construct() public function getItem(string $key): CacheItemInterface { if ($this->hasItem($key)) { - return (new CacheItem($key, true))->set(apcu_fetch($key)); + return (new CacheItem($key, true)) + ->set($this->marshaller->unmarshall(apcu_fetch($key))) + ; } return new CacheItem($key, false); } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []): iterable - { - foreach ($keys as $key) { - yield $key => $this->getItem($key); - } - } - /** * {@inheritdoc} */ @@ -72,52 +70,11 @@ public function deleteItem(string $key): bool return apcu_delete($key); } - /** - * {@inheritdoc} - */ - public function deleteItems(array $keys): bool - { - $ret = true; - foreach ($keys as $key) { - if (!$this->deleteItem($key)) { - $ret = false; - } - } - - return $ret; - } - /** * {@inheritdoc} */ public function save(CacheItemInterface $item): bool { - $this->saveDeferred($item); - - return $this->commit(); - } - - /** - * {@inheritdoc} - */ - public function saveDeferred(CacheItemInterface $item): bool - { - $this->deferred[$item->getKey()] = $item; - - return true; - } - - /** - * {@inheritdoc} - */ - public function commit(): bool - { - foreach ($this->deferred as $key => $item) { - apcu_store($key, $item->get(), 0); - } - - $this->deferred = []; - - return true; + return apcu_store($item->getKey(), $this->marshaller->marshall($item->get()), 0); } } diff --git a/src/SonsOfPHP/Component/Cache/Adapter/ArrayAdapter.php b/src/SonsOfPHP/Component/Cache/Adapter/ArrayAdapter.php index ec79435c..125df5f2 100644 --- a/src/SonsOfPHP/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/SonsOfPHP/Component/Cache/Adapter/ArrayAdapter.php @@ -10,7 +10,7 @@ /** * @author Joshua Estes */ -class ArrayAdapter implements AdapterInterface +final class ArrayAdapter extends AbstractAdapter { private array $values = []; @@ -26,16 +26,6 @@ public function getItem(string $key): CacheItemInterface return new CacheItem($key, false); } - /** - * {@inheritdoc} - */ - public function getItems(array $keys = []): iterable - { - foreach ($keys as $key) { - yield $key => $this->getItem($key); - } - } - /** * {@inheritdoc} */ @@ -68,18 +58,6 @@ public function deleteItem(string $key): bool return true; } - /** - * {@inheritdoc} - */ - public function deleteItems(array $keys): bool - { - foreach ($keys as $key) { - $this->deleteItem($key); - } - - return true; - } - /** * {@inheritdoc} */ @@ -89,20 +67,4 @@ public function save(CacheItemInterface $item): bool return true; } - - /** - * {@inheritdoc} - */ - public function saveDeferred(CacheItemInterface $item): bool - { - return $this->save($item); - } - - /** - * {@inheritdoc} - */ - public function commit(): bool - { - return true; - } } diff --git a/src/SonsOfPHP/Component/Cache/Adapter/ChainAdapter.php b/src/SonsOfPHP/Component/Cache/Adapter/ChainAdapter.php index 0058eda3..daf324a2 100644 --- a/src/SonsOfPHP/Component/Cache/Adapter/ChainAdapter.php +++ b/src/SonsOfPHP/Component/Cache/Adapter/ChainAdapter.php @@ -23,10 +23,10 @@ * * @author Joshua Estes */ -class ChainAdapter implements AdapterInterface +final readonly class ChainAdapter implements AdapterInterface { public function __construct( - private readonly array $adapters, + private array $adapters, ) { foreach ($this->adapters as $adapter) { if (!$adapter instanceof AdapterInterface) { diff --git a/src/SonsOfPHP/Component/Cache/Adapter/FilesystemAdapter.php b/src/SonsOfPHP/Component/Cache/Adapter/FilesystemAdapter.php new file mode 100644 index 00000000..c9168ada --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Adapter/FilesystemAdapter.php @@ -0,0 +1,141 @@ + + */ +final class FilesystemAdapter extends AbstractAdapter +{ + public function __construct( + private ?string $directory = null, + private readonly int $defaultPermission = 0o777, + int $defaultTTL = 0, + ?MarshallerInterface $marshaller = null, + ) { + parent::__construct( + defaultTTL: $defaultTTL, + marshaller: $marshaller, + ); + + if (null !== $this->directory) { + $this->directory = realpath($this->directory) ?: $this->directory; + } + + if (null === $this->directory) { + $this->directory = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'sonsofphp-cache'; + } + + if (!is_dir($this->directory)) { + @mkdir($this->directory, $this->defaultPermission, true); + } + + $this->directory .= \DIRECTORY_SEPARATOR; + } + + /** + * {@inheritdoc} + */ + public function getItem(string $key): CacheItemInterface + { + if ($this->hasItem($key)) { + return (new CacheItem($key, true)) + ->set($this->marshaller->unmarshall(file_get_contents($this->getFile($key)))) + ; + } + + return new CacheItem($key, false); + } + + /** + * {@inheritdoc} + */ + public function hasItem(string $key): bool + { + CacheItem::validateKey($key); + + $filename = $this->getFile($key); + + return file_exists($filename) && filemtime($filename) > time(); + } + + /** + * {@inheritdoc} + */ + public function clear(): bool + { + $it = new \RecursiveDirectoryIterator($this->directory, \RecursiveDirectoryIterator::SKIP_DOTS); + $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($files as $file) { + if ($file->isDir()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + + return rmdir($this->directory); + } + + /** + * {@inheritdoc} + */ + public function deleteItem(string $key): bool + { + CacheItem::validateKey($key); + + $filename = $this->getFile($key); + + if (!file_exists($filename)) { + $this->logger?->debug(sprintf('Cache file "%s" does not exist', $filename)); + return false; + } + + return unlink($filename); + } + + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item): bool + { + $filename = $this->getFile($item->getKey()); + + if (false === file_put_contents($filename, $this->marshaller->marshall($item->get()))) { + $this->logger?->debug(sprintf('Could not write "%s"', $filename)); + return false; + } + + if (false === chmod($filename, $this->defaultPermission)) { + $this->logger?->debug(sprintf('Could not chmod "%s"', $filename)); + return false; + } + + if (null === $item->expiry()) { + $item->expiresAfter($this->defaultTTL); + } + + $mtime = (int) round($item->expiry()); + + return touch($filename, $mtime); + } + + /** + * Returns the full path and file name + */ + private function getFile(string $key): string + { + $hash = str_replace('/', '-', base64_encode(hash('xxh128', self::class . $key, true))); + $dir = $this->directory . strtoupper($hash[0] . \DIRECTORY_SEPARATOR . $hash[1] . \DIRECTORY_SEPARATOR); + @mkdir($dir, $this->defaultPermission, true); + + return $dir . substr($hash, 2, 20); + } +} diff --git a/src/SonsOfPHP/Component/Cache/CacheItem.php b/src/SonsOfPHP/Component/Cache/CacheItem.php index c44e2ee3..a941332b 100644 --- a/src/SonsOfPHP/Component/Cache/CacheItem.php +++ b/src/SonsOfPHP/Component/Cache/CacheItem.php @@ -100,4 +100,9 @@ public function expiresAfter(int|DateInterval|null $time): static return $this; } + + public function expiry(): int|float|null + { + return $this->expiry; + } } diff --git a/src/SonsOfPHP/Component/Cache/Marshaller/JsonMarshaller.php b/src/SonsOfPHP/Component/Cache/Marshaller/JsonMarshaller.php new file mode 100644 index 00000000..29586ee1 --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Marshaller/JsonMarshaller.php @@ -0,0 +1,27 @@ + + */ +final class JsonMarshaller implements MarshallerInterface +{ + /** + * {@inheritdoc} + */ + public function marshall(mixed $value): string + { + return json_encode($value); + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value): mixed + { + return json_decode($value); + } +} diff --git a/src/SonsOfPHP/Component/Cache/Marshaller/MarshallerInterface.php b/src/SonsOfPHP/Component/Cache/Marshaller/MarshallerInterface.php new file mode 100644 index 00000000..ca3064cb --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Marshaller/MarshallerInterface.php @@ -0,0 +1,25 @@ + + */ +interface MarshallerInterface +{ + /** + * Serialize PHP value and return a string that can be stored + */ + public function marshall(mixed $value): string; + + /** + * Unserialize the stored value + * + * @throws CacheException + */ + public function unmarshall(string $value): mixed; +} diff --git a/src/SonsOfPHP/Component/Cache/Marshaller/SerializableMarshaller.php b/src/SonsOfPHP/Component/Cache/Marshaller/SerializableMarshaller.php new file mode 100644 index 00000000..473c565a --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Marshaller/SerializableMarshaller.php @@ -0,0 +1,27 @@ + + */ +final class SerializableMarshaller implements MarshallerInterface +{ + /** + * {@inheritdoc} + */ + public function marshall(mixed $value): string + { + return serialize($value); + } + + /** + * {@inheritdoc} + */ + public function unmarshall(string $value): mixed + { + return unserialize($value); + } +} diff --git a/src/SonsOfPHP/Component/Cache/Tests/Adapter/AbstractAdapterTest.php b/src/SonsOfPHP/Component/Cache/Tests/Adapter/AbstractAdapterTest.php new file mode 100644 index 00000000..af577474 --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Tests/Adapter/AbstractAdapterTest.php @@ -0,0 +1,94 @@ +adapter = $this->createStub(AbstractAdapter::class); + $this->adapter = new class extends AbstractAdapter { + public function getItem(string $key): CacheItemInterface + { + return new CacheItem($key, false); + } + + public function hasItem(string $key): bool + { + return $this->getItem($key)->isHit(); + } + + public function clear(): bool + { + return true; + } + + public function deleteItem(string $key): bool + { + return true; + } + + public function save(CacheItemInterface $item): bool + { + return true; + } + }; + } + + public function testItHasTheCorrectInterface(): void + { + $this->assertInstanceOf(AdapterInterface::class, $this->adapter); + $this->assertInstanceOf(CacheItemPoolInterface::class, $this->adapter); + } + + public function testItWillExecuteGetItemWhenGetItemsIsExecuted(): void + { + $items = iterator_to_array($this->adapter->getItems(['test'])); + $this->assertArrayHasKey('test', $items); + } + + public function testItWillDeleteItems(): void + { + $this->assertTrue($this->adapter->deleteItems(['test'])); + } + + public function testItWillSaveDeferred(): void + { + $cacheItem = $this->adapter->getItem('test'); + + $this->assertTrue($this->adapter->saveDeferred($cacheItem)); + } + + public function testItWillCommit(): void + { + $cacheItem = $this->adapter->getItem('test'); + $this->adapter->saveDeferred($cacheItem); + + $this->assertTrue($this->adapter->commit()); + } + + public function testItWillDestruct(): void + { + $cacheItem = $this->adapter->getItem('test'); + $this->adapter->saveDeferred($cacheItem); + + $this->assertTrue($this->adapter->commit()); + unset($this->adapter); + } +} diff --git a/src/SonsOfPHP/Component/Cache/Tests/Adapter/ApcuAdapterTest.php b/src/SonsOfPHP/Component/Cache/Tests/Adapter/ApcuAdapterTest.php index 94fd96ec..b130273c 100644 --- a/src/SonsOfPHP/Component/Cache/Tests/Adapter/ApcuAdapterTest.php +++ b/src/SonsOfPHP/Component/Cache/Tests/Adapter/ApcuAdapterTest.php @@ -10,13 +10,17 @@ use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use SonsOfPHP\Component\Cache\Adapter\AbstractAdapter; use SonsOfPHP\Component\Cache\Adapter\AdapterInterface; use SonsOfPHP\Component\Cache\Adapter\ApcuAdapter; use SonsOfPHP\Component\Cache\CacheItem; +use SonsOfPHP\Component\Cache\Marshaller\SerializableMarshaller; #[RequiresPhpExtension('apcu')] #[CoversClass(ApcuAdapter::class)] #[UsesClass(CacheItem::class)] +#[UsesClass(AbstractAdapter::class)] +#[UsesClass(SerializableMarshaller::class)] final class ApcuAdapterTest extends TestCase { protected function setUp(): void diff --git a/src/SonsOfPHP/Component/Cache/Tests/Adapter/ArrayAdapterTest.php b/src/SonsOfPHP/Component/Cache/Tests/Adapter/ArrayAdapterTest.php index bb18e9d3..b8b61229 100644 --- a/src/SonsOfPHP/Component/Cache/Tests/Adapter/ArrayAdapterTest.php +++ b/src/SonsOfPHP/Component/Cache/Tests/Adapter/ArrayAdapterTest.php @@ -9,12 +9,14 @@ use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use SonsOfPHP\Component\Cache\Adapter\AbstractAdapter; use SonsOfPHP\Component\Cache\Adapter\AdapterInterface; use SonsOfPHP\Component\Cache\Adapter\ArrayAdapter; use SonsOfPHP\Component\Cache\CacheItem; #[CoversClass(ArrayAdapter::class)] #[UsesClass(CacheItem::class)] +#[UsesClass(AbstractAdapter::class)] final class ArrayAdapterTest extends TestCase { /** diff --git a/src/SonsOfPHP/Component/Cache/Tests/Adapter/ChainAdapterTest.php b/src/SonsOfPHP/Component/Cache/Tests/Adapter/ChainAdapterTest.php index 759c92a8..7a5d53ef 100644 --- a/src/SonsOfPHP/Component/Cache/Tests/Adapter/ChainAdapterTest.php +++ b/src/SonsOfPHP/Component/Cache/Tests/Adapter/ChainAdapterTest.php @@ -9,6 +9,7 @@ use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use SonsOfPHP\Component\Cache\Adapter\AbstractAdapter; use SonsOfPHP\Component\Cache\Adapter\AdapterInterface; use SonsOfPHP\Component\Cache\Adapter\ArrayAdapter; use SonsOfPHP\Component\Cache\Adapter\ChainAdapter; @@ -19,6 +20,7 @@ #[CoversClass(ChainAdapter::class)] #[UsesClass(ArrayAdapter::class)] #[UsesClass(CacheItem::class)] +#[UsesClass(AbstractAdapter::class)] final class ChainAdapterTest extends TestCase { private array $adapters = []; diff --git a/src/SonsOfPHP/Component/Cache/Tests/Adapter/FilesystemAdapterTest.php b/src/SonsOfPHP/Component/Cache/Tests/Adapter/FilesystemAdapterTest.php new file mode 100644 index 00000000..ae21d051 --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Tests/Adapter/FilesystemAdapterTest.php @@ -0,0 +1,71 @@ +adapter = new FilesystemAdapter(); + } + + protected function tearDown(): void + { + $this->adapter->clear(); + } + + public function testItHasTheCorrectInterface(): void + { + $this->assertInstanceOf(AdapterInterface::class, $this->adapter); + $this->assertInstanceOf(CacheItemPoolInterface::class, $this->adapter); + } + + public function testItCanGetItemWhenCold(): void + { + $cacheItem = $this->adapter->getItem('test'); + $this->assertFalse($cacheItem->isHit()); + } + + public function testItIsAbleToSaveCacheItem(): void + { + $cacheItem = $this->adapter->getItem('test'); + $this->assertFalse($cacheItem->isHit()); + $this->adapter->save($cacheItem->set('just a test')->expiresAfter(60)); + + $cacheItem = $this->adapter->getItem('test'); + $this->assertTrue($cacheItem->isHit()); + $this->assertSame('just a test', $cacheItem->get()); + } + + public function testItCanDeleteCacheItem(): void + { + $cacheItem = $this->adapter->getItem('test'); + $this->adapter->save($cacheItem->expiresAfter(60)); + $this->assertTrue($this->adapter->hasItem('test')); + $this->assertTrue($this->adapter->deleteItem('test')); + $this->assertFalse($this->adapter->hasItem('test')); + } + + public function testItWillReturnFalseWhenDeletingKeyThatDoesNotExist(): void + { + $this->assertFalse($this->adapter->deleteItem('test')); + } +} diff --git a/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php b/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php index b5a35f8f..6dcb487e 100644 --- a/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php +++ b/src/SonsOfPHP/Component/Cache/Tests/CacheItemTest.php @@ -129,4 +129,12 @@ public static function invalidKeysProvider(): iterable yield 'reserved' => ['contains@reserved}characters']; } + + public function testItCanReturnExpiry(): void + { + $item = new CacheItem('testing'); + $this->assertNull($item->expiry()); + $item->expiresAfter(3600); + $this->assertNotNull($item->expiry()); + } } diff --git a/src/SonsOfPHP/Component/Cache/Tests/Marshaller/JsonMarshallerTest.php b/src/SonsOfPHP/Component/Cache/Tests/Marshaller/JsonMarshallerTest.php new file mode 100644 index 00000000..333749c9 --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Tests/Marshaller/JsonMarshallerTest.php @@ -0,0 +1,33 @@ +marshaller = new JsonMarshaller(); + } + + public function testItHasTheCorrectInterface(): void + { + $this->assertInstanceOf(MarshallerInterface::class, $this->marshaller); + } + + public function testItCanMarshallAndUnmarshallValues(): void + { + $value = 'testing'; + + $this->assertSame($value, $this->marshaller->unmarshall($this->marshaller->marshall($value))); + } +} diff --git a/src/SonsOfPHP/Component/Cache/Tests/Marshaller/SerializableMarshallerTest.php b/src/SonsOfPHP/Component/Cache/Tests/Marshaller/SerializableMarshallerTest.php new file mode 100644 index 00000000..d77de40e --- /dev/null +++ b/src/SonsOfPHP/Component/Cache/Tests/Marshaller/SerializableMarshallerTest.php @@ -0,0 +1,33 @@ +marshaller = new SerializableMarshaller(); + } + + public function testItHasTheCorrectInterface(): void + { + $this->assertInstanceOf(MarshallerInterface::class, $this->marshaller); + } + + public function testItCanMarshallAndUnmarshallValues(): void + { + $value = 'testing'; + + $this->assertSame($value, $this->marshaller->unmarshall($this->marshaller->marshall($value))); + } +} diff --git a/src/SonsOfPHP/Component/Cache/Tests/SimpleCacheTest.php b/src/SonsOfPHP/Component/Cache/Tests/SimpleCacheTest.php index c3b1e28f..dea59f3c 100644 --- a/src/SonsOfPHP/Component/Cache/Tests/SimpleCacheTest.php +++ b/src/SonsOfPHP/Component/Cache/Tests/SimpleCacheTest.php @@ -145,4 +145,18 @@ public function testGetMultiple(): void $this->assertSame('default.value', $items['item.key']); $this->assertSame('item2.value', $items['item2']); } + + public function testItWillSaveWhenTTLIsSet(): void + { + $item = $this->createMock(CacheItemInterface::class); + $item->expects($this->once())->method('set'); + $item->expects($this->once())->method('expiresAfter'); + + $this->adapter->expects($this->once())->method('getItem')->willReturn($item); + $this->adapter->expects($this->once())->method('save')->willReturn(true); + + $cache = new SimpleCache($this->adapter); + + $this->assertTrue($cache->set('item.key', 'item.value', 60)); + } } diff --git a/src/SonsOfPHP/Component/Cache/composer.json b/src/SonsOfPHP/Component/Cache/composer.json index 1bf3f7bb..4fbfa0aa 100644 --- a/src/SonsOfPHP/Component/Cache/composer.json +++ b/src/SonsOfPHP/Component/Cache/composer.json @@ -33,17 +33,20 @@ "prefer-stable": true, "require": { "php": ">=8.2", - "psr/cache": "^2.0 || ^3.0" + "psr/cache": "^2.0 || ^3.0", + "psr/log": "^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0" }, "require-dev": { - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + "sonsofphp/filesystem": "0.3.x-dev", + "sonsofphp/logger": "0.3.x-dev" }, "suggest": { - "sonsofphp/filesystem": "Stores cache files using virtual filesystem" + "sonsofphp/logger": "Add ability to log various messages" }, "provide": { "psr/cache-implementation": "^2.0 || ^3.0", - "psr/simple-cache-implementation": "^1.0 || ^2.0 || ^3.0" + "psr/simple-cache-implementation": "^2.0 || ^3.0" }, "extra": { "sort-packages": true, @@ -61,4 +64,4 @@ "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" } ] -} \ No newline at end of file +}