m6web/redis-bundle
Symfony bundle that integrates Redis into your application with configurable client connections, session storage, caching, and profiling support. Simplifies wiring Redis services and managing multiple connections through Symfony configuration.
Install the bundle and its transport:
composer require m6web/redis-bundle predis/predis
Register the bundle — Symfony 2/3: add new M6web\Bundle\RedisBundle\M6webRedisBundle() to AppKernel; Symfony 4/5: add M6web\Bundle\RedisBundle\M6webRedisBundle::class => ['all' => true] to config/bundles.php.
Configure one client in config/packages/m6web_redis.yaml (or app/config/config.yml):
m6web_redis:
clients:
default:
servers:
- { host: '%env(REDIS_HOST)%', port: 6379, database: 0 }
options:
prefix: 'app:'
Use it. The bundle registers m6web_redis.client.default (and one per configured name). Inject the service id or the bundle's Redis wrapper class:
use M6web\Bundle\RedisBundle\Redis\Redis;
class Counter
{
public function __construct(private Redis $redis) {}
public function hit(string $key): int
{
return (int) $this->redis->incr($key);
}
}
Wire it explicitly if autowiring fails: M6web\Bundle\RedisBundle\Redis\Redis: '@m6web_redis.client.default'.
Where to look first
DependencyInjection/Configuration.php — the authoritative list of config keys (clients, servers, options, commands, …).Resources/config/services.yml — exact service ids and aliases.Redis/Redis.php — which methods are wrapped and how the underlying Predis client is exposed.Tests/ — executable examples of config and usage, useful when the README is stale.First use case: a small cache or rate-limiter. Start with one client, set($key, $value, 'EX', 60) / get($key), then turn on the profiler data collector in dev to see the actual commands being emitted before adding more clients.
One named client per concern. Don't overload a single client. Isolate cache, sessions, queues, and rate-limits by separate entries so prefixes, databases, and timeouts can differ:
m6web_redis:
clients:
cache:
servers: [{ host: '%env(REDIS_HOST)%', port: 6379, database: 0 }]
options: { prefix: 'cache:' }
sessions:
servers: [{ host: '%env(REDIS_HOST)%', port: 6379, database: 1 }]
options: { prefix: 'sess:' }
lock:
servers: [{ host: '%env(REDIS_HOST)%', port: 6379, database: 2 }]
options: { read_write_timeout: 0 }
Hide the client behind a domain service. The bundle class is a thin proxy over Predis; wrapping it keeps Predis commands out of controllers and gives you one place to add logging, key naming, and exception fallbacks.
final class UserCache
{
public function __construct(private Redis $redis) {}
public function remember(int $id, callable $loader): array
{
$key = sprintf('user:%d', $id);
if ($raw = $this->redis->get($key)) {
return json_decode($raw, true);
}
$data = $loader();
$this->redis->set($key, json_encode($data), 'EX', 3600);
return $data;
}
}
Pipelines and transactions go through Predis. Get the underlying client from the wrapper and use pipeline() / multi() for batched writes:
$pipe = $this->redis->getClient()->pipeline();
foreach ($ids as $id) { $pipe->get("user:$id"); }
$results = $pipe->execute();
Structured values, not PHP serialize. Store JSON so non-PHP consumers (workers, other services) can read the same keys. Cast on read: Redis returns strings, so (int), (bool), and json_decode are your responsibility.
Sessions. The bundle ships a session storage implementation; point Symfony at it rather than writing your own handler:
framework:
session:
handler_id: m6web_redis.session_storage
Custom commands. If you use Lua scripts or Predis command classes, declare them in the client's commands option so they're registered on the connection instead of ad-hoc instantiation.
Per-environment config. Keep test on a dedicated database index or a separate Redis instance; override servers and prefix in config/packages/test/m6web_redis.yaml. Never let test runs share a database with dev.
Profiler in dev only. The bundle's data collector is genuinely useful for spotting N+1 command patterns and accidental KEYS calls, but leave it disabled in prod.
Health checks. Because connections are lazy, add a small warmup or /health endpoint that runs PING on each client. This turns a runtime surprise into a deploy-time failure.
snc/redis-bundle, symfony/cache Redis adapters, or Predis directly before committing. If you inherit this bundle, pin versions and add integration tests around Redis.predis/predis ^1.x. That series has known PHP 8 rough edges and lacks some newer Predis features. Check composer why predis/predis before upgrading PHP, and test SET with EX/NX options explicitly.PING in a smoke test.prefix option rewrites keys for most commands, but patterns passed to KEYS/SCAN and keys referenced inside Lua scripts need manual handling. keys('*') will return fully prefixed keys; don't assume you can strip the prefix naively.GET returns null, not false. A cached "0" or "" is falsy in PHP. Always compare with !== null, and cast numerics back.BLPOP/BRPOP need read_write_timeout: 0 on that client. Setting it globally means a hung Redis can stall a request indefinitely — scope it to the queue client only.SELECT, WATCH, and multi/exec state can bleed between requests. Prefer non-persistent connections unless you've measured the handshake cost.M6web\Bundle\RedisBundle\Redis\Redis, not Predis\Client. Type-hinting Predis\Client will fail; bind the class to the service id manually.clients is an array node; splitting it across multiple YAML files generally replaces rather than deep-merges. Declare all clients in one file, or verify merge semantics in Configuration.php first.FLUSHDB on a dedicated test database in setUp is the simplest reliable isolation. If you need speed, decorate the Redis wrapper with an in-memory fake for unit tests and keep real Redis only for integration tests.m6web_redis.client_factory and the config's options/commands keys are the intended customization seams — subclassing the bundle is rarely needed. Check for compiler passes before writing a custom service definition.Predis\Connection\ConnectionException around non-critical paths (cache reads, view counters) and fall back gracefully; let it propagate for anything where correctness matters (locks, idempotency keys).How can I help you explore Laravel packages today?