Redis
Redis connection DSN format, key prefix strategy, SCAN-based clear, tag index internals, and database selection.
Redis
RedisAdapter uses PHP's ext-redis C extension — installed in the Vortos Docker image. RedisConnectionFactory handles connection, authentication, and database selection from a DSN string.
DSN Format
redis://[:password@]host[:port][/database]| Component | Required | Default | Example |
|---|---|---|---|
host | Yes | — | redis, 127.0.0.1 |
port | No | 6379 | 6380 |
password | No | none | secretpassword |
database | No | 0 | /1 |
Examples
# Local development — no auth, default port, database 0
redis://redis:6379
# With password
redis://:secretpassword@redis:6379
# Custom port
redis://redis:6380
# With password and database 1
redis://:secretpassword@redis:6379/1
# Remote with all options
redis://:p@ssw0rd@cache.example.com:6380/2Set in environment:
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=$config->dsn(sprintf(
'redis://%s%s:%s',
$_ENV['REDIS_PASSWORD'] ? ':' . $_ENV['REDIS_PASSWORD'] . '@' : '',
$_ENV['REDIS_HOST'],
$_ENV['REDIS_PORT'],
));Key Prefix
All cache keys are automatically prefixed. Always include APP_ENV and app name:
$config->prefix($_ENV['APP_ENV'] . '_myapp_');
// dev environment: dev_myapp_user:123:profile
// prod environment: prod_myapp_user:123:profileThis lets dev and prod safely share one Redis instance without key collisions. It is also why clear() uses SCAN+DEL with the prefix instead of FLUSHDB — only this app's keys are affected.
Key Format Internals
{prefix}{key} — cached value
{prefix}__tag__{tagName} — Redis SET of prefixed keys for this tagExample with prefix prod_myapp_:
prod_myapp_user:123:profile → serialized profile value
prod_myapp___tag__user:123 → SET { "prod_myapp_user:123:profile", "prod_myapp_user:123:settings" }
prod_myapp___tag__permissions → SET { "prod_myapp_permissions:matrix:tenant-1", ... }Serialization
Values are serialized with PHP's native serialize()/unserialize(). This handles all PHP types including objects, arrays, nested structures, and custom classes. ext-redis's built-in serializer is disabled — RedisConnectionFactory sets Redis::OPT_SERIALIZER to Redis::SERIALIZER_NONE explicitly.
By default, unserialize() is called with allowed_classes: false — plain arrays and scalars only. If you need to cache PHP objects, declare the allowed classes explicitly in config/cache.php:
$config->allowSerializedClasses([
\App\User\Domain\UserProfile::class,
\App\Catalog\Domain\ProductSummary::class,
]);Only the listed classes will be instantiated during deserialization. Any other class in the serialized payload will be returned as __PHP_Incomplete_Class instead of being constructed — preventing PHP object injection attacks via a compromised Redis instance.
clear() Uses SCAN, Not FLUSHDB
// CORRECT — only deletes keys with this adapter's prefix
$cache->clear();
// NEVER do this directly — wipes entire Redis database
// $redis->flushDb(); // ← destroys Kafka offsets, sessions, messaging keysclear() uses Redis SCAN with cursor iteration and the configured prefix pattern. It deletes only this adapter's keys in batches of 100:
SCAN 0 MATCH "prod_myapp_*" COUNT 100
→ delete batch
SCAN cursor MATCH "prod_myapp_*" COUNT 100
→ delete batch
... until cursor = 0This is safe on large keyspaces — never blocks Redis.
Tag Index in Redis
setWithTags() uses Redis MULTI/EXEC (pipeline) for atomicity:
MULTI
SETEX prod_myapp_user:123:profile 3600 <serialized>
SADD prod_myapp___tag__user:123 "prod_myapp_user:123:profile"
EXPIRE prod_myapp___tag__user:123 604800 (7 days)
EXECinvalidateTags() reads the SET and deletes all members:
SMEMBERS prod_myapp___tag__user:123
→ ["prod_myapp_user:123:profile", "prod_myapp_user:123:settings"]
DEL prod_myapp_user:123:profile prod_myapp_user:123:settings
DEL prod_myapp___tag__user:123Redis Database Selection
Redis supports 16 logical databases (0-15). The default database is 0. Use database index separation when sharing a Redis instance across multiple applications or concerns:
redis://:password@redis:6379/0 # application cache (default)
redis://:password@redis:6379/1 # sessions
redis://:password@redis:6379/2 # rate limitingKey prefixes are usually sufficient for separation within one application. Use different databases for complete isolation between unrelated applications on the same Redis host.
Redis Connection is Lazy
The Redis-holding services (RedisAdapter, RedisHealthCheck) are registered as lazy services (PHP native lazy objects). RedisConnectionFactory::fromDsn() still connects eagerly when it runs, but it only runs on the first cache/health call — not at container construction. A process that never touches the cache (a control-plane or deploy console command run in the image with no infra reachable) boots and exits without ever opening a Redis socket.
When Redis is used, the first cache operation establishes the connection and fails closed if Redis is unreachable. Gate readiness on Redis health in your deployment pipeline:
# docker-compose.yml
backend:
depends_on:
redis:
condition: service_healthyext-redis Required
RedisAdapter requires ext-redis (the C extension), not predis/predis (pure PHP). ext-redis is pre-installed in the Vortos Docker image via pecl install redis. If you see Class "Redis" not found, the extension is not installed.