Skip to content

Commit a92c8a4

Browse files
committed
Add connection_persistent_id parameter
Fixes #738
1 parent 6c37565 commit a92c8a4

File tree

7 files changed

+135
-2
lines changed

7 files changed

+135
-2
lines changed

docs/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,35 @@ snc_redis:
171171

172172
It also works for the Phpredis Cluster mode.
173173

174+
### Persistent Connections ###
175+
176+
When using persistent connections, you may need to provide a unique identifier to ensure that different clients don't share the same socket connection. This is especially important when operating multiple clients within the same application that communicate with the same Redis server.
177+
178+
``` yaml
179+
snc_redis:
180+
clients:
181+
app_cache:
182+
type: predis # or phpredis
183+
alias: app_cache
184+
dsn: redis://localhost/0
185+
options:
186+
connection_persistent: true
187+
connection_persistent_id: "app_cache_connection"
188+
session_store:
189+
type: predis # or phpredis
190+
alias: session_store
191+
dsn: redis://localhost/1
192+
options:
193+
connection_persistent: true
194+
connection_persistent_id: "session_store_connection"
195+
```
196+
197+
**Why this matters:** Without `connection_persistent_id`, two clients connecting to different databases on the same Redis server would share the same persistent socket, leading to unexpected behavior where commands from one client could affect the database context of another.
198+
199+
**For phpredis clients:** The `connection_persistent_id` is passed directly to the `pconnect()` function as the persistent ID parameter.
200+
201+
**For predis clients:** The `connection_persistent_id` is mapped to the `conn_uid` connection option (requires predis/predis version 2.4.0 or higher). This ensures each client creates its own socket so the connection context won't be shared across clients.
202+
174203
### Sessions ###
175204

176205
Use Redis sessions by utilizing Symfony built-in Redis session handler like so:
@@ -284,6 +313,7 @@ snc_redis:
284313
prefix: foo
285314
connection_timeout: 10
286315
connection_persistent: true
316+
connection_persistent_id: "cluster_connection"
287317
read_write_timeout: 30
288318
iterable_multibulk: false
289319
throw_errors: true

src/DependencyInjection/Configuration/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ private function addClientsSection(ArrayNodeDefinition $rootNode): void
125125
->end()
126126
->booleanNode('connection_async')->defaultFalse()->end()
127127
->booleanNode('connection_persistent')->defaultFalse()->end()
128+
->scalarNode('connection_persistent_id')->defaultNull()->end()
128129
->scalarNode('connection_timeout')->cannotBeEmpty()->defaultValue(5)->end()
129130
->scalarNode('read_write_timeout')->defaultNull()->end()
130131
->booleanNode('iterable_multibulk')->defaultFalse()->end()

src/DependencyInjection/SncRedisExtension.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,25 @@ private function loadPredisClient(array $client, ContainerBuilder $container): v
139139
$client['options']['exceptions'] = $client['options']['throw_errors'];
140140
// fix ssl configuration key name
141141
$client['options']['ssl'] = $client['options']['parameters']['ssl_context'] ?? [];
142+
143+
// Handle connection_persistent_id for predis conn_uid (requires predis >= 2.4.0)
144+
if (isset($client['options']['connection_persistent_id'])) {
145+
if (class_exists('Predis\Client')) {
146+
if (version_compare(\Predis\Client::VERSION, '2.4.0', '>=')) {
147+
$client['options']['conn_uid'] = $client['options']['connection_persistent_id'];
148+
} else {
149+
throw new InvalidConfigurationException(
150+
'The connection_persistent_id parameter for Predis requires predis/predis version 2.4.0 or higher. ' .
151+
sprintf('Current version: %s', \Predis\Client::VERSION)
152+
);
153+
}
154+
}
155+
}
156+
142157
unset($client['options']['connection_async']);
143158
unset($client['options']['connection_timeout']);
144159
unset($client['options']['connection_persistent']);
160+
unset($client['options']['connection_persistent_id']);
145161
unset($client['options']['throw_errors']);
146162
unset($client['options']['parameters']['ssl_context']);
147163

