Skip to content

Commit db5108d

Browse files
committed
feature #59869 [Cache] Add support for valkey: / valkeys: schemes (nicolas-grekas)
This PR was merged into the 7.3 branch. Discussion ---------- [Cache] Add support for `valkey:` / `valkeys:` schemes | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Valkey is getting a lot of traction, so let's support it as a new scheme (I'm not much into renaming everything :) ) Commits ------- 5546196 Add support for `valkey:` / `valkeys:` schemes
2 parents 89bae9c + 5546196 commit db5108d

File tree

22 files changed

+108
-63
lines changed

22 files changed

+108
-63
lines changed

src/Symfony/Bridge/Doctrine/DependencyInjection/AbstractDoctrineExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ protected function loadCacheDriver(string $cacheName, string $objectManagerName,
301301
$cacheDef->addMethodCall('setMemcached', [new Reference($this->getObjectManagerElementName(\sprintf('%s_memcached_instance', $objectManagerName)))]);
302302
break;
303303
case 'redis':
304+
case 'valkey':
304305
$redisClass = !empty($cacheDriver['class']) ? $cacheDriver['class'] : '%'.$this->getObjectManagerElementName('cache.redis.class').'%';
305306
$redisInstanceClass = !empty($cacheDriver['instance_class']) ? $cacheDriver['instance_class'] : '%'.$this->getObjectManagerElementName('cache.redis_instance.class').'%';
306307
$redisHost = !empty($cacheDriver['host']) ? $cacheDriver['host'] : '%'.$this->getObjectManagerElementName('cache.redis_host').'%';

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,7 @@ private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBe
12851285
->scalarNode('directory')->defaultValue('%kernel.cache_dir%/pools/app')->end()
12861286
->scalarNode('default_psr6_provider')->end()
12871287
->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end()
1288+
->scalarNode('default_valkey_provider')->defaultValue('valkey://localhost')->end()
12881289
->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end()
12891290
->scalarNode('default_doctrine_dbal_provider')->defaultValue('database_connection')->end()
12901291
->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null)->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2501,7 +2501,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
25012501
// Inline any env vars referenced in the parameter
25022502
$container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true));
25032503
}
2504-
foreach (['psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) {
2504+
foreach (['psr6', 'redis', 'valkey', 'memcached', 'doctrine_dbal', 'pdo'] as $name) {
25052505
if (isset($config[$name = 'default_'.$name.'_provider'])) {
25062506
$container->setAlias('cache.'.$name, new Alias(CachePoolPass::getServiceProvider($container, $config[$name]), false));
25072507
}
@@ -2513,12 +2513,13 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con
25132513
'tags' => false,
25142514
];
25152515
}
2516+
$redisTagAwareAdapters = [['cache.adapter.redis_tag_aware'], ['cache.adapter.valkey_tag_aware']];
25162517
foreach ($config['pools'] as $name => $pool) {
25172518
$pool['adapters'] = $pool['adapters'] ?: ['cache.app'];
25182519

2519-
$isRedisTagAware = ['cache.adapter.redis_tag_aware'] === $pool['adapters'];
2520+
$isRedisTagAware = \in_array($pool['adapters'], $redisTagAwareAdapters, true);
25202521
foreach ($pool['adapters'] as $provider => $adapter) {
2521-
if (($config['pools'][$adapter]['adapters'] ?? null) === ['cache.adapter.redis_tag_aware']) {
2522+
if (\in_array($config['pools'][$adapter]['adapters'] ?? null, $redisTagAwareAdapters, true)) {
25222523
$isRedisTagAware = true;
25232524
} elseif ($config['pools'][$adapter]['tags'] ?? false) {
25242525
$pool['adapters'][$provider] = $adapter = '.'.$adapter.'.inner';

src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
'reset' => 'reset',
141141
])
142142
->tag('monolog.logger', ['channel' => 'cache'])
143+
->alias('cache.adapter.valkey', 'cache.adapter.redis')
143144

144145
->set('cache.adapter.redis_tag_aware', RedisTagAwareAdapter::class)
145146
->abstract()
@@ -156,6 +157,7 @@
156157
'reset' => 'reset',
157158
])
158159
->tag('monolog.logger', ['channel' => 'cache'])
160+
->alias('cache.adapter.valkey_tag_aware', 'cache.adapter.redis_tag_aware')
159161

160162
->set('cache.adapter.memcached', MemcachedAdapter::class)
161163
->abstract()

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ protected static function getBundleDefaultConfig()
883883
'system' => 'cache.adapter.system',
884884
'directory' => '%kernel.cache_dir%/pools/app',
885885
'default_redis_provider' => 'redis://localhost',
886+
'default_valkey_provider' => 'valkey://localhost',
886887
'default_memcached_provider' => 'memcached://localhost',
887888
'default_doctrine_dbal_provider' => 'database_connection',
888889
'default_pdo_provider' => ContainerBuilder::willBeAvailable('doctrine/dbal', Connection::class, ['symfony/framework-bundle']) && class_exists(DoctrineAdapter::class) ? 'database_connection' : null,

src/Symfony/Component/Cache/Adapter/AbstractAdapter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public static function createSystemCache(string $namespace, int $defaultLifetime
111111

112112
public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): mixed
113113
{
114-
if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:')) {
114+
if (str_starts_with($dsn, 'redis:') || str_starts_with($dsn, 'rediss:') || str_starts_with($dsn, 'valkey:') || str_starts_with($dsn, 'valkeys:')) {
115115
return RedisAdapter::createConnection($dsn, $options);
116116
}
117117
if (str_starts_with($dsn, 'memcached:')) {
@@ -128,7 +128,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
128128
return PdoAdapter::createConnection($dsn, $options);
129129
}
130130

131-
throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "memcached:", "couchbase:", "mysql:", "oci:", "pgsql:", "sqlsrv:" nor "sqlite:".');
131+
throw new InvalidArgumentException('Unsupported DSN: it does not start with "redis[s]:", "valkey[s]:", "memcached:", "couchbase:", "mysql:", "oci:", "pgsql:", "sqlsrv:" nor "sqlite:".');
132132
}
133133

134134
public function commit(): bool

src/Symfony/Component/Cache/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Add support for `\Relay\Cluster` in `RedisAdapter`
8+
* Add support for `valkey:` / `valkeys:` schemes
9+
* Rename options "redis_cluster" and "redis_sentinel" to "cluster" and "sentinel" respectively
810

911
7.2
1012
---

src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ public static function setUpBeforeClass(): void
3232
self::markTestSkipped('REDIS_SENTINEL_SERVICE env var is not defined.');
3333
}
3434

