From cb332b408b026dddf478f641e478842ad4aa990b Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 10 Oct 2025 11:42:29 +0200 Subject: [PATCH 01/32] still a lot of work to be done on PSR-6 part --- tests/Unit/RedisCacheTest.php | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/Unit/RedisCacheTest.php b/tests/Unit/RedisCacheTest.php index 5d1e3d5..339bc20 100644 --- a/tests/Unit/RedisCacheTest.php +++ b/tests/Unit/RedisCacheTest.php @@ -12,10 +12,36 @@ class RedisCacheTest extends SimpleCacheTest { /** - * @todo remove dummy + * @todo implement this */ - public function testSomething() + public function testFetchFromPool() { $this->assertTrue(true); } + + public function testHasInPool() + { + $this->assertTrue(true); + } + + public function testGetAllCacheStoreAsString() + { + $this->assertTrue(true); + } + +/* + + public function getAllCacheStoreAsArray() + public function getPoolKeys(string $pool): array + public function getInfo(): array + public function getTtl(string $key): int + public function printCacheKeys() + public function printCacheHash(string $pool, $silent = false): string + public function setHsetPoolExpiration(string $pool, int $expirationTime = self::HOURS_EXPIRATION_TIME): bool + public function unserializeFromPool(string $key, string $pool) + public function serializeToPool(string $key, mixed $data, string $pool): bool + public function deleteFromPool(array $keys, string $pool): bool + public function storeToPool(array $values, string $pool): bool + public function fetchAllFromPool(string $pool): array +*/ } From c2f2fc41705e4595320569f5881d56a1b14b9e93 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 15 Oct 2025 11:07:15 +0200 Subject: [PATCH 02/32] getPoolName rework --- src/CacheEntryPool/CacheEntryPool.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index b368b2f..d1d9a9f 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -49,7 +49,7 @@ class CacheEntryPool implements CacheItemPoolInterface */ private array $deferredItems = []; - protected const HASH_DB_PREFIX = 'DEFAULT_Cache_Pool'; + protected const HASH_DB_PREFIX = 'Cache_Pool'; /** * @todo use Psr\SimpleCache\CacheInterface ? @@ -238,7 +238,6 @@ public function commit(): bool /** * - * @todo may be rework this a bit (DEFAULT everywhere lol) * * @param mixed $poolSuffix * @return string @@ -247,7 +246,7 @@ protected function getPoolName(string $poolSuffix): string { return strlen($poolSuffix) ? self::HASH_DB_PREFIX . "_{$poolSuffix}" : - self::HASH_DB_PREFIX + 'DEFAULT_' . self::HASH_DB_PREFIX ; } From a73144b14d9898d70ea7704272119d3c66b42650 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 16 Oct 2025 12:06:31 +0200 Subject: [PATCH 03/32] exceptions refactored a bit --- src/Exception/InvalidArgumentException.php | 10 +++++--- src/Exception/InvalidKeyException.php | 8 ++++-- src/Exception/InvalidKeysException.php | 8 ++++-- src/Exception/InvalidValuesException.php | 8 ++++-- tests/Integration/PoolIntegrationTest.php | 29 ++++++++++++++++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index a5c123c..417c6f6 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -4,14 +4,16 @@ namespace LLegaz\Cache\Exception; -use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; -use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; +use Psr\Cache\InvalidArgumentException as Psr6InvalidArgumentException; +use Psr\SimpleCache\InvalidArgumentException as Psr16InvalidArgumentException; /** * inspired from Symfony Cache (by Nicolas Grekas) * - * @todo rework all package exceptions ? + * PSR-6 and PSR-16 InvalidArgumentException + * + * @todo rework / clean all package(s) exceptions ? */ -class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInterface, SimpleCacheInterface +class InvalidArgumentException extends \InvalidArgumentException implements Psr6InvalidArgumentException, Psr16InvalidArgumentException { } diff --git a/src/Exception/InvalidKeyException.php b/src/Exception/InvalidKeyException.php index 036a543..162bd22 100644 --- a/src/Exception/InvalidKeyException.php +++ b/src/Exception/InvalidKeyException.php @@ -4,9 +4,13 @@ namespace LLegaz\Cache\Exception; -use Psr\SimpleCache\InvalidArgumentException; +use LLegaz\Cache\Exception\InvalidArgumentException; -class InvalidKeyException extends \Exception implements InvalidArgumentException +/** + * + * PSR-6 and PSR-16 InvalidArgumentException + */ +class InvalidKeyException extends InvalidArgumentException { public function __construct(string $message = 'RedisCache says "Can\'t do shit with this Key"' . PHP_EOL, int $code = 400, \Throwable $previous = null) { diff --git a/src/Exception/InvalidKeysException.php b/src/Exception/InvalidKeysException.php index 4e2327d..179e66c 100644 --- a/src/Exception/InvalidKeysException.php +++ b/src/Exception/InvalidKeysException.php @@ -4,9 +4,13 @@ namespace LLegaz\Cache\Exception; -use Psr\SimpleCache\InvalidArgumentException; +use LLegaz\Cache\Exception\InvalidArgumentException; -class InvalidKeysException extends \Exception implements InvalidArgumentException +/** + * + * PSR-6 and PSR-16 InvalidArgumentException + */ +class InvalidKeysException extends InvalidArgumentException { public function __construct(string $message = 'RedisCache says "Can\'t do shit with those keys"' . PHP_EOL, int $code = 400, \Throwable $previous = null) { diff --git a/src/Exception/InvalidValuesException.php b/src/Exception/InvalidValuesException.php index e2ed32d..6f708c6 100644 --- a/src/Exception/InvalidValuesException.php +++ b/src/Exception/InvalidValuesException.php @@ -4,9 +4,13 @@ namespace LLegaz\Cache\Exception; -use Psr\SimpleCache\InvalidArgumentException; +use LLegaz\Cache\Exception\InvalidArgumentException; -class InvalidValuesException extends \Exception implements InvalidArgumentException +/** + * + * PSR-6 and PSR-16 InvalidArgumentException + */ +class InvalidValuesException extends InvalidArgumentException { public function __construct(string $message = 'RedisCache says "Can\'t do shit with those values"' . PHP_EOL, int $code = 400, \Throwable $previous = null) { diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index b8c9964..75ac249 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -42,6 +42,35 @@ protected function setUp(): void parent::setUp(); } + public static function invalidKeys() + { + $bigKey = ''; + for ($i = 102500; $i > 0; $i--) { + $bigKey .= 'a'; + } + + return array_merge( + self::invalidArrayKeys(), + [ + [''], + [$bigKey] + ] + ); + } + + public static function invalidArrayKeys() + { + return [ + [''], + [''], + [''], + [''], + [''], + [''], + [''], + ]; + } + public function createCachePool(): CacheItemPoolInterface { $cache = new RedisEnhancedCache(); From b434f47868137e08d7e9e5045a8e235760707b91 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 16 Oct 2025 12:15:58 +0200 Subject: [PATCH 04/32] cs and fix getItems --- src/Exception/InvalidArgumentException.php | 2 +- src/Exception/InvalidKeyException.php | 2 - src/Exception/InvalidKeysException.php | 2 - src/Exception/InvalidValuesException.php | 2 - src/RedisEnhancedCache.php | 63 ++++++++++------------ tests/Unit/RedisCacheTest.php | 28 +++++----- 6 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 417c6f6..d3ece6f 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -11,7 +11,7 @@ * inspired from Symfony Cache (by Nicolas Grekas) * * PSR-6 and PSR-16 InvalidArgumentException - * + * * @todo rework / clean all package(s) exceptions ? */ class InvalidArgumentException extends \InvalidArgumentException implements Psr6InvalidArgumentException, Psr16InvalidArgumentException diff --git a/src/Exception/InvalidKeyException.php b/src/Exception/InvalidKeyException.php index 162bd22..12f1645 100644 --- a/src/Exception/InvalidKeyException.php +++ b/src/Exception/InvalidKeyException.php @@ -4,8 +4,6 @@ namespace LLegaz\Cache\Exception; -use LLegaz\Cache\Exception\InvalidArgumentException; - /** * * PSR-6 and PSR-16 InvalidArgumentException diff --git a/src/Exception/InvalidKeysException.php b/src/Exception/InvalidKeysException.php index 179e66c..4356210 100644 --- a/src/Exception/InvalidKeysException.php +++ b/src/Exception/InvalidKeysException.php @@ -4,8 +4,6 @@ namespace LLegaz\Cache\Exception; -use LLegaz\Cache\Exception\InvalidArgumentException; - /** * * PSR-6 and PSR-16 InvalidArgumentException diff --git a/src/Exception/InvalidValuesException.php b/src/Exception/InvalidValuesException.php index 6f708c6..636d5c4 100644 --- a/src/Exception/InvalidValuesException.php +++ b/src/Exception/InvalidValuesException.php @@ -4,8 +4,6 @@ namespace LLegaz\Cache\Exception; -use LLegaz\Cache\Exception\InvalidArgumentException; - /** * * PSR-6 and PSR-16 InvalidArgumentException diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index c897a91..27bdfe8 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -23,8 +23,8 @@ class RedisEnhancedCache extends RedisCache * It is to be noted that we use different terminology here from Redis project in the case of a HASH. * for us : pool = key and key = field * but it is only semantic... - * - * + * + * * @todo rework this * * @hint Redis return mostly strings with hget or hmget, maybe we should use serialize to preserve type @@ -43,42 +43,37 @@ class RedisEnhancedCache extends RedisCache public function fetchFromPool(mixed $key, string $pool): mixed { + switch (gettype($key)) { + case 'integer': + case 'string': + $this->checkKeyValidity($key); + $this->begin(); + if ($this->getRedis()->hexists($pool, $key)) { - try { - switch (gettype($key)) { - case 'integer': - case 'string': - $this->checkKeyValidity($key); - $this->begin(); - if ($this->getRedis()->hexists($pool, $key)) { - - return $this->getRedis()->hget($pool, $key); - } + return $this->getRedis()->hget($pool, $key); + } - break; - case 'array': - $this->checkKeysValidity($key); - $this->begin(); - $data = array_combine( - array_values($key), - array_values($this->getRedis()->hmget($pool, $key)) - ); - array_walk($data, function (&$value, $key) use ($pool) { - if (!$this->getRedis()->hexists($pool, $key)) { - $value = self::DOES_NOT_EXIST; - } - }); - if (count($data)) { - - return $data; + break; + case 'array': + $this->checkKeysValidity($key); + $this->begin(); + $data = array_combine( + array_values($key), + array_values($this->getRedis()->hmget($pool, $key)) + ); + array_walk($data, function (&$value, $key) use ($pool) { + if (!$this->getRedis()->hexists($pool, $key)) { + $value = self::DOES_NOT_EXIST; } + }); + if (count($data)) { - break; - default: - throw new InvalidKeyException('Invalid Parameter'); - } - } catch (\Throwable $t) { - $this->formatException($t); + return $data; + } + + break; + default: + throw new InvalidKeyException('Invalid Parameter'); } return self::DOES_NOT_EXIST; diff --git a/tests/Unit/RedisCacheTest.php b/tests/Unit/RedisCacheTest.php index 339bc20..0893a64 100644 --- a/tests/Unit/RedisCacheTest.php +++ b/tests/Unit/RedisCacheTest.php @@ -29,19 +29,19 @@ public function testGetAllCacheStoreAsString() $this->assertTrue(true); } -/* + /* - public function getAllCacheStoreAsArray() - public function getPoolKeys(string $pool): array - public function getInfo(): array - public function getTtl(string $key): int - public function printCacheKeys() - public function printCacheHash(string $pool, $silent = false): string - public function setHsetPoolExpiration(string $pool, int $expirationTime = self::HOURS_EXPIRATION_TIME): bool - public function unserializeFromPool(string $key, string $pool) - public function serializeToPool(string $key, mixed $data, string $pool): bool - public function deleteFromPool(array $keys, string $pool): bool - public function storeToPool(array $values, string $pool): bool - public function fetchAllFromPool(string $pool): array -*/ + public function getAllCacheStoreAsArray() + public function getPoolKeys(string $pool): array + public function getInfo(): array + public function getTtl(string $key): int + public function printCacheKeys() + public function printCacheHash(string $pool, $silent = false): string + public function setHsetPoolExpiration(string $pool, int $expirationTime = self::HOURS_EXPIRATION_TIME): bool + public function unserializeFromPool(string $key, string $pool) + public function serializeToPool(string $key, mixed $data, string $pool): bool + public function deleteFromPool(array $keys, string $pool): bool + public function storeToPool(array $values, string $pool): bool + public function fetchAllFromPool(string $pool): array + */ } From df9bdb5d1bbde67cfc0f676ee30ed9696d885126 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 16 Oct 2025 12:21:58 +0200 Subject: [PATCH 05/32] get has and delete OK (for now) --- composer.json | 2 +- tests/Integration/CacheIntegrationTest.php | 5 +++++ tests/Integration/PoolIntegrationTest.php | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1b36584..9410606 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testGetItems", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItem", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index 481c6fb..110af23 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -58,6 +58,11 @@ public static function invalidKeys() ); } + /** + * meh + * + * @return array + */ public static function invalidArrayKeys() { return [ diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 75ac249..505795e 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -58,6 +58,11 @@ public static function invalidKeys() ); } + /** + * meh + * + * @return array + */ public static function invalidArrayKeys() { return [ @@ -71,6 +76,14 @@ public static function invalidArrayKeys() ]; } + /** + * @todo TypeError suite tests needed see CacheIntegrationTest class + */ + + /** + * + * @return CacheItemPoolInterface + */ public function createCachePool(): CacheItemPoolInterface { $cache = new RedisEnhancedCache(); From 550f077f9513f56ef132c92d6643bb59ac1a019a Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 16 Oct 2025 13:19:05 +0200 Subject: [PATCH 06/32] has function rly OK now, but could still be enhanced though adapter package --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 4 +++- src/RedisEnhancedCache.php | 24 ++++++++++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 9410606..dda7c12 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItem", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testHasItem", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index d1d9a9f..118973b 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -71,6 +71,8 @@ public function __construct(RedisEnhancedCache $cache, ?string $pool = null) */ public function clear(): bool { + $e = new \Exception(); + dump("pool cleared by : ", $e->getTrace()[1]["function"]); try { $this->cache->set($this->poolName, null); $this->cache->delete($this->poolName); @@ -233,7 +235,7 @@ public function commit(): bool $deferred[$item->getKey()] = $item->get(); } - return $this->cache->storeToPool($deferred); + return $this->cache->storeToPool($deferred, $this->poolName); } /** diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 27bdfe8..40a0841 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -98,7 +98,15 @@ public function hasInPool(string $key, string $pool): bool $redisResponse = null; $this->formatException($t); } finally { - return ($redisResponse === 1) ? true : false; + /** + * php-redis hexists returns true while predis returns 1 + * + * @see adapter classes in adapter (or gateway, or facade) package, + * namely PredisClient and RedisClient + * + * @todo in order to simplify and unify those returns mechanisms properly + */ + return ($redisResponse === true || $redisResponse === 1) ? true : false; } } @@ -136,6 +144,7 @@ public function storeToPool(array $values, string $pool): bool /** * @todo enhance keys / values treatment (see / homogenize with RedisCache::setMultiple and RedisCache::checkKeysValidity) * @todo need better handling on serialization and its reverse method in fetches. + * @todo check keys arguments are valid */ array_walk($values, function (&$value) { if (is_array($value) || is_object($value)) { @@ -148,7 +157,18 @@ public function storeToPool(array $values, string $pool): bool */ dump('store to pool :', $values); - return $this->getRedis()->hmset($pool, $values) == 'OK'; + $cnt = count($values); + if ($cnt>1) { + return $this->getRedis()->hmset($pool, $values) == 'OK'; + } elseif ($cnt===1) { + $key = array_keys($values)[0]; + $value = isset($key) ? $values[$key] : (isset($values[0]) ? $values[0] : null); + if (isset($value)) { + return $this->getRedis()->hset($pool, $key, $value) == 'OK'; + } + + } + } /** From e6ab302f57546e3cf9cc495aa3e1e25356ce70b5 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 17 Oct 2025 10:54:30 +0200 Subject: [PATCH 07/32] get functions OK --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 3 +- src/RedisEnhancedCache.php | 44 +++++++++++----------- tests/Integration/CacheIntegrationTest.php | 2 +- tests/Integration/PoolIntegrationTest.php | 4 +- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index dda7c12..9410606 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testHasItem", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItem", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 118973b..5d1d498 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -72,7 +72,8 @@ public function __construct(RedisEnhancedCache $cache, ?string $pool = null) public function clear(): bool { $e = new \Exception(); - dump("pool cleared by : ", $e->getTrace()[1]["function"]); + dump('pool cleared by : ', $e->getTrace()[1]['function']); + try { $this->cache->set($this->poolName, null); $this->cache->delete($this->poolName); diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 40a0841..0aa2e06 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -55,20 +55,22 @@ public function fetchFromPool(mixed $key, string $pool): mixed break; case 'array': - $this->checkKeysValidity($key); - $this->begin(); - $data = array_combine( - array_values($key), - array_values($this->getRedis()->hmget($pool, $key)) - ); - array_walk($data, function (&$value, $key) use ($pool) { - if (!$this->getRedis()->hexists($pool, $key)) { - $value = self::DOES_NOT_EXIST; + if (count($key)) { + $this->checkKeysValidity($key); + $this->begin(); + $data = array_combine( + array_values($key), + array_values($this->getRedis()->hmget($pool, $key)) + ); + array_walk($data, function (&$value, $key) use ($pool) { + if (!$this->getRedis()->hexists($pool, $key)) { + $value = self::DOES_NOT_EXIST; + } + }); + if (count($data)) { + + return $data; } - }); - if (count($data)) { - - return $data; } break; @@ -100,10 +102,10 @@ public function hasInPool(string $key, string $pool): bool } finally { /** * php-redis hexists returns true while predis returns 1 - * + * * @see adapter classes in adapter (or gateway, or facade) package, * namely PredisClient and RedisClient - * + * * @todo in order to simplify and unify those returns mechanisms properly */ return ($redisResponse === true || $redisResponse === 1) ? true : false; @@ -153,22 +155,22 @@ public function storeToPool(array $values, string $pool): bool }); /** - * @todo rework exception handling and returns + * @todo rework exception handling and returns (yup rework that below) */ dump('store to pool :', $values); $cnt = count($values); - if ($cnt>1) { + if ($cnt > 1) { return $this->getRedis()->hmset($pool, $values) == 'OK'; - } elseif ($cnt===1) { + } elseif ($cnt === 1) { $key = array_keys($values)[0]; $value = isset($key) ? $values[$key] : (isset($values[0]) ? $values[0] : null); if (isset($value)) { return $this->getRedis()->hset($pool, $key, $value) == 'OK'; } - - } - + + } + } /** diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index 110af23..2963ff9 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -60,7 +60,7 @@ public static function invalidKeys() /** * meh - * + * * @return array */ public static function invalidArrayKeys() diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 505795e..708371a 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -60,7 +60,7 @@ public static function invalidKeys() /** * meh - * + * * @return array */ public static function invalidArrayKeys() @@ -81,7 +81,7 @@ public static function invalidArrayKeys() */ /** - * + * * @return CacheItemPoolInterface */ public function createCachePool(): CacheItemPoolInterface From 7052b422b4d08e21ac8c1c596e88cbabada615a3 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 17 Oct 2025 12:02:55 +0200 Subject: [PATCH 08/32] fix deleteFromPool, refacto, todo= isValueSet + use serialize with RedisEnhancedCache --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 13 ++----------- src/RedisCache.php | 14 ++++++++++++-- src/RedisEnhancedCache.php | 15 +++++++++++---- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index 9410606..9180f20 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItem", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItems", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 5d1d498..988b1f7 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -121,7 +121,7 @@ public function getItem(string $key): CacheItemInterface //dump('getItem', $key, $value); /** @todo handle hit, ttl */ $item = new CacheEntry($key); - if ($this->exist($value)) { + if ($this->cache->exist($value)) { $item->set($value); $item->hit(); } @@ -154,7 +154,7 @@ public function getItems(array $keys = []): iterable foreach ($keys as $key) { /** @todo handle hit, ttl */ $item = new CacheEntry($key); - if (isset($values[$key]) && $this->exist($values[$key])) { + if (isset($values[$key]) && $this->cache->exist($values[$key])) { $item->set($values[$key]); $item->hit(); } @@ -252,13 +252,4 @@ protected function getPoolName(string $poolSuffix): string 'DEFAULT_' . self::HASH_DB_PREFIX ; } - - private function exist(mixed $value): bool - { - if (is_string($value) && strlen($value) && $value === RedisEnhancedCache::DOES_NOT_EXIST) { - return false; - } - - return true; - } } diff --git a/src/RedisCache.php b/src/RedisCache.php index d6793d7..e7392f1 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -203,7 +203,7 @@ public function get(string $key, mixed $default = null): mixed * @param mixed $default * @return mixed */ - private function get2(mixed $key, mixed $default = null): mixed + private function getWithNonStrKey(mixed $key, mixed $default = null): mixed { return $this->get($this->checkKeyValidity($key), $default); } @@ -244,7 +244,7 @@ public function getMultiple(iterable $keys, mixed $default = null): iterable } } catch (\Throwable $t) { if (!count($values)) { - array_fill(0, count($keys), $default); + $values = array_fill(0, count($keys), $default); } $this->formatException($t); } finally { @@ -480,6 +480,16 @@ private function keyToString(mixed $key): string return $key; } + /** + * value is either a serialized string or a nil value returned from Redis server + * (predis / php-redis implementations either return null, false or "nil" directly + * + * @param mixed $value + * @return bool + */ + protected function isValueSet(mixed $value): bool { + + } /** * begin redis communication * diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 0aa2e06..808fac9 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -149,9 +149,8 @@ public function storeToPool(array $values, string $pool): bool * @todo check keys arguments are valid */ array_walk($values, function (&$value) { - if (is_array($value) || is_object($value)) { + // we serialize all data (to differentiate with false returned by hget) $value = serialize($value); - } }); /** @@ -182,7 +181,6 @@ public function storeToPool(array $values, string $pool): bool */ public function deleteFromPool(array $keys, string $pool): bool { - $cnt = count($keys); $payload = ''; $keys = $this->checkKeysValidity($keys); array_walk($keys, function (&$key, $i) use (&$payload) { @@ -202,7 +200,7 @@ public function deleteFromPool(array $keys, string $pool): bool $this->formatException($e); } - return $redisResponse === $cnt; + return $redisResponse >= 0; } /** @@ -460,4 +458,13 @@ private function getAllkeys(): array { return $this->getRedis()->keys('*'); } + + public function exist(mixed $value): bool + { + if (is_string($value) && strlen($value) && $value === RedisEnhancedCache::DOES_NOT_EXIST) { + return false; + } + + return true; + } } From 764e7994797a695e29a16638ca0dfe2d03224b4b Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 17 Oct 2025 13:54:33 +0200 Subject: [PATCH 09/32] ofc this isn't good at it is, still in WIP --- src/RedisCache.php | 7 ++++--- src/RedisEnhancedCache.php | 9 ++++++++- tests/Integration/CacheIntegrationTest.php | 23 ++++++++++++++-------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index e7392f1..4a69a5b 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -483,12 +483,13 @@ private function keyToString(mixed $key): string /** * value is either a serialized string or a nil value returned from Redis server * (predis / php-redis implementations either return null, false or "nil" directly - * + * * @param mixed $value * @return bool */ - protected function isValueSet(mixed $value): bool { - + protected function isValueSet(mixed $value): bool + { + } /** * begin redis communication diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 808fac9..ebd2395 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -42,6 +42,10 @@ class RedisEnhancedCache extends RedisCache */ public function fetchFromPool(mixed $key, string $pool): mixed { + + /*** + * finish to implment serializes properly you mofo + */ switch (gettype($key)) { case 'integer': @@ -148,9 +152,12 @@ public function storeToPool(array $values, string $pool): bool * @todo need better handling on serialization and its reverse method in fetches. * @todo check keys arguments are valid */ + /*** + * finish to implment serializes properly you mofo + */ array_walk($values, function (&$value) { // we serialize all data (to differentiate with false returned by hget) - $value = serialize($value); + $value = serialize($value); }); /** diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index 2963ff9..d0df599 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -20,6 +20,16 @@ */ class CacheIntegrationTest extends SimpleCacheTest { + private static string $bigKey = ''; + + public static function setUpBeforeClass(): void + { + for ($i = 102500; $i > 0; $i--) { + self::$bigKey .= 'a'; + } + parent::setUpBeforeClass(); + } + /** * @before */ @@ -44,16 +54,11 @@ protected function setUp(): void public static function invalidKeys() { - $bigKey = ''; - for ($i = 102500; $i > 0; $i--) { - $bigKey .= 'a'; - } - return array_merge( self::invalidArrayKeys(), [ [''], - [$bigKey] + [self::$bigKey] ] ); } @@ -67,11 +72,13 @@ public static function invalidArrayKeys() { return [ [''], + [self::$bigKey], [''], + [self::$bigKey], [''], + [self::$bigKey], [''], - [''], - [''], + [self::$bigKey], [''], ]; } From 1098c08c8541bf228267ca7ed018de3f83d40efc Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 18 Oct 2025 12:45:39 +0200 Subject: [PATCH 10/32] removing of old unused funcs as it will be merge into storing and fetching from pools funcs --- src/RedisEnhancedCache.php | 68 ++++++-------------------------------- 1 file changed, 10 insertions(+), 58 deletions(-) diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index ebd2395..a9ec0a7 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -11,6 +11,12 @@ * This is built on top of PSR-16 implementation to complete it for PSR-6 CacheEntries Pools. * My implementation is based on Redis Hashes implying some technical limitations. * + * + * + * Here we use the Hash implementation from redis. Expiration Time is set with the setHsetPoolExpiration method + * on the entire Hash Set HASH_DB_PREFIX. $suffix (private HSET Pool in Redis, specified with $suffix + * with those methods you can store and retrieve specific data linked together in a separate data set) + * * * @package RedisCache * @author Laurent LEGAZ @@ -44,8 +50,11 @@ public function fetchFromPool(mixed $key, string $pool): mixed { /*** - * finish to implment serializes properly you mofo + * finish to implment unserializes properly you mofo + * @todo remove hexist + * use : */ + //return unserialize($this->getRedis()->hget($pool, $key)); switch (gettype($key)) { case 'integer': @@ -210,63 +219,6 @@ public function deleteFromPool(array $keys, string $pool): bool return $redisResponse >= 0; } - /** - * Here we use the Hash implementation from redis. Expiration Time is set with the setHsetPoolExpiration method - * on the entire Hash Set HASH_DB_PREFIX. $suffix (private HSET Pool in Redis, specified with $suffix - * with those methods you can store and retrieve specific data linked together in a separate data set) - * - * @todo rework this - * - * @param string $key - * @param mixed $data string, object and array are preferred - * @param string $pool the pool's name - * @return bool - * @throws ConnectionLostException - */ - public function serializeToPool(string $key, mixed $data, string $pool): bool - { - if (!$this->isConnected()) { - $this->throwCLEx(); - } - - $serializeData = serialize($data); - - if (!empty($serializeData)) { - $redisResponse = $this->getRedis()->hset($pool, $key, $serializeData); - - return ($redisResponse >= 0) ? true : false; - } - - return false; - } - - /** - * Here we use the Hash implementation from redis. Expiration Time is set with the setHsetPoolExpiration method - * on the entire Hash Set HASH_DB_PREFIX. $suffix (private HSET Pool in Redis, specified with $suffix - * with those methods you can store and retrieve specific data linked together in a separate data set) - * - * @todo rework this - * - * @param string $key - * @param string $pool the pool's name - * @return mixed

The converted value is returned, and can be a boolean, - * integer, float, string, - * array or object. - *

- *

- * In case the passed string is not unserializable, FALSE is returned and - * E_NOTICE is issued. - * @throws ConnectionLostException - */ - public function unserializeFromPool(string $key, string $pool) - { - if (!$this->isConnected()) { - $this->throwCLEx(); - } - - return unserialize($this->getRedis()->hget($pool, $key)); - } - /** * @todo rework this * From 41ff6712878a416cbbafefa4d2488f1d73fa4077 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 18 Oct 2025 12:55:15 +0200 Subject: [PATCH 11/32] no real diffs --- src/RedisEnhancedCache.php | 109 ++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index a9ec0a7..b3836b8 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -7,7 +7,9 @@ use LLegaz\Cache\Exception\InvalidKeyException; /** - * Class RedisEnhancedCache + * ------------------------------------------------------------------------------------------------------------ + * | Class RedisEnhancedCache | + * ------------------------------------------------------------------------------------------------------------ * This is built on top of PSR-16 implementation to complete it for PSR-6 CacheEntries Pools. * My implementation is based on Redis Hashes implying some technical limitations. * @@ -17,7 +19,13 @@ * on the entire Hash Set HASH_DB_PREFIX. $suffix (private HSET Pool in Redis, specified with $suffix * with those methods you can store and retrieve specific data linked together in a separate data set) * - * + * + * + * It is to be noted that we use different terminology here from Redis project in the case of a HASH. + * for us : pool = key and key = field, but it is only semantic differences... + * ------------------------------------------------------------------------------------------------------------ + * + * * @package RedisCache * @author Laurent LEGAZ */ @@ -25,10 +33,55 @@ class RedisEnhancedCache extends RedisCache { public const DOES_NOT_EXIST = '%=%=% item does not exist %=%=%'; + + /** + * @todo rework this + * + * + * @param array $values A flat array of key => value pairs to store in GIVEN POOL name + * @param string $pool the pool name + * @return bool True on success + * @throws LLegaz\Redis\Exception\ConnectionLostException + * @throws LLegaz\Redis\Exception\LocalIntegrityException + */ + public function storeToPool(array $values, string $pool): bool + { + $this->begin(); + + /** + * @todo enhance keys / values treatment (see / homogenize with RedisCache::setMultiple and RedisCache::checkKeysValidity) + * @todo need better handling on serialization and its reverse method in fetches. + * @todo check keys arguments are valid + */ + /*** + * finish to implment serializes properly you mofo + */ + array_walk($values, function (&$value) { + // we serialize all data (to differentiate with false returned by hget) + $value = serialize($value); + }); + + /** + * @todo rework exception handling and returns (yup rework that below) + */ + dump('store to pool :', $values); + + $cnt = count($values); + if ($cnt > 1) { + return $this->getRedis()->hmset($pool, $values) == 'OK'; + } elseif ($cnt === 1) { + $key = array_keys($values)[0]; + $value = isset($key) ? $values[$key] : (isset($values[0]) ? $values[0] : null); + if (isset($value)) { + return $this->getRedis()->hset($pool, $key, $value) == 'OK'; + } + + } + + } + /** - * It is to be noted that we use different terminology here from Redis project in the case of a HASH. - * for us : pool = key and key = field - * but it is only semantic... + * * * * @todo rework this @@ -142,52 +195,6 @@ public function fetchAllFromPool(string $pool): array return $this->getRedis()->hgetall($pool); } - /** - * @todo rework this - * - * - * @param array $values A flat array of key => value pairs to store in GIVEN POOL name - * @param string $pool the pool name - * @return bool True on success - * @throws LLegaz\Redis\Exception\ConnectionLostException - * @throws LLegaz\Redis\Exception\LocalIntegrityException - */ - public function storeToPool(array $values, string $pool): bool - { - $this->begin(); - - /** - * @todo enhance keys / values treatment (see / homogenize with RedisCache::setMultiple and RedisCache::checkKeysValidity) - * @todo need better handling on serialization and its reverse method in fetches. - * @todo check keys arguments are valid - */ - /*** - * finish to implment serializes properly you mofo - */ - array_walk($values, function (&$value) { - // we serialize all data (to differentiate with false returned by hget) - $value = serialize($value); - }); - - /** - * @todo rework exception handling and returns (yup rework that below) - */ - dump('store to pool :', $values); - - $cnt = count($values); - if ($cnt > 1) { - return $this->getRedis()->hmset($pool, $values) == 'OK'; - } elseif ($cnt === 1) { - $key = array_keys($values)[0]; - $value = isset($key) ? $values[$key] : (isset($values[0]) ? $values[0] : null); - if (isset($value)) { - return $this->getRedis()->hset($pool, $key, $value) == 'OK'; - } - - } - - } - /** * * @param array $keys From 21c055009d4707c56a29618141257e9fba28afeb Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 18 Oct 2025 13:03:39 +0200 Subject: [PATCH 12/32] store to pool ~ v1 --- src/RedisEnhancedCache.php | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index b3836b8..b092683 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -13,19 +13,19 @@ * This is built on top of PSR-16 implementation to complete it for PSR-6 CacheEntries Pools. * My implementation is based on Redis Hashes implying some technical limitations. * - * - * + * + * * Here we use the Hash implementation from redis. Expiration Time is set with the setHsetPoolExpiration method * on the entire Hash Set HASH_DB_PREFIX. $suffix (private HSET Pool in Redis, specified with $suffix * with those methods you can store and retrieve specific data linked together in a separate data set) - * - * - * + * + * + * * It is to be noted that we use different terminology here from Redis project in the case of a HASH. * for us : pool = key and key = field, but it is only semantic differences... * ------------------------------------------------------------------------------------------------------------ - * - * + * + * * @package RedisCache * @author Laurent LEGAZ */ @@ -51,21 +51,20 @@ public function storeToPool(array $values, string $pool): bool /** * @todo enhance keys / values treatment (see / homogenize with RedisCache::setMultiple and RedisCache::checkKeysValidity) * @todo need better handling on serialization and its reverse method in fetches. - * @todo check keys arguments are valid */ - /*** - * finish to implment serializes properly you mofo + /** + * check keys arguments are valid, and values are all stored as strings */ - array_walk($values, function (&$value) { - // we serialize all data (to differentiate with false returned by hget) - $value = serialize($value); + array_walk($values, function (&$value, &$key) use ($this) { + + $this->checkKeyValuePair($key, $value); }); - /** - * @todo rework exception handling and returns (yup rework that below) - */ dump('store to pool :', $values); + /** + * @todo rework exception handling and returns + */ $cnt = count($values); if ($cnt > 1) { return $this->getRedis()->hmset($pool, $values) == 'OK'; @@ -75,13 +74,11 @@ public function storeToPool(array $values, string $pool): bool if (isset($value)) { return $this->getRedis()->hset($pool, $key, $value) == 'OK'; } - } - } /** - * + * * * * @todo rework this @@ -101,7 +98,7 @@ public function storeToPool(array $values, string $pool): bool */ public function fetchFromPool(mixed $key, string $pool): mixed { - + /*** * finish to implment unserializes properly you mofo * @todo remove hexist From 2df1df273881b548ac4c48527d30b995d0f6ad2f Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 18 Oct 2025 13:47:13 +0200 Subject: [PATCH 13/32] fetch from pool ~ v1 --- src/RedisCache.php | 34 +++++++++++++++++++--------------- src/RedisEnhancedCache.php | 12 ++++++++---- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index 4a69a5b..d8c7a2d 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -178,10 +178,7 @@ public function get(string $key, mixed $default = null): mixed if (!is_string($value)) { $toReturn = $default; } else { - $toReturn = @unserialize($value); - if ($toReturn === false && $value !== 'b:0;') { - $toReturn = $value; - } + $this->setCorrectValue($value); } } catch (\Throwable $t) { $toReturn = $default; @@ -236,10 +233,7 @@ public function getMultiple(iterable $keys, mixed $default = null): iterable if (!is_string($value)) { $value = $default; } else { - $tmp = @unserialize($value); - if ($tmp !== false || $value === 'b:0;') { - $value = $tmp; - } + $this->setCorrectValue($value); } } } catch (\Throwable $t) { @@ -418,7 +412,7 @@ public function setMultiple(iterable $values, null|int|\DateInterval $ttl = self * @return void * @throws InvalidArgumentException */ - protected function checkKeyValuePair(string $key, mixed &$value): void + protected function checkKeyValuePair(string &$key, mixed &$value): void { $this->checkKeyValidity($key); if (!is_string($value)) { @@ -433,7 +427,7 @@ protected function checkKeyValuePair(string $key, mixed &$value): void * @return void * @throws InvalidArgumentException */ - protected function checkKeyValidity(mixed $key): void + protected function checkKeyValidity(mixed &$key): void { if (!is_string($key)) { $key = $this->keyToString($key); @@ -481,16 +475,26 @@ private function keyToString(mixed $key): string } /** - * value is either a serialized string or a nil value returned from Redis server - * (predis / php-redis implementations either return null, false or "nil" directly + * value is either a serialized thing as a string or directly a string + * or false if not set correctly(empty or null string) but this case + * SHOULD be handled beforehand * * @param mixed $value * @return bool */ - protected function isValueSet(mixed $value): bool - { - + protected function setCorrectValue(string &$value): void { + try { + $tmp = unserialize($value); + } catch (\Throwable $t) { + $tmp = false; + } finally { + if ($tmp === false && $value !== 'b:0;') { + return; // do nothing $value was a simple string + } + $value = $tmp; + } } + /** * begin redis communication * diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index b092683..124a47a 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -111,9 +111,11 @@ public function fetchFromPool(mixed $key, string $pool): mixed case 'string': $this->checkKeyValidity($key); $this->begin(); - if ($this->getRedis()->hexists($pool, $key)) { + $value = $this->getRedis()->hget($pool, $key); + if (is_string($value)) { + $value = $this->setCorrectValue($value); - return $this->getRedis()->hget($pool, $key); + return $value; } break; @@ -125,8 +127,10 @@ public function fetchFromPool(mixed $key, string $pool): mixed array_values($key), array_values($this->getRedis()->hmget($pool, $key)) ); - array_walk($data, function (&$value, $key) use ($pool) { - if (!$this->getRedis()->hexists($pool, $key)) { + array_walk($data, function (&$value) use ($this) { + if (is_string($value)) { + $value = $this->setCorrectValue($value); + } else { $value = self::DOES_NOT_EXIST; } }); From 076845ab05f9c4f822dad94149ab6c794af4ce78 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 18 Oct 2025 19:49:37 +0200 Subject: [PATCH 14/32] important refacto, PSR16 ok but will have to take back all PSR6 part, get has, delete, etc. --- composer.json | 2 +- src/RedisCache.php | 34 ++++++++++++++++++++++++++-------- src/RedisEnhancedCache.php | 25 ++++++++----------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index 9180f20..1a85941 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItems", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testGetItem", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/RedisCache.php b/src/RedisCache.php index d8c7a2d..677865d 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -48,6 +48,8 @@ class RedisCache extends RedisAdapter implements CacheInterface public const VERY_LONG_EXPIRATION_TIME = 7776000; // 90 days + public const DOES_NOT_EXIST = '%=%=% item does not exist %=%=%'; + public function __construct( string $host = RedisClientInterface::DEFAULTS['host'], int $port = RedisClientInterface::DEFAULTS['port'], @@ -178,7 +180,7 @@ public function get(string $key, mixed $default = null): mixed if (!is_string($value)) { $toReturn = $default; } else { - $this->setCorrectValue($value); + $toReturn = $this->setCorrectValue($value); } } catch (\Throwable $t) { $toReturn = $default; @@ -233,7 +235,10 @@ public function getMultiple(iterable $keys, mixed $default = null): iterable if (!is_string($value)) { $value = $default; } else { - $this->setCorrectValue($value); + $value = $this->setCorrectValue($value); + if (!$this->exist($value)) { + $value = $default; + } } } } catch (\Throwable $t) { @@ -482,19 +487,32 @@ private function keyToString(mixed $key): string * @param mixed $value * @return bool */ - protected function setCorrectValue(string &$value): void { + public function setCorrectValue(string &$value): mixed + { try { - $tmp = unserialize($value); + $tmp = @unserialize(trim($value)); } catch (\Throwable $t) { - $tmp = false; + $this->formatException($t); + + return self::DOES_NOT_EXIST; } finally { - if ($tmp === false && $value !== 'b:0;') { - return; // do nothing $value was a simple string + if ($tmp !== false || ($tmp === false && $value === 'b:0;')) { + $value = $tmp; } - $value = $tmp; + + return $value; } } + public function exist(mixed $value): bool + { + if (is_string($value) && strlen($value) && $value === self::DOES_NOT_EXIST) { + return false; + } + + return true; + } + /** * begin redis communication * diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 124a47a..a58b6b9 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -31,9 +31,6 @@ */ class RedisEnhancedCache extends RedisCache { - public const DOES_NOT_EXIST = '%=%=% item does not exist %=%=%'; - - /** * @todo rework this * @@ -55,9 +52,10 @@ public function storeToPool(array $values, string $pool): bool /** * check keys arguments are valid, and values are all stored as strings */ - array_walk($values, function (&$value, &$key) use ($this) { + $self = $this; + array_walk($values, function (&$value, &$key) use ($self) { - $this->checkKeyValuePair($key, $value); + $self->checkKeyValuePair($key, $value); }); dump('store to pool :', $values); @@ -127,11 +125,13 @@ public function fetchFromPool(mixed $key, string $pool): mixed array_values($key), array_values($this->getRedis()->hmget($pool, $key)) ); - array_walk($data, function (&$value) use ($this) { + $self = $this; + $label = self::DOES_NOT_EXIST; + array_walk($data, function (&$value) use ($self, $label) { if (is_string($value)) { - $value = $this->setCorrectValue($value); + $value = $self->setCorrectValue($value); } else { - $value = self::DOES_NOT_EXIST; + $value = $label; } }); if (count($data)) { @@ -425,13 +425,4 @@ private function getAllkeys(): array { return $this->getRedis()->keys('*'); } - - public function exist(mixed $value): bool - { - if (is_string($value) && strlen($value) && $value === RedisEnhancedCache::DOES_NOT_EXIST) { - return false; - } - - return true; - } } From 522a82121d0334ee681f403a0fb8c9ea91f2649e Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 18 Oct 2025 19:56:43 +0200 Subject: [PATCH 15/32] minor fixes --- src/RedisCache.php | 7 ++++--- src/RedisEnhancedCache.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index 677865d..4413b03 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -417,7 +417,7 @@ public function setMultiple(iterable $values, null|int|\DateInterval $ttl = self * @return void * @throws InvalidArgumentException */ - protected function checkKeyValuePair(string &$key, mixed &$value): void + protected function checkKeyValuePair(string $key, mixed &$value): void { $this->checkKeyValidity($key); if (!is_string($value)) { @@ -426,7 +426,9 @@ protected function checkKeyValuePair(string &$key, mixed &$value): void } /** - * @todo yes rework this and checkKeysValidity too to better integrate object and scalar + * passing by reference here is only needed when the key given isn't already a string + * + * @todo check special cases (or special implementation) when key isn't a string * * @param string $key * @return void @@ -456,7 +458,6 @@ protected function checkKeysValidity(iterable $keys): array $newKeys = []; foreach ($keys as $key) { - $key = $this->keyToString($key); $this->checkKeyValidity($key); $newKeys[] = $key; } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index a58b6b9..7171431 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -53,7 +53,7 @@ public function storeToPool(array $values, string $pool): bool * check keys arguments are valid, and values are all stored as strings */ $self = $this; - array_walk($values, function (&$value, &$key) use ($self) { + array_walk($values, function (&$value, $key) use ($self) { $self->checkKeyValuePair($key, $value); }); From 76f1654da3d172f4f7e29d401fa6e61a9ba9d30f Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 19 Oct 2025 14:39:51 +0200 Subject: [PATCH 16/32] delete from pool --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 7 +++++++ src/RedisCache.php | 2 +- src/RedisEnhancedCache.php | 16 ++++++---------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/composer.json b/composer.json index 1a85941..9410606 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testGetItem", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItem", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 988b1f7..1a30871 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -121,6 +121,7 @@ public function getItem(string $key): CacheItemInterface //dump('getItem', $key, $value); /** @todo handle hit, ttl */ $item = new CacheEntry($key); + dump('getItem: ' . $key . ' - ' . $value); if ($this->cache->exist($value)) { $item->set($value); $item->hit(); @@ -239,6 +240,12 @@ public function commit(): bool return $this->cache->storeToPool($deferred, $this->poolName); } + + public function printCachePool(): string + { + return $this->cache->printCacheHash($this->poolName); + } + /** * * diff --git a/src/RedisCache.php b/src/RedisCache.php index 4413b03..54e0bef 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -427,7 +427,7 @@ protected function checkKeyValuePair(string $key, mixed &$value): void /** * passing by reference here is only needed when the key given isn't already a string - * + * * @todo check special cases (or special implementation) when key isn't a string * * @param string $key diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 7171431..b877117 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -205,20 +205,12 @@ public function fetchAllFromPool(string $pool): array */ public function deleteFromPool(array $keys, string $pool): bool { - $payload = ''; $keys = $this->checkKeysValidity($keys); - array_walk($keys, function (&$key, $i) use (&$payload) { - if (is_string($key)) { - if ($i !== 0) { - $payload .= ' '; - } - $payload .= $key; - } - }); + $params = array_merge([$pool], $keys); $this->begin(); try { - $redisResponse = $this->getRedis()->hdel($pool, $payload); + $redisResponse = call_user_func_array([$this->getRedis(), 'hdel'], $params); } catch (Exception $e) { $redisResponse = false; $this->formatException($e); @@ -272,6 +264,8 @@ public function setHsetPoolExpiration(string $pool, int $expirationTime = self:: public function printCacheHash(string $pool, $silent = false): string { $data = $this->fetchAllFromPool($pool); + dump("from printCacheHash", $data); + /** * @todo - rework this @@ -286,6 +280,8 @@ public function printCacheHash(string $pool, $silent = false): string } } + dump("from printCacheHash to return - " . (strlen($toReturn) ? $toReturn : "\$toReturn is empty")); + return $toReturn; } From d3e839edcbb5626e8b9048573aa5dd9aa8f48c86 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 19 Oct 2025 17:26:56 +0200 Subject: [PATCH 17/32] episode 2 almost done ? todo: fix errors, implement expiration --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 2 +- src/RedisEnhancedCache.php | 16 +++++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 9410606..d61f762 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDeleteItem", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testDataTypeArray", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 1a30871..e43df92 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -121,7 +121,7 @@ public function getItem(string $key): CacheItemInterface //dump('getItem', $key, $value); /** @todo handle hit, ttl */ $item = new CacheEntry($key); - dump('getItem: ' . $key . ' - ' . $value); + dump('getItem: ' . $key . ' - ' . (is_string($value) ? $value : print_r($value))); if ($this->cache->exist($value)) { $item->set($value); $item->hit(); diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index b877117..622e2ca 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -58,7 +58,7 @@ public function storeToPool(array $values, string $pool): bool $self->checkKeyValuePair($key, $value); }); - dump('store to pool :', $values); + //dump('store to pool :', $values); /** * @todo rework exception handling and returns @@ -69,10 +69,20 @@ public function storeToPool(array $values, string $pool): bool } elseif ($cnt === 1) { $key = array_keys($values)[0]; $value = isset($key) ? $values[$key] : (isset($values[0]) ? $values[0] : null); - if (isset($value)) { - return $this->getRedis()->hset($pool, $key, $value) == 'OK'; + if (!$this->exist($value)) { + /*** + * @todo investiguate + */ + dd('I think it could be problematic'); + } + if ($value) { + dump('store to pool : '. $key . ' - '. $value); + //hset should returns the number of fields stored for a single key (always one here) + return $this->getRedis()->hset($pool, $key, $value) === 1; } } + + return false; } /** From c8de90ca5c9c2a8a3a95ff72138f23a26fdc43a7 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 19 Oct 2025 18:27:29 +0200 Subject: [PATCH 18/32] some review annotations added --- src/RedisCache.php | 2 +- src/RedisEnhancedCache.php | 5 +++++ tests/Integration/CacheIntegrationTest.php | 6 +++++- tests/Integration/PoolIntegrationTest.php | 6 +++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index 54e0bef..184ee15 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -498,7 +498,7 @@ public function setCorrectValue(string &$value): mixed return self::DOES_NOT_EXIST; } finally { if ($tmp !== false || ($tmp === false && $value === 'b:0;')) { - $value = $tmp; + $value = $tmp; // if value var wasn't a string affect its original value type to it } return $value; diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 622e2ca..f9c4e5b 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -51,6 +51,8 @@ public function storeToPool(array $values, string $pool): bool */ /** * check keys arguments are valid, and values are all stored as strings + * + * @todo omg are you that tired bro ? replace this shit with a foreach, :face_palm: */ $self = $this; array_walk($values, function (&$value, $key) use ($self) { @@ -137,6 +139,9 @@ public function fetchFromPool(mixed $key, string $pool): mixed ); $self = $this; $label = self::DOES_NOT_EXIST; + /** + * @todo same remark here use a freaking foreach ! + */ array_walk($data, function (&$value) use ($self, $label) { if (is_string($value)) { $value = $self->setCorrectValue($value); diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index d0df599..d2a2bae 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -64,7 +64,11 @@ public static function invalidKeys() } /** - * meh + * Yup this isn't optimal but I've only 2 restricted key scenario when keys + * are forced into strings type + * (which is the case thanks to PSR-16 v3 from psr/simple-cache repository). + * + * @link https://github.com/php-fig/simple-cache The psr/simple-cache repository. * * @return array */ diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 708371a..be82c01 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -59,7 +59,11 @@ public static function invalidKeys() } /** - * meh + * Yup this isn't optimal but I've only 2 restricted key scenario when keys + * are forced into strings type + * (which is the case thanks to PSR-6 v3 from psr/cache repository). + * + * @link https://github.com/php-fig/cache The psr/cache repository. * * @return array */ From a717541541fbca1a11f1a2f52584d9c2dc454c0a Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 25 Oct 2025 16:04:01 +0200 Subject: [PATCH 19/32] Tests state added --- .phpunit_full | 5 ++++- src/RedisEnhancedCache.php | 9 ++++---- tests/Integration/CacheIntegrationTest.php | 24 +++++++++++++++------- tests/Integration/PoolIntegrationTest.php | 13 ++++++++++-- tests/TestState.php | 17 +++++++++++++++ 5 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 tests/TestState.php diff --git a/.phpunit_full b/.phpunit_full index fa12b7f..4198cc7 100644 --- a/.phpunit_full +++ b/.phpunit_full @@ -1,5 +1,8 @@ strings - * + * * @todo omg are you that tired bro ? replace this shit with a foreach, :face_palm: */ $self = $this; @@ -78,7 +78,8 @@ public function storeToPool(array $values, string $pool): bool dd('I think it could be problematic'); } if ($value) { - dump('store to pool : '. $key . ' - '. $value); + dump('store to pool : ' . $key . ' - ' . $value); + //hset should returns the number of fields stored for a single key (always one here) return $this->getRedis()->hset($pool, $key, $value) === 1; } @@ -279,7 +280,7 @@ public function setHsetPoolExpiration(string $pool, int $expirationTime = self:: public function printCacheHash(string $pool, $silent = false): string { $data = $this->fetchAllFromPool($pool); - dump("from printCacheHash", $data); + dump('from printCacheHash', $data); /** @@ -295,7 +296,7 @@ public function printCacheHash(string $pool, $silent = false): string } } - dump("from printCacheHash to return - " . (strlen($toReturn) ? $toReturn : "\$toReturn is empty")); + dump('from printCacheHash to return - ' . (strlen($toReturn) ? $toReturn : '$toReturn is empty')); return $toReturn; } diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index d2a2bae..c7c3e2c 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -6,6 +6,7 @@ use Cache\IntegrationTests\SimpleCacheTest; use LLegaz\Cache\RedisCache as SUT; +use LLegaz\Cache\Tests\TestState; use Psr\SimpleCache\CacheInterface; use TypeError; @@ -64,10 +65,10 @@ public static function invalidKeys() } /** - * Yup this isn't optimal but I've only 2 restricted key scenario when keys + * Yup this isn't optimal but I've only 2 restricted key scenario when keys * are forced into strings type * (which is the case thanks to PSR-16 v3 from psr/simple-cache repository). - * + * * @link https://github.com/php-fig/simple-cache The psr/simple-cache repository. * * @return array @@ -303,15 +304,24 @@ public function testSetMultipleWithIntegerArrayKey() public function createSimpleCache(): CacheInterface { - /* $sut = new SUT(); + $client = $sut->getRedis(); // test with persistent co - $sut = new SUT('localhost', 6379, null, 'tcp', 0, true); - dump($sut->getRedis()); + /*$sut = new SUT('localhost', 6379, null, 'tcp', 0, true); + dump($sut->getRedis());*/ + + /** + * display adapter class used (Predis or php-redis) + */ + if (!TestState::$adapterClassDisplayed) { + TestState::$adapterClassDisplayed = true; + dump($client->toString() . ' adapter used.'); + /* $this->assertTrue(TestState::$adapterClassDisplayed); + $this->assertTrue($sut instanceof \LLegaz\Redis\RedisAdapter); + $this->assertTrue($client instanceof \LLegaz\Redis\RedisClientInterface);*/ + } return $sut; -*/ - return new SUT(); } } diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index be82c01..6dfd71d 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -7,6 +7,7 @@ use Cache\IntegrationTests\CachePoolTest; use LLegaz\Cache\Pool\CacheEntryPool as SUT; use LLegaz\Cache\RedisEnhancedCache; +use LLegaz\Cache\Tests\TestState; use Psr\Cache\CacheItemPoolInterface; if (!defined('SKIP_INTEGRATION_TESTS')) { @@ -59,10 +60,10 @@ public static function invalidKeys() } /** - * Yup this isn't optimal but I've only 2 restricted key scenario when keys + * Yup this isn't optimal but I've only 2 restricted key scenario when keys * are forced into strings type * (which is the case thanks to PSR-6 v3 from psr/cache repository). - * + * * @link https://github.com/php-fig/cache The psr/cache repository. * * @return array @@ -92,6 +93,14 @@ public function createCachePool(): CacheItemPoolInterface { $cache = new RedisEnhancedCache(); + /** + * display adapter class used (Predis or php-redis) + */ + if (!TestState::$adapterClassDisplayed) { + TestState::$adapterClassDisplayed = true; + dump($cache->getRedis()->toString() . ' adapter used.'); + } + return new SUT($cache); } } diff --git a/tests/TestState.php b/tests/TestState.php new file mode 100644 index 0000000..0abccaa --- /dev/null +++ b/tests/TestState.php @@ -0,0 +1,17 @@ + + */ +class TestState +{ + public static $adapterClassDisplayed = false; +} From 3bb6e4083b963fa3a9ada3c1c9105ba122cec270 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 25 Oct 2025 18:08:57 +0200 Subject: [PATCH 20/32] set expiration on entire pool --- .phpunit_full | 2 +- composer.json | 2 +- src/CacheEntry/CacheEntry.php | 32 ++++++++++----------------- src/CacheEntryPool/CacheEntryPool.php | 17 +++++++++++++- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/.phpunit_full b/.phpunit_full index 4198cc7..1334470 100644 --- a/.phpunit_full +++ b/.phpunit_full @@ -1,6 +1,6 @@ ttl = Utils::dateIntervalToSeconds($time); - } else { + } elseif (is_int($time)) { $this->ttl = $time; + } else { + // null case + $this->ttl = RedisCache::DAY_EXPIRATION_TIME; // 24h } $this->isTimeStamp = false; @@ -43,8 +44,12 @@ public function expiresAfter(int|\DateInterval|null $time): static public function expiresAt(?\DateTimeInterface $expiration): static { // hexpireat - $this->ttl = $expiration->getTimestamp(); - $this->isTimeStamp = true; + if ($expiration) { + $this->ttl = $expiration->getTimestamp(); + } else { + // null case + $this->ttl = RedisCache::DAY_EXPIRATION_TIME; // 24h + } return $this; } @@ -83,19 +88,6 @@ public function set($value): static return $this; } - /** - * if true = expireAt - * - * - * this bool seems useless @todo : remove it - * - * @return bool - */ - public function isTimeStamp(): bool - { - return $this->isTimeStamp; - } - /** * return TTL in seconds OR unix timestamp * diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index e43df92..b8eba2b 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -196,6 +196,10 @@ public function hasItem(string $key): bool /** * Persists a cache item immediately. * + * CRITICAL: if an expiration time, a ttl or expiration date is set, THEN the + * ENTIRE pool will be expired ! Thus deleting all the pool's key values (hash fields). + * @caution @warning + * * @param CacheItemInterface $item * The cache item to save. * @@ -205,7 +209,18 @@ public function hasItem(string $key): bool public function save(CacheItemInterface $item): bool { //dump("save", $item); - return $this->cache->storeToPool([$item->getKey() => $item->get()], $this->poolName); + $bln = $this->cache->storeToPool([$item->getKey() => $item->get()], $this->poolName); + if ($bln && $item instanceof CacheEntry && $item->getTTL() > 0) { + /** + * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ + * /!\ expires entire pool! /!\ + * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ + */ + //dump('/!\ expires entire pool! /!\\', $this->poolName, $item->getTTL()); + $bln = $this->cache->setHsetPoolExpiration($this->poolName, $item->getTTL()); + } + + return $bln; } /** From b662f2dc88d6da68a4cf73c82fb8ca4fbecb9232 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 28 Oct 2025 11:56:58 +0100 Subject: [PATCH 21/32] wip on deferred items and expiration --- composer.json | 2 +- src/CacheEntry/CacheEntry.php | 13 ++++-- src/CacheEntryPool/CacheEntryPool.php | 57 +++++++++++++++++---------- src/Utils.php | 12 ++++++ 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index b72cfe7..ee4a74b 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testExpir", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testSaveExpir", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntry/CacheEntry.php b/src/CacheEntry/CacheEntry.php index 831d4f8..511500f 100644 --- a/src/CacheEntry/CacheEntry.php +++ b/src/CacheEntry/CacheEntry.php @@ -30,7 +30,11 @@ public function expiresAfter(int|\DateInterval|null $time): static if ($time instanceof \DateInterval) { $this->ttl = Utils::dateIntervalToSeconds($time); } elseif (is_int($time)) { - $this->ttl = $time; + if ($time <= 0) { + $this->ttl = 0; + } else { + $this->ttl = $time; + } } else { // null case $this->ttl = RedisCache::DAY_EXPIRATION_TIME; // 24h @@ -43,9 +47,12 @@ public function expiresAfter(int|\DateInterval|null $time): static public function expiresAt(?\DateTimeInterface $expiration): static { - // hexpireat if ($expiration) { - $this->ttl = $expiration->getTimestamp(); + if (Utils::getNow()->diff($expiration)->invert >= 0) { + $this->ttl = 0; + } else { + $this->ttl = $expiration->getTimestamp(); + } } else { // null case $this->ttl = RedisCache::DAY_EXPIRATION_TIME; // 24h diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index b8eba2b..1c8061c 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -77,6 +77,8 @@ public function clear(): bool try { $this->cache->set($this->poolName, null); $this->cache->delete($this->poolName); + unset($this->deferredItems); + $this->deferredItems = []; } catch (Exception $e) { return false; } @@ -117,14 +119,18 @@ public function deleteItems(array $keys): bool */ public function getItem(string $key): CacheItemInterface { - $value = $this->cache->fetchFromPool($key, $this->poolName); - //dump('getItem', $key, $value); - /** @todo handle hit, ttl */ - $item = new CacheEntry($key); - dump('getItem: ' . $key . ' - ' . (is_string($value) ? $value : print_r($value))); - if ($this->cache->exist($value)) { - $item->set($value); + /** @todo handle hit, ttl, refacto */ + if (isset($this->deferredItems[$key]) && ($this->deferredItems[$key] instanceof CacheEntry)) { + $item = $this->deferredItems[$key]; $item->hit(); + } else { + $value = $this->cache->fetchFromPool($key, $this->poolName); + $item = new CacheEntry($key); + dump('getItem: ' . $key . ' - ' . (is_string($value) ? $value : print_r($value))); + if ($this->cache->exist($value)) { + $item->set($value); + $item->hit(); + } } return $item; @@ -153,15 +159,20 @@ public function getItems(array $keys = []): iterable $values = $this->cache->fetchFromPool($keys, $this->poolName); //dump($values); foreach ($keys as $key) { - /** @todo handle hit, ttl */ - $item = new CacheEntry($key); - if (isset($values[$key]) && $this->cache->exist($values[$key])) { - $item->set($values[$key]); + /** @todo handle hit, ttl and refacto */ + if (isset($this->deferredItems[$key])) { + $item = $this->deferredItems[$key]; $item->hit(); + } else { + $item = new CacheEntry($key); + if (isset($values[$key]) && $this->cache->exist($values[$key])) { + $item->set($values[$key]); + $item->hit(); + } } //$items[] = $item; /** - * because of his code (wrong) + * because of this code (that is wrong IMHO) * https://github.com/php-cache/integration-tests/blob/fb7d78718f1e5bbfd7c63e5c5734000999ac7366/src/CachePoolTest.php#L208C40-L208C44 */ $items[$key] = $item; @@ -189,7 +200,7 @@ public function getItems(array $keys = []): iterable */ public function hasItem(string $key): bool { - return $this->cache->hasInPool($key, $this->poolName); + return isset($this->deferredItems[$key]) || $this->cache->hasInPool($key, $this->poolName); } @@ -208,7 +219,11 @@ public function hasItem(string $key): bool */ public function save(CacheItemInterface $item): bool { - //dump("save", $item); + if ($this->isExpired($item)) { + return false; + } + + dump('save', $item); $bln = $this->cache->storeToPool([$item->getKey() => $item->get()], $this->poolName); if ($bln && $item instanceof CacheEntry && $item->getTTL() > 0) { /** @@ -234,7 +249,7 @@ public function save(CacheItemInterface $item): bool */ public function saveDeferred(CacheItemInterface $item): bool { - $this->deferredItems[] = $item; + $this->deferredItems[$item->getKey()] = $item; return true; } @@ -247,12 +262,7 @@ public function saveDeferred(CacheItemInterface $item): bool */ public function commit(): bool { - $deferred = []; - foreach ($this->deferredItems as $item) { - $deferred[$item->getKey()] = $item->get(); - } - - return $this->cache->storeToPool($deferred, $this->poolName); + return $this->cache->storeToPool($this->deferredItems, $this->poolName); } @@ -274,4 +284,9 @@ protected function getPoolName(string $poolSuffix): string 'DEFAULT_' . self::HASH_DB_PREFIX ; } + + private function isExpired(CacheEntry $item): bool + { + return !($item instanceof CacheEntry) || $item->getTTL() === 0; + } } diff --git a/src/Utils.php b/src/Utils.php index 5f34fb6..ef1b655 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -8,6 +8,18 @@ class Utils { + private static DateTimeImmutable $NOW; + private static int $called = 0; + + public static function getNow(): DateTimeImmutable + { + if (self::$called++ === 0) { + self::$NOW = new \DateTimeImmutable('NOW'); + } + + return self::$NOW; + } + /** * return time to live in seconds * From 937a780684d20c87b4257ba041b1d1b425c2d970 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 28 Oct 2025 13:02:34 +0100 Subject: [PATCH 22/32] Almost there indy! On deferred items and refacto --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 53 ++++++++++++++++++++++----- src/RedisEnhancedCache.php | 2 +- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index ee4a74b..045287a 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testSaveExpir", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testCommit", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 1c8061c..3a91c69 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -63,6 +63,11 @@ public function __construct(RedisEnhancedCache $cache, ?string $pool = null) $this->poolName = $this->getPoolName($pool ?? ''); } + public function __destruct() + { + $this->commit(); + } + /** * Deletes all items in the pool. * @@ -88,16 +93,25 @@ public function clear(): bool public function deleteItem(string $key): bool { + if ($this->isDeferred($key)) { + unset ($this->deferredItems[$key]); + + return $this->isDeferred($key); + } + return $this->deleteItems([$key]); } public function deleteItems(array $keys): bool { - $bln = $this->cache->deleteFromPool($keys, $this->poolName); - dump('deleteItems', $keys, $bln); + foreach ($keys as $key) { + if ($this->isDeferred($key)) { + unset ($this->deferredItems[$key]); + } + } - return $bln;/*$this->cache->deleteFromPool($keys, $this->poolName);*/ + return $this->cache->deleteFromPool($keys, $this->poolName); } @@ -120,7 +134,7 @@ public function deleteItems(array $keys): bool public function getItem(string $key): CacheItemInterface { /** @todo handle hit, ttl, refacto */ - if (isset($this->deferredItems[$key]) && ($this->deferredItems[$key] instanceof CacheEntry)) { + if ($this->isDeferred($key)) { $item = $this->deferredItems[$key]; $item->hit(); } else { @@ -160,7 +174,7 @@ public function getItems(array $keys = []): iterable //dump($values); foreach ($keys as $key) { /** @todo handle hit, ttl and refacto */ - if (isset($this->deferredItems[$key])) { + if ($this->isDeferred($key)) { $item = $this->deferredItems[$key]; $item->hit(); } else { @@ -249,9 +263,13 @@ public function save(CacheItemInterface $item): bool */ public function saveDeferred(CacheItemInterface $item): bool { - $this->deferredItems[$item->getKey()] = $item; + if (!$this->isExpired($item)) { + $this->deferredItems[$item->getKey()] = $item; - return true; + return true; + } + + return false; } /** @@ -262,9 +280,21 @@ public function saveDeferred(CacheItemInterface $item): bool */ public function commit(): bool { - return $this->cache->storeToPool($this->deferredItems, $this->poolName); - } + if (!count($this->deferredItems)) { + + return true; + } + foreach ($this->deferredItems as $key => $item) { + if (!$this->isExpired($item)) { + $deferred[$key] = $item->get(); + } + unset($this->deferredItems[$key]); + } + $this->deferredItems = []; + + return $this->cache->storeToPool($deferred, $this->poolName); + } public function printCachePool(): string { @@ -289,4 +319,9 @@ private function isExpired(CacheEntry $item): bool { return !($item instanceof CacheEntry) || $item->getTTL() === 0; } + + private function isDeferred(string $key): bool + { + return isset($this->deferredItems[$key]) && ($this->deferredItems[$key] instanceof CacheEntry); + } } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 328d64c..822e70a 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -60,7 +60,7 @@ public function storeToPool(array $values, string $pool): bool $self->checkKeyValuePair($key, $value); }); - //dump('store to pool :', $values); + dump('store to pool :', $values); /** * @todo rework exception handling and returns From 001c712e777741b1a27d5eca71e53a1a84fc0872 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 29 Oct 2025 11:31:27 +0100 Subject: [PATCH 23/32] todo: testExpiresAt, testHasItemReturnsFalseWhenDeferredItemIsExpired and testSaveDeferredOverwrite --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 17 ++++++++++------- src/RedisEnhancedCache.php | 2 +- tests/Integration/CacheIntegrationTest.php | 1 + tests/Integration/PoolIntegrationTest.php | 1 + 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 045287a..f660f29 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testCommit", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testSaveDeferredWhenChangingValues", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 3a91c69..888ce3b 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -94,7 +94,7 @@ public function clear(): bool public function deleteItem(string $key): bool { if ($this->isDeferred($key)) { - unset ($this->deferredItems[$key]); + unset($this->deferredItems[$key]); return $this->isDeferred($key); } @@ -107,7 +107,7 @@ public function deleteItems(array $keys): bool { foreach ($keys as $key) { if ($this->isDeferred($key)) { - unset ($this->deferredItems[$key]); + unset($this->deferredItems[$key]); } } @@ -133,20 +133,21 @@ public function deleteItems(array $keys): bool */ public function getItem(string $key): CacheItemInterface { + dump($this->isDeferred($key)); /** @todo handle hit, ttl, refacto */ if ($this->isDeferred($key)) { - $item = $this->deferredItems[$key]; + $item = clone $this->deferredItems[$key]; $item->hit(); } else { $value = $this->cache->fetchFromPool($key, $this->poolName); $item = new CacheEntry($key); - dump('getItem: ' . $key . ' - ' . (is_string($value) ? $value : print_r($value))); if ($this->cache->exist($value)) { $item->set($value); $item->hit(); } } + dump('getItem: ' . $item->getKey(). ' - TTL= ' . $item->getTTL() . ' - ' . (is_string($item->get()) ? $item->get() : print_r($item->get()))); return $item; } @@ -175,7 +176,7 @@ public function getItems(array $keys = []): iterable foreach ($keys as $key) { /** @todo handle hit, ttl and refacto */ if ($this->isDeferred($key)) { - $item = $this->deferredItems[$key]; + $item = clone $this->deferredItems[$key]; $item->hit(); } else { $item = new CacheEntry($key); @@ -263,10 +264,12 @@ public function save(CacheItemInterface $item): bool */ public function saveDeferred(CacheItemInterface $item): bool { - if (!$this->isExpired($item)) { - $this->deferredItems[$item->getKey()] = $item; + if (!isset($this->deferredItems[$item->getKey()]) && !$this->isExpired($item)) { + $this->deferredItems[$item->getKey()] = clone $item; return true; + } elseif (isset($this->deferredItems[$item->getKey()]) && $this->isExpired($item)) { + unset($this->deferredItems[$item->getKey()]); } return false; diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 822e70a..60b3d79 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -81,7 +81,7 @@ public function storeToPool(array $values, string $pool): bool dump('store to pool : ' . $key . ' - ' . $value); //hset should returns the number of fields stored for a single key (always one here) - return $this->getRedis()->hset($pool, $key, $value) === 1; + return $this->getRedis()->hset($pool, $key, $value) >= 0; } } diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index c7c3e2c..1cee6d1 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -316,6 +316,7 @@ public function createSimpleCache(): CacheInterface if (!TestState::$adapterClassDisplayed) { TestState::$adapterClassDisplayed = true; dump($client->toString() . ' adapter used.'); + sleep(1); /* $this->assertTrue(TestState::$adapterClassDisplayed); $this->assertTrue($sut instanceof \LLegaz\Redis\RedisAdapter); $this->assertTrue($client instanceof \LLegaz\Redis\RedisClientInterface);*/ diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 6dfd71d..7ec1608 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -99,6 +99,7 @@ public function createCachePool(): CacheItemPoolInterface if (!TestState::$adapterClassDisplayed) { TestState::$adapterClassDisplayed = true; dump($cache->getRedis()->toString() . ' adapter used.'); + sleep(1); } return new SUT($cache); From b73266abf2bc3a55062487e4e571c0eab8ff973e Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 29 Oct 2025 12:23:58 +0100 Subject: [PATCH 24/32] big fix on expireAt, remember this expiration process implementation will expire the entire pool (hash) proceed carefully or use PSR-16 implementation for more granularity on key / value pairs expiration --- composer.json | 2 +- src/CacheEntry/CacheEntry.php | 2 +- src/CacheEntryPool/CacheEntryPool.php | 7 ++++--- tests/Integration/CacheIntegrationTest.php | 3 +++ tests/Integration/PoolIntegrationTest.php | 3 +++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index f660f29..1a66ddc 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testSaveDeferredWhenChangingValues", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testExpire", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntry/CacheEntry.php b/src/CacheEntry/CacheEntry.php index 511500f..e984440 100644 --- a/src/CacheEntry/CacheEntry.php +++ b/src/CacheEntry/CacheEntry.php @@ -48,7 +48,7 @@ public function expiresAfter(int|\DateInterval|null $time): static public function expiresAt(?\DateTimeInterface $expiration): static { if ($expiration) { - if (Utils::getNow()->diff($expiration)->invert >= 0) { + if (Utils::getNow()->diff($expiration)->invert > 0) { $this->ttl = 0; } else { $this->ttl = $expiration->getTimestamp(); diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 888ce3b..5704459 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -133,7 +133,6 @@ public function deleteItems(array $keys): bool */ public function getItem(string $key): CacheItemInterface { - dump($this->isDeferred($key)); /** @todo handle hit, ttl, refacto */ if ($this->isDeferred($key)) { $item = clone $this->deferredItems[$key]; @@ -148,6 +147,7 @@ public function getItem(string $key): CacheItemInterface } dump('getItem: ' . $item->getKey(). ' - TTL= ' . $item->getTTL() . ' - ' . (is_string($item->get()) ? $item->get() : print_r($item->get()))); + dump($item); return $item; } @@ -234,6 +234,7 @@ public function hasItem(string $key): bool */ public function save(CacheItemInterface $item): bool { + dump('savi,ng', $this->isExpired($item), $item); if ($this->isExpired($item)) { return false; } @@ -264,11 +265,11 @@ public function save(CacheItemInterface $item): bool */ public function saveDeferred(CacheItemInterface $item): bool { - if (!isset($this->deferredItems[$item->getKey()]) && !$this->isExpired($item)) { + if (!$this->isExpired($item)) { $this->deferredItems[$item->getKey()] = clone $item; return true; - } elseif (isset($this->deferredItems[$item->getKey()]) && $this->isExpired($item)) { + } elseif (isset($this->deferredItems[$item->getKey()])) { unset($this->deferredItems[$item->getKey()]); } diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index 1cee6d1..c9e5757 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -316,6 +316,9 @@ public function createSimpleCache(): CacheInterface if (!TestState::$adapterClassDisplayed) { TestState::$adapterClassDisplayed = true; dump($client->toString() . ' adapter used.'); + /** + * @todo remove sleep + */ sleep(1); /* $this->assertTrue(TestState::$adapterClassDisplayed); $this->assertTrue($sut instanceof \LLegaz\Redis\RedisAdapter); diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 7ec1608..3a1ae4a 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -99,6 +99,9 @@ public function createCachePool(): CacheItemPoolInterface if (!TestState::$adapterClassDisplayed) { TestState::$adapterClassDisplayed = true; dump($cache->getRedis()->toString() . ' adapter used.'); + /** + * @todo remove sleep + */ sleep(1); } From cbc68a3e99219b3ac521556cf0e291f24a9b6a3a Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 4 Nov 2025 10:56:05 +0100 Subject: [PATCH 25/32] refacto and expiration fix --- composer.json | 2 +- src/CacheEntryPool/CacheEntryPool.php | 6 ++++-- tests/Unit/SimpleCacheRCTest.php | 5 +++++ tests/Unit/SimpleCacheTest.php | 5 +++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 1a66ddc..9960234 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testExpire", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testSaveExpired", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 5704459..6beb30e 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -234,12 +234,14 @@ public function hasItem(string $key): bool */ public function save(CacheItemInterface $item): bool { - dump('savi,ng', $this->isExpired($item), $item); if ($this->isExpired($item)) { + $this->deleteItem($item->getKey()); + dump($item->getKey() . ' deleted bc expired'); + return false; } - dump('save', $item); + //dump('save', $item); $bln = $this->cache->storeToPool([$item->getKey() => $item->get()], $this->poolName); if ($bln && $item instanceof CacheEntry && $item->getTTL() > 0) { /** diff --git a/tests/Unit/SimpleCacheRCTest.php b/tests/Unit/SimpleCacheRCTest.php index 25e9f42..030f022 100644 --- a/tests/Unit/SimpleCacheRCTest.php +++ b/tests/Unit/SimpleCacheRCTest.php @@ -363,4 +363,9 @@ public function testSetWithTtl() ; $this->assertTrue($this->cache->set($key, 'bbbbbbbbbbbbbbbbbbbb', 1337)); } + + protected function getSelfClient() { + $this->redisClient; + } + } diff --git a/tests/Unit/SimpleCacheTest.php b/tests/Unit/SimpleCacheTest.php index 21461d8..011aa0e 100644 --- a/tests/Unit/SimpleCacheTest.php +++ b/tests/Unit/SimpleCacheTest.php @@ -351,4 +351,9 @@ public function testSetMultipleWithTtl() $this->assertTrue($this->cache->setMultiple($values, $ttl)); } + + protected function getSelfClient() { + $this->predisClient; + } + } From 56a5ff854d28539a493d188162e672b25c9016e5 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 4 Nov 2025 11:03:20 +0100 Subject: [PATCH 26/32] still on expiration, plus hide some dumps --- src/CacheEntryPool/CacheEntryPool.php | 9 +++++++-- src/RedisEnhancedCache.php | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 6beb30e..180b0af 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -145,9 +145,9 @@ public function getItem(string $key): CacheItemInterface $item->hit(); } } +/* dump('getItem: ' . $item->getKey(). ' - TTL= ' . $item->getTTL() . ' - ' . (is_string($item->get()) ? $item->get() : print_r($item->get()))); + dump($item);*/ - dump('getItem: ' . $item->getKey(). ' - TTL= ' . $item->getTTL() . ' - ' . (is_string($item->get()) ? $item->get() : print_r($item->get()))); - dump($item); return $item; } @@ -248,6 +248,8 @@ public function save(CacheItemInterface $item): bool * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ * /!\ expires entire pool! /!\ * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ + * + * @todo maybe throw a PHP warning here ? */ //dump('/!\ expires entire pool! /!\\', $this->poolName, $item->getTTL()); $bln = $this->cache->setHsetPoolExpiration($this->poolName, $item->getTTL()); @@ -294,6 +296,9 @@ public function commit(): bool foreach ($this->deferredItems as $key => $item) { if (!$this->isExpired($item)) { $deferred[$key] = $item->get(); + } else { + // clear cache of expired item + $this->deleteItem($item->getKey()); } unset($this->deferredItems[$key]); } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 60b3d79..66afccb 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -60,7 +60,7 @@ public function storeToPool(array $values, string $pool): bool $self->checkKeyValuePair($key, $value); }); - dump('store to pool :', $values); + //dump('store to pool :', $values); /** * @todo rework exception handling and returns From e795b641c1565195d68171c3bfd5f38d6bfae1a4 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 4 Nov 2025 12:04:09 +0100 Subject: [PATCH 27/32] expiration is gucci tier now ! --- composer.json | 2 +- src/CacheEntry/CacheEntry.php | 35 +++++++++++++++---- src/CacheEntryPool/CacheEntryPool.php | 49 +++++++++++++++++++++------ src/Utils.php | 13 ++++--- tests/Unit/SimpleCacheRCTest.php | 3 +- tests/Unit/SimpleCacheTest.php | 3 +- 6 files changed, 80 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index 9960234..f888545 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,7 @@ "puf":"@phpunit-func-suite", "cs":"@phpcsfixer", "test": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full", - "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testSaveExpired", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest::testHasItemReturnsFalseWhenDeferredItemIsExpired", "test-psr16": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter CacheIntegrationTest", "test-psr6": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter PoolIntegrationTest", "phpunit" : "./vendor/bin/phpunit --colors=always --configuration ./phpunit.xml --testsuite unit", diff --git a/src/CacheEntry/CacheEntry.php b/src/CacheEntry/CacheEntry.php index e984440..4eefa14 100644 --- a/src/CacheEntry/CacheEntry.php +++ b/src/CacheEntry/CacheEntry.php @@ -4,6 +4,9 @@ namespace LLegaz\Cache\Entry; +use DateInterval; +use DateTimeImmutable; +use DateTimeInterface; use LLegaz\Cache\RedisCache; use LLegaz\Cache\Utils; @@ -13,6 +16,7 @@ class CacheEntry extends AbstractCacheEntry private mixed $value = null; private bool $isHit = false; private ?int $ttl = -1; + private ?DateTimeInterface $dlc = null; public function __construct(string $key) { @@ -25,9 +29,9 @@ public function __construct(string $key) * @param int|\DateInterval|null $time * @return static */ - public function expiresAfter(int|\DateInterval|null $time): static + public function expiresAfter(int|DateInterval|null $time): static { - if ($time instanceof \DateInterval) { + if ($time instanceof DateInterval) { $this->ttl = Utils::dateIntervalToSeconds($time); } elseif (is_int($time)) { if ($time <= 0) { @@ -39,16 +43,15 @@ public function expiresAfter(int|\DateInterval|null $time): static // null case $this->ttl = RedisCache::DAY_EXPIRATION_TIME; // 24h } - - $this->isTimeStamp = false; + $this->setDLC(); return $this; } - public function expiresAt(?\DateTimeInterface $expiration): static + public function expiresAt(?DateTimeInterface $expiration): static { if ($expiration) { - if (Utils::getNow()->diff($expiration)->invert > 0) { + if ((new DateTimeImmutable())->diff($expiration)->invert > 0) { $this->ttl = 0; } else { $this->ttl = $expiration->getTimestamp(); @@ -57,6 +60,7 @@ public function expiresAt(?\DateTimeInterface $expiration): static // null case $this->ttl = RedisCache::DAY_EXPIRATION_TIME; // 24h } + $this->setDLC(); return $this; } @@ -105,4 +109,23 @@ public function getTTL(): int return $this->ttl; } + public function isExpiredFromDLC(): bool + { + if ($this->dlc) { + return (new DateTimeImmutable())->diff($this->dlc)->invert > 0; + } + + return false; + } + + public function getDLC(): ?DateTimeInterface + { + return $this->dlc; + } + + private function setDLC() + { + $this->dlc = (new \DateTime((new DateTimeImmutable())->format(DateTimeInterface::ISO8601)))->add(new DateInterval("PT{$this->ttl}S")); + } + } diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 180b0af..05a4858 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -133,11 +133,23 @@ public function deleteItems(array $keys): bool */ public function getItem(string $key): CacheItemInterface { - /** @todo handle hit, ttl, refacto */ + + /** + * if item is saved in the deferred pool and not expired we retrieve it + * else we try to retrieve it from the "normal" pool. + */ + $item = null; if ($this->isDeferred($key)) { - $item = clone $this->deferredItems[$key]; - $item->hit(); - } else { + if ($this->deferredItems[$key]->isExpiredFromDLC()) { + unset($this->deferredItems[$key]); + $this->deleteItem($key); + } else { + $item = clone $this->deferredItems[$key]; + $item->hit(); + } + } + + if (!$item) { $value = $this->cache->fetchFromPool($key, $this->poolName); $item = new CacheEntry($key); if ($this->cache->exist($value)) { @@ -145,8 +157,8 @@ public function getItem(string $key): CacheItemInterface $item->hit(); } } -/* dump('getItem: ' . $item->getKey(). ' - TTL= ' . $item->getTTL() . ' - ' . (is_string($item->get()) ? $item->get() : print_r($item->get()))); - dump($item);*/ + /* dump('getItem: ' . $item->getKey(). ' - TTL= ' . $item->getTTL() . ' - ' . (is_string($item->get()) ? $item->get() : print_r($item->get()))); + dump($item);*/ return $item; @@ -174,11 +186,21 @@ public function getItems(array $keys = []): iterable $values = $this->cache->fetchFromPool($keys, $this->poolName); //dump($values); foreach ($keys as $key) { - /** @todo handle hit, ttl and refacto */ + /** + * if item is saved in the deferred pool and not expired we retrieve it + * else we try to retrieve it from the "normal" pool. + */ + $item = null; if ($this->isDeferred($key)) { - $item = clone $this->deferredItems[$key]; - $item->hit(); - } else { + if ($this->deferredItems[$key]->isExpiredFromDLC()) { + unset($this->deferredItems[$key]); + $this->deleteItem($key); + } else { + $item = clone $this->deferredItems[$key]; + $item->hit(); + } + } + if (!$item) { $item = new CacheEntry($key); if (isset($values[$key]) && $this->cache->exist($values[$key])) { $item->set($values[$key]); @@ -215,6 +237,11 @@ public function getItems(array $keys = []): iterable */ public function hasItem(string $key): bool { + if ($this->isDeferred($key) && $this->deferredItems[$key]->isExpiredFromDLC()) { + unset($this->deferredItems[$key]); + $this->deleteItem($key); + } + return isset($this->deferredItems[$key]) || $this->cache->hasInPool($key, $this->poolName); } @@ -248,7 +275,7 @@ public function save(CacheItemInterface $item): bool * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ * /!\ expires entire pool! /!\ * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ - * + * * @todo maybe throw a PHP warning here ? */ //dump('/!\ expires entire pool! /!\\', $this->poolName, $item->getTTL()); diff --git a/src/Utils.php b/src/Utils.php index ef1b655..e5fa5be 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -4,21 +4,24 @@ namespace LLegaz\Cache; -use DateTimeImmutable; - class Utils { + /** + * @todo clean here + */ + // beautifully ugly + /* private static DateTimeImmutable $NOW; private static int $called = 0; public static function getNow(): DateTimeImmutable { if (self::$called++ === 0) { - self::$NOW = new \DateTimeImmutable('NOW'); + self::$NOW = new DateTimeImmutable('NOW'); } return self::$NOW; - } + }*/ /** * return time to live in seconds @@ -28,7 +31,7 @@ public static function getNow(): DateTimeImmutable */ public static function dateIntervalToSeconds(\DateInterval $ttl): int { - $reference = new DateTimeImmutable(); + $reference = new \DateTimeImmutable(); $endTime = $reference->add($ttl); return $endTime->getTimestamp() - $reference->getTimestamp(); diff --git a/tests/Unit/SimpleCacheRCTest.php b/tests/Unit/SimpleCacheRCTest.php index 030f022..4452e6f 100644 --- a/tests/Unit/SimpleCacheRCTest.php +++ b/tests/Unit/SimpleCacheRCTest.php @@ -364,7 +364,8 @@ public function testSetWithTtl() $this->assertTrue($this->cache->set($key, 'bbbbbbbbbbbbbbbbbbbb', 1337)); } - protected function getSelfClient() { + protected function getSelfClient() + { $this->redisClient; } diff --git a/tests/Unit/SimpleCacheTest.php b/tests/Unit/SimpleCacheTest.php index 011aa0e..f216e96 100644 --- a/tests/Unit/SimpleCacheTest.php +++ b/tests/Unit/SimpleCacheTest.php @@ -352,7 +352,8 @@ public function testSetMultipleWithTtl() $this->assertTrue($this->cache->setMultiple($values, $ttl)); } - protected function getSelfClient() { + protected function getSelfClient() + { $this->predisClient; } From 542fddfc0227d7f73587f576d5009e2ed16c04dd Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 4 Nov 2025 12:38:30 +0100 Subject: [PATCH 28/32] EPISODE 2 is DONE --- src/CacheEntryPool/CacheEntryPool.php | 5 ++--- src/RedisEnhancedCache.php | 2 +- tests/Unit/CacheItemTest.php | 7 ++++--- tests/Unit/SimpleCacheRCTest.php | 2 +- tests/Unit/SimpleCacheTest.php | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 05a4858..07871c0 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -76,8 +76,8 @@ public function __destruct() */ public function clear(): bool { - $e = new \Exception(); - dump('pool cleared by : ', $e->getTrace()[1]['function']); + /*$e = new \Exception(); + dump('pool cleared by : ', $e->getTrace()[1]['function']);*/ try { $this->cache->set($this->poolName, null); @@ -263,7 +263,6 @@ public function save(CacheItemInterface $item): bool { if ($this->isExpired($item)) { $this->deleteItem($item->getKey()); - dump($item->getKey() . ' deleted bc expired'); return false; } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 66afccb..eed9c0b 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -78,7 +78,7 @@ public function storeToPool(array $values, string $pool): bool dd('I think it could be problematic'); } if ($value) { - dump('store to pool : ' . $key . ' - ' . $value); + //dump('store to pool : ' . $key . ' - ' . $value); //hset should returns the number of fields stored for a single key (always one here) return $this->getRedis()->hset($pool, $key, $value) >= 0; diff --git a/tests/Unit/CacheItemTest.php b/tests/Unit/CacheItemTest.php index 14cb8da..e56a0af 100644 --- a/tests/Unit/CacheItemTest.php +++ b/tests/Unit/CacheItemTest.php @@ -37,17 +37,18 @@ protected function tearDown(): void } /** + * @todo just do * get value + get key * test on empty pool */ public function testGet() { - + $this->assertFalse(false); } public function testSet() { - + $this->assertFalse(false); } /** @@ -56,7 +57,7 @@ public function testSet() */ public function testGetWithCacheHit() { - + $this->assertFalse(false); } } diff --git a/tests/Unit/SimpleCacheRCTest.php b/tests/Unit/SimpleCacheRCTest.php index 4452e6f..fb81bf0 100644 --- a/tests/Unit/SimpleCacheRCTest.php +++ b/tests/Unit/SimpleCacheRCTest.php @@ -366,7 +366,7 @@ public function testSetWithTtl() protected function getSelfClient() { - $this->redisClient; + return $this->redisClient; } } diff --git a/tests/Unit/SimpleCacheTest.php b/tests/Unit/SimpleCacheTest.php index f216e96..d94d18d 100644 --- a/tests/Unit/SimpleCacheTest.php +++ b/tests/Unit/SimpleCacheTest.php @@ -354,7 +354,7 @@ public function testSetMultipleWithTtl() protected function getSelfClient() { - $this->predisClient; + return $this->predisClient; } } From 421a626c1d00c6510773818076c2ee3f80f24d84 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 5 Nov 2025 11:19:27 +0100 Subject: [PATCH 29/32] refacto on expiration process for deferred items --- src/CacheEntry/CacheEntry.php | 7 ++++ src/CacheEntryPool/CacheEntryPool.php | 48 +++++++++++++++++++-------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/CacheEntry/CacheEntry.php b/src/CacheEntry/CacheEntry.php index 4eefa14..1c98593 100644 --- a/src/CacheEntry/CacheEntry.php +++ b/src/CacheEntry/CacheEntry.php @@ -88,6 +88,13 @@ public function hit(): static return $this; } + public function miss(): static + { + $this->isHit = false; + + return $this; + } + public function set($value): static { /*$type = gettype($value); diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 07871c0..84912df 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -140,11 +140,16 @@ public function getItem(string $key): CacheItemInterface */ $item = null; if ($this->isDeferred($key)) { - if ($this->deferredItems[$key]->isExpiredFromDLC()) { - unset($this->deferredItems[$key]); - $this->deleteItem($key); + $item = clone $this->deferredItems[$key]; + if ($item->isExpiredFromDLC()) { + /** + * if item is expired return it without hit (= miss), + * the item deletion (expiration) in real pool is handled in next commit + */ + //unset($this->deferredItems[$key]); + //$this->deleteItem($key); + $item->miss(); } else { - $item = clone $this->deferredItems[$key]; $item->hit(); } } @@ -192,11 +197,16 @@ public function getItems(array $keys = []): iterable */ $item = null; if ($this->isDeferred($key)) { - if ($this->deferredItems[$key]->isExpiredFromDLC()) { - unset($this->deferredItems[$key]); - $this->deleteItem($key); + $item = clone $this->deferredItems[$key]; + if ($item->isExpiredFromDLC()) { + /** + * if item is expired return it without hit (= miss), + * the item deletion (expiration) in real pool is handled in next commit + */ + //unset($this->deferredItems[$key]); + //$this->deleteItem($key); + $item->miss(); } else { - $item = clone $this->deferredItems[$key]; $item->hit(); } } @@ -237,12 +247,21 @@ public function getItems(array $keys = []): iterable */ public function hasItem(string $key): bool { - if ($this->isDeferred($key) && $this->deferredItems[$key]->isExpiredFromDLC()) { - unset($this->deferredItems[$key]); - $this->deleteItem($key); + if ($this->isDeferred($key)) { + if ($this->deferredItems[$key]->isExpiredFromDLC()) { + /** + * if item is expired we should handle the item deletion (expiration) + * from the real pool in a future commit + */ + //unset($this->deferredItems[$key]); + //$this->deleteItem($key); + return false; + } else { + return true; + } } - return isset($this->deferredItems[$key]) || $this->cache->hasInPool($key, $this->poolName); + return $this->cache->hasInPool($key, $this->poolName); } @@ -295,6 +314,9 @@ public function save(CacheItemInterface $item): bool */ public function saveDeferred(CacheItemInterface $item): bool { + /** + * @todo handle commit history for return value + */ if (!$this->isExpired($item)) { $this->deferredItems[$item->getKey()] = clone $item; @@ -354,7 +376,7 @@ protected function getPoolName(string $poolSuffix): string private function isExpired(CacheEntry $item): bool { - return !($item instanceof CacheEntry) || $item->getTTL() === 0; + return !($item instanceof CacheEntry) || $item->getTTL() === 0 || $item->isExpiredFromDLC(); } private function isDeferred(string $key): bool From 2c164969845c32e4f8d909b3a8030b1c53c36069 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 5 Nov 2025 11:46:42 +0100 Subject: [PATCH 30/32] clean dumps, plus some miscellaneous added --- src/CacheEntry/CacheEntry.php | 4 ---- src/CacheEntryPool/CacheEntryPool.php | 7 +------ src/RedisEnhancedCache.php | 4 ---- tests/Integration/CacheIntegrationTest.php | 5 +++++ tests/Integration/PoolIntegrationTest.php | 4 ++++ 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/CacheEntry/CacheEntry.php b/src/CacheEntry/CacheEntry.php index 1c98593..e5ca0f7 100644 --- a/src/CacheEntry/CacheEntry.php +++ b/src/CacheEntry/CacheEntry.php @@ -97,10 +97,6 @@ public function miss(): static public function set($value): static { - /*$type = gettype($value); - dump("CacheEntry::set value= " . $value ." and type= " . $type); - $e = new \Exception(); - dump($e->getTraceAsString());*/ $this->value = $value; return $this; diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 84912df..8a36afd 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -76,9 +76,6 @@ public function __destruct() */ public function clear(): bool { - /*$e = new \Exception(); - dump('pool cleared by : ', $e->getTrace()[1]['function']);*/ - try { $this->cache->set($this->poolName, null); $this->cache->delete($this->poolName); @@ -189,7 +186,7 @@ public function getItems(array $keys = []): iterable { $items = []; $values = $this->cache->fetchFromPool($keys, $this->poolName); - //dump($values); + foreach ($keys as $key) { /** * if item is saved in the deferred pool and not expired we retrieve it @@ -286,7 +283,6 @@ public function save(CacheItemInterface $item): bool return false; } - //dump('save', $item); $bln = $this->cache->storeToPool([$item->getKey() => $item->get()], $this->poolName); if ($bln && $item instanceof CacheEntry && $item->getTTL() > 0) { /** @@ -296,7 +292,6 @@ public function save(CacheItemInterface $item): bool * * @todo maybe throw a PHP warning here ? */ - //dump('/!\ expires entire pool! /!\\', $this->poolName, $item->getTTL()); $bln = $this->cache->setHsetPoolExpiration($this->poolName, $item->getTTL()); } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index eed9c0b..ede56e1 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -60,8 +60,6 @@ public function storeToPool(array $values, string $pool): bool $self->checkKeyValuePair($key, $value); }); - //dump('store to pool :', $values); - /** * @todo rework exception handling and returns */ @@ -78,8 +76,6 @@ public function storeToPool(array $values, string $pool): bool dd('I think it could be problematic'); } if ($value) { - //dump('store to pool : ' . $key . ' - ' . $value); - //hset should returns the number of fields stored for a single key (always one here) return $this->getRedis()->hset($pool, $key, $value) >= 0; } diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index c9e5757..1fef265 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -18,6 +18,9 @@ * Test PSR-16 implementation * * check @link https://github.com/php-cache/integration-tests + * + * + * @todo clone those in order to test persistent connections too */ class CacheIntegrationTest extends SimpleCacheTest { @@ -46,6 +49,7 @@ public function setupService() protected function setUp(): void { + //dump($this->cache->getRedisClientID()); // persistent co if (SKIP_INTEGRATION_TESTS) { // don't forget that tests are deleoppers' tools (and not only an approval seal) $this->markTestSkipped('INTEGRATION TESTS are skipped by default when executing Units tests only.'); @@ -315,6 +319,7 @@ public function createSimpleCache(): CacheInterface */ if (!TestState::$adapterClassDisplayed) { TestState::$adapterClassDisplayed = true; + fwrite(STDERR, PHP_EOL); dump($client->toString() . ' adapter used.'); /** * @todo remove sleep diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 3a1ae4a..aa05138 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -18,6 +18,9 @@ * Test PSR-6 implementation * * check @link https://github.com/php-cache/integration-tests + * + * + * @todo clone those in order to test persistent connections too */ class PoolIntegrationTest extends CachePoolTest { @@ -98,6 +101,7 @@ public function createCachePool(): CacheItemPoolInterface */ if (!TestState::$adapterClassDisplayed) { TestState::$adapterClassDisplayed = true; + fwrite(STDERR, PHP_EOL); dump($cache->getRedis()->toString() . ' adapter used.'); /** * @todo remove sleep From b19571c4b1550909b519ba5e324cdb25946a3d9a Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 6 Nov 2025 11:23:44 +0100 Subject: [PATCH 31/32] more cleaning and documentation --- src/CacheEntry/CacheEntry.php | 3 +++ src/CacheEntryPool/CacheEntryPool.php | 27 +++++++++++++++++++++++++-- src/RedisEnhancedCache.php | 9 ++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/CacheEntry/CacheEntry.php b/src/CacheEntry/CacheEntry.php index e5ca0f7..2f6e47b 100644 --- a/src/CacheEntry/CacheEntry.php +++ b/src/CacheEntry/CacheEntry.php @@ -60,6 +60,9 @@ public function expiresAt(?DateTimeInterface $expiration): static // null case $this->ttl = RedisCache::DAY_EXPIRATION_TIME; // 24h } + /** + * @todo optimization required here (pass $expiration as parameter) + */ $this->setDLC(); return $this; diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 8a36afd..c2d5149 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -14,12 +14,16 @@ * * Our Redis CacheEntryPool will typically be a redis hash * + * CRITICAL: * this brings limitation on expiration part so take it into account for your future design * (expiration on an entire pool only, redis version 7.4 (not free?) has the feature to hexpire fields inside a hash) * * + * + * @todo homogenize rework documentation through this package * -- * @todo dig into Redict ? (or just respect Salvatore's vision, see below) + * @todo dig into Valkey.io * -- * https://groups.google.com/g/redis-db/c/IqA3O8Fq494?pli=1 * @@ -100,6 +104,9 @@ public function deleteItem(string $key): bool } + /** + * @todo need a better return value taking deferred items into account + */ public function deleteItems(array $keys): bool { foreach ($keys as $key) { @@ -159,8 +166,6 @@ public function getItem(string $key): CacheItemInterface $item->hit(); } } - /* dump('getItem: ' . $item->getKey(). ' - TTL= ' . $item->getTTL() . ' - ' . (is_string($item->get()) ? $item->get() : print_r($item->get()))); - dump($item);*/ return $item; @@ -286,6 +291,7 @@ public function save(CacheItemInterface $item): bool $bln = $this->cache->storeToPool([$item->getKey() => $item->get()], $this->poolName); if ($bln && $item instanceof CacheEntry && $item->getTTL() > 0) { /** + * CRITICAL: * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ * /!\ expires entire pool! /!\ * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ @@ -347,6 +353,23 @@ public function commit(): bool } $this->deferredItems = []; + /** + * CRITICAL: + * + * Yes we are not handling expiration on bulked save here + * it is because expire the entire hash (the pool) + * is not ideal, it is a "temporary" workaround for now to pass + * cache integration tests.. + * I may disable the feature depending on the redis version used + * (as newer versions of redis do support hash field expiration but are + * no more free to use). For now there is only a disclaimer. + * + * I will also have to dig in, and test this package with Valkey... + * (maybe fork this package and make it exclusive to Valkey ?) + * + * @todo dig in, and test this package with Valkey + * @link https://valkey.io/blog/hash-fields-expiration Valkey + */ return $this->cache->storeToPool($deferred, $this->poolName); } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index ede56e1..a8a007f 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -14,10 +14,14 @@ * My implementation is based on Redis Hashes implying some technical limitations. * * - * + *CRITICAL: * Here we use the Hash implementation from redis. Expiration Time is set with the setHsetPoolExpiration method * on the entire Hash Set HASH_DB_PREFIX. $suffix (private HSET Pool in Redis, specified with $suffix * with those methods you can store and retrieve specific data linked together in a separate data set) + * THUS THE ENTIRE POOL (redis hash) is EXPIRED as there is no way to expire a hash field per field only + * the firsts redis server versions. + * + * @todo test valkey and reddict * * * @@ -236,8 +240,7 @@ public function deleteFromPool(array $keys, string $pool): bool * * * - * Expiration Time is set with this method on the entire Hash Set HASH_DB_PREFIX concatenate - * with a private $pool + * Expiration Time is set with this method on the entire Redis Hash : the pool $pool argument given. * * Caution: expired Hash SET will EXPIRE ALL SUBKEYS as well (even more recent entries) * From 373ab265135010922b6eda787df731a21d198f72 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 6 Nov 2025 11:24:37 +0100 Subject: [PATCH 32/32] forgot to cs, no interractive rebase osef --- src/CacheEntryPool/CacheEntryPool.php | 18 +++++++++--------- src/RedisEnhancedCache.php | 2 +- tests/Integration/CacheIntegrationTest.php | 4 ++-- tests/Integration/PoolIntegrationTest.php | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index c2d5149..f2a9a8f 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -14,12 +14,12 @@ * * Our Redis CacheEntryPool will typically be a redis hash * - * CRITICAL: + * CRITICAL: * this brings limitation on expiration part so take it into account for your future design * (expiration on an entire pool only, redis version 7.4 (not free?) has the feature to hexpire fields inside a hash) * * - * + * * @todo homogenize rework documentation through this package * -- * @todo dig into Redict ? (or just respect Salvatore's vision, see below) @@ -291,7 +291,7 @@ public function save(CacheItemInterface $item): bool $bln = $this->cache->storeToPool([$item->getKey() => $item->get()], $this->poolName); if ($bln && $item instanceof CacheEntry && $item->getTTL() > 0) { /** - * CRITICAL: + * CRITICAL: * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ * /!\ expires entire pool! /!\ * /!\/!\/!\/!\/!\/!\/!\/!\/!\/!\ @@ -355,20 +355,20 @@ public function commit(): bool /** * CRITICAL: - * - * Yes we are not handling expiration on bulked save here + * + * Yes we are not handling expiration on bulked save here * it is because expire the entire hash (the pool) * is not ideal, it is a "temporary" workaround for now to pass * cache integration tests.. - * I may disable the feature depending on the redis version used + * I may disable the feature depending on the redis version used * (as newer versions of redis do support hash field expiration but are * no more free to use). For now there is only a disclaimer. - * + * * I will also have to dig in, and test this package with Valkey... * (maybe fork this package and make it exclusive to Valkey ?) - * + * * @todo dig in, and test this package with Valkey - * @link https://valkey.io/blog/hash-fields-expiration Valkey + * @link https://valkey.io/blog/hash-fields-expiration Valkey */ return $this->cache->storeToPool($deferred, $this->poolName); } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index a8a007f..395ccc3 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -20,7 +20,7 @@ * with those methods you can store and retrieve specific data linked together in a separate data set) * THUS THE ENTIRE POOL (redis hash) is EXPIRED as there is no way to expire a hash field per field only * the firsts redis server versions. - * + * * @todo test valkey and reddict * * diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index 1fef265..05c8b1e 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -18,8 +18,8 @@ * Test PSR-16 implementation * * check @link https://github.com/php-cache/integration-tests - * - * + * + * * @todo clone those in order to test persistent connections too */ class CacheIntegrationTest extends SimpleCacheTest diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index aa05138..b183290 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -18,8 +18,8 @@ * Test PSR-6 implementation * * check @link https://github.com/php-cache/integration-tests - * - * + * + * * @todo clone those in order to test persistent connections too */ class PoolIntegrationTest extends CachePoolTest