src/Factory/PhpredisClientFactory.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ private function createClientFromSentinel(string $class, array $dsns, string $al
123123
$sentinelClass = $isRelay ? Sentinel::class : RedisSentinel::class;
124124
$masterName = $options['service'];
125125
$connectionTimeout = $options['connection_timeout'] ?? 0;
126-
$connectionPersistent = $options['connection_persistent'] ? $masterName : null;
126+
$connectionPersistent = null;
127+
if ($options['connection_persistent']) {
128+
$connectionPersistent = $options['connection_persistent_id'] ?? $masterName;
129+
}
127130
$readTimeout = $options['read_write_timeout'] ?? 0;
128131
$parameters = $options['parameters'];
129132

@@ -254,11 +257,16 @@ private function createClient(RedisDsn $dsn, string $class, string $alias, array
254257
$context['stream'] = $options['parameters']['ssl_context'];
255258
}
256259

260+
$persistentId = null;
261+
if (!empty($options['connection_persistent'])) {
262+
$persistentId = $options['connection_persistent_id'] ?? $dsn->getPersistentId();
263+
}
264+
257265
$connectParameters = [
258266
$socket ?? ($dsn->getTls() ? 'tls://' : '') . $dsn->getHost(),
259267
$dsn->getPort(),
260268
$options['connection_timeout'],
261-
empty($options['connection_persistent']) ? null : $dsn->getPersistentId(),
269+
$persistentId,
262270
5, // retry interval
263271
5, // read timeout
264272
$context,

tests/DependencyInjection/SncRedisExtensionEnvTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public function testPhpredisDefaultParameterConfig(string $config, string $class
9696
[
9797
'connection_async' => false,
9898
'connection_persistent' => false,
99+
'connection_persistent_id' => null,
99100
'connection_timeout' => 5,
100101
'read_write_timeout' => null,
101102
'iterable_multibulk' => false,
@@ -139,6 +140,7 @@ public function testPhpredisFullConfig(): void
139140
'logging' => false,
140141
],
141142
'connection_async' => false,
143+
'connection_persistent_id' => null,
142144
'read_write_timeout' => null,
143145
'iterable_multibulk' => false,
144146
'throw_errors' => true,
@@ -166,6 +168,7 @@ public function testPhpredisWithAclConfig(): void
166168
'cluster' => null,
167169
'connection_async' => false,
168170
'connection_persistent' => true,
171+
'connection_persistent_id' => null,
169172
'connection_timeout' => 10,
170173
'iterable_multibulk' => false,
171174
'parameters' => [
@@ -219,6 +222,7 @@ public function testPhpRedisClusterOption(): void
219222
'cluster' => true,
220223
'connection_async' => false,
221224
'connection_persistent' => false,
225+
'connection_persistent_id' => null,
222226
'connection_timeout' => 5,
223227
'read_write_timeout' => null,
224228
'iterable_multibulk' => false,
@@ -248,6 +252,7 @@ public function testPhpRedisSentinelOption(): void
248252
'service' => 'mymaster',
249253
'connection_async' => false,
250254
'connection_persistent' => false,
255+
'connection_persistent_id' => null,
251256
'connection_timeout' => 5,
252257
'read_write_timeout' => null,
253258
'iterable_multibulk' => false,
@@ -279,6 +284,7 @@ public function testPhpRedisClusterOptionMultipleDsn(): void
279284
'connection_timeout' => 1.5,
280285
'connection_persistent' => true,
281286
'connection_async' => false,
287+
'connection_persistent_id' => null,
282288
'iterable_multibulk' => false,
283289
'throw_errors' => true,
284290
'serialization' => 'default',

tests/DependencyInjection/SncRedisExtensionTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,58 @@ private function getPhpRedisWithInvalidACLYamlMinimalConfig(): string
755755
username: user
756756
password: password
757757

758+
YAML;
759+
}
760+
761+
public function testPredisWithConnectionPersistentId(): void
762+
{
763+
if (!class_exists('Predis\Client')) {
764+
$this->markTestSkipped('Predis not available');
765+
}
766+
767+
if (version_compare(\Predis\Client::VERSION, '2.4.0', '<')) {
768+
$this->markTestSkipped('Predis version 2.4.0 or higher required for connection_persistent_id');
769+
}
770+
771+
$extension = new SncRedisExtension();
772+
$config = $this->parseYaml($this->getPredisWithConnectionPersistentIdYamlConfig());
773+
$extension->load([$config], $container = $this->getContainer());
774+
775+
$this->assertTrue($container->hasDefinition('snc_redis.default'));
776+
$definition = $container->getDefinition('snc_redis.connection.default_parameters.default');
777+
$this->assertSame('my_custom_conn_uid', $definition->getArgument(0)['conn_uid']);
778+
}
779+
780+
public function testPredisWithConnectionPersistentIdVersionTooOld(): void
781+
{
782+
if (!class_exists('Predis\Client')) {
783+
$this->markTestSkipped('Predis not available');
784+
}
785+
786+
if (version_compare(\Predis\Client::VERSION, '2.4.0', '>=')) {
787+
$this->markTestSkipped('This test requires Predis version < 2.4.0');
788+
}
789+
790+
$this->expectException(InvalidConfigurationException::class);
791+
$this->expectExceptionMessage('The connection_persistent_id parameter for Predis requires predis/predis version 2.4.0 or higher');
792+
793+
$extension = new SncRedisExtension();
794+
$config = $this->parseYaml($this->getPredisWithConnectionPersistentIdYamlConfig());
795+
$extension->load([$config], $container = $this->getContainer());
796+
}
797+
798+
private function getPredisWithConnectionPersistentIdYamlConfig(): string
799+
{
800+
return <<<'YAML'
801+
clients:
802+
default:
803+
type: predis
804+
alias: default
805+
dsn: redis://localhost:6379/0
806+
options:
807+
connection_persistent: true
808+
connection_persistent_id: my_custom_conn_uid
809+
758810
YAML;
759811
}
760812

tests/Factory/PhpredisClientFactoryTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,4 +411,24 @@ public function testMethodWithPassByRefArgument(): void
411411
/** @psalm-suppress TooFewArguments */
412412
$this->assertSame(['mykey'], $client->scan($iterator2));
413413
}
414+
415+
public function testCreateWithCustomPersistentId(): void
416+
{
417+
$factory = new PhpredisClientFactory(new RedisCallInterceptor($this->redisLogger));
418+
419+
$client = $factory->create(
420+
Redis::class,
421+
['redis://localhost:6379'],
422+
[
423+
'connection_timeout' => 5,
424+
'connection_persistent' => true,
425+
'connection_persistent_id' => 'my_custom_persistent_id',
426+
],
427+
'default',
428+
false,
429+
);
430+
431+
$this->assertInstanceOf(Redis::class, $client);
432+
$this->assertSame('my_custom_persistent_id', $client->getPersistentID());
433+
}
414434
}

0 commit comments

Comments
 (0)