35-
self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']&timeout=0&retry_interval=0&read_timeout=0', ['redis_sentinel' => $service, 'prefix' => 'prefix_']);
35+
self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']&timeout=0&retry_interval=0&read_timeout=0', ['sentinel' => $service, 'prefix' => 'prefix_']);
3636
}
3737

3838
public function testInvalidDSNHasBothClusterAndSentinel()
3939
{
40-
$dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&redis_cluster=1&redis_sentinel=mymaster';
40+
$dsn = 'redis:?host[redis1]&host[redis2]&host[redis3]&cluster=1&sentinel=mymaster';
4141

4242
$this->expectException(InvalidArgumentException::class);
43-
$this->expectExceptionMessage('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.');
43+
$this->expectExceptionMessage('Cannot use both "cluster" and "sentinel" at the same time.');
4444

4545
RedisAdapter::createConnection($dsn);
4646
}
@@ -51,6 +51,6 @@ public function testExceptionMessageWhenFailingToRetrieveMasterInformation()
5151
$dsn = 'redis:?host['.str_replace(' ', ']&host[', $hosts).']';
5252
$this->expectException(InvalidArgumentException::class);
5353
$this->expectExceptionMessage('Failed to retrieve master information from sentinel "invalid-masterset-name".');
54-
AbstractAdapter::createConnection($dsn, ['redis_sentinel' => 'invalid-masterset-name']);
54+
AbstractAdapter::createConnection($dsn, ['sentinel' => 'invalid-masterset-name']);
5555
}
5656
}

