From 08de6c46f57948ab14e7c800af3821e0c925049c Mon Sep 17 00:00:00 2001 From: SonyPradana Date: Wed, 22 Apr 2026 19:00:05 +0700 Subject: [PATCH 1/3] fix redis fail to detect unix socket DSN --- src/System/Redis/RedisConnector.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/System/Redis/RedisConnector.php b/src/System/Redis/RedisConnector.php index a76a33f0..8139f351 100644 --- a/src/System/Redis/RedisConnector.php +++ b/src/System/Redis/RedisConnector.php @@ -34,14 +34,14 @@ public function connect(array $config): object unset($config['dsn']); } - $redis = new \Redis(); - $timeout = (float) ($config['timeout'] ?? 0.0); + $redis = new \Redis(); + $timeout = (float) ($config['timeout'] ?? 0.0); $retry_interval = (int) ($config['retry_interval'] ?? 0); - $read_timeout = (float) ($config['read_timeout'] ?? 0.0); - $persistent = (bool) ($config['persistent'] ?? false); - $persistent_id = (string) ($config['persistent_id'] ?? ''); + $read_timeout = (float) ($config['read_timeout'] ?? 0.0); + $persistent = (bool) ($config['persistent'] ?? false); + $persistent_id = (string) ($config['persistent_id'] ?? ''); - $host = (string) ($config['unix_socket'] ?? $config['host'] ?? '127.0.0.1'); + $host = (string) ($config['unix_socket'] ?? $config['host'] ?? '127.0.0.1'); $isSocket = isset($config['unix_socket']) || str_starts_with($host, '/'); try { @@ -50,7 +50,7 @@ public function connect(array $config): object $redis, 'connect', [$host, 0, $timeout, $persistent_id, $retry_interval], - $persistent + $persistent, ); } else { $this->establishConnection( @@ -63,7 +63,7 @@ public function connect(array $config): object $persistent_id, $retry_interval, ], - $persistent + $persistent, ); } @@ -112,7 +112,7 @@ protected function parseDsn(string $dsn): array $config = []; - if (isset($parsed['path']) && str_starts_with($parsed['path'], '/') && !isset($parsed['host'])) { + if (isset($parsed['path']) && str_starts_with($parsed['path'], '/') && empty($parsed['host'])) { $config['unix_socket'] = $parsed['path']; return $config; From f021e2e0b310c368bf7b0770338c0c98e6681968 Mon Sep 17 00:00:00 2001 From: SonyPradana Date: Wed, 22 Apr 2026 19:34:16 +0700 Subject: [PATCH 2/3] add test and add more cover dsn pasre host --- src/System/Redis/RedisConnector.php | 6 +- tests/Redis/RedisConnectorTest.php | 316 ++++++++++++++++++++++++++++ 2 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 tests/Redis/RedisConnectorTest.php diff --git a/src/System/Redis/RedisConnector.php b/src/System/Redis/RedisConnector.php index 8139f351..234ff60a 100644 --- a/src/System/Redis/RedisConnector.php +++ b/src/System/Redis/RedisConnector.php @@ -104,6 +104,10 @@ public function connect(array $config): object */ protected function parseDsn(string $dsn): array { + if (str_starts_with($dsn, 'redis:///')) { + return ['unix_socket' => substr($dsn, 8)]; + } + $parsed = parse_url($dsn); if (false === $parsed || ($parsed['scheme'] ?? '') !== 'redis') { @@ -118,7 +122,7 @@ protected function parseDsn(string $dsn): array return $config; } - if (isset($parsed['host'])) { + if (false === empty($parsed['host'])) { $config['host'] = $parsed['host']; } diff --git a/tests/Redis/RedisConnectorTest.php b/tests/Redis/RedisConnectorTest.php new file mode 100644 index 00000000..107fabf9 --- /dev/null +++ b/tests/Redis/RedisConnectorTest.php @@ -0,0 +1,316 @@ +parseDsn($dsn); + }; + + $config = $parseDsn->call($connector, $dsn); + + $this->assertEquals($expected, $config); + } + + public static function dsnProvider(): array + { + return [ + 'basic' => [ + 'redis://127.0.0.1:6379', + [ + 'host' => '127.0.0.1', + 'port' => 6379, + ], + ], + 'with password' => [ + 'redis://:password@127.0.0.1:6379', + [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => 'password', + ], + ], + 'with database' => [ + 'redis://127.0.0.1:6379/2', + [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 2, + ], + ], + 'with password and database' => [ + 'redis://:secret@localhost:6379/1', + [ + 'host' => 'localhost', + 'port' => 6379, + 'password' => 'secret', + 'database' => 1, + ], + ], + 'unix socket' => [ + 'redis:///var/run/redis.sock', + [ + 'unix_socket' => '/var/run/redis.sock', + ], + ], + 'unix socket single slash' => [ + 'redis:/var/run/redis.sock', + [ + 'unix_socket' => '/var/run/redis.sock', + ], + ], + 'host only' => [ + 'redis://localhost', + [ + 'host' => 'localhost', + ], + ], + 'with trailing slash' => [ + 'redis://localhost:6379/', + [ + 'host' => 'localhost', + 'port' => 6379, + ], + ], + ]; + } + + /** + * @test + */ + public function it_throws_exception_for_invalid_dsn_scheme() + { + $connector = new RedisConnector(); + + $parseDsn = function (string $dsn) { + return $this->parseDsn($dsn); + }; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid Redis DSN: http://127.0.0.1'); + + $parseDsn->call($connector, 'http://127.0.0.1'); + } + + /** + * @test + */ + public function it_throws_exception_for_malformed_dsn() + { + $connector = new RedisConnector(); + + $parseDsn = function (string $dsn) { + return $this->parseDsn($dsn); + }; + + $this->expectException(\InvalidArgumentException::class); + + $parseDsn->call($connector, 'redis://:6379:invalid'); + } + + /** + * @test + */ + public function it_can_connect_with_dsn_config() + { + $connector = new class extends RedisConnector { + public $establishConnectionCalled = false; + public $parameters = []; + public $method = ''; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void + { + $this->establishConnectionCalled = true; + $this->method = $method; + $this->parameters = $parameters; + } + }; + + $config = [ + 'dsn' => 'redis://:secret@localhost:6379/2', + 'timeout' => 2.5, + ]; + + // We use a real \Redis object but we don't connect it. + // Some methods might throw if not connected, but select/auth usually just return false or throw \RedisException + // if the extension is strictly checking connection state. + try { + $redis = $connector->connect($config); + $this->assertInstanceOf(\Redis::class, $redis); + } catch (\RedisException $e) { + // If it throws RedisException because of select/auth on non-connected redis, + // we at least verified it reached that point. + $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); + } + + $this->assertTrue($connector->establishConnectionCalled); + $this->assertEquals('localhost', $connector->parameters[0]); + $this->assertEquals(6379, $connector->parameters[1]); + $this->assertEquals(2.5, $connector->parameters[2]); + } + + /** + * @test + */ + public function it_can_connect_with_array_config() + { + $connector = new class extends RedisConnector { + public $establishConnectionCalled = false; + public $parameters = []; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void + { + $this->establishConnectionCalled = true; + $this->parameters = $parameters; + } + }; + + $config = [ + 'host' => '127.0.0.1', + 'port' => 6380, + 'timeout' => 1.0, + ]; + + try { + $redis = $connector->connect($config); + $this->assertInstanceOf(\Redis::class, $redis); + } catch (\RedisException $e) { + $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); + } + + $this->assertTrue($connector->establishConnectionCalled); + $this->assertEquals('127.0.0.1', $connector->parameters[0]); + $this->assertEquals(6380, $connector->parameters[1]); + } + + /** + * @test + */ + public function it_can_connect_via_unix_socket() + { + $connector = new class extends RedisConnector { + public $establishConnectionCalled = false; + public $parameters = []; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void + { + $this->establishConnectionCalled = true; + $this->parameters = $parameters; + } + }; + + $config = [ + 'unix_socket' => '/tmp/redis.sock', + ]; + + try { + $redis = $connector->connect($config); + $this->assertInstanceOf(\Redis::class, $redis); + } catch (\RedisException $e) { + $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); + } + + $this->assertTrue($connector->establishConnectionCalled); + $this->assertEquals('/tmp/redis.sock', $connector->parameters[0]); + $this->assertEquals(0, $connector->parameters[1]); + } + + /** + * @test + */ + public function it_can_handle_persistent_connection() + { + $connector = new class extends RedisConnector { + public $method = ''; + public $persistent = false; + public $parameters = []; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void + { + $this->method = $method; + $this->persistent = $persistent; + $this->parameters = $parameters; + } + }; + + $config = [ + 'host' => '127.0.0.1', + 'persistent' => true, + 'persistent_id' => 'my-id', + ]; + + $connector->connect($config); + + $this->assertTrue($connector->persistent); + $this->assertEquals('my-id', $connector->parameters[3]); + } + + /** + * @test + */ + public function it_can_set_read_timeout() + { + // This test is a bit tricky as it calls setOption on real Redis object. + // But we can check if it runs without crashing. + $connector = new class extends RedisConnector { + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void + { + // Don't connect + } + }; + + $config = [ + 'host' => '127.0.0.1', + 'read_timeout' => 5.0, + ]; + + try { + $redis = $connector->connect($config); + $this->assertInstanceOf(\Redis::class, $redis); + } catch (\RedisException $e) { + $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); + } + } + + /** + * @test + */ + public function it_calls_establish_connection_with_correct_method() + { + $connector = new RedisConnector(); + + $establishConnection = function ($redis, $method, $parameters, $persistent) { + $this->establishConnection($redis, $method, $parameters, $persistent); + }; + + $redis = $this->getMockBuilder('Redis') + ->onlyMethods(['connect', 'pconnect']) + ->getMock(); + + $redis->expects($this->once()) + ->method('connect') + ->with('127.0.0.1', 6379); + + $establishConnection->call($connector, $redis, 'any', ['127.0.0.1', 6379], false); + + $redis2 = $this->getMockBuilder('Redis') + ->onlyMethods(['connect', 'pconnect']) + ->getMock(); + + $redis2->expects($this->once()) + ->method('pconnect') + ->with('127.0.0.1', 6379); + + $establishConnection->call($connector, $redis2, 'any', ['127.0.0.1', 6379], true); + } +} From f49ae7f8a26e2badf93393fd1d433fae9af9c5af Mon Sep 17 00:00:00 2001 From: SonyPradana Date: Wed, 22 Apr 2026 19:37:47 +0700 Subject: [PATCH 3/3] formatting --- src/System/Redis/RedisConnector.php | 12 ++--- tests/Redis/RedisConnectorTest.php | 83 +++++++++++++++-------------- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/src/System/Redis/RedisConnector.php b/src/System/Redis/RedisConnector.php index 234ff60a..7d0444de 100644 --- a/src/System/Redis/RedisConnector.php +++ b/src/System/Redis/RedisConnector.php @@ -34,14 +34,14 @@ public function connect(array $config): object unset($config['dsn']); } - $redis = new \Redis(); - $timeout = (float) ($config['timeout'] ?? 0.0); + $redis = new \Redis(); + $timeout = (float) ($config['timeout'] ?? 0.0); $retry_interval = (int) ($config['retry_interval'] ?? 0); - $read_timeout = (float) ($config['read_timeout'] ?? 0.0); - $persistent = (bool) ($config['persistent'] ?? false); - $persistent_id = (string) ($config['persistent_id'] ?? ''); + $read_timeout = (float) ($config['read_timeout'] ?? 0.0); + $persistent = (bool) ($config['persistent'] ?? false); + $persistent_id = (string) ($config['persistent_id'] ?? ''); - $host = (string) ($config['unix_socket'] ?? $config['host'] ?? '127.0.0.1'); + $host = (string) ($config['unix_socket'] ?? $config['host'] ?? '127.0.0.1'); $isSocket = isset($config['unix_socket']) || str_starts_with($host, '/'); try { diff --git a/tests/Redis/RedisConnectorTest.php b/tests/Redis/RedisConnectorTest.php index 107fabf9..311d300f 100644 --- a/tests/Redis/RedisConnectorTest.php +++ b/tests/Redis/RedisConnectorTest.php @@ -11,12 +11,13 @@ class RedisConnectorTest extends TestCase { /** * @test + * * @dataProvider dsnProvider */ - public function it_can_parse_redis_dsn(string $dsn, array $expected) + public function itCanParseRedisDsn(string $dsn, array $expected) { $connector = new RedisConnector(); - + $parseDsn = function (string $dsn) { return $this->parseDsn($dsn); }; @@ -39,24 +40,24 @@ public static function dsnProvider(): array 'with password' => [ 'redis://:password@127.0.0.1:6379', [ - 'host' => '127.0.0.1', - 'port' => 6379, + 'host' => '127.0.0.1', + 'port' => 6379, 'password' => 'password', ], ], 'with database' => [ 'redis://127.0.0.1:6379/2', [ - 'host' => '127.0.0.1', - 'port' => 6379, + 'host' => '127.0.0.1', + 'port' => 6379, 'database' => 2, ], ], 'with password and database' => [ 'redis://:secret@localhost:6379/1', [ - 'host' => 'localhost', - 'port' => 6379, + 'host' => 'localhost', + 'port' => 6379, 'password' => 'secret', 'database' => 1, ], @@ -92,10 +93,10 @@ public static function dsnProvider(): array /** * @test */ - public function it_throws_exception_for_invalid_dsn_scheme() + public function itThrowsExceptionForInvalidDsnScheme() { $connector = new RedisConnector(); - + $parseDsn = function (string $dsn) { return $this->parseDsn($dsn); }; @@ -109,38 +110,39 @@ public function it_throws_exception_for_invalid_dsn_scheme() /** * @test */ - public function it_throws_exception_for_malformed_dsn() + public function itThrowsExceptionForMalformedDsn() { $connector = new RedisConnector(); - + $parseDsn = function (string $dsn) { return $this->parseDsn($dsn); }; $this->expectException(\InvalidArgumentException::class); - + $parseDsn->call($connector, 'redis://:6379:invalid'); } /** * @test */ - public function it_can_connect_with_dsn_config() + public function itCanConnectWithDsnConfig() { $connector = new class extends RedisConnector { public $establishConnectionCalled = false; - public $parameters = []; - public $method = ''; + public $parameters = []; + public $method = ''; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void { $this->establishConnectionCalled = true; - $this->method = $method; - $this->parameters = $parameters; + $this->method = $method; + $this->parameters = $parameters; } }; $config = [ - 'dsn' => 'redis://:secret@localhost:6379/2', + 'dsn' => 'redis://:secret@localhost:6379/2', 'timeout' => 2.5, ]; @@ -165,21 +167,22 @@ protected function establishConnection($redis, string $method, array $parameters /** * @test */ - public function it_can_connect_with_array_config() + public function itCanConnectWithArrayConfig() { $connector = new class extends RedisConnector { public $establishConnectionCalled = false; - public $parameters = []; + public $parameters = []; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void { $this->establishConnectionCalled = true; - $this->parameters = $parameters; + $this->parameters = $parameters; } }; $config = [ - 'host' => '127.0.0.1', - 'port' => 6380, + 'host' => '127.0.0.1', + 'port' => 6380, 'timeout' => 1.0, ]; @@ -187,7 +190,7 @@ protected function establishConnection($redis, string $method, array $parameters $redis = $connector->connect($config); $this->assertInstanceOf(\Redis::class, $redis); } catch (\RedisException $e) { - $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); + $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); } $this->assertTrue($connector->establishConnectionCalled); @@ -198,15 +201,16 @@ protected function establishConnection($redis, string $method, array $parameters /** * @test */ - public function it_can_connect_via_unix_socket() + public function itCanConnectViaUnixSocket() { $connector = new class extends RedisConnector { public $establishConnectionCalled = false; - public $parameters = []; + public $parameters = []; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void { $this->establishConnectionCalled = true; - $this->parameters = $parameters; + $this->parameters = $parameters; } }; @@ -218,7 +222,7 @@ protected function establishConnection($redis, string $method, array $parameters $redis = $connector->connect($config); $this->assertInstanceOf(\Redis::class, $redis); } catch (\RedisException $e) { - $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); + $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); } $this->assertTrue($connector->establishConnectionCalled); @@ -229,23 +233,24 @@ protected function establishConnection($redis, string $method, array $parameters /** * @test */ - public function it_can_handle_persistent_connection() + public function itCanHandlePersistentConnection() { $connector = new class extends RedisConnector { - public $method = ''; + public $method = ''; public $persistent = false; public $parameters = []; + protected function establishConnection($redis, string $method, array $parameters, bool $persistent): void { - $this->method = $method; + $this->method = $method; $this->persistent = $persistent; $this->parameters = $parameters; } }; $config = [ - 'host' => '127.0.0.1', - 'persistent' => true, + 'host' => '127.0.0.1', + 'persistent' => true, 'persistent_id' => 'my-id', ]; @@ -258,7 +263,7 @@ protected function establishConnection($redis, string $method, array $parameters /** * @test */ - public function it_can_set_read_timeout() + public function itCanSetReadTimeout() { // This test is a bit tricky as it calls setOption on real Redis object. // But we can check if it runs without crashing. @@ -270,7 +275,7 @@ protected function establishConnection($redis, string $method, array $parameters }; $config = [ - 'host' => '127.0.0.1', + 'host' => '127.0.0.1', 'read_timeout' => 5.0, ]; @@ -278,17 +283,17 @@ protected function establishConnection($redis, string $method, array $parameters $redis = $connector->connect($config); $this->assertInstanceOf(\Redis::class, $redis); } catch (\RedisException $e) { - $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); + $this->assertStringContainsString('Could not connect to Redis', $e->getMessage()); } } /** * @test */ - public function it_calls_establish_connection_with_correct_method() + public function itCallsEstablishConnectionWithCorrectMethod() { $connector = new RedisConnector(); - + $establishConnection = function ($redis, $method, $parameters, $persistent) { $this->establishConnection($redis, $method, $parameters, $persistent); };