diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ab71cc..6f7e2b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,3 +18,5 @@ - [2026-01-09] DamImpr: Redis and memcache cache, instance passed in construction [#32](https://github.com/DamImpr/cache-multi-layer/pull/32) - [2026-01-09] DamImpr: Instance configuration [#33](https://github.com/DamImpr/cache-multi-layer/pull/33) + +- [2026-01-12] DamImpr: redis cache with phpredis or predis [#34](https://github.com/DamImpr/cache-multi-layer/pull/34) diff --git a/src/Enum/CacheEnum.php b/src/Enum/CacheEnum.php index 0d8d895..18b6924 100644 --- a/src/Enum/CacheEnum.php +++ b/src/Enum/CacheEnum.php @@ -11,4 +11,5 @@ enum CacheEnum: int case APCU = 1; case REDIS = 2; case MEMCACHE = 3; + case PREDIS = 4; } diff --git a/src/Exception/CacheMissingConfigurationException.php b/src/Exception/CacheMissingConfigurationException.php index 3663371..af21e4e 100644 --- a/src/Exception/CacheMissingConfigurationException.php +++ b/src/Exception/CacheMissingConfigurationException.php @@ -12,8 +12,4 @@ */ class CacheMissingConfigurationException extends Exception { - public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null) - { - parent::__construct($message, $code, $previous); - } } diff --git a/src/Service/Cache.php b/src/Service/Cache.php index ae22bc9..b76919b 100644 --- a/src/Service/Cache.php +++ b/src/Service/Cache.php @@ -117,6 +117,7 @@ public static function factory(CacheEnum $cacheEnum, int $ttl, array $configurat return match ($cacheEnum) { CacheEnum::APCU => new ApcuCache($ttl, $configuration), CacheEnum::REDIS => new RedisCache($ttl, $configuration), + CacheEnum::PREDIS => new PRedisCache($ttl, $configuration), CacheEnum::MEMCACHE => new MemcacheCache($ttl, $configuration) }; } diff --git a/src/Service/CacheManagerImplDryMode.php b/src/Service/CacheManagerImplDryMode.php index 96d5276..157cb68 100644 --- a/src/Service/CacheManagerImplDryMode.php +++ b/src/Service/CacheManagerImplDryMode.php @@ -12,11 +12,6 @@ */ class CacheManagerImplDryMode extends CacheManagerImpl { - protected function __construct(?CacheConfiguration $cacheConfiguration = null) - { - parent::__construct($cacheConfiguration); - } - #[\Override] public function appendCache(Cache $cache): bool { diff --git a/src/Service/MemcacheCache.php b/src/Service/MemcacheCache.php index e14b9b7..0e09dc0 100644 --- a/src/Service/MemcacheCache.php +++ b/src/Service/MemcacheCache.php @@ -172,7 +172,7 @@ protected function __construct(int $ttl, array $configuration = []) } else { $this->memcache = new Memcache(); $port = $configuration['port'] ?? 11211; - if (array_key_exists('persistent', $configuration)) { + if (array_key_exists('persistent', $configuration) && $configuration['persistent']) { $resultConnection = $this->memcache->pconnect($configuration['server_address'], $port); } else { $resultConnection = $this->memcache->connect($configuration['server_address'], $port); @@ -182,6 +182,7 @@ protected function __construct(int $ttl, array $configuration = []) throw new Exception("Connection not found"); } } + $this->compress = array_key_exists('compress', $configuration) && $configuration['compress']; } @@ -194,8 +195,11 @@ protected function assertConfig(array $configuration): void throw new CacheMissingConfigurationException("instance must be " . Memcache::class . " class"); } } + private readonly Memcache $memcache; + private readonly bool $compress; + private array $mandatoryKeys = [ 'server_address' ]; diff --git a/src/Service/PRedisCache.php b/src/Service/PRedisCache.php new file mode 100644 index 0000000..be9ff43 --- /dev/null +++ b/src/Service/PRedisCache.php @@ -0,0 +1,173 @@ + + */ +class PRedisCache extends Cache +{ + /** + * + * {@InheritDoc} + */ + #[\Override] + public function decrement(string $key, ?int $ttl = null): int|false + { + $value = $this->predisClient->decr($key); + if (empty($this->getRemainingTTL($key))) { + $this->predisClient->expire($key, $this->getTtlToUse($ttl)); + } + + return $value; + } + + /** + * + * {@InheritDoc} + */ + #[\Override] + public function get(string $key): int|float|string|Cacheable|array|null + { + $val = $this->predisClient->get($key); + if ($val === null) { + return null; + } + + $valDecoded = json_decode($val, true); + return is_array($valDecoded) ? $this->unserializeVal($valDecoded) : $valDecoded; + } + + /** + * + * {@InheritDoc} + */ + #[\Override] + public function set(string $key, int|float|string|Cacheable|array $val, ?int $ttl = null): bool + { + $data = is_array($val) ? $this->serializeValArray($val) : $this->serializeVal($val); + return $this->predisClient->setex($key, $this->getTtlToUse($ttl), json_encode($data)) !== null; + } + + /** + * + * {@InheritDoc} + */ + #[Override] + public function increment(string $key, ?int $ttl = null): int|false + { + $value = $this->predisClient->incr($key); + if (empty($this->getRemainingTTL($key))) { + $this->predisClient->expire($key, $this->getTtlToUse($ttl)); + } + + return $value; + } + + /** + * + * {@inheritDoc} + */ + #[Override] + public function clear(string $key): bool + { + return (bool) $this->predisClient->del($key); + } + + /** + * + * {@inheritDoc} + */ + #[Override] + public function clearAllCache(): bool + { + return $this->predisClient->flushall() !== null; + } + + /** + * + * {@InheritDoc} + */ + #[Override] + public function getRemainingTTL(string $key): ?int + { + $ttl = $this->predisClient->ttl($key); + return $ttl !== false ? $ttl : null; + } + + /** + * + * {@InheritDoc} + */ + protected function __construct(int $ttl, array $configuration = []) + { + parent::__construct($ttl, $configuration); + if (array_key_exists('instance', $configuration)) { + $this->predisClient = $configuration['instance']; + } else { + $this->predisClient = new PredisClient([ + 'scheme' => $configuration['tcp'] ?? 'tcp', + 'host' => $configuration['server_address'], + 'port' => $configuration['port'] ?? 6379, + 'password' => $configuration['password'] ?? '', + 'database' => $configuration['database'] ?? 0, + 'persistent' => $configuration['persistent'] ?? false, + 'conn_uid' => $configuration['connection_id'] ?? '' + ]); + } + } + + /** + * + * {@InheritDoc} + */ + #[\Override] + public function isConnected(): bool + { + return $this->predisClient->ping() !== null; + } + + /** + * + * {@InheritDoc} + */ + #[\Override] + public function getEnum(): CacheEnum + { + return CacheEnum::PREDIS; + } + + /** + * + * {@InheritDoc} + */ + #[\Override] + protected function getMandatoryConfig(): array + { + return $this->mandatoryKeys; + } + + #[\Override] + protected function assertConfig(array $configuration): void + { + if (!array_key_exists('instance', $configuration) || $configuration['instance'] instanceof PredisClient) { + parent::assertConfig($configuration); + } elseif (array_key_exists('instance', $configuration)) { + throw new CacheMissingConfigurationException("instance must be " . PredisClient::class . " class"); + } + } + + private readonly PredisClient $predisClient; + + private array $mandatoryKeys = [ + 'server_address' + ]; +} diff --git a/src/Service/RedisCache.php b/src/Service/RedisCache.php index a31635c..afb29f5 100644 --- a/src/Service/RedisCache.php +++ b/src/Service/RedisCache.php @@ -6,7 +6,6 @@ use CacheMultiLayer\Exception\CacheMissingConfigurationException; use CacheMultiLayer\Interface\Cacheable; use Override; -use Predis\Client as PredisClient; /** * @@ -22,9 +21,9 @@ class RedisCache extends Cache #[\Override] public function decrement(string $key, ?int $ttl = null): int|false { - $value = $this->predisClient->decr($key); + $value = $this->redis->decr($key); if (empty($this->getRemainingTTL($key))) { - $this->predisClient->expire($key, $this->getTtlToUse($ttl)); + $this->redis->expire($key, $this->getTtlToUse($ttl)); } return $value; @@ -37,12 +36,12 @@ public function decrement(string $key, ?int $ttl = null): int|false #[\Override] public function get(string $key): int|float|string|Cacheable|array|null { - $val = $this->predisClient->get($key); + $val = $this->redis->get($key); if ($val === null) { return null; } - $valDecoded = json_decode($val, true); + $valDecoded = json_decode((string) $val, true); return is_array($valDecoded) ? $this->unserializeVal($valDecoded) : $valDecoded; } @@ -54,7 +53,7 @@ public function get(string $key): int|float|string|Cacheable|array|null public function set(string $key, int|float|string|Cacheable|array $val, ?int $ttl = null): bool { $data = is_array($val) ? $this->serializeValArray($val) : $this->serializeVal($val); - return $this->predisClient->setex($key, $this->getTtlToUse($ttl), json_encode($data)) !== null; + return $this->redis->setex($key, $this->getTtlToUse($ttl), json_encode($data)) !== null; } /** @@ -64,9 +63,9 @@ public function set(string $key, int|float|string|Cacheable|array $val, ?int $tt #[Override] public function increment(string $key, ?int $ttl = null): int|false { - $value = $this->predisClient->incr($key); + $value = $this->redis->incr($key); if (empty($this->getRemainingTTL($key))) { - $this->predisClient->expire($key, $this->getTtlToUse($ttl)); + $this->redis->expire($key, $this->getTtlToUse($ttl)); } return $value; @@ -79,7 +78,7 @@ public function increment(string $key, ?int $ttl = null): int|false #[Override] public function clear(string $key): bool { - return (bool) $this->predisClient->del($key); + return (bool) $this->redis->del($key); } /** @@ -89,7 +88,7 @@ public function clear(string $key): bool #[Override] public function clearAllCache(): bool { - return $this->predisClient->flushall() !== null; + return $this->redis->flushall() !== null; } /** @@ -99,7 +98,7 @@ public function clearAllCache(): bool #[Override] public function getRemainingTTL(string $key): ?int { - $ttl = $this->predisClient->ttl($key); + $ttl = $this->redis->ttl($key); return $ttl !== false ? $ttl : null; } @@ -111,16 +110,14 @@ protected function __construct(int $ttl, array $configuration = []) { parent::__construct($ttl, $configuration); if (array_key_exists('instance', $configuration)) { - $this->predisClient = $configuration['instance']; + $this->redis = $configuration['instance']; } else { - $this->predisClient = new PredisClient([ - 'scheme' => $configuration['tcp'] ?? 'tcp', - 'host' => $configuration['server_address'], - 'port' => $configuration['port'] ?? 6379, - 'password' => $configuration['password'] ?? '', - 'database' => $configuration['database'] ?? 0, - 'persistent' => $configuration['persistent'] ?? false - ]); + $this->redis = new \Redis(); + if (array_key_exists('persistent', $configuration) && $configuration['persistent']) { + $this->redis->connect($configuration['server_address'], $configuration['port'] ?? 6379, $configuration['timeout'] ?? 3, $configuration['connection_id'] ?? 'app_redis_connection'); + } else { + $this->redis->connect($configuration['server_address'], $configuration['port'] ?? 6379, $configuration['timeout'] ?? 3); + } } } @@ -131,7 +128,7 @@ protected function __construct(int $ttl, array $configuration = []) #[\Override] public function isConnected(): bool { - return $this->predisClient->ping() !== null; + return $this->redis->ping() !== null; } /** @@ -157,13 +154,15 @@ protected function getMandatoryConfig(): array #[\Override] protected function assertConfig(array $configuration): void { - if (!array_key_exists('instance', $configuration) || $configuration['instance'] instanceof PredisClient) { + if (!array_key_exists('instance', $configuration) || $configuration['instance'] instanceof \Redis) { parent::assertConfig($configuration); } elseif (array_key_exists('instance', $configuration)) { - throw new CacheMissingConfigurationException("instance must be " . PredisClient::class . " class"); + throw new CacheMissingConfigurationException("instance must be " . \Redis::class . " class"); } } - private readonly PredisClient $predisClient; + + private readonly \Redis $redis; + private array $mandatoryKeys = [ 'server_address' ]; diff --git a/tests/PRedisCacheTest.php b/tests/PRedisCacheTest.php new file mode 100644 index 0000000..15b299e --- /dev/null +++ b/tests/PRedisCacheTest.php @@ -0,0 +1,165 @@ + + */ +class PRedisCacheTest extends AbstractCache +{ + + #[Override] + protected function setUp(): void + { + parent::setUp(); + $this->setCache(Cache::factory(CacheEnum::PREDIS, 60, ['server_address' => 'redis-server'])); + } + + #[Override] + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { + // error was suppressed with the @-operator + if (0 === error_reporting()) { + return false; + } + + return match ($errno) { + E_USER_WARNING => true, + default => throw new Exception($errstr . ' -> ' . $errfile . ':' . $errline, 0), + }; + }); + } + + #[Override] + public function testArray(): void + { + parent::testArray(); + } + + #[Override] + public function testClass(): void + { + parent::testClass(); + } + + #[Override] + public function testClear(): void + { + parent::testClear(); + } + + #[Override] + public function testClearAllCache(): void + { + parent::testClearAllCache(); + } + + #[Override] + public function testExpireTtl(): void + { + parent::testExpireTtl(); + } + + #[Override] + public function testFloat(): void + { + parent::testFloat(); + } + + #[Override] + public function testIncrDecr(): void + { + parent::testIncrDecr(); + } + + #[Override] + public function testInteger(): void + { + parent::testInteger(); + } + + #[Override] + public function testString(): void + { + parent::testString(); + } + + #[\Override] + public function testIsConnected(): void + { + parent::testIsConnected(); + } + + #[\Override] + public function testRemainingTTL(): void + { + parent::testRemainingTTL(); + } + + #[\Override] + public function testEmptyDecrement(): void + { + parent::testEmptyDecrement(); + } + + #[\Override] + public function testEmptyIncrement(): void + { + parent::testEmptyIncrement(); + } + + public function testMissingServer(): void + { + $this->expectException(CacheMissingConfigurationException::class); + Cache::factory(CacheEnum::PREDIS, 60); + } + + public function testConnectionNotFound(): void + { + $this->expectException(Exception::class); + Cache::factory(CacheEnum::PREDIS, 60, ['server_address' => 'ip-no-redis'])->isConnected(); + } + + public function testEnum(): void + { + $this->doTestRealEnum(CacheEnum::PREDIS); + } + + public function testConnectionPersistent(): void + { + $this->assertTrue(Cache::factory(CacheEnum::PREDIS, 60, ['server_address' => 'redis-server', 'persistent' => true])->isConnected()); + } + + public function testInstance(): void + { + $client = new Client([ + 'host' => 'redis-server', + 'port' => 6379 + ]); + Cache::factory(CacheEnum::PREDIS, 60, ['instance' => $client]); + $this->assertTrue(true); //no exception throwns + } + + public function testMissingInstance(): void + { + $this->expectException(CacheMissingConfigurationException::class); + Cache::factory(CacheEnum::PREDIS, 60, ['instance' => 5]); + } + + #[\Override] + public static function tearDownAfterClass(): void + { + restore_error_handler(); + Cache::factory(CacheEnum::PREDIS, 60, ['server_address' => 'redis-server'])->clearAllCache(); + } +} \ No newline at end of file diff --git a/tests/RedisCacheTest.php b/tests/RedisCacheTest.php index af9d235..50a2990 100644 --- a/tests/RedisCacheTest.php +++ b/tests/RedisCacheTest.php @@ -7,7 +7,6 @@ use CacheMultiLayer\Service\Cache; use Exception; use Override; -use Predis\Client; /** * REDIS unit test class implementation @@ -142,10 +141,8 @@ public function testConnectionPersistent(): void public function testInstance(): void { - $redis = new Client([ - 'host' => 'redis-server', - 'port' => 6379 - ]); + $redis = new \Redis(); + $redis->connect('redis-server', 6379); Cache::factory(CacheEnum::REDIS, 60, ['instance' => $redis]); $this->assertTrue(true); //no exception throwns }