src/Symfony/Component/Cache/Traits/RedisTrait.php

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ trait RedisTrait
4646
'retry_interval' => 0,
4747
'tcp_keepalive' => 0,
4848
'lazy' => null,
49-
'redis_cluster' => false,
50-
'relay_cluster_context' => [],
5149
'redis_sentinel' => null,
50+
'cluster' => false,
51+
'sentinel' => null,
52+
'relay_cluster_context' => [],
5253
'dbindex' => 0,
5354
'failover' => 'none',
5455
'ssl' => null, // see https://php.net/context.ssl
@@ -90,13 +91,13 @@ private function init(\Redis|Relay|RelayCluster|\RedisArray|\RedisCluster|\Predi
9091
*/
9192
public static function createConnection(#[\SensitiveParameter] string $dsn, array $options = []): \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|Relay|RelayCluster
9293
{
93-
if (str_starts_with($dsn, 'redis:')) {
94-
$scheme = 'redis';
95-
} elseif (str_starts_with($dsn, 'rediss:')) {
96-
$scheme = 'rediss';
97-
} else {
98-
throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:".');
99-
}
94+
$scheme = match (true) {
95+
str_starts_with($dsn, 'redis:') => 'redis',
96+
str_starts_with($dsn, 'rediss:') => 'rediss',
97+
str_starts_with($dsn, 'valkey:') => 'valkey',
98+
str_starts_with($dsn, 'valkeys:') => 'valkeys',
99+
default => throw new InvalidArgumentException('Invalid Redis DSN: it does not start with "redis[s]:" nor "valkey[s]:".'),
100+
};
100101

101102
if (!\extension_loaded('redis') && !class_exists(\Predis\Client::class)) {
102103
throw new CacheException('Cannot find the "redis" extension nor the "predis/predis" package.');
@@ -124,7 +125,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
124125

125126
$query = $hosts = [];
126127

127-
$tls = 'rediss' === $scheme;
128+
$tls = 'rediss' === $scheme || 'valkeys' === $scheme;
128129
$tcpScheme = $tls ? 'tls' : 'tcp';
129130

130131
if (isset($params['query'])) {
@@ -177,32 +178,41 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
177178

178179
$params += $query + $options + self::$defaultConnectionOptions;
179180

180-
if (isset($params['redis_sentinel']) && isset($params['sentinel_master'])) {
181-
throw new InvalidArgumentException('Cannot use both "redis_sentinel" and "sentinel_master" at the same time.');
181+
$aliases = [
182+
'sentinel_master' => 'sentinel',
183+
'redis_sentinel' => 'sentinel',
184+
'redis_cluster' => 'cluster',
185+
];
186+
foreach ($aliases as $alias => $key) {
187+
$params[$key] = match (true) {
188+
\array_key_exists($key, $query) => $query[$key],
189+
\array_key_exists($alias, $query) => $query[$alias],
190+
\array_key_exists($key, $options) => $options[$key],
191+
\array_key_exists($alias, $options) => $options[$alias],
192+
default => $params[$key],
193+
};
182194
}
183195

184-
$params['redis_sentinel'] ??= $params['sentinel_master'] ?? null;
185-
186-
if (isset($params['redis_sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) {
196+
if (isset($params['sentinel']) && !class_exists(\Predis\Client::class) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) {
187197
throw new CacheException('Redis Sentinel support requires one of: "predis/predis", "ext-redis >= 5.2", "ext-relay".');
188198
}
189199

190200
if (isset($params['lazy'])) {
191201
$params['lazy'] = filter_var($params['lazy'], \FILTER_VALIDATE_BOOLEAN);
192202
}
203+
$params['cluster'] = filter_var($params['cluster'], \FILTER_VALIDATE_BOOLEAN);
193204

194-
$params['redis_cluster'] = filter_var($params['redis_cluster'], \FILTER_VALIDATE_BOOLEAN);
195-
if ($params['redis_cluster'] && isset($params['redis_sentinel'])) {
196-
throw new InvalidArgumentException('Cannot use both "redis_cluster" and "redis_sentinel" at the same time.');
205+
if ($params['cluster'] && isset($params['sentinel'])) {
206+
throw new InvalidArgumentException('Cannot use both "cluster" and "sentinel" at the same time.');
197207
}
198208

199209
$class = $params['class'] ?? match (true) {
200-
$params['redis_cluster'] => match (true) {
210+
$params['cluster'] => match (true) {
201211
\extension_loaded('redis') => \RedisCluster::class,
202212
\extension_loaded('relay') => RelayCluster::class,
203213
default => \Predis\Client::class,
204214
},
205-
isset($params['redis_sentinel']) => match (true) {
215+
isset($params['sentinel']) => match (true) {
206216
\extension_loaded('redis') => \Redis::class,
207217
\extension_loaded('relay') => Relay::class,
208218
default => \Predis\Client::class,
@@ -213,7 +223,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
213223
default => \Predis\Client::class,
214224
};
215225

216-
if (isset($params['redis_sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) {
226+
if (isset($params['sentinel']) && !is_a($class, \Predis\Client::class, true) && !class_exists(\RedisSentinel::class) && !class_exists(Sentinel::class)) {
217227
throw new CacheException(\sprintf('Cannot use Redis Sentinel: class "%s" does not extend "Predis\Client" and neither ext-redis >= 5.2 nor ext-relay have been found.', $class));
218228
}
219229

@@ -237,7 +247,7 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
237247
$host = 'tls://'.$host;
238248
}
239249

240-
if (!isset($params['redis_sentinel'])) {
250+
if (!isset($params['sentinel'])) {
241251
break;
242252
}
243253

@@ -263,15 +273,15 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
263273
$sentinel = @new $sentinelClass($host, $port, $params['timeout'], (string) $params['persistent_id'], $params['retry_interval'], $params['read_timeout'], ...$extra);
264274
}
265275

266-
if ($address = @$sentinel->getMasterAddrByName($params['redis_sentinel'])) {
276+
if ($address = @$sentinel->getMasterAddrByName($params['sentinel'])) {
267277
[$host, $port] = $address;
268278
}
269279
} catch (\RedisException|\Relay\Exception $redisException) {
270280
}
271281
} while (++$hostIndex < \count($hosts) && !$address);
272282

273-
if (isset($params['redis_sentinel']) && !$address) {
274-
throw new InvalidArgumentException(\sprintf('Failed to retrieve master information from sentinel "%s".', $params['redis_sentinel']), previous: $redisException ?? null);
283+
if (isset($params['sentinel']) && !$address) {
284+
throw new InvalidArgumentException(\sprintf('Failed to retrieve master information from sentinel "%s".', $params['sentinel']), previous: $redisException ?? null);
275285
}
276286

277287
try {
@@ -446,11 +456,14 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
446456

447457
$redis = $params['lazy'] ? RedisClusterProxy::createLazyProxy($initializer) : $initializer();
448458
} elseif (is_a($class, \Predis\ClientInterface::class, true)) {
449-
if ($params['redis_cluster']) {
459+
if ($params['cluster']) {
450460
$params['cluster'] = 'redis';
451-
} elseif (isset($params['redis_sentinel'])) {
461+
} else {
462+
unset($params['cluster']);
463+
}
464+
if (isset($params['sentinel'])) {
452465
$params['replication'] = 'sentinel';
453-
$params['service'] = $params['redis_sentinel'];
466+
$params['service'] = $params['sentinel'];
454467
}
455468
$params += ['parameters' => []];
456469
$params['parameters'] += [
@@ -478,16 +491,16 @@ public static function createConnection(#[\SensitiveParameter] string $dsn, arra
478491
}
479492
}
480493

481-
if (1 === \count($hosts) && !($params['redis_cluster'] || $params['redis_sentinel'])) {
494+
if (1 === \count($hosts) && !isset($params['cluster']) & !isset($params['sentinel'])) {
482495
$hosts = $hosts[0];
483496
} elseif (\in_array($params['failover'], ['slaves', 'distribute'], true) && !isset($params['replication'])) {
484497
$params['replication'] = true;
485498
$hosts[0] += ['alias' => 'master'];
486499
}
487500
$params['exceptions'] = false;
488501

489-
$redis = new $class($hosts, array_diff_key($params, self::$defaultConnectionOptions));
490-
if (isset($params['redis_sentinel'])) {
502+
$redis = new $class($hosts, array_diff_key($params, array_diff_key(self::$defaultConnectionOptions, ['cluster' => null])));
503+
if (isset($params['sentinel'])) {
491504
$redis->getConnection()->setSentinelTimeout($params['timeout']);
492505
}
493506
} elseif (class_exists($class, false)) {

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add support for iterable of string in `StreamedResponse`
88
* Add `EventStreamResponse` and `ServerEvent` classes to streamline server event streaming
9+
* Add support for `valkey:` / `valkeys:` schemes for sessions
910

1011
7.2
1112
---

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public static function createHandler(object|string $connection, array $options =
5757

5858
case str_starts_with($connection, 'redis:'):
5959
case str_starts_with($connection, 'rediss:'):
60+
case str_starts_with($connection, 'valkey:'):
61+
case str_starts_with($connection, 'valkeys:'):
6062
case str_starts_with($connection, 'memcached:'):
6163
if (!class_exists(AbstractAdapter::class)) {
6264
throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');

src/Symfony/Component/Lock/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Add support for `valkey:` / `valkeys:` schemes
8+
49
7.2
510
---
611

src/Symfony/Component/Lock/Store/StoreFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public static function createStore(#[\SensitiveParameter] object|string $connect
6262

6363
case str_starts_with($connection, 'redis:'):
6464
case str_starts_with($connection, 'rediss:'):
65+
case str_starts_with($connection, 'valkey:'):
66+
case str_starts_with($connection, 'valkeys:'):
6567
case str_starts_with($connection, 'memcached:'):
6668
if (!class_exists(AbstractAdapter::class)) {
6769
throw new InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');

src/Symfony/Component/Messenger/Bridge/Redis/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Implement the `CloseableTransportInterface` to allow closing the Redis connection
88
* Implement the `KeepaliveReceiverInterface` to enable asynchronously notifying Redis that the job is still being processed, in order to avoid timeouts
9+
* Add support for `valkey:` / `valkeys:` schemes
910

1011
6.3
1112
---

src/Symfony/Component/Messenger/Bridge/Redis/Tests/Transport/ConnectionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ public function testInvalidSentinelMasterName()
423423
$this->expectException(\InvalidArgumentException::class);
424424
$this->expectExceptionMessage(\sprintf('Failed to retrieve master information from master name "%s" and address "%s".', $uid, $exp));
425425

426-
Connection::fromDsn(\sprintf('%s/messenger-clearlasterror', $master), ['delete_after_ack' => true, 'sentinel_master' => $uid], null);
426+
Connection::fromDsn(\sprintf('%s/messenger-clearlasterror', $master), ['delete_after_ack' => true, 'sentinel' => $uid], null);
427427
}
428428

429429
public function testFromDsnOnUnixSocketWithUserAndPassword()

0 commit comments

Comments
 (0)