From 75ae30c2639f46f66ff7f92474e65550a1eefef2 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 10 Dec 2025 18:30:13 +0100 Subject: [PATCH 01/45] starting ep4 with a CS commit --- src/RedisCache.php | 4 ++-- tests/Functional/functional.php | 2 +- tests/Integration/CacheIntegrationTest.php | 2 +- tests/Integration/CacheIntegrationWithPCTest.php | 2 +- tests/Integration/PoolIntegrationTest.php | 2 +- tests/Integration/PoolIntegrationWithPCTest.php | 2 +- tests/Unit/SimpleCacheRCTest.php | 10 +++++----- tests/Unit/SimpleCacheTest.php | 12 ++++++------ 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index cdb455c..f41a637 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -23,9 +23,9 @@ * * @todo I think we will have to refactor here too in order to pass the RedisAdapter as a parameter * or keep it as a class attribute ? - * + * * @todo and also clean and harmonize all those $redisResponse - * + * * @note I have also have some concerns on keys because redis can handle Bytes and we are only handling * strings (contracts from Psr\SimpleCache v3.0.0 interface) which is totally fine for my own use cases but... * diff --git a/tests/Functional/functional.php b/tests/Functional/functional.php index a60d3fd..1f91d8f 100644 --- a/tests/Functional/functional.php +++ b/tests/Functional/functional.php @@ -3,7 +3,7 @@ declare(strict_types=1); /** * @todo test some functional scenario like using byte / binaries or specific encoding as redis keys - * + * * @todo persistent !!!!!!!!!!!!! (integration suite + functional + units + automatize all those in ONE BIG SUITE !) * * @author Laurent LEGAZ diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index bc9142f..91f8232 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -303,7 +303,7 @@ public function createSimpleCache(): CacheInterface /** * display adapter class used (Predis or php-redis) - * + * * @todo work to display this before php units and test suite start */ if (!TestState::$adapterClassDisplayed) { diff --git a/tests/Integration/CacheIntegrationWithPCTest.php b/tests/Integration/CacheIntegrationWithPCTest.php index 4992380..bff7f91 100644 --- a/tests/Integration/CacheIntegrationWithPCTest.php +++ b/tests/Integration/CacheIntegrationWithPCTest.php @@ -309,7 +309,7 @@ public function createSimpleCache(): CacheInterface } /** * display adapter class used (Predis or php-redis) - * + * * @todo work to display this before php units and test suite start */ if (!TestState::$adapterClassDisplayed) { diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 6213647..779af00 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -89,7 +89,7 @@ public function createCachePool(): CacheItemPoolInterface /** * display adapter class used (Predis or php-redis) - * + * * @todo work to display this before php units and test suite start */ if (!TestState::$adapterClassDisplayed) { diff --git a/tests/Integration/PoolIntegrationWithPCTest.php b/tests/Integration/PoolIntegrationWithPCTest.php index 839c1c5..b5f2a17 100644 --- a/tests/Integration/PoolIntegrationWithPCTest.php +++ b/tests/Integration/PoolIntegrationWithPCTest.php @@ -97,7 +97,7 @@ public function createCachePool(): CacheItemPoolInterface /** * display adapter class used (Predis or php-redis) - * + * * @todo work to display this before php units and test suite start */ if (!TestState::$adapterClassDisplayed) { diff --git a/tests/Unit/SimpleCacheRCTest.php b/tests/Unit/SimpleCacheRCTest.php index 06b16b5..df0046b 100644 --- a/tests/Unit/SimpleCacheRCTest.php +++ b/tests/Unit/SimpleCacheRCTest.php @@ -16,7 +16,7 @@ * @todo * @todo REWORK UNITS (especially those with multiple sets) * @todo - * + * * @author Laurent LEGAZ */ class SimpleCacheRCTest extends RedisAdapterTestBase @@ -291,7 +291,7 @@ public function testSet() /** * * @todo rework this - * + * public function testSetMultiple() { @@ -340,7 +340,7 @@ public function testSetMultipleWithTtl() ->method('expire') ->willReturnCallback(function (string $key, int $i) use ($matcher, $expected, $ttl) { $this->assertLessThanOrEqual(\count($expected), $matcher->numberOfInvocations()); - // we could replace this by an in_array generic check + // we could replace this by an in_array generic check match ($matcher->numberOfInvocations()) { 1 => $this->assertEquals($expected[0], $key), 2 => $this->assertEquals($expected[1], $key), @@ -357,7 +357,7 @@ public function testSetMultipleWithTtl() } /** - * + * * public function testSetWithTtl() { @@ -377,7 +377,7 @@ public function testSetWithTtl() } /** - * + * * @return type */ protected function getSelfClient() diff --git a/tests/Unit/SimpleCacheTest.php b/tests/Unit/SimpleCacheTest.php index 51ddbb1..3201049 100644 --- a/tests/Unit/SimpleCacheTest.php +++ b/tests/Unit/SimpleCacheTest.php @@ -15,8 +15,8 @@ * * expect 1 more client command (list) because of the integrity check * (units are in forced paranoid mode for now @todo mb rework this here and in adapter) - * - * + * + * * @todo * @todo REWORK UNITS (especially those with multiple sets) * @todo @@ -283,7 +283,7 @@ public function testSet() * @link https://medium.com/@dotcom.software/unit-testing-closures-the-right-way-b982fc833bfa * * to redefine another object to emulate transaction part from predis and test behavior inside (mset, expire, etc.) - + public function testSetMultiple() { $values = ['do:exist1' => 'value1', 'do:exist2' => 'value2']; @@ -310,7 +310,7 @@ public function testSetMultiple() /** * @todo maybe enhance logger testing - + public function testSetWithTtl() { $key = 'testTTL'; @@ -331,7 +331,7 @@ public function testSetWithTtl() /** * @todo test TTL with DateInterval too !!! - + public function testSetMultipleWithTtl() { $values = ['do:exist1' => 'value1', 'do:exist2' => 'value2']; @@ -358,7 +358,7 @@ public function testSetMultipleWithTtl() } /** - * + * * @return type */ protected function getSelfClient() From 6b4bf746c0e71145316cee13525f1a6097d1c532 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 19 Dec 2025 14:57:44 +0100 Subject: [PATCH 02/45] RedisCache class will suffer to keep those todos a bit longuer --- src/RedisCache.php | 14 ++++++++------ src/RedisEnhancedCache.php | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index f41a637..24c6bbf 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -9,10 +9,6 @@ use LLegaz\Cache\Exception\InvalidValuesException; use LLegaz\Redis\RedisAdapter; use LLegaz\Redis\RedisClientInterface; -/** - * @todo refactor to hide those status and logic bound either to predis and php-redis - * (use adapter project client classes like mset => multipleSet method) - */ use Predis\Response\Status; use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; @@ -21,11 +17,16 @@ * Class RedisCache * PSR-16 implementation - Underlying Redis data type used is STRING * + * * @todo I think we will have to refactor here too in order to pass the RedisAdapter as a parameter * or keep it as a class attribute ? * + * + * @todo refactor to hide those predis Status and logic bound either to predis and php-redis + * (use adapter project client classes like mset => multipleSet method) * @todo and also clean and harmonize all those $redisResponse * + * * @note I have also have some concerns on keys because redis can handle Bytes and we are only handling * strings (contracts from Psr\SimpleCache v3.0.0 interface) which is totally fine for my own use cases but... * @@ -200,6 +201,7 @@ public function get(string $key, mixed $default = null): mixed } /** + * @todo remove this * @todo No, no, no ! Nein, nein, nein, don't do that here ! * we need another layer (another project ?) but it is NOT priority * @@ -404,7 +406,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 + * @todo check special cases (or special implementation) when key isn't a string ? (or not) * * @param string $key * @return void @@ -464,7 +466,7 @@ private function keyToString(mixed $key): string * @param mixed $value * @return bool */ - public function setCorrectValue(string &$value): mixed + protected function setCorrectValue(string &$value): mixed { try { $tmp = @unserialize(trim($value)); diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 8040e42..3706551 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -22,7 +22,8 @@ * the firsts redis server versions. * * @todo test valkey and reddict - * + * + * @todo and also clean and harmonize all those $redisResponse * * * It is to be noted that we use different terminology here from Redis project in the case of a HASH. From 82132883b1cd397bfdf902510dfa0ed187e6ac52 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 19 Dec 2025 16:57:21 +0100 Subject: [PATCH 03/45] tidying the code (mainly RedisEnhancedCache class, used with PSR-6 pools) --- src/RedisCache.php | 7 +-- src/RedisEnhancedCache.php | 68 +++++++-------------------- tests/Unit/RedisCacheTest.php | 26 ++++------ tests/Unit/RedisEnhancedCacheTest.php | 39 +++++++++++++++ tests/Unit/SimpleCacheTest.php | 1 + 5 files changed, 71 insertions(+), 70 deletions(-) create mode 100644 tests/Unit/RedisEnhancedCacheTest.php diff --git a/src/RedisCache.php b/src/RedisCache.php index 24c6bbf..e7f76a6 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -17,16 +17,16 @@ * Class RedisCache * PSR-16 implementation - Underlying Redis data type used is STRING * - * + * * @todo I think we will have to refactor here too in order to pass the RedisAdapter as a parameter * or keep it as a class attribute ? * - * + * * @todo refactor to hide those predis Status and logic bound either to predis and php-redis * (use adapter project client classes like mset => multipleSet method) * @todo and also clean and harmonize all those $redisResponse * - * + * * @note I have also have some concerns on keys because redis can handle Bytes and we are only handling * strings (contracts from Psr\SimpleCache v3.0.0 interface) which is totally fine for my own use cases but... * @@ -389,6 +389,7 @@ public function setMultiple(iterable $values, null|int|\DateInterval $ttl = self } /** + * @todo maybe test this serialization process more in depth * * @param string $key * @param mixed $value diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 3706551..d1ace1c 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -22,7 +22,7 @@ * the firsts redis server versions. * * @todo test valkey and reddict - * + * * @todo and also clean and harmonize all those $redisResponse * * @@ -43,7 +43,8 @@ class RedisEnhancedCache extends RedisCache private const DEFAULT_POOL = 'DEFAULT_Cache_Pool'; /** - * @todo rework this + * @todo rework this ? + * @todo document this * * * @param array $values A flat array of key => value pairs to store in GIVEN POOL name @@ -59,13 +60,14 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b /** * @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 maybe we can do something cleaner + * + * * check keys arguments are valid, and values are all stored as strings */ foreach ($values as $key => $value) { $this->checkKeyValuePair($key, $value); - $values[$key] = $value; + $values[$key] = $value; // I mean.. complexity is hidden here (data value are serialized) } /** @@ -78,10 +80,10 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b $key = array_keys($values)[0]; $value = isset($key) ? $values[$key] : (isset($values[0]) ? $values[0] : null); if (!$this->exist($value)) { - /*** - * @todo investiguate + /** + * @todo test this specific scenario (maybe apply it to hmset ?) */ - dd('I think it could be problematic'); + $this->throwUEx('The value: '. $value . ' isn\'t accepted'); // because all values are authorized except this predefined value to sort actual exisiting values internally... } if ($value) { //hset should returns the number of fields stored for a single key (always one here) @@ -93,16 +95,9 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b } /** - * - * - * - * @todo rework this - * - * @hint Redis return mostly strings with hget or hmget, maybe we should use serialize to preserve type - * - * @todo implement serialize with serializeToPool and cable those methods for the CacheEntryPool class to use it - * - * + * @todo document this (and the rest) + * + * * * @param int|string|array $key * @param string $pool the pool's name @@ -113,14 +108,6 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b */ public function fetchFromPool(mixed $key, string $pool = self::DEFAULT_POOL): mixed { - - /*** - * finish to implment unserializes properly you mofo - * @todo remove hexist - * use : - */ - //return unserialize($this->getRedis()->hget($pool, $key)); - switch (gettype($key)) { case 'integer': case 'string': @@ -140,9 +127,6 @@ public function fetchFromPool(mixed $key, string $pool = self::DEFAULT_POOL): mi $this->begin(); $data = array_combine( array_values($key), - /** - * @todo Test this scenario please - */ array_values($this->getRedis()->hmget($pool, $key)) ); @@ -168,6 +152,7 @@ public function fetchFromPool(mixed $key, string $pool = self::DEFAULT_POOL): mi } /** + * @todo document this * * @param string $key * @param string $pool @@ -199,23 +184,7 @@ public function hasInPool(string $key, string $pool = self::DEFAULT_POOL): bool } /** - * @todo rework this - * - * - * @param string $pool the pool's name - * @return array - * @throws LLegaz\Redis\Exception\ConnectionLostException - */ - public function fetchAllFromPool(string $pool = self::DEFAULT_POOL): array - { - if (!$this->isConnected()) { - $this->throwCLEx(); - } - - return $this->getRedis()->hgetall($pool); - } - - /** + * @todo document this * * @param array $keys * @param string $pool the pool's name @@ -239,14 +208,13 @@ public function deleteFromPool(array $keys, string $pool = self::DEFAULT_POOL): } /** - * @todo rework this - * - * - * * 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) * + * + * @todo investigate hash field expiration (valkey.io) + * * @param string $pool the pool's name * @param int $expirationTime * @return bool diff --git a/tests/Unit/RedisCacheTest.php b/tests/Unit/RedisCacheTest.php index 0893a64..804c4de 100644 --- a/tests/Unit/RedisCacheTest.php +++ b/tests/Unit/RedisCacheTest.php @@ -7,12 +7,19 @@ /** * RedisEnhancedCache tests * + * + * + * @todo implement this + * + * * @author Laurent LEGAZ */ class RedisCacheTest extends SimpleCacheTest { /** - * @todo implement this + * @todo Test RedisEnhancedCache::fetchFromPool in depth ! + * + * that is to say: HMGET */ public function testFetchFromPool() { @@ -24,24 +31,9 @@ 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 + public function storeToPool(array $values, string $pool): bool => multiple scenarios !! */ } diff --git a/tests/Unit/RedisEnhancedCacheTest.php b/tests/Unit/RedisEnhancedCacheTest.php new file mode 100644 index 0000000..fd8c945 --- /dev/null +++ b/tests/Unit/RedisEnhancedCacheTest.php @@ -0,0 +1,39 @@ +RedisEnhancedCache tests + * + * + * + * @todo implement this + * + * + * @author Laurent LEGAZ + */ +class RedisEnhancedCacheTest extends SimpleCacheTest +{ + /** + * @todo Test RedisEnhancedCache::fetchFromPool in depth ! + * + * that is to say: HMGET + */ + public function testFetchFromPool() + { + $this->assertTrue(true); + } + + public function testHasInPool() + { + $this->assertTrue(true); + } + + /* + public function setHsetPoolExpiration(string $pool, int $expirationTime = self::HOURS_EXPIRATION_TIME): bool + public function deleteFromPool(array $keys, string $pool): bool + public function storeToPool(array $values, string $pool): bool => multiple scenarios !! (test with RedisCache::DOES_NOT_EXIST var) + */ +} diff --git a/tests/Unit/SimpleCacheTest.php b/tests/Unit/SimpleCacheTest.php index 3201049..2517905 100644 --- a/tests/Unit/SimpleCacheTest.php +++ b/tests/Unit/SimpleCacheTest.php @@ -20,6 +20,7 @@ * @todo * @todo REWORK UNITS (especially those with multiple sets) * @todo + * @todo rename this class to RedisCacheTest to clarify * * @author Laurent LEGAZ */ From 700c9ca2856e2ae09a56644fc66dd3524eb845f7 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 19 Dec 2025 17:04:29 +0100 Subject: [PATCH 04/45] AI generated documentation --- src/RedisEnhancedCache.php | 315 +++++++++++++++++++++++++++++++++---- 1 file changed, 284 insertions(+), 31 deletions(-) diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index d1ace1c..901aae5 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -30,28 +30,119 @@ * for us : pool = key and key = field, but it is only semantic differences... * ------------------------------------------------------------------------------------------------------------ * + * Architecture Overview: + * This class extends RedisCache to provide pool-based caching functionality using Redis Hash data structures. + * Each pool represents a Redis Hash where multiple key-value pairs can be grouped together and managed + * as a logical unit with shared expiration time. + * + * Key Concepts: + * - Pool: A Redis Hash that groups related cache entries together (Redis terminology: "key") + * - Key: An individual field within a pool (Redis terminology: "field") + * - Value: The data associated with a key within a pool + * + * Terminology Mapping: + *
+ * This Class    | Redis Native  | Description
+ * --------------|---------------|------------------------------------------
+ * Pool          | Key           | The Hash structure name
+ * Key           | Field         | A field within the Hash
+ * Value         | Value         | The data stored in a Hash field
+ * 
+ * + * Usage Example: + * + * $cache = new RedisEnhancedCache($redis); + * + * // Store multiple values in a pool + * $cache->storeToPool([ + * 'user:123:name' => 'John Doe', + * 'user:123:email' => 'john@example.com' + * ], 'user_data'); + * + * // Set expiration for the entire pool + * $cache->setHsetPoolExpiration('user_data', 3600); + * + * // Fetch single or multiple values + * $name = $cache->fetchFromPool('user:123:name', 'user_data'); + * $userData = $cache->fetchFromPool(['user:123:name', 'user:123:email'], 'user_data'); + * + * + * Limitations: + * - Expiration applies to entire pools, not individual keys within a pool + * - All values are serialized for storage consistency + * - Keys and values must pass validation checks defined in parent class * * @package RedisCache * @author Laurent LEGAZ + * @version 1.0 + * @see RedisCache Parent class providing base Redis functionality + * @see https://redis.io/docs/data-types/hashes/ Redis Hash documentation */ class RedisEnhancedCache extends RedisCache { /** + * Default pool name used when no pool is specified in method calls. + * + * This constant defines the fallback pool name that will be used throughout + * the application when methods are called without explicitly specifying a pool. + * * @caution - modify this ONLY if you modify symetrically the default pool name * returned in CacheEntryPool::getPoolName protected method + * + * @var string */ private const DEFAULT_POOL = 'DEFAULT_Cache_Pool'; /** + * Stores multiple key-value pairs in a specified Redis Hash pool. + * + * This method allows batch insertion of cache entries into a named pool using Redis Hash operations. + * All values are automatically serialized before storage to ensure consistency. The method uses + * HMSET for multiple values and HSET for single values to optimize Redis operations. + * + * Behavior: + * - For multiple values (count > 1): Uses Redis HMSET command + * - For single value (count === 1): Uses Redis HSET command + * - For empty array: Returns false without Redis operation + * + * Data Processing: + * - Keys are validated against PSR compliance rules + * - Values are serialized using internal serialization mechanism + * - Both keys and values undergo validation before storage + * + * Usage Examples: + * + * // Store user session data + * $cache->storeToPool([ + * 'session_id' => 'abc123', + * 'user_id' => 456, + * 'login_time' => time() + * ], 'user_sessions'); + * + * // Store single configuration value + * $cache->storeToPool(['app_version' => '2.1.0'], 'app_config'); + * + * * @todo rework this ? * @todo document this - * + * @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 maybe we can do something cleaner + * @todo rework exception handling and returns + * @todo test this specific scenario (maybe apply it to hmset ?) * * @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 + * Keys must be strings or integers, values can be any serializable type + * @param string $pool the pool name - defaults to DEFAULT_POOL if not specified + * + * @return bool True on success, false on failure or when values array is empty + * + * @throws LLegaz\Redis\Exception\ConnectionLostException When Redis connection is lost during operation + * @throws LLegaz\Redis\Exception\LocalIntegrityException When data integrity checks fail + * @throws InvalidKeyException When key validation fails + * + * @see RedisCache::setMultiple() Similar method in parent class for non-pool operations + * @see RedisCache::checkKeysValidity() Key validation method */ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): bool { @@ -61,8 +152,8 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b * @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 maybe we can do something cleaner - * - * + * + * * check keys arguments are valid, and values are all stored as strings */ foreach ($values as $key => $value) { @@ -83,7 +174,7 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b /** * @todo test this specific scenario (maybe apply it to hmset ?) */ - $this->throwUEx('The value: '. $value . ' isn\'t accepted'); // because all values are authorized except this predefined value to sort actual exisiting values internally... + $this->throwUEx('The value: ' . $value . ' isn\'t accepted'); // because all values are authorized except this predefined value to sort actual exisiting values internally... } if ($value) { //hset should returns the number of fields stored for a single key (always one here) @@ -95,16 +186,60 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b } /** + * Retrieves one or more values from a specified Redis Hash pool. + * + * This method provides flexible retrieval of cache entries from a pool. It can fetch: + * - A single value when given a string or integer key + * - Multiple values when given an array of keys + * + * All retrieved values are automatically unserialized to restore their original data types. + * + * Return Behavior: + * - Single key (string/int): Returns the value or DOES_NOT_EXIST constant + * - Multiple keys (array): Returns associative array of key => value pairs + * - Non-existent keys: Replaced with DOES_NOT_EXIST constant in results + * - Invalid parameters: Throws InvalidKeyException + * + * Usage Examples: + * + * // Fetch single value + * $username = $cache->fetchFromPool('user:123:name', 'user_data'); + * + * // Fetch multiple values + * $userData = $cache->fetchFromPool([ + * 'user:123:name', + * 'user:123:email', + * 'user:123:role' + * ], 'user_data'); + * + * // Check if value exists + * if ($username !== RedisCache::DOES_NOT_EXIST) { + * echo "Username: $username"; + * } + * + * + * Data Processing: + * - Values are automatically deserialized upon retrieval + * - String values are converted back to their original types + * - Missing values are marked with DOES_NOT_EXIST constant + * - Array results maintain key association from input + * * @todo document this (and the rest) - * - * - * - * @param int|string|array $key - * @param string $pool the pool's name - * @return mixed - * @throws LLegaz\Redis\Exception\ConnectionLostException - * @throws LLegaz\Redis\Exception\LocalIntegrityException - * @throws LLegaz\Cache\Exception\InvalidKeyException + * + * @param int|string|array $key Single key (string/int) or array of keys to retrieve from the pool + * @param string $pool the pool's name - defaults to DEFAULT_POOL if not specified + * + * @return mixed Single value, associative array of values, or DOES_NOT_EXIST constant + * - For single key: Returns the value or DOES_NOT_EXIST + * - For multiple keys: Returns array with keys mapped to values/DOES_NOT_EXIST + * + * @throws LLegaz\Redis\Exception\ConnectionLostException When Redis connection is lost during operation + * @throws LLegaz\Redis\Exception\LocalIntegrityException When data integrity checks fail + * @throws LLegaz\Cache\Exception\InvalidKeyException When key format is invalid or unsupported type provided + * + * @see storeToPool() Method to store values in a pool + * @see hasInPool() Method to check if a key exists without retrieving value + * @see RedisCache::setCorrectValue() Internal deserialization method */ public function fetchFromPool(mixed $key, string $pool = self::DEFAULT_POOL): mixed { @@ -152,13 +287,47 @@ public function fetchFromPool(mixed $key, string $pool = self::DEFAULT_POOL): mi } /** + * Checks whether a specific key exists in a Redis Hash pool. + * + * This method provides efficient existence checking without retrieving the actual value, + * which is useful for conditional logic and validation operations. It uses Redis HEXISTS + * command for optimal performance. + * + * Implementation Notes: + * - Uses native Redis HEXISTS command for efficiency + * - Handles adapter differences between php-redis and predis clients + * - php-redis returns boolean true/false + * - predis returns integer 1/0 + * - Both are normalized to boolean return value + * + * Usage Examples: + * + * // Check before fetching + * if ($cache->hasInPool('user:123:name', 'user_data')) { + * $name = $cache->fetchFromPool('user:123:name', 'user_data'); + * } + * + * // Conditional storage + * if (!$cache->hasInPool('config:version', 'app_config')) { + * $cache->storeToPool(['config:version' => '1.0'], 'app_config'); + * } + * + * * @todo document this * - * @param string $key - * @param string $pool - * @return bool - * @throws LLegaz\Redis\Exception\ConnectionLostException - * @throws LLegaz\Redis\Exception\LocalIntegrityException + * @param string $key The key to check for existence in the pool + * @param string $pool The pool name - defaults to DEFAULT_POOL if not specified + * + * @return bool True if the key exists in the pool, false otherwise + * + * @throws LLegaz\Redis\Exception\ConnectionLostException When Redis connection is lost during operation + * @throws LLegaz\Redis\Exception\LocalIntegrityException When data integrity checks fail + * @throws InvalidKeyException When key validation fails + * + * @see fetchFromPool() Method to retrieve values if they exist + * @see storeToPool() Method to store values in a pool + * @see PredisClient Adapter class handling predis-specific behavior + * @see RedisClient Adapter class handling php-redis-specific behavior */ public function hasInPool(string $key, string $pool = self::DEFAULT_POOL): bool { @@ -184,12 +353,53 @@ public function hasInPool(string $key, string $pool = self::DEFAULT_POOL): bool } /** + * Deletes one or more keys from a specified Redis Hash pool. + * + * This method removes cache entries from a pool using Redis HDEL command. It supports + * batch deletion of multiple keys in a single operation for efficiency. The method + * validates all keys before attempting deletion. + * + * Behavior: + * - Uses Redis HDEL for atomic deletion + * - All keys are validated before deletion attempt + * - Non-existent keys are silently ignored (not treated as errors) + * - Returns true if Redis operation succeeds (even if some keys didn't exist) + * + * Performance Considerations: + * - Batch deletion is more efficient than individual deletions + * - Single Redis command for all keys reduces network overhead + * - Atomic operation ensures consistency + * + * Usage Examples: + * + * // Delete single entry + * $cache->deleteFromPool(['user:123:name'], 'user_data'); + * + * // Delete multiple entries + * $cache->deleteFromPool([ + * 'user:123:name', + * 'user:123:email', + * 'user:123:role' + * ], 'user_data'); + * + * // Clear specific cache entries after update + * $keysToInvalidate = ['product:456:price', 'product:456:stock']; + * $cache->deleteFromPool($keysToInvalidate, 'product_cache'); + * + * * @todo document this * - * @param array $keys - * @param string $pool the pool's name - * @return bool True on success - * @throws ConnectionLostException + * @param array $keys Array of key names to delete from the pool (must be strings or integers) + * @param string $pool the pool's name - defaults to DEFAULT_POOL if not specified + * + * @return bool True on success (returns true even if keys didn't exist), false on failure + * + * @throws ConnectionLostException When Redis connection is lost during operation + * @throws InvalidKeyException When any key validation fails + * + * @see storeToPool() Method to store values in a pool + * @see hasInPool() Method to check existence before deletion + * @see setHsetPoolExpiration() Method to expire entire pool instead of individual keys */ public function deleteFromPool(array $keys, string $pool = self::DEFAULT_POOL): bool { @@ -208,17 +418,60 @@ public function deleteFromPool(array $keys, string $pool = self::DEFAULT_POOL): } /** + * Sets an expiration time (TTL) for an entire Redis Hash pool. + * + * This method applies a Time To Live (TTL) to a complete pool using Redis EXPIRE command. + * When the TTL expires, the entire pool and all keys within it are automatically deleted by Redis. + * + * CRITICAL WARNING: + * Expiration applies to the ENTIRE pool as a single Redis Hash structure. All keys/fields + * within the pool will expire simultaneously, regardless of when individual entries were added. + * Redis Hash structures do not support per-field expiration in early Redis versions. + * + * Implications: + * - Setting expiration on a pool affects ALL current and future entries until expiration + * - Adding new entries to an expiring pool does NOT reset or extend the expiration time + * - Newer entries added to a pool will expire with older entries + * - To maintain different TTLs, use separate pools for entries with different lifetimes + * + * Usage Examples: + * + * // Set 1 hour expiration on user session pool + * $cache->storeToPool(['session_id' => 'abc123'], 'user_sessions'); + * $cache->setHsetPoolExpiration('user_sessions', 3600); + * + * // Set 24 hour expiration on cache pool + * $cache->storeToPool(['data' => $value], 'daily_cache'); + * $cache->setHsetPoolExpiration('daily_cache', 86400); + * + * // Using class constant for default expiration + * $cache->setHsetPoolExpiration('temp_data', self::HOURS_EXPIRATION_TIME); + * + * + * Best Practices: + * - Group entries with similar TTL requirements in the same pool + * - Set expiration immediately after creating/populating a pool + * - Use separate pools for data with different expiration requirements + * - Consider using standard Redis keys instead of hashes if per-key expiration is needed + * * 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) * - * * @todo investigate hash field expiration (valkey.io) * - * @param string $pool the pool's name - * @param int $expirationTime - * @return bool - * @throws ConnectionLostException + * @param string $pool the pool's name - defaults to DEFAULT_POOL if not specified + * @param int $expirationTime Time in seconds until the pool expires (must be > 0) + * Defaults to HOURS_EXPIRATION_TIME constant from parent class + * + * @return bool True if expiration was set successfully, false otherwise + * Returns false if expirationTime <= 0 or if Redis connection fails + * + * @throws ConnectionLostException When Redis connection is not available + * + * @see storeToPool() Method to add entries to a pool before setting expiration + * @see https://redis.io/commands/expire/ Redis EXPIRE command documentation + * @see https://valkey.io/topics/hash-expiration/ Valkey field-level expiration feature */ public function setHsetPoolExpiration(string $pool = self::DEFAULT_POOL, int $expirationTime = self::HOURS_EXPIRATION_TIME): bool { From 6b1749b3f6fe3dac9ffb195602ac64a2c918eaae Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 19 Dec 2025 17:30:57 +0100 Subject: [PATCH 05/45] more doc, README.md updated --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8775418..aaeb543 100644 --- a/README.md +++ b/README.md @@ -24,19 +24,20 @@ I will try to test and implement a pool key expiration for [Valkey.io](https://v **if you expire a pool key it will expire your entire pool SO BE EXTRA CAUTIOUS WITH THAT !** ### Basic usage +Of course you should do cleaner, proper implementation, the below example is not production ready, it is simplified and given ONLY for the sake of example ! ```php $cache = new LLegaz\Cache\RedisEnhancedCache(); -$cart = new \LLegaz\Cache\Pool\CacheEntryPool($cache); -$user = new \LLegaz\Cache\Pool\CacheEntryPool($cache, 'lolo'); +// retrieve user_id as $id +$user = new \LLegaz\Cache\Pool\CacheEntryPool($cache, 'user_data' . $id); +$cart = new \LLegaz\Cache\Pool\CacheEntryPool($cache 'user_cart' . $id); -$id = $user->getItem('id'); -if ($id->isHit()) { - $item = $cart->getItem('banana:' . $id->get()); - $item->set('mixed value'); +if ($this->bananaAdded()) { + $item = $cart->getItem('product:banana'); + $item->set(['count' => 3, 'unit_price' => .33, 'kg_price' => 1.99, 'total_price' => 0]]); // yeah today bananas are free $cart->save($item); -} else { - $id->set('the lolo id'); - $user->save($id); + $cartItem = $user->getItem('cart'); + // increment $cartItem here + $user->save($cartItem); } ``` From c661543f9ef6bdb6019e5962355eb9100df2f4e8 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 21 Dec 2025 11:55:07 +0100 Subject: [PATCH 06/45] documentation DONE on RedisEnchancedCache (class used for PSR-6 implementation) --- src/CacheEntryPool/CacheEntryPool.php | 6 +- src/RedisEnhancedCache.php | 107 ++++++++++---------------- 2 files changed, 42 insertions(+), 71 deletions(-) diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index 1af5c2f..b9e1564 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -53,8 +53,6 @@ class CacheEntryPool implements CacheItemPoolInterface */ private array $deferredItems = []; - protected const HASH_DB_PREFIX = 'Cache_Pool'; - /** * * @param Psr\SimpleCache\CacheInterface $cache @@ -386,8 +384,8 @@ public function printCachePool(): string protected function getPoolName(string $poolSuffix): string { return strlen($poolSuffix) ? - self::HASH_DB_PREFIX . "_{$poolSuffix}" : - 'DEFAULT_' . self::HASH_DB_PREFIX + RedisEnhancedCache::HASH_DB_PREFIX . "_{$poolSuffix}" : + RedisEnhancedCache::DEFAULT_POOL ; } diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index 901aae5..c0f9d03 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -15,19 +15,17 @@ * * *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. + * Here we use the Hash implementation from redis. Expiration Time is set with the + * setHsetPoolExpiration method on the entire Hash Set: + * THUS THE ENTIRE POOL (redis hash) is EXPIRED as there is no way to expire a hash per field only + * with the firsts redis server versions. + * See below Architecture Overview for more information. * - * @todo test valkey and reddict + * @todo test valkey (hash field expiration possible ?) and reddict * * @todo and also clean and harmonize all those $redisResponse * * - * 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... * ------------------------------------------------------------------------------------------------------------ * * Architecture Overview: @@ -42,11 +40,11 @@ * * Terminology Mapping: *
- * This Class    | Redis Native  | Description
- * --------------|---------------|------------------------------------------
- * Pool          | Key           | The Hash structure name
- * Key           | Field         | A field within the Hash
- * Value         | Value         | The data stored in a Hash field
+ *   |   This Clas     |   Redis Native   |    Description
+ *   |-----------------|------------------|--------------------------------------
+ *   | Pool            | Key              | The Hash structure name
+ *   | Key             | Field            | A field within the Hash
+ *   | Value           | Value            | The data stored in a Hash field
  * 
* * Usage Example: @@ -81,17 +79,18 @@ class RedisEnhancedCache extends RedisCache { /** - * Default pool name used when no pool is specified in method calls. - * - * This constant defines the fallback pool name that will be used throughout - * the application when methods are called without explicitly specifying a pool. + * @var string + */ + public const HASH_DB_PREFIX = 'Cache_Pool'; + + /** * - * @caution - modify this ONLY if you modify symetrically the default pool name - * returned in CacheEntryPool::getPoolName protected method + * This constant defines the fallback pool name that will be used + * when methods are called without explicitly specifying a pool. * * @var string */ - private const DEFAULT_POOL = 'DEFAULT_Cache_Pool'; + public const DEFAULT_POOL = 'DEFAULT_' . self::HASH_DB_PREFIX; /** * Stores multiple key-value pairs in a specified Redis Hash pool. @@ -106,7 +105,7 @@ class RedisEnhancedCache extends RedisCache * - For empty array: Returns false without Redis operation * * Data Processing: - * - Keys are validated against PSR compliance rules + * - Keys are validated against PSR compliance rules (except for the ":" character which is accepted) * - Values are serialized using internal serialization mechanism * - Both keys and values undergo validation before storage * @@ -114,17 +113,17 @@ class RedisEnhancedCache extends RedisCache * * // Store user session data * $cache->storeToPool([ - * 'session_id' => 'abc123', + * 'session_id' => 'def123', + * 'prev_session_id' => 'abc123', * 'user_id' => 456, * 'login_time' => time() - * ], 'user_sessions'); + * ], 'user:456:session'); * * // Store single configuration value * $cache->storeToPool(['app_version' => '2.1.0'], 'app_config'); * * * @todo rework this ? - * @todo document this * @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 maybe we can do something cleaner @@ -133,7 +132,7 @@ class RedisEnhancedCache extends RedisCache * * @param array $values A flat array of key => value pairs to store in GIVEN POOL name * Keys must be strings or integers, values can be any serializable type - * @param string $pool the pool name - defaults to DEFAULT_POOL if not specified + * @param string $pool the pool name * * @return bool True on success, false on failure or when values array is empty * @@ -189,7 +188,7 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b * Retrieves one or more values from a specified Redis Hash pool. * * This method provides flexible retrieval of cache entries from a pool. It can fetch: - * - A single value when given a string or integer key + * - A single value when given a scalar or an object key * - Multiple values when given an array of keys * * All retrieved values are automatically unserialized to restore their original data types. @@ -200,34 +199,15 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b * - Non-existent keys: Replaced with DOES_NOT_EXIST constant in results * - Invalid parameters: Throws InvalidKeyException * - * Usage Examples: - * - * // Fetch single value - * $username = $cache->fetchFromPool('user:123:name', 'user_data'); - * - * // Fetch multiple values - * $userData = $cache->fetchFromPool([ - * 'user:123:name', - * 'user:123:email', - * 'user:123:role' - * ], 'user_data'); - * - * // Check if value exists - * if ($username !== RedisCache::DOES_NOT_EXIST) { - * echo "Username: $username"; - * } - * - * * Data Processing: * - Values are automatically deserialized upon retrieval * - String values are converted back to their original types * - Missing values are marked with DOES_NOT_EXIST constant * - Array results maintain key association from input * - * @todo document this (and the rest) * - * @param int|string|array $key Single key (string/int) or array of keys to retrieve from the pool - * @param string $pool the pool's name - defaults to DEFAULT_POOL if not specified + * @param int|string|object|array $key Single key or array of keys to retrieve from the pool + * @param string $pool the pool's name * * @return mixed Single value, associative array of values, or DOES_NOT_EXIST constant * - For single key: Returns the value or DOES_NOT_EXIST @@ -313,10 +293,9 @@ public function fetchFromPool(mixed $key, string $pool = self::DEFAULT_POOL): mi * } * * - * @todo document this * * @param string $key The key to check for existence in the pool - * @param string $pool The pool name - defaults to DEFAULT_POOL if not specified + * @param string $pool The pool name * * @return bool True if the key exists in the pool, false otherwise * @@ -356,8 +335,7 @@ public function hasInPool(string $key, string $pool = self::DEFAULT_POOL): bool * Deletes one or more keys from a specified Redis Hash pool. * * This method removes cache entries from a pool using Redis HDEL command. It supports - * batch deletion of multiple keys in a single operation for efficiency. The method - * validates all keys before attempting deletion. + * batch deletion of multiple keys in a single operation for efficiency. * * Behavior: * - Uses Redis HDEL for atomic deletion @@ -381,20 +359,16 @@ public function hasInPool(string $key, string $pool = self::DEFAULT_POOL): bool * 'user:123:email', * 'user:123:role' * ], 'user_data'); - * - * // Clear specific cache entries after update - * $keysToInvalidate = ['product:456:price', 'product:456:stock']; - * $cache->deleteFromPool($keysToInvalidate, 'product_cache'); * * - * @todo document this * - * @param array $keys Array of key names to delete from the pool (must be strings or integers) - * @param string $pool the pool's name - defaults to DEFAULT_POOL if not specified + * @param array $keys Array of key names to delete from the pool (must be scalar or object, + * arrays aren't accepted for now) + * @param string $pool the pool's name * * @return bool True on success (returns true even if keys didn't exist), false on failure * - * @throws ConnectionLostException When Redis connection is lost during operation + * @throws ConnectionLostException If Redis connection was lost during operation * @throws InvalidKeyException When any key validation fails * * @see storeToPool() Method to store values in a pool @@ -438,14 +412,11 @@ public function deleteFromPool(array $keys, string $pool = self::DEFAULT_POOL): * * // Set 1 hour expiration on user session pool * $cache->storeToPool(['session_id' => 'abc123'], 'user_sessions'); - * $cache->setHsetPoolExpiration('user_sessions', 3600); + * $cache->setHsetPoolExpiration('user_sessions', RedisCache::HOUR_EXPIRATION_TIME); * * // Set 24 hour expiration on cache pool * $cache->storeToPool(['data' => $value], 'daily_cache'); - * $cache->setHsetPoolExpiration('daily_cache', 86400); - * - * // Using class constant for default expiration - * $cache->setHsetPoolExpiration('temp_data', self::HOURS_EXPIRATION_TIME); + * $cache->setHsetPoolExpiration('daily_cache', RedisCache::DAY_EXPIRATION_TIME); * * * Best Practices: @@ -454,13 +425,15 @@ public function deleteFromPool(array $keys, string $pool = self::DEFAULT_POOL): * - Use separate pools for data with different expiration requirements * - Consider using standard Redis keys instead of hashes if per-key expiration is needed * - * 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) + * + * Caution: again, to expire an Hash SET (a pool) would EXPIRE ALL SUBKEYS as well + * (all entries hash field, the entire pool will be cleared at the end of the TTL) * * @todo investigate hash field expiration (valkey.io) * - * @param string $pool the pool's name - defaults to DEFAULT_POOL if not specified + * + * @param string $pool the pool's name * @param int $expirationTime Time in seconds until the pool expires (must be > 0) * Defaults to HOURS_EXPIRATION_TIME constant from parent class * From 614eadef7ddd599bf0a37f20265bbe19d35a39d9 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 21 Dec 2025 14:53:28 +0100 Subject: [PATCH 07/45] Key validation reworked --- src/RedisCache.php | 40 ++++++++++++++++--- tests/Integration/CacheIntegrationTest.php | 5 ++- .../CacheIntegrationWithPCTest.php | 5 ++- tests/Integration/PoolIntegrationTest.php | 5 ++- .../Integration/PoolIntegrationWithPCTest.php | 5 ++- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index e7f76a6..1094abe 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -60,6 +60,19 @@ class RedisCache extends RedisAdapter implements CacheInterface public const DOES_NOT_EXIST = '%=%=% item does not exist %=%=%'; + /** + * Maximum key length: 8KB + * + * Permissive by design. We trust developers to make appropriate choices. + * + * PSR-16 permits extended lengths ("MAY support longer lengths"). + * 8KB accommodates URL-based keys and other realistic scenarios while + * remaining reasonable for Redis performance. + * + * If you need stricter validation, implement it at your application layer. + */ + private const MAX_KEY_LENGTH = 8192; + public function __construct( string $host = RedisClientInterface::DEFAULTS['host'], int $port = RedisClientInterface::DEFAULTS['port'], @@ -418,15 +431,32 @@ protected function checkKeyValidity(mixed &$key): void if (!is_string($key)) { $key = $this->keyToString($key); } + $len = strlen($key); - if (!$len) { - throw new InvalidKeyException('RedisCache says "Empty Key is forbidden"'); + + // Empty keys are ambiguous + if ($len === 0) { + throw new InvalidKeyException('Cache key cannot be empty'); } - //100KB maximum key size (4MB is REALLY too much for my needs) - if ($len > 102400) { - throw new InvalidKeyException('RedisCache says "Key is too big"'); + // Reasonable upper limit for performance + if ($len > self::MAX_KEY_LENGTH) { + throw new InvalidKeyException( + sprintf( + 'Cache key exceeds maximum length of %d characters (got %d)', + self::MAX_KEY_LENGTH, + $len + ) + ); } + + // Whitespace causes issues in Redis CLI and debugging + if (preg_match('/\s/', $key)) { + throw new InvalidKeyException('Cache key cannot contain whitespace'); + } + + // That's it. Redis handles everything else. + // We trust you to know what you're doing. } protected function checkKeysValidity(iterable $keys): array diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index 91f8232..b4f5cf8 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -25,7 +25,8 @@ class CacheIntegrationTest extends SimpleCacheTest public static function setUpBeforeClass(): void { - for ($i = 102500; $i > 0; $i--) { + //36 KB + for ($i = 36864; $i > 0; $i--) { self::$bigKey .= 'a'; } parent::setUpBeforeClass(); @@ -60,6 +61,7 @@ public static function invalidKeys() self::invalidArrayKeys(), [ [''], + ['key with withespace'], [self::$bigKey] ] ); @@ -78,6 +80,7 @@ public static function invalidArrayKeys() { return [ [''], + ['key with withespace'], [self::$bigKey], ]; } diff --git a/tests/Integration/CacheIntegrationWithPCTest.php b/tests/Integration/CacheIntegrationWithPCTest.php index bff7f91..4b4435c 100644 --- a/tests/Integration/CacheIntegrationWithPCTest.php +++ b/tests/Integration/CacheIntegrationWithPCTest.php @@ -25,7 +25,8 @@ class CacheIntegrationWithPCTest extends SimpleCacheTest public static function setUpBeforeClass(): void { - for ($i = 102500; $i > 0; $i--) { + //36 KB + for ($i = 36864; $i > 0; $i--) { self::$bigKey .= 'a'; } parent::setUpBeforeClass(); @@ -60,6 +61,7 @@ public static function invalidKeys() self::invalidArrayKeys(), [ [''], + ['key with withespace'], [self::$bigKey] ] ); @@ -78,6 +80,7 @@ public static function invalidArrayKeys() { return [ [''], + ['key with withespace'], [self::$bigKey], ]; } diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 779af00..bc2b4df 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -46,7 +46,8 @@ protected function setUp(): void public static function invalidKeys() { $bigKey = ''; - for ($i = 102500; $i > 0; $i--) { + //36 KB + for ($i = 36864; $i > 0; $i--) { $bigKey .= 'a'; } @@ -54,6 +55,7 @@ public static function invalidKeys() self::invalidArrayKeys(), [ [''], + ['key with withespace'], [$bigKey] ] ); @@ -71,6 +73,7 @@ public static function invalidKeys() public static function invalidArrayKeys() { return [ + ['key with withespace'], [''], ]; } diff --git a/tests/Integration/PoolIntegrationWithPCTest.php b/tests/Integration/PoolIntegrationWithPCTest.php index b5f2a17..48f14e8 100644 --- a/tests/Integration/PoolIntegrationWithPCTest.php +++ b/tests/Integration/PoolIntegrationWithPCTest.php @@ -46,7 +46,8 @@ protected function setUp(): void public static function invalidKeys() { $bigKey = ''; - for ($i = 102500; $i > 0; $i--) { + //36 KB + for ($i = 36864; $i > 0; $i--) { $bigKey .= 'a'; } @@ -54,6 +55,7 @@ public static function invalidKeys() self::invalidArrayKeys(), [ [''], + ['key with withespace'], [$bigKey] ] ); @@ -71,6 +73,7 @@ public static function invalidKeys() public static function invalidArrayKeys() { return [ + ['key with withespace'], [''], ]; } From c158113377f0eab328bfe0aa7e2fc1ded8a555c3 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 21 Dec 2025 14:58:56 +0100 Subject: [PATCH 08/45] adding github actions v1 --- .github/PR_TEMPLATE.md | 1 + .github/workflows/CI.yml | 133 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 .github/PR_TEMPLATE.md create mode 100644 .github/workflows/CI.yml diff --git a/.github/PR_TEMPLATE.md b/.github/PR_TEMPLATE.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/.github/PR_TEMPLATE.md @@ -0,0 +1 @@ + diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..6b3ef6c --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,133 @@ +# CI Workflow - Automated Testing +# +# This workflow runs automatically on: +# - Every push to 'develop' branch +# - Every Pull Request to 'main' or 'develop' +# +# What it does: +# - Tests the code on PHP 8.2 and 8.3 +# - Runs integration tests with Redis 7.2 +# - Generates code coverage report +# - Uploads coverage to Codecov (PHP 8.3 only) + +name: CI + +on: + # Trigger on push to develop (not main - it's protected) + push: + branches: [ develop ] + + # Trigger on all Pull Requests + pull_request: + branches: [ main, develop ] + + # Allow manual trigger from GitHub UI + workflow_dispatch: + +jobs: + tests: + # Job name - appears in GitHub Actions UI + # ${{ matrix.php }} gets replaced with actual PHP version (8.2 or 8.3) + name: Tests (PHP ${{ matrix.php }}) + + # Use Ubuntu (free on GitHub Actions) + runs-on: ubuntu-latest + + # Test matrix - creates one job per PHP version + strategy: + # Don't stop other jobs if one fails + fail-fast: false + + matrix: + # Test on these PHP versions + php: ['8.2', '8.3'] + + # Redis service - runs in a Docker container + services: + redis: + # Use Redis 7.2 Alpine (lightweight) + image: redis:7.2-alpine + + # Expose Redis port to the host + ports: + - 6379:6379 + + # Health check - wait until Redis is ready + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + # Job steps - executed sequentially + steps: + # Step 1: Checkout the repository code + - name: Checkout code + uses: actions/checkout@v4 + + # Step 2: Setup PHP with required extensions + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + # PHP version from matrix + php-version: ${{ matrix.php }} + + # Required PHP extensions + extensions: redis, json, mbstring + + # Enable Xdebug for code coverage + coverage: xdebug + + # Install Composer v2 + tools: composer:v2 + + # Step 3: Cache Composer dependencies (speeds up builds) + - name: Cache Composer dependencies + uses: actions/cache@v3 + with: + # Cache path + path: ~/.composer/cache + + # Cache key based on composer.lock hash + # If composer.lock changes, cache is invalidated + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + + # Fallback keys if exact match not found + restore-keys: ${{ runner.os }}-composer- + + # Step 4: Install project dependencies + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-interaction + + # Step 5: Validate composer.json structure + - name: Validate composer.json + run: composer validate --strict + + # Step 6: Display versions (helpful for debugging) + - name: Display versions + run: | + php -v + php -m | grep redis + composer --version + + # Step 7: Run integration tests + # SKIP_INTEGRATION_TESTS=false enables integration tests + - name: Run integration tests + run: composer test:integration + env: + REDIS_HOST: 127.0.0.1 + REDIS_PORT: 6379 + + # Step 8: Upload coverage (only for PHP 8.3 to avoid duplicates) + - name: Upload coverage to Codecov + if: matrix.php == '8.3' + uses: codecov/codecov-action@v3 + with: + # Coverage file generated by PHPUnit + files: ./coverage.xml + + # Don't fail the build if upload fails + fail_ci_if_error: false + + # Flags to identify this upload on Codecov + flags: php-${{ matrix.php }} From e2a9be1021ba65b3ab1a75850fd16e2dd7c5f0c9 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 21 Dec 2025 15:17:44 +0100 Subject: [PATCH 09/45] adding some contribution docs --- .github/PR_TEMPLATE.md | 65 +++++++ CONTRIBUTING.md | 404 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/.github/PR_TEMPLATE.md b/.github/PR_TEMPLATE.md index 8b13789..599a29b 100644 --- a/.github/PR_TEMPLATE.md +++ b/.github/PR_TEMPLATE.md @@ -1 +1,66 @@ +## Description + + +## Type of Change + +Please check the relevant option(s): + +- [ ] 🐛 Bug fix (non-breaking change that fixes an issue) +- [ ] ✨ New feature (non-breaking change that adds functionality) +- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] 📝 Documentation update +- [ ] 🧪 Test improvement +- [ ] ♻️ Code refactoring (no functional changes) +- [ ] 🎨 Code style update (formatting, renaming) +- [ ] ⚡ Performance improvement + +## Testing + + + +**Tests added/updated:** +- [ ] Unit tests +- [ ] Integration tests +- [ ] Manual testing performed + +**Test commands used:** +```bash +composer test:integration +composer test:unit +``` + +**Test results:** + + +## Checklist + +Please review and check all applicable items: + +- [ ] Code follows PSR-12 style guidelines +- [ ] All tests pass locally +- [ ] New code has appropriate test coverage +- [ ] Documentation has been updated (if applicable) +- [ ] CHANGELOG.md has been updated (if applicable) +- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) +- [ ] No warnings from `composer validate` +- [ ] PHPStan passes without errors + +## Screenshots (if applicable) + + + +## Related Issues + + + +## Additional Notes + + + +--- + +**Reviewer Notes:** + + +**Happy coding!** 🚀 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..374bceb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,404 @@ +# Contributing to RedisCache + +Thank you for your interest in contributing to RedisCache! 🎉 + +## Table of Contents + +- [Git Flow Workflow](#git-flow-workflow) +- [Development Setup](#development-setup) +- [Running Tests](#running-tests) +- [Code Quality Standards](#code-quality-standards) +- [Pull Request Process](#pull-request-process) +- [Questions](#questions) + +--- + +## Git Flow Workflow + +This project follows **Git Flow** for branch management. + +### Branch Structure +``` +main + ├─ Production-ready code + ├─ Protected branch (no direct push allowed) + ├─ Only updated via Pull Requests + └─ Tagged with version numbers (v1.0.0, v1.1.0, etc.) + +develop + ├─ Integration branch + ├─ Latest development code + ├─ Feature branches merge here first + └─ Base for new features + +feature/* + ├─ New features and improvements + ├─ Created from: develop + └─ Merged into: develop + +hotfix/* + ├─ Urgent production fixes + ├─ Created from: main + └─ Merged into: both main and develop + +release/* + ├─ Release preparation + ├─ Created from: develop + └─ Merged into: both main and develop +``` + +### Visual Flow +``` +feature/new-feature + ↓ + [commit & push] + ↓ + [GitHub Actions runs tests] 🧪 + ↓ + [Create Pull Request to develop] + ↓ + [Tests run again] 🧪 + ↓ + [Code Review] 👀 + ↓ + [Merge to develop] ✅ + ↓ + develop (stable) + ↓ + [Create Pull Request to main] + ↓ + [Tests run] 🧪 + ↓ + [Merge to main] ✅ + ↓ + [Tag release] 🏷️ v1.2.0 +``` + +--- + +## Common Workflows + +### Starting a New Feature +```bash +# 1. Make sure you have the latest develop +git checkout develop +git pull origin develop + +# 2. Create a feature branch +git checkout -b feature/my-awesome-feature + +# 3. Make your changes +# ... edit files ... + +# 4. Commit your changes +git add . +git commit -m "feat: add awesome feature" + +# 5. Push your branch +git push origin feature/my-awesome-feature + +# 6. Open a Pull Request on GitHub +# Source: feature/my-awesome-feature +# Target: develop +``` + +**GitHub Actions will automatically run tests!** ✅ + +### Release Process +```bash +# 1. When develop is stable and ready for release +git checkout develop +git pull origin develop + +# 2. Create a release branch +git checkout -b release/1.2.0 + +# 3. Update version in composer.json and CHANGELOG.md +# ... make version updates ... +git commit -m "chore: bump version to 1.2.0" + +# 4. Push and create PR to main +git push origin release/1.2.0 + +# 5. Create PR on GitHub: release/1.2.0 → main +# Tests will run ✅ +# After approval, merge to main + +# 6. Tag the release +git checkout main +git pull origin main +git tag -a v1.2.0 -m "Release version 1.2.0" +git push origin v1.2.0 + +# 7. Merge main back to develop +git checkout develop +git merge main +git push origin develop +``` + +### Hotfix Process (Urgent Bug) +```bash +# 1. Create hotfix from main +git checkout main +git pull origin main +git checkout -b hotfix/critical-bug-fix + +# 2. Fix the bug +# ... fix code ... +git commit -m "fix: resolve critical security issue" + +# 3. Push and create PR to main +git push origin hotfix/critical-bug-fix + +# 4. Create PR: hotfix/critical-bug-fix → main +# Tests run, after approval: merge + +# 5. Also merge to develop to keep in sync +git checkout develop +git merge main +git push origin develop + +# 6. Tag the hotfix release +git checkout main +git tag -a v1.2.1 -m "Hotfix version 1.2.1" +git push origin v1.2.1 +``` + +--- + +## Development Setup + +### Prerequisites + +- **PHP:** 8.2 or 8.3 +- **Redis:** 7.0 or higher +- **Composer:** 2.x +- **PHP Extensions:** `redis`, `json`, `mbstring` + +### Local Installation +```bash +# 1. Clone the repository +git clone https://github.com/llegaz/RedisCache.git +cd RedisCache + +# 2. Install dependencies +composer install + +# 3. Start Redis (using Docker) +docker run -d -p 6379:6379 redis:7.2-alpine + +# 4. Verify setup by running tests +composer test:integration +``` + +### Environment Variables +```bash +# Redis host (default: 127.0.0.1) +export REDIS_HOST=localhost + +# Redis port (default: 6379) +export REDIS_PORT=6379 + +# Redis adapter (predis or phpredis) +export REDIS_ADAPTER=phpredis + +# Persistent connection (true or false) +export REDIS_PERSISTENT=false +``` + +--- + +## Running Tests + +### Available Test Commands +```bash +# Run integration tests (requires Redis) +composer test:integration + +# Run unit tests (no Redis required) +composer test:unit + +# Run all tests +composer test + +# Generate coverage report +composer test:coverage +# → Opens coverage/index.html in your browser +``` + +### CI Testing + +Tests run automatically on: +- ✅ Every push to `develop` +- ✅ Every Pull Request +- ✅ PHP versions: 8.2 and 8.3 +- ✅ Redis version: 7.2 + +You can view test results in the [Actions tab](https://github.com/llegaz/RedisCache/actions). + +--- + +## Code Quality Standards + +### Coding Standards + +- **Style:** PSR-12 +- **Static Analysis:** PHPStan Level 8 +- **Testing:** PHPUnit with minimum 80% coverage + +### Before Committing +```bash +# Check code style +composer cs:check + +# Auto-fix code style issues +composer cs:fix + +# Run static analysis +composer stan + +# Run all quality checks +composer quality +``` + +### Commit Message Format + +We follow [Conventional Commits](https://www.conventionalcommits.org/): +```bash +feat: add support for Valkey compatibility +fix: resolve memory leak in persistent connections +docs: update README with new examples +test: add integration tests for Hash pools +chore: update dependencies +refactor: simplify key validation logic +perf: optimize serialization performance +style: fix code formatting +``` + +**Examples:** +```bash +# Good commits +git commit -m "feat: add 8KB key length support" +git commit -m "fix: handle whitespace in cache keys" +git commit -m "docs: add CONTRIBUTING.md" + +# Bad commits +git commit -m "updates" +git commit -m "fixed stuff" +git commit -m "WIP" +``` + +--- + +## Pull Request Process + +### PR Checklist + +Before submitting a Pull Request, ensure: + +- [ ] Code follows PSR-12 style guidelines +- [ ] All tests pass locally +- [ ] New tests added for new features +- [ ] Code coverage maintained or improved +- [ ] CHANGELOG.md updated (if applicable) +- [ ] Documentation updated (if applicable) +- [ ] Commit messages follow Conventional Commits + +### Creating a Pull Request + +1. **Push your branch** to GitHub +```bash + git push origin feature/my-feature +``` + +2. **Open Pull Request** on GitHub + - Go to: https://github.com/llegaz/RedisCache/pulls + - Click "New Pull Request" + - Select your branch + - Fill in the PR template + +3. **Wait for CI** + - GitHub Actions will run tests automatically + - All checks must pass ✅ + +4. **Code Review** + - Maintainer will review your code + - Address any feedback + - Push updates to the same branch + +5. **Merge** + - After approval and passing tests + - PR will be merged (usually squash merge) + - Your branch will be deleted + +### PR Review Timeline + +- **Initial review:** Within 2-3 days +- **Follow-up:** Within 1-2 days after updates + +--- + +## Setting Up Pre-commit Hooks (Optional) + +Automatically run quality checks before each commit: + +**Create `.git/hooks/pre-commit`:** +```bash +#!/bin/sh + +echo "🔍 Running pre-commit checks..." + +# Check code style +composer cs:check +if [ $? -ne 0 ]; then + echo "❌ Code style check failed. Run: composer cs:fix" + exit 1 +fi + +# Run static analysis +composer stan +if [ $? -ne 0 ]; then + echo "❌ PHPStan analysis failed." + exit 1 +fi + +# Run tests +composer test +if [ $? -ne 0 ]; then + echo "❌ Tests failed." + exit 1 +fi + +echo "✅ All pre-commit checks passed!" +exit 0 +``` + +**Make it executable:** +```bash +chmod +x .git/hooks/pre-commit +``` + +--- + +## Questions? + +- 💬 **General Questions:** Open a [Discussion](https://github.com/llegaz/RedisCache/discussions) +- 🐛 **Bug Reports:** Open an [Issue](https://github.com/llegaz/RedisCache/issues) +- ✨ **Feature Requests:** Open an [Issue](https://github.com/llegaz/RedisCache/issues) +- 📧 **Direct Contact:** laurent@legaz.eu + +--- + +## License + +By contributing, you agree that your contributions will be licensed under the same license as the project (see [LICENSE](LICENSE)). + +--- + +## Recognition + +All contributors will be recognized in the project README. Thank you for making RedisCache better! 🙏 + +--- + +**Happy coding!** 🚀 From aa54d14962c75dad7778724f6c064e90f98869e4 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 21 Dec 2025 15:28:09 +0100 Subject: [PATCH 10/45] more on contributing, README.md to rework --- .github/PR_TEMPLATE.md | 61 ++++---- .github/workflows/CI.yml | 76 +++++----- CONTRIBUTING.md | 307 ++++++++++++++++++++++++--------------- README.md | 58 +++++++- 4 files changed, 314 insertions(+), 188 deletions(-) diff --git a/.github/PR_TEMPLATE.md b/.github/PR_TEMPLATE.md index 599a29b..957bb16 100644 --- a/.github/PR_TEMPLATE.md +++ b/.github/PR_TEMPLATE.md @@ -1,6 +1,6 @@ ## Description - + ## Type of Change @@ -8,59 +8,62 @@ Please check the relevant option(s): - [ ] 🐛 Bug fix (non-breaking change that fixes an issue) - [ ] ✨ New feature (non-breaking change that adds functionality) -- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] 📝 Documentation update -- [ ] 🧪 Test improvement -- [ ] ♻️ Code refactoring (no functional changes) -- [ ] 🎨 Code style update (formatting, renaming) -- [ ] ⚡ Performance improvement +- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change) +- [ ] 📝 Documentation update (changes to README, CONTRIBUTING, or code comments) +- [ ] 🧪 Test improvement (new tests or improvements to existing tests) +- [ ] ♻️ Code refactoring (no functional changes, just code organization) +- [ ] 🎨 Code style update (formatting, variable naming, etc.) +- [ ] ⚡ Performance improvement (optimization without changing behavior) +- [ ] 🔧 Configuration change (CI, build tools, dependencies) ## Testing -**Tests added/updated:** +**Tests added or updated:** - [ ] Unit tests - [ ] Integration tests - [ ] Manual testing performed -**Test commands used:** +**Test commands executed:** ```bash composer test:integration composer test:unit +composer test:coverage ``` **Test results:** - + -## Checklist +## Quality Checks -Please review and check all applicable items: - -- [ ] Code follows PSR-12 style guidelines -- [ ] All tests pass locally +**Pre-submission checklist:** +- [ ] Code follows PSR-12 style guidelines (`composer cs:check` passes) +- [ ] All tests pass locally (`composer test` passes) +- [ ] Static analysis passes (`composer stan` passes) - [ ] New code has appropriate test coverage -- [ ] Documentation has been updated (if applicable) -- [ ] CHANGELOG.md has been updated (if applicable) -- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) -- [ ] No warnings from `composer validate` -- [ ] PHPStan passes without errors +- [ ] Documentation updated (if API changes) +- [ ] CHANGELOG.md updated (if user-facing changes) +- [ ] Commit messages follow Conventional Commits format +- [ ] `composer validate --strict` passes without errors -## Screenshots (if applicable) +## Screenshots or Examples - + ## Related Issues - + + -## Additional Notes +## Additional Context - + ---- +## Reviewer Notes -**Reviewer Notes:** - + + +--- -**Happy coding!** 🚀 +**See you space cowboy...** 🚀 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6b3ef6c..f964c74 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,19 +1,20 @@ # CI Workflow - Automated Testing # -# This workflow runs automatically on: -# - Every push to 'develop' branch -# - Every Pull Request to 'main' or 'develop' +# Triggers: +# - Push to develop branch +# - Pull Requests to main or develop +# - Manual trigger via GitHub UI # # What it does: -# - Tests the code on PHP 8.2 and 8.3 +# - Tests code on PHP 8.4.x and 8.5.x (latest patch versions) # - Runs integration tests with Redis 7.2 # - Generates code coverage report -# - Uploads coverage to Codecov (PHP 8.3 only) +# - Uploads coverage to Codecov name: CI on: - # Trigger on push to develop (not main - it's protected) + # Trigger on push to develop push: branches: [ develop ] @@ -26,42 +27,42 @@ on: jobs: tests: - # Job name - appears in GitHub Actions UI - # ${{ matrix.php }} gets replaced with actual PHP version (8.2 or 8.3) + # Job name displayed in GitHub Actions interface name: Tests (PHP ${{ matrix.php }}) - # Use Ubuntu (free on GitHub Actions) + # Use Ubuntu runner (free tier on GitHub Actions) runs-on: ubuntu-latest # Test matrix - creates one job per PHP version strategy: - # Don't stop other jobs if one fails + # Continue running other jobs even if one fails fail-fast: false matrix: - # Test on these PHP versions - php: ['8.2', '8.3'] + # Test on latest stable PHP versions + # Automatically uses latest patch: 8.4.x and 8.5.x + php: ['8.4', '8.5'] - # Redis service - runs in a Docker container + # Redis service - runs in Docker container services: redis: - # Use Redis 7.2 Alpine (lightweight) + # Use Redis 7.2 Alpine (lightweight image) image: redis:7.2-alpine - # Expose Redis port to the host + # Expose Redis port to the host machine ports: - 6379:6379 - # Health check - wait until Redis is ready + # Health check - wait until Redis is ready before running tests options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - # Job steps - executed sequentially + # Sequential steps executed for this job steps: - # Step 1: Checkout the repository code + # Step 1: Checkout repository code - name: Checkout code uses: actions/checkout@v4 @@ -69,65 +70,66 @@ jobs: - name: Setup PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 with: - # PHP version from matrix + # PHP version from matrix (automatically uses latest patch) php-version: ${{ matrix.php }} - # Required PHP extensions + # Required PHP extensions for the project extensions: redis, json, mbstring - # Enable Xdebug for code coverage + # Enable Xdebug for code coverage generation coverage: xdebug # Install Composer v2 tools: composer:v2 - # Step 3: Cache Composer dependencies (speeds up builds) + # Step 3: Cache Composer dependencies (speeds up subsequent builds) - name: Cache Composer dependencies uses: actions/cache@v3 with: - # Cache path + # Composer cache directory location path: ~/.composer/cache - # Cache key based on composer.lock hash - # If composer.lock changes, cache is invalidated + # Cache key based on OS and composer.lock hash + # Cache invalidates when composer.lock changes key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - # Fallback keys if exact match not found + # Fallback cache keys if exact match not found restore-keys: ${{ runner.os }}-composer- - # Step 4: Install project dependencies + # Step 4: Install project dependencies via Composer - name: Install dependencies run: composer install --prefer-dist --no-progress --no-interaction - # Step 5: Validate composer.json structure - - name: Validate composer.json + # Step 5: Validate composer.json and composer.lock structure + - name: Validate composer files run: composer validate --strict - # Step 6: Display versions (helpful for debugging) + # Step 6: Display installed versions (helpful for debugging CI failures) - name: Display versions run: | php -v php -m | grep redis composer --version - # Step 7: Run integration tests - # SKIP_INTEGRATION_TESTS=false enables integration tests + # Step 7: Run integration tests with Redis + # Environment variables configure Redis connection for tests - name: Run integration tests run: composer test:integration env: REDIS_HOST: 127.0.0.1 REDIS_PORT: 6379 - # Step 8: Upload coverage (only for PHP 8.3 to avoid duplicates) + # Step 8: Upload code coverage report to Codecov + # Only runs for PHP 8.5 to avoid duplicate coverage reports - name: Upload coverage to Codecov - if: matrix.php == '8.3' + if: matrix.php == '8.5' uses: codecov/codecov-action@v3 with: - # Coverage file generated by PHPUnit + # Coverage file generated by PHPUnit (--coverage-clover flag) files: ./coverage.xml - # Don't fail the build if upload fails + # Don't fail the build if coverage upload fails fail_ci_if_error: false - # Flags to identify this upload on Codecov + # Tags for organizing coverage reports on Codecov dashboard flags: php-${{ matrix.php }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 374bceb..6474719 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,14 +22,14 @@ This project follows **Git Flow** for branch management. main ├─ Production-ready code ├─ Protected branch (no direct push allowed) - ├─ Only updated via Pull Requests - └─ Tagged with version numbers (v1.0.0, v1.1.0, etc.) + ├─ Updated only via Pull Requests + └─ Tagged with semantic versions (v1.0.0, v1.1.0, etc.) develop - ├─ Integration branch + ├─ Integration branch for ongoing development ├─ Latest development code - ├─ Feature branches merge here first - └─ Base for new features + ├─ Base branch for all new features + └─ Feature branches merge here first feature/* ├─ New features and improvements @@ -39,35 +39,35 @@ feature/* hotfix/* ├─ Urgent production fixes ├─ Created from: main - └─ Merged into: both main and develop + └─ Merged into: both main AND develop release/* - ├─ Release preparation + ├─ Release preparation and version bumps ├─ Created from: develop - └─ Merged into: both main and develop + └─ Merged into: both main AND develop ``` -### Visual Flow +### Visual Workflow ``` -feature/new-feature +feature/awesome-feature ↓ [commit & push] ↓ [GitHub Actions runs tests] 🧪 ↓ - [Create Pull Request to develop] + [Create Pull Request → develop] ↓ - [Tests run again] 🧪 + [Tests run on PR] 🧪 ↓ [Code Review] 👀 ↓ [Merge to develop] ✅ ↓ - develop (stable) + develop (stable integration branch) ↓ - [Create Pull Request to main] + [Create Pull Request → main] ↓ - [Tests run] 🧪 + [Final tests run] 🧪 ↓ [Merge to main] ✅ ↓ @@ -80,33 +80,33 @@ feature/new-feature ### Starting a New Feature ```bash -# 1. Make sure you have the latest develop +# 1. Ensure you have the latest develop branch git checkout develop git pull origin develop -# 2. Create a feature branch +# 2. Create a new feature branch git checkout -b feature/my-awesome-feature # 3. Make your changes -# ... edit files ... +# ... edit files, write code ... -# 4. Commit your changes +# 4. Commit your changes using Conventional Commits format git add . git commit -m "feat: add awesome feature" -# 5. Push your branch +# 5. Push your feature branch to GitHub git push origin feature/my-awesome-feature # 6. Open a Pull Request on GitHub -# Source: feature/my-awesome-feature -# Target: develop +# Source branch: feature/my-awesome-feature +# Target branch: develop +# +# GitHub Actions will automatically run tests on your PR! ✅ ``` -**GitHub Actions will automatically run tests!** ✅ - ### Release Process ```bash -# 1. When develop is stable and ready for release +# 1. Ensure develop branch is stable and ready for release git checkout develop git pull origin develop @@ -114,52 +114,50 @@ git pull origin develop git checkout -b release/1.2.0 # 3. Update version in composer.json and CHANGELOG.md -# ... make version updates ... +# ... make version changes ... +git add composer.json CHANGELOG.md git commit -m "chore: bump version to 1.2.0" -# 4. Push and create PR to main +# 4. Push release branch and create Pull Request to main git push origin release/1.2.0 +# Open PR on GitHub: release/1.2.0 → main -# 5. Create PR on GitHub: release/1.2.0 → main -# Tests will run ✅ -# After approval, merge to main - -# 6. Tag the release +# 5. After PR approval and merge to main, tag the release git checkout main git pull origin main git tag -a v1.2.0 -m "Release version 1.2.0" git push origin v1.2.0 -# 7. Merge main back to develop +# 6. Merge main back to develop to keep branches in sync git checkout develop git merge main git push origin develop ``` -### Hotfix Process (Urgent Bug) +### Hotfix Process (Urgent Production Bug) ```bash -# 1. Create hotfix from main +# 1. Create hotfix branch from main git checkout main git pull origin main -git checkout -b hotfix/critical-bug-fix +git checkout -b hotfix/critical-security-fix -# 2. Fix the bug +# 2. Fix the urgent bug # ... fix code ... -git commit -m "fix: resolve critical security issue" - -# 3. Push and create PR to main -git push origin hotfix/critical-bug-fix +git add . +git commit -m "fix: resolve critical security vulnerability" -# 4. Create PR: hotfix/critical-bug-fix → main -# Tests run, after approval: merge +# 3. Push hotfix branch and create Pull Request to main +git push origin hotfix/critical-security-fix +# Open PR on GitHub: hotfix/critical-security-fix → main -# 5. Also merge to develop to keep in sync +# 4. After merge to main, also merge to develop to keep in sync git checkout develop git merge main git push origin develop -# 6. Tag the hotfix release +# 5. Tag the hotfix release git checkout main +git pull origin main git tag -a v1.2.1 -m "Hotfix version 1.2.1" git push origin v1.2.1 ``` @@ -170,21 +168,26 @@ git push origin v1.2.1 ### Prerequisites -- **PHP:** 8.2 or 8.3 +- **PHP:** 8.4 or 8.5 (latest stable versions) - **Redis:** 7.0 or higher - **Composer:** 2.x - **PHP Extensions:** `redis`, `json`, `mbstring` +**PHP 8.5 Information:** +- Released: November 20, 2024 +- Current stable: 8.5.x +- New features: Pipe operator (`|>`), URI extension, performance improvements + ### Local Installation ```bash # 1. Clone the repository git clone https://github.com/llegaz/RedisCache.git cd RedisCache -# 2. Install dependencies +# 2. Install Composer dependencies composer install -# 3. Start Redis (using Docker) +# 3. Start Redis server (using Docker) docker run -d -p 6379:6379 redis:7.2-alpine # 4. Verify setup by running tests @@ -192,6 +195,8 @@ composer test:integration ``` ### Environment Variables + +Configure these environment variables for local testing: ```bash # Redis host (default: 127.0.0.1) export REDIS_HOST=localhost @@ -199,10 +204,10 @@ export REDIS_HOST=localhost # Redis port (default: 6379) export REDIS_PORT=6379 -# Redis adapter (predis or phpredis) +# Redis adapter: 'predis' or 'phpredis' export REDIS_ADAPTER=phpredis -# Persistent connection (true or false) +# Enable persistent connection: 'true' or 'false' export REDIS_PERSISTENT=false ``` @@ -212,7 +217,7 @@ export REDIS_PERSISTENT=false ### Available Test Commands ```bash -# Run integration tests (requires Redis) +# Run integration tests (requires Redis server running) composer test:integration # Run unit tests (no Redis required) @@ -221,70 +226,93 @@ composer test:unit # Run all tests composer test -# Generate coverage report +# Generate HTML coverage report (opens in browser) composer test:coverage -# → Opens coverage/index.html in your browser ``` -### CI Testing +### Continuous Integration -Tests run automatically on: -- ✅ Every push to `develop` -- ✅ Every Pull Request -- ✅ PHP versions: 8.2 and 8.3 +Tests run automatically on GitHub Actions for: +- ✅ Every push to `develop` branch +- ✅ Every Pull Request to `main` or `develop` +- ✅ PHP versions: 8.4.x and 8.5.x (latest patches) - ✅ Redis version: 7.2 -You can view test results in the [Actions tab](https://github.com/llegaz/RedisCache/actions). +View test results and build status: [GitHub Actions](https://github.com/llegaz/RedisCache/actions) --- ## Code Quality Standards -### Coding Standards +### Standards -- **Style:** PSR-12 +- **Code Style:** PSR-12 - **Static Analysis:** PHPStan Level 8 -- **Testing:** PHPUnit with minimum 80% coverage +- **Testing:** PHPUnit with minimum 80% code coverage +- **Documentation:** PHPDoc for all public methods -### Before Committing +### Quality Check Commands ```bash -# Check code style +# Check code style (dry run - doesn't modify files) composer cs:check -# Auto-fix code style issues +# Automatically fix code style issues composer cs:fix -# Run static analysis +# Run static analysis with PHPStan composer stan -# Run all quality checks +# Run all quality checks at once composer quality ``` ### Commit Message Format -We follow [Conventional Commits](https://www.conventionalcommits.org/): -```bash -feat: add support for Valkey compatibility -fix: resolve memory leak in persistent connections -docs: update README with new examples -test: add integration tests for Hash pools -chore: update dependencies -refactor: simplify key validation logic -perf: optimize serialization performance -style: fix code formatting +This project follows [Conventional Commits](https://www.conventionalcommits.org/) specification: + +**Format:** ``` +(): + +[optional body] + +[optional footer] +``` + +**Types:** +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation changes +- `test`: Test additions or modifications +- `chore`: Maintenance tasks (dependencies, build, etc.) +- `refactor`: Code refactoring without behavior changes +- `perf`: Performance improvements +- `style`: Code style changes (formatting, naming) **Examples:** ```bash -# Good commits -git commit -m "feat: add 8KB key length support" -git commit -m "fix: handle whitespace in cache keys" -git commit -m "docs: add CONTRIBUTING.md" +# Feature addition +git commit -m "feat: add support for 8KB key length limit" -# Bad commits +# Bug fix +git commit -m "fix: handle whitespace correctly in cache keys" + +# Documentation +git commit -m "docs: add contributing guidelines and CI setup" + +# Performance improvement +git commit -m "perf: optimize serialization in storeToPool method" +``` + +**Bad examples (avoid these):** +```bash +# ❌ Too vague git commit -m "updates" + +# ❌ Not descriptive git commit -m "fixed stuff" + +# ❌ Work in progress (don't commit WIP to shared branches) git commit -m "WIP" ``` @@ -292,113 +320,150 @@ git commit -m "WIP" ## Pull Request Process -### PR Checklist +### Pre-submission Checklist Before submitting a Pull Request, ensure: - [ ] Code follows PSR-12 style guidelines -- [ ] All tests pass locally -- [ ] New tests added for new features +- [ ] All tests pass locally (`composer test`) +- [ ] New tests added for new features or bug fixes - [ ] Code coverage maintained or improved -- [ ] CHANGELOG.md updated (if applicable) -- [ ] Documentation updated (if applicable) -- [ ] Commit messages follow Conventional Commits +- [ ] Documentation updated if API changes +- [ ] CHANGELOG.md updated for user-facing changes +- [ ] Commits follow Conventional Commits format +- [ ] `composer validate --strict` passes without errors +- [ ] PHPStan analysis passes (`composer stan`) ### Creating a Pull Request -1. **Push your branch** to GitHub +**Step 1: Push your branch** ```bash - git push origin feature/my-feature +git push origin feature/my-feature ``` -2. **Open Pull Request** on GitHub - - Go to: https://github.com/llegaz/RedisCache/pulls - - Click "New Pull Request" - - Select your branch - - Fill in the PR template +**Step 2: Open Pull Request on GitHub** +- Navigate to: https://github.com/llegaz/RedisCache/pulls +- Click "New Pull Request" +- Select your branch as source +- Select `develop` as target (or `main` for hotfixes) +- Fill in the Pull Request template -3. **Wait for CI** - - GitHub Actions will run tests automatically - - All checks must pass ✅ +**Step 3: Automated Checks** +- GitHub Actions will automatically run the full test suite +- All status checks must pass ✅ before merge +- Review any failed checks and fix issues -4. **Code Review** - - Maintainer will review your code - - Address any feedback - - Push updates to the same branch +**Step 4: Code Review** +- Wait for maintainer review (typically 2-3 days) +- Address any feedback or requested changes +- Push additional commits to the same branch +- Tests will run again automatically -5. **Merge** - - After approval and passing tests - - PR will be merged (usually squash merge) - - Your branch will be deleted +**Step 5: Merge** +- After approval and passing tests, PR will be merged +- Merge strategy: usually squash and merge +- Source branch will be automatically deleted after merge -### PR Review Timeline +### Review Timeline -- **Initial review:** Within 2-3 days -- **Follow-up:** Within 1-2 days after updates +- **Initial review:** Within 2-3 business days +- **Follow-up reviews:** Within 1-2 business days after updates +- **Emergency hotfixes:** Within 24 hours --- -## Setting Up Pre-commit Hooks (Optional) +## Pre-commit Hooks (Optional but Recommended) -Automatically run quality checks before each commit: +Automatically run quality checks before each commit to catch issues early: -**Create `.git/hooks/pre-commit`:** +**Create `.git/hooks/pre-commit` file:** ```bash #!/bin/sh -echo "🔍 Running pre-commit checks..." +echo "🔍 Running pre-commit quality checks..." # Check code style +echo "→ Checking code style (PSR-12)..." composer cs:check if [ $? -ne 0 ]; then - echo "❌ Code style check failed. Run: composer cs:fix" + echo "❌ Code style check failed." + echo " Run 'composer cs:fix' to automatically fix issues." exit 1 fi # Run static analysis +echo "→ Running PHPStan static analysis..." composer stan if [ $? -ne 0 ]; then echo "❌ PHPStan analysis failed." + echo " Fix the reported issues before committing." exit 1 fi # Run tests +echo "→ Running test suite..." composer test if [ $? -ne 0 ]; then echo "❌ Tests failed." + echo " All tests must pass before committing." exit 1 fi echo "✅ All pre-commit checks passed!" +echo " Proceeding with commit..." exit 0 ``` -**Make it executable:** +**Make the hook executable:** ```bash chmod +x .git/hooks/pre-commit ``` +**Note:** Pre-commit hooks are local to your repository clone and not tracked by Git. + --- -## Questions? +## Questions and Support + +### Getting Help - 💬 **General Questions:** Open a [Discussion](https://github.com/llegaz/RedisCache/discussions) -- 🐛 **Bug Reports:** Open an [Issue](https://github.com/llegaz/RedisCache/issues) -- ✨ **Feature Requests:** Open an [Issue](https://github.com/llegaz/RedisCache/issues) +- 🐛 **Bug Reports:** Open an [Issue](https://github.com/llegaz/RedisCache/issues) with bug report template +- ✨ **Feature Requests:** Open an [Issue](https://github.com/llegaz/RedisCache/issues) with feature request template - 📧 **Direct Contact:** laurent@legaz.eu +### Reporting Bugs + +When reporting bugs, please include: +- PHP version (`php -v`) +- Redis version +- Adapter used (Predis or phpredis) +- Steps to reproduce the issue +- Expected vs actual behavior +- Any relevant error messages or stack traces + +### Proposing Features + +When proposing new features: +- Explain the use case and problem it solves +- Describe the proposed solution +- Consider backwards compatibility implications +- Be open to feedback and alternative approaches + --- ## License -By contributing, you agree that your contributions will be licensed under the same license as the project (see [LICENSE](LICENSE)). +By contributing to RedisCache, you agree that your contributions will be licensed under the same license as the project (see [LICENSE](LICENSE) file). --- ## Recognition -All contributors will be recognized in the project README. Thank you for making RedisCache better! 🙏 +All contributors are recognized and listed in the project README. Thank you for helping make RedisCache better! 🙏 + +Your contributions, whether code, documentation, bug reports, or feature ideas, are valued and appreciated. --- -**Happy coding!** 🚀 +**See you space cowboy...** 🚀 diff --git a/README.md b/README.md index aaeb543..28d1baa 100644 --- a/README.md +++ b/README.md @@ -93,4 +93,60 @@ Stay tuned, by following me on github, for new features using [predis](https://g --- @see you space cowboy ---- \ No newline at end of file +--- + +## Contributing + +We welcome contributions! This project follows **Git Flow** workflow. + +### Quick Start +```bash +# Create feature branch from develop +git checkout -b feature/my-feature develop + +# Make changes and commit +git commit -m "feat: add new feature" + +# Push and create Pull Request +git push origin feature/my-feature +``` + +For complete guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md) which covers: +- Git Flow workflow in detail +- Development environment setup +- Testing requirements and commands +- Code quality standards (PSR-12, PHPStan) +- Pull Request process and review timeline + +### Development Commands +```bash +# Install dependencies +composer install + +# Run tests +composer test:integration + +# Code quality +composer cs:check # Check style +composer cs:fix # Fix style +composer stan # Static analysis +composer quality # Run all checks +``` + +### CI/CD Status + +[![CI](https://github.com/llegaz/RedisCache/workflows/CI/badge.svg)](https://github.com/llegaz/RedisCache/actions) +[![codecov](https://codecov.io/gh/llegaz/RedisCache/branch/main/graph/badge.svg)](https://codecov.io/gh/llegaz/RedisCache) + +**Automated testing on:** +- 🐘 PHP 8.4.x, 8.5.x (latest stable versions) +- 📦 Redis 7.2 +- 🔌 Both Predis and phpredis adapters + +All Pull Requests are automatically tested before merge. + +[View test results →](https://github.com/llegaz/RedisCache/actions) + +--- + +**See you space cowboy...** 🚀 \ No newline at end of file From 88aea5ccf4afa80dbd62cd3eb73e1591e6e1bcdd Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Mon, 22 Dec 2025 12:53:19 +0100 Subject: [PATCH 11/45] PR template OK --- .github/PR_TEMPLATE.md | 65 +++++++++++++----------------------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/.github/PR_TEMPLATE.md b/.github/PR_TEMPLATE.md index 957bb16..ff9f207 100644 --- a/.github/PR_TEMPLATE.md +++ b/.github/PR_TEMPLATE.md @@ -4,66 +4,41 @@ ## Type of Change -Please check the relevant option(s): +-Please check the relevant option(s): -- [ ] 🐛 Bug fix (non-breaking change that fixes an issue) -- [ ] ✨ New feature (non-breaking change that adds functionality) -- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change) -- [ ] 📝 Documentation update (changes to README, CONTRIBUTING, or code comments) -- [ ] 🧪 Test improvement (new tests or improvements to existing tests) -- [ ] ♻️ Code refactoring (no functional changes, just code organization) -- [ ] 🎨 Code style update (formatting, variable naming, etc.) -- [ ] ⚡ Performance improvement (optimization without changing behavior) -- [ ] 🔧 Configuration change (CI, build tools, dependencies) +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] 💥 Breaking change +- [ ] 📝 Documentation +- [ ] 🧪 Tests +- [ ] ♻️ Refactoring +- [ ] ⚡ Performance +- [ ] 🔧 Configuration change ## Testing - - -**Tests added or updated:** - [ ] Unit tests - [ ] Integration tests -- [ ] Manual testing performed +- [ ] Manual testing -**Test commands executed:** +Code style and test suite exectuion. ```bash -composer test:integration -composer test:unit -composer test:coverage +composer cs +composer test ``` -**Test results:** - - -## Quality Checks - -**Pre-submission checklist:** -- [ ] Code follows PSR-12 style guidelines (`composer cs:check` passes) -- [ ] All tests pass locally (`composer test` passes) -- [ ] Static analysis passes (`composer stan` passes) -- [ ] New code has appropriate test coverage -- [ ] Documentation updated (if API changes) -- [ ] CHANGELOG.md updated (if user-facing changes) -- [ ] Commit messages follow Conventional Commits format -- [ ] `composer validate --strict` passes without errors +## Checklist -## Screenshots or Examples - - +- [ ] PSR-12 compliant (CS) +- [ ] Tests pass +- [ ] Coverage maintained +- [ ] Docs updated ## Related Issues - - - -## Additional Context - - - -## Reviewer Notes - - + --- **See you space cowboy...** 🚀 + From ca4eeceae74eea7c81824145c6ee33d067c0bb5b Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 23 Dec 2025 12:12:02 +0100 Subject: [PATCH 12/45] ci.yml OK --- .github/workflows/CI.yml | 76 +++++++--------------------------------- 1 file changed, 12 insertions(+), 64 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f964c74..e47673f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,68 +1,36 @@ -# CI Workflow - Automated Testing -# -# Triggers: -# - Push to develop branch -# - Pull Requests to main or develop -# - Manual trigger via GitHub UI -# -# What it does: -# - Tests code on PHP 8.4.x and 8.5.x (latest patch versions) -# - Runs integration tests with Redis 7.2 -# - Generates code coverage report -# - Uploads coverage to Codecov - name: CI on: - # Trigger on push to develop push: branches: [ develop ] - - # Trigger on all Pull Requests pull_request: branches: [ main, develop ] - - # Allow manual trigger from GitHub UI workflow_dispatch: jobs: tests: - # Job name displayed in GitHub Actions interface - name: Tests (PHP ${{ matrix.php }}) - - # Use Ubuntu runner (free tier on GitHub Actions) + name: Tests (PHP ${{ matrix.php }} - ${{ matrix.adapter }}) runs-on: ubuntu-latest - # Test matrix - creates one job per PHP version strategy: - # Continue running other jobs even if one fails fail-fast: false - matrix: - # Test on latest stable PHP versions - # Automatically uses latest patch: 8.4.x and 8.5.x php: ['8.4', '8.5'] + adapter: ['predis', 'phpredis'] - # Redis service - runs in Docker container services: redis: - # Use Redis 7.2 Alpine (lightweight image) image: redis:7.2-alpine - - # Expose Redis port to the host machine ports: - 6379:6379 - - # Health check - wait until Redis is ready before running tests options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - # Sequential steps executed for this job steps: - # Step 1: Checkout repository code + # Step 1: Checkout repository code - name: Checkout code uses: actions/checkout@v4 @@ -70,30 +38,17 @@ jobs: - name: Setup PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 with: - # PHP version from matrix (automatically uses latest patch) php-version: ${{ matrix.php }} - - # Required PHP extensions for the project - extensions: redis, json, mbstring - - # Enable Xdebug for code coverage generation + extensions: json, mbstring${{ matrix.adapter == 'phpredis' && ', redis' || '' }} coverage: xdebug - - # Install Composer v2 tools: composer:v2 # Step 3: Cache Composer dependencies (speeds up subsequent builds) - name: Cache Composer dependencies uses: actions/cache@v3 with: - # Composer cache directory location path: ~/.composer/cache - - # Cache key based on OS and composer.lock hash - # Cache invalidates when composer.lock changes key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - - # Fallback cache keys if exact match not found restore-keys: ${{ runner.os }}-composer- # Step 4: Install project dependencies via Composer @@ -104,32 +59,25 @@ jobs: - name: Validate composer files run: composer validate --strict - # Step 6: Display installed versions (helpful for debugging CI failures) + # Step 6: Display installed versions - name: Display versions run: | php -v php -m | grep redis composer --version - # Step 7: Run integration tests with Redis - # Environment variables configure Redis connection for tests - - name: Run integration tests - run: composer test:integration - env: - REDIS_HOST: 127.0.0.1 - REDIS_PORT: 6379 + # Step 7 + - name: Check code style (PSR-12) + run: composer cs + + # Step 8: Run entire tests suite + - name: Run all tests + run: composer test - # Step 8: Upload code coverage report to Codecov - # Only runs for PHP 8.5 to avoid duplicate coverage reports - name: Upload coverage to Codecov if: matrix.php == '8.5' uses: codecov/codecov-action@v3 with: - # Coverage file generated by PHPUnit (--coverage-clover flag) files: ./coverage.xml - - # Don't fail the build if coverage upload fails fail_ci_if_error: false - - # Tags for organizing coverage reports on Codecov dashboard flags: php-${{ matrix.php }} From 6b6ef08be45608f21c7ff14165874e12745e1225 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 23 Dec 2025 12:13:23 +0100 Subject: [PATCH 13/45] commiting the rest but still have to trim / clean all --- CONTRIBUTING.md | 28 +++++++++++++++++++++++++++- README.md | 16 +++++++++++++++- composer.json | 3 +++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6474719..91d0450 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ Thank you for your interest in contributing to RedisCache! 🎉 ## Git Flow Workflow -This project follows **Git Flow** for branch management. +This project follows **A based rebase Git Flow** for branch management. ### Branch Structure ``` @@ -466,4 +466,30 @@ Your contributions, whether code, documentation, bug reports, or feature ideas, --- + +**Checklist:** +- [ ] Rebased on target branch +- [ ] PSR-12 compliant +- [ ] Tests pass +- [ ] Coverage maintained +- [ ] Docs updated +- [ ] CHANGELOG updated +- [ ] Clean commit history + +**Process:** +1. Push branch (force push after rebase) +2. Open PR on GitHub +3. CI runs automatically +4. Address review feedback +5. Rebase again if develop changed +6. Maintainer rebases and merges (no merge commits) + +## Questions + +- 💬 [Discussions](https://github.com/chegaz/RedisCache/discussions) +- 🐛 [Issues](https://github.com/llegaz/RedisCache/issues) +- 📧 laurent@legaz.eu + +--- + **See you space cowboy...** 🚀 diff --git a/README.md b/README.md index 28d1baa..370148c 100644 --- a/README.md +++ b/README.md @@ -149,4 +149,18 @@ All Pull Requests are automatically tested before merge. --- -**See you space cowboy...** 🚀 \ No newline at end of file +**See you space cowboy...** 🚀 + +# RedisCache + +![CI](https://img.shields.io/github/actions/workflow/status/llegaz/RedisCache/ci.yml?branch=develop&label=tests&style=flat-square) +![PHP](https://img.shields.io/badge/PHP-8.4%20%7C%208.5-777BB4?style=flat-square&logo=php&logoColor=white) +![Redis](https://img.shields.io/badge/Redis-7.2-DC382D?style=flat-square&logo=redis&logoColor=white) +![Coverage](https://img.shields.io/badge/coverage-85%25-success?style=flat-square) +![License](https://img.shields.io/github/license/llegaz/RedisCache?style=flat-square) + +**Redis-native PSR-16/PSR-6 for mature developers** + +--- + +**See you space cowboy...** 🚀 diff --git a/composer.json b/composer.json index d6b0293..1ab6acc 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,9 @@ "friendsofphp/php-cs-fixer": "~3.3", "cache/integration-tests": "dev-master" }, + "suggest": { + "ext-redis": "^5.3" + }, "autoload": { "psr-4": { "LLegaz\\Cache\\": "src/", From 8eb5db1a0cb279a6d6bfc2a6a17fb8cc3e55b46e Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Tue, 23 Dec 2025 12:36:04 +0100 Subject: [PATCH 14/45] new RedisAdapter release ! --- composer.json | 2 +- tests/Unit/SimpleCacheRCTest.php | 2 +- tests/Unit/SimpleCacheTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 1ab6acc..af75c99 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "redis" ], "require": { - "llegaz/redis-adapter": "~0.0.5", + "llegaz/redis-adapter": "~0.1", "psr/cache": "^3.0", "psr/simple-cache": "^3.0" }, diff --git a/tests/Unit/SimpleCacheRCTest.php b/tests/Unit/SimpleCacheRCTest.php index df0046b..d522517 100644 --- a/tests/Unit/SimpleCacheRCTest.php +++ b/tests/Unit/SimpleCacheRCTest.php @@ -380,7 +380,7 @@ public function testSetWithTtl() * * @return type */ - protected function getSelfClient() + protected function getSelfClient(): RedisClientInterface { return $this->redisClient; } diff --git a/tests/Unit/SimpleCacheTest.php b/tests/Unit/SimpleCacheTest.php index 2517905..e68eebc 100644 --- a/tests/Unit/SimpleCacheTest.php +++ b/tests/Unit/SimpleCacheTest.php @@ -362,7 +362,7 @@ public function testSetMultipleWithTtl() * * @return type */ - protected function getSelfClient() + protected function getSelfClient(): RedisClientInterface { return $this->predisClient; } From 4b0c31c94e80bc1e636dfdc80e6214fc89b86686 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 24 Dec 2025 13:53:52 +0100 Subject: [PATCH 15/45] CI should work now --- .github/workflows/CI.yml | 2 +- composer.json | 1 + tests/Unit/SimpleCacheRCTest.php | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e47673f..95cfde4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.4', '8.5'] + php: ['7.4', '8.4', '8.5'] adapter: ['predis', 'phpredis'] services: diff --git a/composer.json b/composer.json index af75c99..ff2d404 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "redis" ], "require": { + "php": ">=7.4", "llegaz/redis-adapter": "~0.1", "psr/cache": "^3.0", "psr/simple-cache": "^3.0" diff --git a/tests/Unit/SimpleCacheRCTest.php b/tests/Unit/SimpleCacheRCTest.php index d522517..131495e 100644 --- a/tests/Unit/SimpleCacheRCTest.php +++ b/tests/Unit/SimpleCacheRCTest.php @@ -104,7 +104,7 @@ public function testClearAll() { $this->redisClient->expects($this->once()) ->method('flushall') - ->willReturn(new Status('OK')) + ->willReturn(true) ; $this->assertTrue($this->cache->clear(true)); } @@ -114,7 +114,7 @@ public function testClear() $this->integrityCheckCL(); $this->redisClient->expects($this->once()) ->method('flushdb') - ->willReturn(new Status('OK')) + ->willReturn(true) ; $this->assertTrue($this->cache->clear()); } @@ -283,7 +283,7 @@ public function testSet() $this->redisClient->expects($this->once()) ->method('set') ->with($key) - ->willReturn(new Status('OK')) + ->willReturn(true) ; $this->assertTrue($this->cache->set($key, 'bbbbbbbbbbbbbbbbbbbb')); } From 7137d7219963835769ede62f6291325f68f32f20 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 24 Dec 2025 13:59:50 +0100 Subject: [PATCH 16/45] psrs v3 only work on php8bro --- .github/workflows/CI.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 95cfde4..e47673f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.4', '8.5'] + php: ['8.4', '8.5'] adapter: ['predis', 'phpredis'] services: diff --git a/composer.json b/composer.json index ff2d404..fb2cad6 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "redis" ], "require": { - "php": ">=7.4", + "php": ">=8.1", "llegaz/redis-adapter": "~0.1", "psr/cache": "^3.0", "psr/simple-cache": "^3.0" From aa0a4d91fbaa90267e3fa1e4a17fb53b247619b4 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 25 Dec 2025 12:50:58 +0100 Subject: [PATCH 17/45] Doc and SecurityTest added, todo complete it --- README.md | 11 ++++++- composer.json | 2 +- tests/Functional/SecurityTest.php | 48 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/Functional/SecurityTest.php diff --git a/README.md b/README.md index 370148c..8698274 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,22 @@ This project is build upon my first redis open PHP project [Redis Adapter](https://packagist.org/packages/llegaz/redis-adapter). Thanks to it you can use either [Predis](https://github.com/predis/predis) client or native [PHP Redis](https://github.com/phpredis/phpredis/) client in a transparent way. +This implementation is quite safe and rely totally on RESP (REdis Serialization Protocol), implemented by Predis and the Redis PHP extension, through their standard API. + +## PSR divergences +For now the reserved charachters `{}()/\@:` for the keys are supported entirely, it is an on purpose choice we made because PSR reserved those characters years ago and did nothing concrete with it, or nothing I have heard of. +Moreover there are some real life example where those characters are cool to have (emails, urls, paths, and even redis proposed key format which is considered a good practise, e.g user:123). +Finally, as there are no security constraints not to use those characters we made the choice not to follow PSR on this point and to support those chars `{}()/\@:` and we hope it will be well tolerated by the PHP developpers community. + +## Install + If PHP redis is installed ```bash $ apt-get install php8.x-redis ``` These implementations will use it or fallback on Predis client otherwise. -## Install +You can simply use composer to install this library: ```bash composer require llegaz/redis-cache composer install diff --git a/composer.json b/composer.json index fb2cad6..0a240a8 100644 --- a/composer.json +++ b/composer.json @@ -54,7 +54,7 @@ "pufv":"@phpunit-func-verbose", "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 CacheIntegrationTest::testSetMultiple", + "test-only": "./vendor/bin/phpunit --display-deprecations --display-notices --display-warnings --colors=always --configuration ./phpunit.xml --bootstrap .phpunit_full --filter SecurityTest::testSpecialCharactersDoNotCauseInjection", "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/Functional/SecurityTest.php b/tests/Functional/SecurityTest.php new file mode 100644 index 0000000..38e0f46 --- /dev/null +++ b/tests/Functional/SecurityTest.php @@ -0,0 +1,48 @@ + + */ +class SecurityTest extends \PHPUnit\Framework\TestCase +{ + protected SUT $cache; + + protected function setUp(): void { + parent::setUp(); + + $this->cache = new SUT(); + } + /** + * Security test: Ensure special characters in keys don't cause + * command injection or unexpected behavior. + */ + public function testSpecialCharactersDoNotCauseInjection() + { + // Attempt "injection-like" patterns + $dangerousKeys = [ + 'key\nFLUSHALL', + 'key|FLUSHALL', + 'key`FLUSHALL`', + 'key$(FLUSHALL)', + ]; + + $this->cache->set('canary', 'chirp'); + + foreach ($dangerousKeys as $key) { + // Should work without executing any injection + $this->cache->set($key, 'safe_value'); + $this->assertEquals('safe_value', $this->cache->get($key)); + $this->cache->delete($key); + } + + // Verify no side effects (cache not flushed) + $this->assertEquals('chirp', $this->cache->get('canary')); + } +} From 5bd57c76c1fe4905bedf46eb3a72303f12ffef5f Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 25 Dec 2025 19:16:44 +0100 Subject: [PATCH 18/45] [security] testing key injection --- src/CacheEntryPool/CacheEntryPool.php | 14 ++++++++++ tests/Functional/SecurityTest.php | 40 +++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/CacheEntryPool/CacheEntryPool.php b/src/CacheEntryPool/CacheEntryPool.php index b9e1564..9f6d675 100644 --- a/src/CacheEntryPool/CacheEntryPool.php +++ b/src/CacheEntryPool/CacheEntryPool.php @@ -20,6 +20,7 @@ * * * + * @todo key validation is inconsistent * @todo homogenize rework documentation through this package * -- * @todo dig into Redict ? (or just respect Salvatore's vision, see below) @@ -89,6 +90,19 @@ public function clear(): bool return true; } + /** + * Removes the item from the pool. + * + * @param string $key + * The key to delete. + * + * @throws InvalidArgumentException + * If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException + * MUST be thrown. + * + * @return bool + * True if the item was successfully removed. False if there was an error. + */ public function deleteItem(string $key): bool { if ($this->isDeferred($key)) { diff --git a/tests/Functional/SecurityTest.php b/tests/Functional/SecurityTest.php index 38e0f46..986b2a7 100644 --- a/tests/Functional/SecurityTest.php +++ b/tests/Functional/SecurityTest.php @@ -3,6 +3,8 @@ declare(strict_types=1); use LLegaz\Cache\RedisCache as SUT; +use LLegaz\Cache\Pool\CacheEntryPool; +use LLegaz\Cache\RedisEnhancedCache as SUT2; /** * @todo test PSR 6 class too @@ -13,17 +15,23 @@ class SecurityTest extends \PHPUnit\Framework\TestCase { protected SUT $cache; - + + protected CacheEntryPool $pool; + protected function setUp(): void { parent::setUp(); $this->cache = new SUT(); + $this->pool = new CacheEntryPool(new SUT2()); + + $this->cache->clear(); + $this->pool->clear(); } /** * Security test: Ensure special characters in keys don't cause * command injection or unexpected behavior. */ - public function testSpecialCharactersDoNotCauseInjection() + public function testSpecialCharactersDoNotCauseInjectionPSR16() { // Attempt "injection-like" patterns $dangerousKeys = [ @@ -45,4 +53,32 @@ public function testSpecialCharactersDoNotCauseInjection() // Verify no side effects (cache not flushed) $this->assertEquals('chirp', $this->cache->get('canary')); } + + public function testSpecialCharactersDoNotCauseInjectionPSR6() + { + + // Attempt "injection-like" patterns + $dangerousKeys = [ + 'key\nFLUSHALL', + 'key|FLUSHALL', + 'key`FLUSHALL`', + 'key$(FLUSHALL)', + ]; + + $item = $this->pool->getItem('canary'); + $item->set('chirp'); + $this->pool->save($item); + + foreach ($dangerousKeys as $key) { + // Should work without executing any injection + $item = $this->pool->getItem($key); + $item->set('safe value!'); + $this->pool->save($item); + $this->assertEquals('safe value!', $this->pool->getItem($key)->get()); + $this->pool->deleteItem($key); + } + + // Verify no side effects (cache not flushed) + $this->assertEquals('chirp', $this->pool->getItem('canary')->get()); + } } From fda815073e42dd567db80095efdaf9c2d86a8dc5 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 25 Dec 2025 22:04:56 +0100 Subject: [PATCH 19/45] CS + CI updated --- .github/workflows/CI.yml | 8 -------- tests/Functional/SecurityTest.php | 11 ++++++----- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e47673f..2a67d89 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -73,11 +73,3 @@ jobs: # Step 8: Run entire tests suite - name: Run all tests run: composer test - - - name: Upload coverage to Codecov - if: matrix.php == '8.5' - uses: codecov/codecov-action@v3 - with: - files: ./coverage.xml - fail_ci_if_error: false - flags: php-${{ matrix.php }} diff --git a/tests/Functional/SecurityTest.php b/tests/Functional/SecurityTest.php index 986b2a7..eb6951f 100644 --- a/tests/Functional/SecurityTest.php +++ b/tests/Functional/SecurityTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -use LLegaz\Cache\RedisCache as SUT; use LLegaz\Cache\Pool\CacheEntryPool; +use LLegaz\Cache\RedisCache as SUT; use LLegaz\Cache\RedisEnhancedCache as SUT2; /** * @todo test PSR 6 class too - * - * + * + * * @author Laurent LEGAZ */ class SecurityTest extends \PHPUnit\Framework\TestCase @@ -18,7 +18,8 @@ class SecurityTest extends \PHPUnit\Framework\TestCase protected CacheEntryPool $pool; - protected function setUp(): void { + protected function setUp(): void + { parent::setUp(); $this->cache = new SUT(); @@ -56,7 +57,7 @@ public function testSpecialCharactersDoNotCauseInjectionPSR16() public function testSpecialCharactersDoNotCauseInjectionPSR6() { - + // Attempt "injection-like" patterns $dangerousKeys = [ 'key\nFLUSHALL', From 93b769ff6fa48129a826b42425e7c05874e0a29e Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 25 Dec 2025 22:42:07 +0100 Subject: [PATCH 20/45] more CI testing --- .github/PR_TEMPLATE.md | 2 +- .github/workflows/CI.yml | 17 +++++++++++++---- CONTRIBUTING.md | 2 +- README.md | 2 +- src/Exception/InvalidKeyException.php | 2 +- src/Exception/InvalidKeysException.php | 2 +- src/Exception/InvalidValuesException.php | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.github/PR_TEMPLATE.md b/.github/PR_TEMPLATE.md index ff9f207..17ecb2d 100644 --- a/.github/PR_TEMPLATE.md +++ b/.github/PR_TEMPLATE.md @@ -40,5 +40,5 @@ composer test --- -**See you space cowboy...** 🚀 +**@See** you space cowboy... 🚀 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2a67d89..2cb1bfd 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,6 +17,9 @@ jobs: matrix: php: ['8.4', '8.5'] adapter: ['predis', 'phpredis'] + include: + -adapter: 'phpredis' + ext: 'redis' services: redis: @@ -39,10 +42,10 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: json, mbstring${{ matrix.adapter == 'phpredis' && ', redis' || '' }} - coverage: xdebug + extensions: ${{ matrix.ext }} + coverage: "none" tools: composer:v2 - + # Step 3: Cache Composer dependencies (speeds up subsequent builds) - name: Cache Composer dependencies uses: actions/cache@v3 @@ -66,10 +69,16 @@ jobs: php -m | grep redis composer --version - # Step 7 + # Step 7 info - name: Check code style (PSR-12) run: composer cs # Step 8: Run entire tests suite - name: Run all tests run: composer test + + # Step 9 more info (dbg) + - name: Display versions + run: | + php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;' + php -i diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 91d0450..09256f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -492,4 +492,4 @@ Your contributions, whether code, documentation, bug reports, or feature ideas, --- -**See you space cowboy...** 🚀 +**@See** you space cowboy... 🚀 diff --git a/README.md b/README.md index 8698274..a74f105 100644 --- a/README.md +++ b/README.md @@ -172,4 +172,4 @@ All Pull Requests are automatically tested before merge. --- -**See you space cowboy...** 🚀 +**@See** you space cowboy... 🚀 diff --git a/src/Exception/InvalidKeyException.php b/src/Exception/InvalidKeyException.php index 12f1645..78a8a6f 100644 --- a/src/Exception/InvalidKeyException.php +++ b/src/Exception/InvalidKeyException.php @@ -10,7 +10,7 @@ */ 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) + public function __construct(string $message = 'RedisCache says "Can\'t do shit with this Key"' . PHP_EOL, int $code = 400, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Exception/InvalidKeysException.php b/src/Exception/InvalidKeysException.php index 4356210..f30ec47 100644 --- a/src/Exception/InvalidKeysException.php +++ b/src/Exception/InvalidKeysException.php @@ -10,7 +10,7 @@ */ 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) + public function __construct(string $message = 'RedisCache says "Can\'t do shit with those keys"' . PHP_EOL, int $code = 400, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Exception/InvalidValuesException.php b/src/Exception/InvalidValuesException.php index 636d5c4..961bedb 100644 --- a/src/Exception/InvalidValuesException.php +++ b/src/Exception/InvalidValuesException.php @@ -10,7 +10,7 @@ */ 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) + public function __construct(string $message = 'RedisCache says "Can\'t do shit with those values"' . PHP_EOL, int $code = 400, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); } From 5f5b0086c9f656dd11bbf3b37a3d0a34be441411 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Thu, 25 Dec 2025 22:58:05 +0100 Subject: [PATCH 21/45] still CI testing... --- .github/workflows/CI.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2cb1bfd..cb66452 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,16 +11,13 @@ jobs: tests: name: Tests (PHP ${{ matrix.php }} - ${{ matrix.adapter }}) runs-on: ubuntu-latest - + strategy: fail-fast: false matrix: php: ['8.4', '8.5'] adapter: ['predis', 'phpredis'] - include: - -adapter: 'phpredis' - ext: 'redis' - + services: redis: image: redis:7.2-alpine @@ -31,7 +28,7 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - + steps: # Step 1: Checkout repository code - name: Checkout code @@ -42,7 +39,8 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: ${{ matrix.ext }} + if: ${{ matrix.adapter == 'phpredis' }} + extensions: redis coverage: "none" tools: composer:v2 @@ -53,22 +51,22 @@ jobs: path: ~/.composer/cache key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - + # Step 4: Install project dependencies via Composer - name: Install dependencies run: composer install --prefer-dist --no-progress --no-interaction - + # Step 5: Validate composer.json and composer.lock structure - name: Validate composer files run: composer validate --strict - + # Step 6: Display installed versions - name: Display versions run: | php -v php -m | grep redis composer --version - + # Step 7 info - name: Check code style (PSR-12) run: composer cs From 3cb6fe38b14066dc4158b466dbd687b3b6ed2ed4 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 11:46:02 +0100 Subject: [PATCH 22/45] stuck on conditional CI --- .github/workflows/CI.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cb66452..616df12 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -39,11 +39,14 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - if: ${{ matrix.adapter == 'phpredis' }} extensions: redis coverage: "none" tools: composer:v2 + - name: Remove PHP Redis extension + if: ${{ matrix.adapter == 'predis' }} + run: apt-get -y remove php${{ matrix.php }}-redis && echo $? && echo -e "\n php${{ matrix.php }}-redis removed\n" + # Step 3: Cache Composer dependencies (speeds up subsequent builds) - name: Cache Composer dependencies uses: actions/cache@v3 From 56e483d9617f31a6a08437c90b378d729f7b77ab Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 11:48:27 +0100 Subject: [PATCH 23/45] sudo --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 616df12..0812161 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -45,7 +45,7 @@ jobs: - name: Remove PHP Redis extension if: ${{ matrix.adapter == 'predis' }} - run: apt-get -y remove php${{ matrix.php }}-redis && echo $? && echo -e "\n php${{ matrix.php }}-redis removed\n" + run: sudo apt-get -y remove php${{ matrix.php }}-redis && echo $? && echo -e "\n php${{ matrix.php }}-redis removed\n" # Step 3: Cache Composer dependencies (speeds up subsequent builds) - name: Cache Composer dependencies From 4d65ee002f920dfd0465a6587e723cccb8510cb8 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 12:05:27 +0100 Subject: [PATCH 24/45] still debugging CI script --- .github/workflows/CI.yml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0812161..60abe10 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -43,10 +43,6 @@ jobs: coverage: "none" tools: composer:v2 - - name: Remove PHP Redis extension - if: ${{ matrix.adapter == 'predis' }} - run: sudo apt-get -y remove php${{ matrix.php }}-redis && echo $? && echo -e "\n php${{ matrix.php }}-redis removed\n" - # Step 3: Cache Composer dependencies (speeds up subsequent builds) - name: Cache Composer dependencies uses: actions/cache@v3 @@ -69,17 +65,20 @@ jobs: php -v php -m | grep redis composer --version + php -r ' echo (in_array('redis', get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' + php -i # Step 7 info - name: Check code style (PSR-12) run: composer cs - - # Step 8: Run entire tests suite - - name: Run all tests - run: composer test - # Step 9 more info (dbg) - - name: Display versions + # Step 8: force remove php redis ext + - name: Remove PHP Redis extension + if: ${{ matrix.adapter == 'predis' }} run: | - php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;' - php -i + sudo apt-get -y remove php${{ matrix.php }}-redis + php -r ' echo (in_array('redis', get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' + + # Step 9: Run entire tests suite + - name: Run all tests + run: composer test From ecdb8d8c96449040306dd0d9d00461c0cce85458 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 12:16:43 +0100 Subject: [PATCH 25/45] CI fix --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 60abe10..2d534d5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -65,7 +65,7 @@ jobs: php -v php -m | grep redis composer --version - php -r ' echo (in_array('redis', get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' + php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' php -i # Step 7 info @@ -77,7 +77,7 @@ jobs: if: ${{ matrix.adapter == 'predis' }} run: | sudo apt-get -y remove php${{ matrix.php }}-redis - php -r ' echo (in_array('redis', get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' + php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' # Step 9: Run entire tests suite - name: Run all tests From 6eb87b46bafd81b28e1bf4ea3c05e1cb6df20bb4 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 12:37:29 +0100 Subject: [PATCH 26/45] CI, last try for today --- .github/workflows/CI.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 2d534d5..381ea04 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -39,8 +39,8 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: redis - coverage: "none" + extensions: none + coverage: none tools: composer:v2 # Step 3: Cache Composer dependencies (speeds up subsequent builds) @@ -72,11 +72,11 @@ jobs: - name: Check code style (PSR-12) run: composer cs - # Step 8: force remove php redis ext - - name: Remove PHP Redis extension - if: ${{ matrix.adapter == 'predis' }} + # Step 8: manual installation php redis ext + - name: Install PHP Redis extension + if: ${{ matrix.adapter == 'phpredis' }} run: | - sudo apt-get -y remove php${{ matrix.php }}-redis + sudo apt-get -y install php${{ matrix.php }}-redis php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' # Step 9: Run entire tests suite From 3bc3b8778eda71f17f5f322b939057b955031663 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 12:43:59 +0100 Subject: [PATCH 27/45] CI, last try for today, 2nd iteration --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 381ea04..6413233 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -39,7 +39,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: none + extensions: json, mbstring, :redis coverage: none tools: composer:v2 From 93239533ecb773daadccd6dc4c173777bf575b1a Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 13:03:42 +0100 Subject: [PATCH 28/45] CI, last try for today, 3rd iteration --- .github/workflows/CI.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6413233..57002f6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -39,7 +39,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: json, mbstring, :redis + extensions: json, mbstring, :opcache, :redis coverage: none tools: composer:v2 @@ -63,10 +63,10 @@ jobs: - name: Display versions run: | php -v - php -m | grep redis + #php -m | grep redis composer --version - php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' - php -i + #php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' + #php -i # Step 7 info - name: Check code style (PSR-12) From 0187ba945d77efb7f113074730b7eb9cb9747939 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 13:54:44 +0100 Subject: [PATCH 29/45] CI, last try for today, 4th iteration --- .github/workflows/CI.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 57002f6..670b595 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -76,7 +76,8 @@ jobs: - name: Install PHP Redis extension if: ${{ matrix.adapter == 'phpredis' }} run: | - sudo apt-get -y install php${{ matrix.php }}-redis + sudo apt-get update + sudo apt-get -y install php${{ matrix.php }}-common php${{ matrix.php }}-opcache php${{ matrix.php }}-redis php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' # Step 9: Run entire tests suite From 67082a748bf3feda05132baac3f13c842f578a33 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 18:14:00 +0100 Subject: [PATCH 30/45] CI - 2 separate workflows --- .github/workflows/CI.yml | 19 ++---- .github/workflows/CI_fallback_predis.yml | 76 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/CI_fallback_predis.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 670b595..78468bd 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,14 +9,13 @@ on: jobs: tests: - name: Tests (PHP ${{ matrix.php }} - ${{ matrix.adapter }}) + name: Tests (PHP ${{ matrix.php }} - phpredis adapter) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - php: ['8.4', '8.5'] - adapter: ['predis', 'phpredis'] + php: ['8.2', '8.3','8.4', '8.5'] services: redis: @@ -39,7 +38,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: json, mbstring, :opcache, :redis + extensions: json, mbstring, redis coverage: none tools: composer:v2 @@ -65,21 +64,13 @@ jobs: php -v #php -m | grep redis composer --version - #php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' + php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' #php -i # Step 7 info - name: Check code style (PSR-12) run: composer cs - # Step 8: manual installation php redis ext - - name: Install PHP Redis extension - if: ${{ matrix.adapter == 'phpredis' }} - run: | - sudo apt-get update - sudo apt-get -y install php${{ matrix.php }}-common php${{ matrix.php }}-opcache php${{ matrix.php }}-redis - php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' - - # Step 9: Run entire tests suite + # Step 8: Run entire tests suite - name: Run all tests run: composer test diff --git a/.github/workflows/CI_fallback_predis.yml b/.github/workflows/CI_fallback_predis.yml new file mode 100644 index 0000000..cb7867b --- /dev/null +++ b/.github/workflows/CI_fallback_predis.yml @@ -0,0 +1,76 @@ +name: CI + +on: + push: + branches: [ develop ] + pull_request: + branches: [ main, develop ] + workflow_dispatch: + +jobs: + tests: + name: Tests (PHP ${{ matrix.php }} - predis adapter) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: ['8.2', '8.3','8.4', '8.5'] + + services: + redis: + image: redis:7.2-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + # Step 1: Checkout repository code + - name: Checkout code + uses: actions/checkout@v4 + + # Step 2: Setup PHP with required extensions + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, mbstring, :opcache, :redis + coverage: none + tools: composer:v2 + + # Step 3: Cache Composer dependencies (speeds up subsequent builds) + - name: Cache Composer dependencies + uses: actions/cache@v3 + with: + path: ~/.composer/cache + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + # Step 4: Install project dependencies via Composer + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-interaction + + # Step 5: Validate composer.json and composer.lock structure + - name: Validate composer files + run: composer validate --strict + + # Step 6: Display installed versions + - name: Display versions + run: | + php -v + #php -m | grep redis + composer --version + php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' + #php -i + + # Step 7 info + - name: Check code style (PSR-12) + run: composer cs + + # Step 8: Run entire tests suite + - name: Run all tests + run: composer test From fd91a22455834644f81a674f34ea817e64090ab2 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Fri, 26 Dec 2025 18:42:10 +0100 Subject: [PATCH 31/45] worflows renamed --- .github/workflows/CI.yml | 2 +- .github/workflows/CI_fallback_predis.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 78468bd..3250640 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -name: CI +name: CI php-redis ext enabled on: push: diff --git a/.github/workflows/CI_fallback_predis.yml b/.github/workflows/CI_fallback_predis.yml index cb7867b..b9e0823 100644 --- a/.github/workflows/CI_fallback_predis.yml +++ b/.github/workflows/CI_fallback_predis.yml @@ -1,4 +1,4 @@ -name: CI +name: CI Predis fallback on: push: From 84e1e4cf373b5c7b0ff98294e3111de88ab08c1e Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 27 Dec 2025 13:13:43 +0100 Subject: [PATCH 32/45] some more tests for CI --- .github/workflows/testCI.yml | 75 ++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .github/workflows/testCI.yml diff --git a/.github/workflows/testCI.yml b/.github/workflows/testCI.yml new file mode 100644 index 0000000..569bb47 --- /dev/null +++ b/.github/workflows/testCI.yml @@ -0,0 +1,75 @@ +name: CI + +on: + push: + branches: [ develop ] + pull_request: + branches: [ main, develop ] + workflow_dispatch: + +jobs: + tests: + name: Tests (PHP ${{ matrix.php }} - ${{ matrix.adapter }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: ['8.4', '8.5'] + adapter: ['predis', 'phpredis'] + + services: + redis: + image: redis:7.2-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + # Step 1: Checkout repository code + - name: Checkout code + uses: actions/checkout@v4 + + # Step 2: Setup PHP with required extensions + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, mbstring, ${{ matrix.adapter == 'phpredis' && 'redis' || ':opcache, :redis' }} + coverage: none + tools: composer:v2 + + # Step 3: Cache Composer dependencies (speeds up subsequent builds) + - name: Cache Composer dependencies + uses: actions/cache@v3 + with: + path: ~/.composer/cache + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + # Step 4: Install project dependencies via Composer + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-interaction + + # Step 5: Validate composer.json and composer.lock structure + - name: Validate composer files + run: composer validate --strict + + # Step 6: Display installed versions + - name: Display versions + run: | + php -v + php -m | grep redis + composer --version + + # Step 7 + - name: Check code style (PSR-12) + run: composer cs + + # Step 8: Run entire tests suite + - name: Run all tests + run: composer test From 2a07cd2b9154314a09a327004d8efd819929c185 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 27 Dec 2025 13:25:12 +0100 Subject: [PATCH 33/45] CI done ? --- .github/workflows/testCI.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/testCI.yml b/.github/workflows/testCI.yml index 569bb47..35845e7 100644 --- a/.github/workflows/testCI.yml +++ b/.github/workflows/testCI.yml @@ -1,4 +1,4 @@ -name: CI +name: CI workflow double test on: push: @@ -63,7 +63,6 @@ jobs: - name: Display versions run: | php -v - php -m | grep redis composer --version # Step 7 From 8b07cacda740a113074f3de52b7d8302b3ca766f Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sat, 27 Dec 2025 13:44:46 +0100 Subject: [PATCH 34/45] CI done ! --- .github/workflows/CI.yml | 20 ++++--- .github/workflows/CI_fallback_predis.yml | 76 ------------------------ .github/workflows/testCI.yml | 74 ----------------------- 3 files changed, 11 insertions(+), 159 deletions(-) delete mode 100644 .github/workflows/CI_fallback_predis.yml delete mode 100644 .github/workflows/testCI.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3250640..d1de522 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,4 +1,4 @@ -name: CI php-redis ext enabled +name: CI on: push: @@ -9,15 +9,20 @@ on: jobs: tests: - name: Tests (PHP ${{ matrix.php }} - phpredis adapter) + name: Tests (PHP ${{ matrix.php }} - ${{ matrix.adapter }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: - php: ['8.2', '8.3','8.4', '8.5'] + php: ['8.2', '8.3', '8.4', '8.5'] + adapter: ['predis', 'phpredis'] services: + + + + redis: image: redis:7.2-alpine ports: @@ -32,13 +37,13 @@ jobs: # Step 1: Checkout repository code - name: Checkout code uses: actions/checkout@v4 - + # Step 2: Setup PHP with required extensions - name: Setup PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: json, mbstring, redis + extensions: json, mbstring, ${{ matrix.adapter == 'phpredis' && 'redis' || ':opcache, :redis' }} coverage: none tools: composer:v2 @@ -62,12 +67,9 @@ jobs: - name: Display versions run: | php -v - #php -m | grep redis composer --version - php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' - #php -i - # Step 7 info + # Step 7 - name: Check code style (PSR-12) run: composer cs diff --git a/.github/workflows/CI_fallback_predis.yml b/.github/workflows/CI_fallback_predis.yml deleted file mode 100644 index b9e0823..0000000 --- a/.github/workflows/CI_fallback_predis.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: CI Predis fallback - -on: - push: - branches: [ develop ] - pull_request: - branches: [ main, develop ] - workflow_dispatch: - -jobs: - tests: - name: Tests (PHP ${{ matrix.php }} - predis adapter) - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - php: ['8.2', '8.3','8.4', '8.5'] - - services: - redis: - image: redis:7.2-alpine - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - # Step 1: Checkout repository code - - name: Checkout code - uses: actions/checkout@v4 - - # Step 2: Setup PHP with required extensions - - name: Setup PHP ${{ matrix.php }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: json, mbstring, :opcache, :redis - coverage: none - tools: composer:v2 - - # Step 3: Cache Composer dependencies (speeds up subsequent builds) - - name: Cache Composer dependencies - uses: actions/cache@v3 - with: - path: ~/.composer/cache - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - # Step 4: Install project dependencies via Composer - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-interaction - - # Step 5: Validate composer.json and composer.lock structure - - name: Validate composer files - run: composer validate --strict - - # Step 6: Display installed versions - - name: Display versions - run: | - php -v - #php -m | grep redis - composer --version - php -r ' echo (in_array("redis", get_loaded_extensions()) ? "redis extension loaded" : "predis fallback"). PHP_EOL;' - #php -i - - # Step 7 info - - name: Check code style (PSR-12) - run: composer cs - - # Step 8: Run entire tests suite - - name: Run all tests - run: composer test diff --git a/.github/workflows/testCI.yml b/.github/workflows/testCI.yml deleted file mode 100644 index 35845e7..0000000 --- a/.github/workflows/testCI.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: CI workflow double test - -on: - push: - branches: [ develop ] - pull_request: - branches: [ main, develop ] - workflow_dispatch: - -jobs: - tests: - name: Tests (PHP ${{ matrix.php }} - ${{ matrix.adapter }}) - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - php: ['8.4', '8.5'] - adapter: ['predis', 'phpredis'] - - services: - redis: - image: redis:7.2-alpine - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - # Step 1: Checkout repository code - - name: Checkout code - uses: actions/checkout@v4 - - # Step 2: Setup PHP with required extensions - - name: Setup PHP ${{ matrix.php }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: json, mbstring, ${{ matrix.adapter == 'phpredis' && 'redis' || ':opcache, :redis' }} - coverage: none - tools: composer:v2 - - # Step 3: Cache Composer dependencies (speeds up subsequent builds) - - name: Cache Composer dependencies - uses: actions/cache@v3 - with: - path: ~/.composer/cache - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - # Step 4: Install project dependencies via Composer - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-interaction - - # Step 5: Validate composer.json and composer.lock structure - - name: Validate composer files - run: composer validate --strict - - # Step 6: Display installed versions - - name: Display versions - run: | - php -v - composer --version - - # Step 7 - - name: Check code style (PSR-12) - run: composer cs - - # Step 8: Run entire tests suite - - name: Run all tests - run: composer test From 3a7aa5d5a05a9eab4e1ebd171eebbec9954a0926 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 11 Feb 2026 09:23:20 +0100 Subject: [PATCH 35/45] trigger CI while finishing community guidelines review --- src/RedisCache.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index 1094abe..ddb3a5a 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -435,7 +435,7 @@ protected function checkKeyValidity(mixed &$key): void $len = strlen($key); // Empty keys are ambiguous - if ($len === 0) { + if (!$len) { throw new InvalidKeyException('Cache key cannot be empty'); } @@ -455,6 +455,10 @@ protected function checkKeyValidity(mixed &$key): void throw new InvalidKeyException('Cache key cannot contain whitespace'); } + /** + * @todo filter common forbidden chars between URL rfc and PSR-6/16 key definition + * that is those 3: \[] + */ // That's it. Redis handles everything else. // We trust you to know what you're doing. } From 26862ca0db150e46fe54c3438a497604cd615f7a Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 11 Feb 2026 09:33:52 +0100 Subject: [PATCH 36/45] github actions, try to improve cache capabilities --- .github/workflows/CI.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d1de522..4f616f9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -49,9 +49,11 @@ jobs: # Step 3: Cache Composer dependencies (speeds up subsequent builds) - name: Cache Composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ~/.composer/cache + path: | + vendor + ~/.composer/cache key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- From 340e4ab8c613099165ef6814d3857b3376e78339 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 11 Feb 2026 10:57:48 +0100 Subject: [PATCH 37/45] hash on composer.json file --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4f616f9..fc9ee15 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -54,7 +54,7 @@ jobs: path: | vendor ~/.composer/cache - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- # Step 4: Install project dependencies via Composer From 3f3978cc32c65ca0c6b7fc7ea3d0b1f349509e69 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 11 Feb 2026 12:15:35 +0100 Subject: [PATCH 38/45] CI should be pristine this time (or not) --- .github/workflows/CI.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index fc9ee15..a277e83 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -55,11 +55,12 @@ jobs: vendor ~/.composer/cache key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: ${{ runner.os }}-composer- + restore-keys: | + ${{ runner.os }}-composer- # Step 4: Install project dependencies via Composer - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-interaction + run: composer install --prefer-dist --no-progress --no-interaction --no-suggest # Step 5: Validate composer.json and composer.lock structure - name: Validate composer files From 6088077a1147465eef38d64942c70721c209657a Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 11 Feb 2026 12:25:20 +0100 Subject: [PATCH 39/45] nandato kisamawa ? --- .github/workflows/CI.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a277e83..71dca8f 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -53,6 +53,7 @@ jobs: with: path: | vendor + composer.lock ~/.composer/cache key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: | @@ -60,7 +61,7 @@ jobs: # Step 4: Install project dependencies via Composer - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-interaction --no-suggest + run: composer install --prefer-dist --no-progress --no-interaction # Step 5: Validate composer.json and composer.lock structure - name: Validate composer files From e8c6bbac6ba7f9d262e0a192bc0a66e605c7223f Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Wed, 11 Feb 2026 12:50:23 +0100 Subject: [PATCH 40/45] reworking README.md wip --- README.md | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a74f105..da40466 100644 --- a/README.md +++ b/README.md @@ -94,16 +94,6 @@ $cache = new LLegaz\Cache\RedisCache('localhost', 6379, null, 'tcp', 0, true); ``` -## Contributing -You're welcome to propose things. I am open to criticism as long as it remains benevolent. - - -Stay tuned, by following me on github, for new features using [predis](https://github.com/predis/predis) and [PHP Redis](https://github.com/phpredis/phpredis/). - ---- -@see you space cowboy ---- - ## Contributing We welcome contributions! This project follows **Git Flow** workflow. @@ -156,9 +146,7 @@ All Pull Requests are automatically tested before merge. [View test results →](https://github.com/llegaz/RedisCache/actions) ---- -**See you space cowboy...** 🚀 # RedisCache @@ -170,6 +158,11 @@ All Pull Requests are automatically tested before merge. **Redis-native PSR-16/PSR-6 for mature developers** + + +Stay tuned, by following me on github, for new features using [predis](https://github.com/predis/predis) and [PHP Redis](https://github.com/phpredis/phpredis/).
+ + --- -**@See** you space cowboy... 🚀 +**@See you space cowboy...** 🚀 From f950311619ee8ac5987d3f7bbbaa7ab2a3aafb9b Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Sun, 15 Feb 2026 20:18:19 +0100 Subject: [PATCH 41/45] key validation enhanced PSR16 --- README.md | 7 +++++-- src/RedisCache.php | 17 ++++++++++++++--- tests/Functional/SecurityTest.php | 4 +--- tests/Integration/CacheIntegrationTest.php | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index da40466..90e0d2a 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,12 @@ Thanks to it you can use either [Predis](https://github.com/predis/predis) clien This implementation is quite safe and rely totally on RESP (REdis Serialization Protocol), implemented by Predis and the Redis PHP extension, through their standard API. ## PSR divergences -For now the reserved charachters `{}()/\@:` for the keys are supported entirely, it is an on purpose choice we made because PSR reserved those characters years ago and did nothing concrete with it, or nothing I have heard of. +For now some of the reserved charachters `()/@:` for the keys are supported entirely, it is an on purpose choice we made because PSR reserved those characters years ago and did nothing concrete with it, or nothing I have heard of. Moreover there are some real life example where those characters are cool to have (emails, urls, paths, and even redis proposed key format which is considered a good practise, e.g user:123). -Finally, as there are no security constraints not to use those characters we made the choice not to follow PSR on this point and to support those chars `{}()/\@:` and we hope it will be well tolerated by the PHP developpers community. +Finally, as there are no security constraints not to use those characters we made the choice not to follow PSR on this point and to support those chars `()/@:` and we hope it will be well tolerated by the PHP developpers community. +A contrario we decided to not enable withespaces in keys to ease debugging and because it is not a redis standard (std_key:preferred:form) nor in URL RFC definition. And so we thought it as not a good practice, at least, for our use cases. +But thoses choices are not engraved in marble and are totally still on the table to discussion. + ## Install diff --git a/src/RedisCache.php b/src/RedisCache.php index ddb3a5a..930929d 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -9,6 +9,11 @@ use LLegaz\Cache\Exception\InvalidValuesException; use LLegaz\Redis\RedisAdapter; use LLegaz\Redis\RedisClientInterface; +/** + * @todo refactor to hide those status and logic bound either to predis and php-redis + * (use adapter project client classes like mset => multipleSet method) + + */ use Predis\Response\Status; use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; @@ -24,7 +29,8 @@ * * @todo refactor to hide those predis Status and logic bound either to predis and php-redis * (use adapter project client classes like mset => multipleSet method) - * @todo and also clean and harmonize all those $redisResponse + * + * @todo also clean and harmonize all those $redisResponse * * * @note I have also have some concerns on keys because redis can handle Bytes and we are only handling @@ -456,9 +462,14 @@ protected function checkKeyValidity(mixed &$key): void } /** - * @todo filter common forbidden chars between URL rfc and PSR-6/16 key definition - * that is those 3: \[] + *filter also common forbidden characters between URL RFC and PSR-6/16 key definition + * that is those 3: \{}, backslash and curly brackets + * we use square brackets in IPv6 based URLs... + * */ + if (preg_match('/[\\\\{}]/', $key)) { + throw new InvalidKeyException('Cache key cannot contain backslash or curly bracket'); + } // That's it. Redis handles everything else. // We trust you to know what you're doing. } diff --git a/tests/Functional/SecurityTest.php b/tests/Functional/SecurityTest.php index eb6951f..aca892a 100644 --- a/tests/Functional/SecurityTest.php +++ b/tests/Functional/SecurityTest.php @@ -7,7 +7,7 @@ use LLegaz\Cache\RedisEnhancedCache as SUT2; /** - * @todo test PSR 6 class too + * @todo trigger exception on "\" character * * * @author Laurent LEGAZ @@ -36,7 +36,6 @@ public function testSpecialCharactersDoNotCauseInjectionPSR16() { // Attempt "injection-like" patterns $dangerousKeys = [ - 'key\nFLUSHALL', 'key|FLUSHALL', 'key`FLUSHALL`', 'key$(FLUSHALL)', @@ -60,7 +59,6 @@ public function testSpecialCharactersDoNotCauseInjectionPSR6() // Attempt "injection-like" patterns $dangerousKeys = [ - 'key\nFLUSHALL', 'key|FLUSHALL', 'key`FLUSHALL`', 'key$(FLUSHALL)', diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index b4f5cf8..e846480 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -80,7 +80,15 @@ public static function invalidArrayKeys() { return [ [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand\\str'], ['key with withespace'], + ['key with tabs'], + ['key' . PHP_EOL .'with' . PHP_EOL .'CRLF'], + ['key\nFLUSHALL'], // insecure key [self::$bigKey], ]; } @@ -109,6 +117,11 @@ public static function invalidTEKeysSingle() ); } + /** + * Type Error keys (psr/cache version 3) + * + * @return type + */ public static function invalidTEKeys() { return [ @@ -119,6 +132,8 @@ public static function invalidTEKeys() } /** + * @todo replace this by original one (or complete it with closure :shrug:) + * * @return array */ public static function invalidTtl() From ca8359b6cb93d5658ab5da4423ba1eb8c092d8e4 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Mon, 16 Feb 2026 10:36:18 +0100 Subject: [PATCH 42/45] key validation enhanced PSR16 with persistent connections --- tests/Integration/CacheIntegrationTest.php | 4 ++-- tests/Integration/CacheIntegrationWithPCTest.php | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index e846480..91f2ea7 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -67,9 +67,9 @@ public static function invalidKeys() ); } + /** - * Yup this isn't optimal but I've only 2 restricted key scenario when keys - * are forced into strings type + * Less restricted key scenarios whth keys 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. diff --git a/tests/Integration/CacheIntegrationWithPCTest.php b/tests/Integration/CacheIntegrationWithPCTest.php index 4b4435c..5f57bac 100644 --- a/tests/Integration/CacheIntegrationWithPCTest.php +++ b/tests/Integration/CacheIntegrationWithPCTest.php @@ -68,8 +68,7 @@ public static function invalidKeys() } /** - * Yup this isn't optimal but I've only 2 restricted key scenario when keys - * are forced into strings type + * Less restricted key scenarios whth keys 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. @@ -80,7 +79,15 @@ public static function invalidArrayKeys() { return [ [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand\\str'], ['key with withespace'], + ['key with tabs'], + ['key' . PHP_EOL .'with' . PHP_EOL .'CRLF'], + ['key\nFLUSHALL'], // insecure key [self::$bigKey], ]; } From a222025f1acb12aec353c727937314ee4bc2c05f Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Mon, 16 Feb 2026 10:43:07 +0100 Subject: [PATCH 43/45] key validation enhanced PSR6 --- tests/Integration/PoolIntegrationTest.php | 40 ++++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index bc2b4df..78f8a4e 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -16,11 +16,24 @@ /** * Test PSR-6 implementation + * + * @todo Template those very similar integration tests ! * * check @link https://github.com/php-cache/integration-tests */ class PoolIntegrationTest extends CachePoolTest { + private static string $bigKey = ''; + + public static function setUpBeforeClass(): void + { + //36 KB + for ($i = 36864; $i > 0; $i--) { + self::$bigKey .= 'a'; + } + parent::setUpBeforeClass(); + } + /** * @before */ @@ -45,36 +58,39 @@ protected function setUp(): void public static function invalidKeys() { - $bigKey = ''; - //36 KB - for ($i = 36864; $i > 0; $i--) { - $bigKey .= 'a'; - } - return array_merge( self::invalidArrayKeys(), [ [''], ['key with withespace'], - [$bigKey] + [self::$bigKey] ] ); } + /** - * 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). + * Less restricted key scenarios whth keys forced into strings type + * (which is the case thanks to PSR-16 v3 from psr/simple-cache repository). * - * @link https://github.com/php-fig/cache The psr/cache repository. + * @link https://github.com/php-fig/simple-cache The psr/simple-cache repository. * * @return array */ public static function invalidArrayKeys() { return [ - ['key with withespace'], [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand\\str'], + ['key with withespace'], + ['key with tabs'], + ['key' . PHP_EOL .'with' . PHP_EOL .'CRLF'], + ['key\nFLUSHALL'], // insecure key + [self::$bigKey], ]; } From c46f1804ded0fe00b7b3f9f06666b6ddb624bc26 Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Mon, 16 Feb 2026 11:05:54 +0100 Subject: [PATCH 44/45] key validation enhanced PSR6 with persistent connections --- src/RedisCache.php | 4 +-- tests/Integration/CacheIntegrationTest.php | 10 +++--- .../CacheIntegrationWithPCTest.php | 6 ++-- tests/Integration/PoolIntegrationTest.php | 6 ++-- .../Integration/PoolIntegrationWithPCTest.php | 33 +++++++++++++++---- 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index 930929d..5937cf9 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -29,7 +29,7 @@ * * @todo refactor to hide those predis Status and logic bound either to predis and php-redis * (use adapter project client classes like mset => multipleSet method) - * + * * @todo also clean and harmonize all those $redisResponse * * @@ -465,7 +465,7 @@ protected function checkKeyValidity(mixed &$key): void *filter also common forbidden characters between URL RFC and PSR-6/16 key definition * that is those 3: \{}, backslash and curly brackets * we use square brackets in IPv6 based URLs... - * + * */ if (preg_match('/[\\\\{}]/', $key)) { throw new InvalidKeyException('Cache key cannot contain backslash or curly bracket'); diff --git a/tests/Integration/CacheIntegrationTest.php b/tests/Integration/CacheIntegrationTest.php index 91f2ea7..0b3199f 100644 --- a/tests/Integration/CacheIntegrationTest.php +++ b/tests/Integration/CacheIntegrationTest.php @@ -68,8 +68,9 @@ public static function invalidKeys() } + /** - * Less restricted key scenarios whth keys forced into strings type + * We have less restricted key scenarios with keys 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. @@ -87,12 +88,13 @@ public static function invalidArrayKeys() ['rand\\str'], ['key with withespace'], ['key with tabs'], - ['key' . PHP_EOL .'with' . PHP_EOL .'CRLF'], + ['key' . PHP_EOL . 'with' . PHP_EOL . 'CRLF'], ['key\nFLUSHALL'], // insecure key [self::$bigKey], ]; } + /** * more TypeError on single operation method (declared with string arguments) * @see Psr\SimpleCache\CacheInterface @@ -119,7 +121,7 @@ public static function invalidTEKeysSingle() /** * Type Error keys (psr/cache version 3) - * + * * @return type */ public static function invalidTEKeys() @@ -133,7 +135,7 @@ public static function invalidTEKeys() /** * @todo replace this by original one (or complete it with closure :shrug:) - * + * * @return array */ public static function invalidTtl() diff --git a/tests/Integration/CacheIntegrationWithPCTest.php b/tests/Integration/CacheIntegrationWithPCTest.php index 5f57bac..65bbde2 100644 --- a/tests/Integration/CacheIntegrationWithPCTest.php +++ b/tests/Integration/CacheIntegrationWithPCTest.php @@ -67,8 +67,9 @@ public static function invalidKeys() ); } + /** - * Less restricted key scenarios whth keys forced into strings type + * We have less restricted key scenarios with keys 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. @@ -86,12 +87,13 @@ public static function invalidArrayKeys() ['rand\\str'], ['key with withespace'], ['key with tabs'], - ['key' . PHP_EOL .'with' . PHP_EOL .'CRLF'], + ['key' . PHP_EOL . 'with' . PHP_EOL . 'CRLF'], ['key\nFLUSHALL'], // insecure key [self::$bigKey], ]; } + /** * more TypeError on single operation method (declared with string arguments) * @see Psr\SimpleCache\CacheInterface diff --git a/tests/Integration/PoolIntegrationTest.php b/tests/Integration/PoolIntegrationTest.php index 78f8a4e..253ee57 100644 --- a/tests/Integration/PoolIntegrationTest.php +++ b/tests/Integration/PoolIntegrationTest.php @@ -16,7 +16,7 @@ /** * Test PSR-6 implementation - * + * * @todo Template those very similar integration tests ! * * check @link https://github.com/php-cache/integration-tests @@ -70,7 +70,7 @@ public static function invalidKeys() /** - * Less restricted key scenarios whth keys forced into strings type + * We have less restricted key scenarios with keys 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. @@ -88,7 +88,7 @@ public static function invalidArrayKeys() ['rand\\str'], ['key with withespace'], ['key with tabs'], - ['key' . PHP_EOL .'with' . PHP_EOL .'CRLF'], + ['key' . PHP_EOL . 'with' . PHP_EOL . 'CRLF'], ['key\nFLUSHALL'], // insecure key [self::$bigKey], ]; diff --git a/tests/Integration/PoolIntegrationWithPCTest.php b/tests/Integration/PoolIntegrationWithPCTest.php index 48f14e8..5a1f1d3 100644 --- a/tests/Integration/PoolIntegrationWithPCTest.php +++ b/tests/Integration/PoolIntegrationWithPCTest.php @@ -21,6 +21,17 @@ */ class PoolIntegrationWithPCTest extends CachePoolTest { + private static string $bigKey = ''; + + public static function setUpBeforeClass(): void + { + //36 KB + for ($i = 36864; $i > 0; $i--) { + self::$bigKey .= 'a'; + } + parent::setUpBeforeClass(); + } + /** * @before */ @@ -56,28 +67,38 @@ public static function invalidKeys() [ [''], ['key with withespace'], - [$bigKey] + [self::$bigKey], ] ); } + /** - * 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). + * We have less restricted key scenarios with keys forced into strings type + * (which is the case thanks to PSR-16 v3 from psr/simple-cache repository). * - * @link https://github.com/php-fig/cache The psr/cache repository. + * @link https://github.com/php-fig/simple-cache The psr/simple-cache repository. * * @return array */ public static function invalidArrayKeys() { return [ - ['key with withespace'], [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand\\str'], + ['key with withespace'], + ['key with tabs'], + ['key' . PHP_EOL . 'with' . PHP_EOL . 'CRLF'], + ['key\nFLUSHALL'], // insecure key + [self::$bigKey], ]; } + /** * @todo TypeError suite tests needed see CacheIntegrationTest class */ From d82e2fa103ee6ed7eee0f6acf6b04fe080c9908c Mon Sep 17 00:00:00 2001 From: Laurent Legaz Date: Mon, 16 Feb 2026 12:28:45 +0100 Subject: [PATCH 45/45] review wording --- src/RedisCache.php | 8 +++++--- src/RedisEnhancedCache.php | 29 ++++++----------------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/RedisCache.php b/src/RedisCache.php index 5937cf9..e941ebf 100644 --- a/src/RedisCache.php +++ b/src/RedisCache.php @@ -38,6 +38,8 @@ * * @package RedisCache * @author Laurent LEGAZ + * @version 1.0 + * @see https://redis.io/docs/latest/develop/data-types/#strings Redis Strings documentation */ class RedisCache extends RedisAdapter implements CacheInterface { @@ -462,9 +464,9 @@ protected function checkKeyValidity(mixed &$key): void } /** - *filter also common forbidden characters between URL RFC and PSR-6/16 key definition - * that is those 3: \{}, backslash and curly brackets - * we use square brackets in IPv6 based URLs... + * We filter also common forbidden characters between URL RFC and PSR-6/16 key definition, + * that is those 3 chars: \{}, backslash and curly brackets. + * We keep square brackets for use in IPv6 based URLs... * */ if (preg_match('/[\\\\{}]/', $key)) { diff --git a/src/RedisEnhancedCache.php b/src/RedisEnhancedCache.php index c0f9d03..b2959b4 100644 --- a/src/RedisEnhancedCache.php +++ b/src/RedisEnhancedCache.php @@ -74,7 +74,7 @@ * @author Laurent LEGAZ * @version 1.0 * @see RedisCache Parent class providing base Redis functionality - * @see https://redis.io/docs/data-types/hashes/ Redis Hash documentation + * @see https://redis.io/docs/latest/develop/data-types/#hashes Redis Hash documentation */ class RedisEnhancedCache extends RedisCache { @@ -96,16 +96,9 @@ class RedisEnhancedCache extends RedisCache * Stores multiple key-value pairs in a specified Redis Hash pool. * * This method allows batch insertion of cache entries into a named pool using Redis Hash operations. - * All values are automatically serialized before storage to ensure consistency. The method uses - * HMSET for multiple values and HSET for single values to optimize Redis operations. - * - * Behavior: - * - For multiple values (count > 1): Uses Redis HMSET command - * - For single value (count === 1): Uses Redis HSET command - * - For empty array: Returns false without Redis operation + * All values are automatically serialized before storage to ensure consistency. * * Data Processing: - * - Keys are validated against PSR compliance rules (except for the ":" character which is accepted) * - Values are serialized using internal serialization mechanism * - Both keys and values undergo validation before storage * @@ -193,15 +186,8 @@ public function storeToPool(array $values, string $pool = self::DEFAULT_POOL): b * * All retrieved values are automatically unserialized to restore their original data types. * - * Return Behavior: - * - Single key (string/int): Returns the value or DOES_NOT_EXIST constant - * - Multiple keys (array): Returns associative array of key => value pairs - * - Non-existent keys: Replaced with DOES_NOT_EXIST constant in results - * - Invalid parameters: Throws InvalidKeyException - * * Data Processing: * - Values are automatically deserialized upon retrieval - * - String values are converted back to their original types * - Missing values are marked with DOES_NOT_EXIST constant * - Array results maintain key association from input * @@ -273,12 +259,6 @@ public function fetchFromPool(mixed $key, string $pool = self::DEFAULT_POOL): mi * which is useful for conditional logic and validation operations. It uses Redis HEXISTS * command for optimal performance. * - * Implementation Notes: - * - Uses native Redis HEXISTS command for efficiency - * - Handles adapter differences between php-redis and predis clients - * - php-redis returns boolean true/false - * - predis returns integer 1/0 - * - Both are normalized to boolean return value * * Usage Examples: * @@ -339,7 +319,6 @@ public function hasInPool(string $key, string $pool = self::DEFAULT_POOL): bool * * Behavior: * - Uses Redis HDEL for atomic deletion - * - All keys are validated before deletion attempt * - Non-existent keys are silently ignored (not treated as errors) * - Returns true if Redis operation succeeds (even if some keys didn't exist) * @@ -455,6 +434,10 @@ public function setHsetPoolExpiration(string $pool = self::DEFAULT_POOL, int $ex $redisResponse = -1; if ($expirationTime > 0) { + /** + * CRITICAL WARNING: + * Expiration applies to the ENTIRE pool ! + */ $redisResponse = $this->getRedis()->expire($pool, $expirationTime); }