short-edition/redis-bundle
short-edition/redis-bundle is a Laravel/PHP package that bundles Redis integration, making it easy to configure connections and use Redis for caching, queues, or storage. Simple setup, sensible defaults, and a lightweight footprint for your app.
First, understand what you're actually holding. short-edition/redis-bundle is a republished copy of SncRedisBundle — the PHP namespace is still Snc\RedisBundle, the bundle class extends Symfony\Component\HttpKernel\Bundle\Bundle, and it is configured through Symfony's DI compiler passes. There is no Laravel service provider, no facade, and no config/*.php merge. A composer require inside a Laravel app either fails to autoload anything useful or drags symfony/http-kernel, symfony/dependency-injection, and symfony/config into your dependency graph so you can boot a second kernel next to Laravel's. Don't.
Verify provenance before anything else, because zero stars and zero dependents means nobody has reviewed this distribution:
composer show short-edition/redis-bundle --all # version, requires, autoload, tags
# compare against the upstream you already know:
# https://github.com/snc/SncRedisBundle (archived / maintenance mode)
If there is no tagged release, you'll be pinning a commit hash or adding a VCS repo entry — which is itself a signal that this isn't a dependency you want on a session path.
If the app is genuinely Symfony, registration is the upstream boilerplate:
// config/bundles.php
return [
// ...
Snc\RedisBundle\SncRedisBundle::class => ['all' => true],
];
# config/packages/snc_redis.yaml
snc_redis:
clients:
default:
type: phpredis
alias: default # service id: snc_redis.default
dsn: '%env(REDIS_URL)%'
options:
prefix: 'app:'
If the app is Laravel — and for a Laravel developer reading this, it is — then your first use case is not "install the bundle," it's "read the bundle's Configuration tree as a spec and wire the equivalent natively." The genuinely portable asset is the connection-option vocabulary (DSN format, cluster/sentinel topology, read_write_timeout, persistent connections, prefix strategy, serializer choice). That vocabulary maps 1:1 onto config/database.php:
// config/database.php
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'prefix' => env('REDIS_PREFIX', 'app:'),
'serializer' => Redis::SERIALIZER_IGBINARY, // or PHP / none — must match across apps
],
'default' => [
'url' => env('REDIS_URL', 'redis://127.0.0.1:6379/0'),
'database' => 0,
'persistent' => true,
'read_write_timeout' => 5,
],
'cache' => [ 'url' => env('REDIS_CACHE_URL'), 'database' => 1 ],
'session' => [ 'url' => env('REDIS_SESSION_URL'), 'database' => 2 ],
'queue' => [ 'url' => env('REDIS_QUEUE_URL'), 'database' => 3 ],
],
Then prove it works before you build on it:
php artisan tinker
>>> Redis::connection('default')->ping(); // "PONG"
>>> Cache::store('redis')->put('smoke', 1, 60);
>>> Cache::store('redis')->get('smoke'); // 1
Where to look first in the repo: DependencyInjection/Configuration.php (the full option surface), DependencyInjection/SncRedisExtension.php (how aliases become service IDs), and Client/ (the wrapper that standardizes phpredis vs Predis). Those three files are the entire value proposition; everything else is glue.
Split connections by purpose, not by convenience. The upstream bundle's strongest idea is named clients with aliases — snc_redis.cache, snc_redis.session, snc_redis.default. Reproduce that as Laravel connections. Day-to-day you then always know which namespace you're writing to:
Redis::connection('cache')->set($key, $value);
Redis::connection('session')->exists($sessionKey);
Redis::connection('queue')->lpush('queues:default', $payload);
Inject the manager, not the facade, in application code. The facade is fine in controllers and tinker; for anything testable, resolve the connection out of the container so you can swap it:
final class HotItems
{
public function __construct(private \Illuminate\Redis\RedisManager $redis) {}
public function top(int $n = 10): array
{
return $this->redis->connection('cache')
->zrevrange('hot:items', 0, $n - 1);
}
}
Cache / session / queue all collapse onto the same handful of config keys. This is the real "daily leverage" — you never instantiate a client again:
// config/cache.php
'redis' => ['driver' => 'redis', 'connection' => 'cache', 'lock_connection' => 'default'],
// session: SESSION_DRIVER=redis, SESSION_CONNECTION=session
// queue: QUEUE_CONNECTION=redis, REDIS_QUEUE_CONNECTION=queue
// Horizon: config/horizon.php -> 'connection' => 'default'
Keep lock_connection on a different database from the cache store; a FLUSHDB on the cache connection during an incident shouldn't drop the locks holding your scheduled jobs together.
Locks and rate limiting are where Redis earns its keep. The upstream bundle doesn't give you these; Laravel's Cache::lock() and RateLimiter do, on the same connection:
$lock = Cache::store('redis')->lock("export:{$id}", 30);
if (! $lock->get()) {
return; // someone else is exporting
}
try {
$this->export($id);
} finally {
$lock->release();
}
Pub/sub differs by client and this bites. With phpredis, Redis::subscribe() blocks the worker; with Predis you drive pubSubLoop(). The Symfony bundle abstracts this behind its client wrapper; Laravel does not. Pick your client per-subscriber accordingly, and never run a blocking subscriber inside an HTTP request.
Testing pattern: use a real Redis, on a disposable database, and flush it explicitly.
protected function setUp(): void
{
parent::setUp();
config(['database.redis.default.database' => 15]);
Redis::connection('default')->flushdb();
}
Do not point tests at database 0. Do not share a Redis instance with a dev environment without a distinct prefix. Prefer a docker compose service in CI over mocking Redis — the mock always lies about scan, pipelines, and expiry semantics.
Write one health-check artisan command and wire it into your deploy. This is the single most useful piece of code you'll add around Redis, and it's a gap the bundle leaves you to fill:
public function handle(): int
{
foreach (array_keys(config('database.redis')) as $name) {
if ($name === 'options') { continue; }
try {
Redis::connection($name)->ping();
$this->line("{$name}: ok");
} catch (\Throwable $e) {
$this->error("{$name}: {$e->getMessage()}");
return self::FAILURE;
}
}
return self::SUCCESS;
}
Extending. In Symfony, you'd decorate snc_redis.* services or register a compiler pass. In Laravel the equivalent extension point is RedisManager::extend() (custom connector) or a service provider binding a pre-configured typed connection. Reach for those only if you need TLS context options or Sentinel, which bring us to the gotchas.
The framework mismatch is the headline, not a footnote. Every hour spent debugging Symfony's container semantics in a Laravel codebase is non-transferable knowledge. If the requirement is "we need Redis," the answer is Illuminate\Redis plus config/database.php. Only symfony/cache's RedisAdapter is a worse trade than native — it's the same fork risk in a different wrapper.
phpredis vs Predis is a decision, not a detail. phpredis is a C extension (faster, ops burden, must be installed everywhere including your CI image). Predis is pure PHP (slower, easier deploy, but its cluster/sentinel behaviour differs subtly). Laravel's 'client' => 'phpredis' vs 'predis' switches method signatures — Redis::scan() returns [cursor, keys] on phpredis but a generator on Predis. Any code doing cursor iteration must be written against one client and pinned to it.
Sentinel is not natively supported by Illuminate\Redis. Laravel's config supports 'clusters' for Redis Cluster, but Sentinel failover requires Predis with options.parameters (replication/sentinel). This is the one area where the bundle's configuration schema is genuinely worth reading — copy the topology vocabulary, but validate against a containerized cluster before committing.
Prefix collisions are the most common production incident here. options.prefix applies globally to the connection; database separates keyspaces. Pick one strategy and document it. If cache, session, queue, and Horizon share a connection and a prefix, a flushdb or a KEYS-based cleanup takes down all four at once.
Silent fallback masks outages. Laravel's cache and session drivers throw on connection failure (good) — but if you've layered a file fallback or an array store in front, sessions silently start living on one server and users get logged out intermittently as traffic hits other nodes. Fail loudly on Redis for session; consider a read-through fallback only for non-critical caches, and alert on it.
Auth has moved on. Legacy AUTH password vs Redis 6+ ACL AUTH username password — the Symfony bundle's docs predate ACLs. In Laravel, set 'username' and 'password' separately. Managed providers (ElastiCache, Upstash) often require TLS: use the tls:// scheme and pass context SSL options, or you'll get opaque connection timeouts with no hints.
Persistent connections under PHP-FPM leak. 'persistent' => true keeps a socket per worker. Long-running queue workers should call Redis::connection()->disconnect() on Queue::looping boundaries, or you'll hit ERR max number of clients reached under scale.
read_write_timeout must exceed blocking-command timeouts. If you use BLPOP/BRPOP for queues, a default 5s read timeout will disconnect mid-block. Set the timeout above the block duration, not below.
Cluster multi-key operations need hash tags. MGET k1 k2 fails cross-slot in Redis Cluster. Since the bundle abstracts the client, this class of bug surfaces at runtime, not in review — {user:123}:profile and {user:123}:settings will hash to the same slot.
Expiry-driven workflows need notify-keyspace-events. Cache::lock() and TTL-based triggers don't rely on keyspace notifications, but any pub/sub-on-expiry pattern does. That's a server config (redis.conf), not something the package or Laravel sets for you — a frequent "why doesn't this fire locally" mystery.
No tags, no releases, no CVE path. If you must adopt the Symfony bundle (mixed repo, legacy app), pin a commit SHA and a composer.json with explicit symfony/* constraints, add a composer audit step to CI, and name an internal owner. Otherwise composer update resolves to whatever the fork's default branch contains on build day.
Debugging checklist when "Redis is broken": redis-cli -h host -p port -a pass PING outside PHP first; then php artisan tinker → Redis::connection()->ping(); then check config('database.redis') actually resolved your env vars (config:cache is the classic culprit — a cached config ignores new .env values). Only after all three pass should you suspect application code.
How can I help you explore Laravel packages today?