From 1be87340023c0acc2387410d70b6fca60899910c Mon Sep 17 00:00:00 2001 From: Carlos MAtos Date: Fri, 13 Dec 2024 02:24:14 +0000 Subject: [PATCH] Serialization option added to the attribute. --- README.md | 36 +++++++++++++++++++++++- composer.lock | 48 +++++++++++++------------------- src/Cache.php | 2 ++ src/CacheTrait.php | 5 ++-- tests/CacheAttributeTest.php | 51 ++++++++++++++++++++++++++++++++++ tests/Fixtures/TestService.php | 26 +++++++++++++++++ 6 files changed, 137 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index e55249b..cadc97a 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ Mnemosyne is a powerful and flexible caching library for PHP 8.0+ that uses attr - Cache tags for group invalidation - PSR-16 (SimpleCache) compatibility - Flexible cache key templates +- Smart serialization handling: + - Automatic serialization of complex objects + - Optional raw storage for simple data types + - Full control over serialization behavior ## Installation @@ -22,6 +26,11 @@ composer require cmatosbc/mnemosyne ## Usage +To use Mnemosyne in your classes, you must: +1. Use the `CacheTrait` in your class +2. Inject a PSR-16 compatible cache implementation +3. Apply the `Cache` attribute to methods you want to cache + ### Basic Usage ```php @@ -31,7 +40,7 @@ use Psr\SimpleCache\CacheInterface; class UserService { - use CacheTrait; + use CacheTrait; // Required to enable caching functionality public function __construct(CacheInterface $cache) { @@ -52,6 +61,31 @@ class UserService } ``` +### Serialization Control + +The `Cache` attribute allows you to control how values are stored in cache: + +```php +class UserService +{ + use CacheTrait; + + // Automatically serialize complex objects + #[Cache(key: 'user:{id}', ttl: 3600, serialize: true)] + public function getUser(int $id): User + { + return $this->cacheCall('doGetUser', func_get_args()); + } + + // Store simple arrays without serialization + #[Cache(key: 'users:list', ttl: 3600, serialize: false)] + public function getUsersList(): array + { + return $this->cacheCall('doGetUsersList', func_get_args()); + } +} +``` + ### Custom Cache Keys ```php diff --git a/composer.lock b/composer.lock index e36b901..3e1485d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,33 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f52818afe9f745763af623d0c09dd93a", + "content-hash": "d2dd625d1bf4047c26f1d0d2c3c0a778", "packages": [ { "name": "psr/cache", - "version": "dev-master", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "0a7c67d0d1c8167b342eb74339d6f961663826ce" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/0a7c67d0d1c8167b342eb74339d6f961663826ce", - "reference": "0a7c67d0d1c8167b342eb74339d6f961663826ce", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { "php": ">=8.0.0" }, - "suggest": { - "fig/cache-util": "Provides some useful PSR-6 utilities" - }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { @@ -55,28 +51,27 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2021-02-24T03:25:37+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { "name": "psr/simple-cache", - "version": "dev-master", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "2d280c2aaa23a120f35d55cfde8581954a8e77fa" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/2d280c2aaa23a120f35d55cfde8581954a8e77fa", - "reference": "2d280c2aaa23a120f35d55cfde8581954a8e77fa", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { "php": ">=8.0.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -107,9 +102,9 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/master" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2022-04-08T16:41:45+00:00" + "time": "2021-10-29T13:26:27+00:00" } ], "packages-dev": [ @@ -814,12 +809,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4e89eff200b801db58f3d580ad7426431949eaa9" + "reference": "54ae58f62c551a1c73498b053bd11697aac16abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4e89eff200b801db58f3d580ad7426431949eaa9", - "reference": "4e89eff200b801db58f3d580ad7426431949eaa9", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/54ae58f62c551a1c73498b053bd11697aac16abe", + "reference": "54ae58f62c551a1c73498b053bd11697aac16abe", "shasum": "" }, "require": { @@ -891,7 +886,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.39" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5" }, "funding": [ { @@ -907,7 +902,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T10:51:07+00:00" + "time": "2024-12-12T06:30:58+00:00" }, { "name": "sebastian/cli-parser", @@ -1885,10 +1880,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": { - "psr/cache": 20, - "psr/simple-cache": 20 - }, + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Cache.php b/src/Cache.php index 68ae5a5..9b9496e 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -32,12 +32,14 @@ class Cache * @param int|null $ttl Time-to-live in seconds. If null, cache will not expire. * @param array $invalidates List of cache key templates to invalidate when this method is called. * @param array $tags List of tags to associate with this cache entry. Supports parameter interpolation. + * @param bool|null $serialize Whether to serialize the result before caching. If null, will be determined automatically. */ public function __construct( public ?string $key = null, public ?int $ttl = null, public array $invalidates = [], public array $tags = [], + public ?bool $serialize = null, ) { } } diff --git a/src/CacheTrait.php b/src/CacheTrait.php index 422d084..ed50fdb 100644 --- a/src/CacheTrait.php +++ b/src/CacheTrait.php @@ -98,7 +98,7 @@ private function cacheCall(string $method, array $args) if ($key !== null) { $result = $this->cache->get($key); if ($result !== null) { - return $result; + return $config->serialize ? unserialize($result) : $result; } } @@ -108,7 +108,8 @@ private function cacheCall(string $method, array $args) // Cache the result if we have a key if ($key !== null) { - $this->cache->set($key, $result, $config->ttl); + $valueToCache = $config->serialize ? serialize($result) : $result; + $this->cache->set($key, $valueToCache, $config->ttl); // Store tags if any are defined if (!empty($config->tags)) { diff --git a/tests/CacheAttributeTest.php b/tests/CacheAttributeTest.php index 6b4fbc3..861d695 100644 --- a/tests/CacheAttributeTest.php +++ b/tests/CacheAttributeTest.php @@ -182,4 +182,55 @@ public function testCacheOperations(): void $this->assertEquals('user:42', $operations[1]['key']); $this->assertEquals(3600, $operations[1]['ttl']); } + + public function testSerializationEnabled(): void + { + echo "\nTest: Method With Serialization\n"; + echo "----------------------------\n"; + + // First call should cache the serialized object + echo "First call (should miss cache and store serialized result):\n"; + $result1 = $this->service->methodWithSerialization(42); + echo "Result: " . json_encode($result1) . "\n"; + + // Verify the cached value is serialized + $cachedValue = $this->cache->get('complex:42'); + $this->assertTrue(is_string($cachedValue)); + $this->assertStringStartsWith('O:8:"stdClass":', $cachedValue); + + // Second call should unserialize from cache + echo "\nSecond call (should hit cache and unserialize):\n"; + $result2 = $this->service->methodWithSerialization(42); + echo "Result: " . json_encode($result2) . "\n"; + + // Verify both results are identical objects + $this->assertEquals($result1->id, $result2->id); + $this->assertEquals($result1->count, $result2->count); + $this->assertEquals($result1->timestamp, $result2->timestamp); + $this->assertEquals(1, $this->service->getCounter()); + } + + public function testSerializationDisabled(): void + { + echo "\nTest: Method Without Serialization\n"; + echo "--------------------------------\n"; + + // First call should cache the raw array + echo "First call (should miss cache and store raw result):\n"; + $result1 = $this->service->methodWithoutSerialization(42); + echo "Result: " . json_encode($result1) . "\n"; + + // Verify the cached value is not serialized + $cachedValue = $this->cache->get('noserialize:42'); + $this->assertIsArray($cachedValue); + $this->assertEquals($result1, $cachedValue); + + // Second call should return the raw cached array + echo "\nSecond call (should hit cache):\n"; + $result2 = $this->service->methodWithoutSerialization(42); + echo "Result: " . json_encode($result2) . "\n"; + + $this->assertEquals($result1, $result2); + $this->assertEquals(1, $this->service->getCounter()); + } } diff --git a/tests/Fixtures/TestService.php b/tests/Fixtures/TestService.php index a1ee144..41e9d01 100644 --- a/tests/Fixtures/TestService.php +++ b/tests/Fixtures/TestService.php @@ -80,6 +80,32 @@ private function doComplexMethod(int $id, int $deptId): array return ['id' => $id, 'deptId' => $deptId, 'count' => ++$this->counter]; } + #[Cache(key: 'complex:{id}', ttl: 3600, serialize: true)] + public function methodWithSerialization(int $id): \stdClass + { + return $this->cacheCall('doMethodWithSerialization', func_get_args()); + } + + private function doMethodWithSerialization(int $id): \stdClass + { + $obj = new \stdClass(); + $obj->id = $id; + $obj->count = ++$this->counter; + $obj->timestamp = time(); + return $obj; + } + + #[Cache(key: 'noserialize:{id}', ttl: 3600, serialize: false)] + public function methodWithoutSerialization(int $id): array + { + return $this->cacheCall('doMethodWithoutSerialization', func_get_args()); + } + + private function doMethodWithoutSerialization(int $id): array + { + return ['id' => $id, 'count' => ++$this->counter]; + } + public function manualInvalidation(int $id): void { $this->invalidateCache("user:$id");