PHPackages                             zenstruck/redis - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Caching](/categories/caching)
4. /
5. zenstruck/redis

ActiveLibrary[Caching](/categories/caching)

zenstruck/redis
===============

Lazy proxy for php-redis with DX helpers, utilities and a unified API.

v1.0.0(5mo ago)610.0k↓100%2MITPHPPHP &gt;=8.1CI passing

Since Mar 18Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/zenstruck/redis)[ Packagist](https://packagist.org/packages/zenstruck/redis)[ Docs](https://github.com/zenstruck/redis)[ GitHub Sponsors](https://github.com/kbond)[ GitHub Sponsors](https://github.com/nikophil)[ RSS](/packages/zenstruck-redis/feed)WikiDiscussions 1.x Synced 1mo ago

READMEChangelog (3)Dependencies (5)Versions (4)Used By (0)

zenstruck/redis
===============

[](#zenstruckredis)

[![CI](https://github.com/zenstruck/redis/actions/workflows/ci.yml/badge.svg)](https://github.com/zenstruck/redis/actions/workflows/ci.yml)[![Code Coverage](https://camo.githubusercontent.com/427b808052568441e7808b2c4cc1c1c7b2ec564149742034044e0286605a1f90/68747470733a2f2f636f6465636f762e696f2f67682f7a656e73747275636b2f72656469732f6272616e63682f312e782f67726170682f62616467652e7376673f746f6b656e3d443753434553534e3351)](https://codecov.io/gh/zenstruck/redis)

Lazy proxy for [php-redis](https://github.com/phpredis/phpredis) with DX helpers, utilities and a unified API.

`Zenstruck\Redis` is a unified proxy for `\Redis|\RedisArray|\RedisCluster`. With a few exceptions and considerations, the API is the same no matter the underlying client. This allows you to use the same API in development, where you are likely just using `\Redis`, and production, where you could be using `\RedisArray` or `\RedisCluster`.

The proxy is lazy in that, if created via a DSN, doesn't instantiate the underlying client until a command is executed.

This library integrates well with [Symfony](#symfony-framework) and a recipe is available.

Installation
------------

[](#installation)

```
composer require zenstruck/redis
```

Redis Factory
-------------

[](#redis-factory)

Creating a Redis client instance is done via a *DSN* string. The DSN must use the following format:

```
redis[s]://[pass@][ip|host|socket[:port]][/db-index][?{option}={value}...]

```

### Redis Proxy Factory

[](#redis-proxy-factory)

It is recommended to use the proxy whenever possible. It has the following benefits over using the *real* client:

1. **Lazy**: a connection is not established until a Redis command is actually called.
2. **Encapsulated**: for the most part, knowledge of the *real* client is not required. You don't need to change your usage depending on the client used. *There are [some](#sequencespipelines-and-transactions) [exceptions](#countableiterable) to this.*
3. **Developer Experience (DX)**: use the [fluent sequence and transaction api](#sequencespipelines-and-transactions).

Here are some examples creating the proxy from a DSN.

```
use Zenstruck\Redis;

$proxy = Redis::create('redis://localhost'); // Zenstruck\Redis
$proxy = Redis::create('redis://localhost?redis_sentinel=sentinel_service'); // Zenstruck\Redis (using Redis Sentinel)
$proxy = Redis::create('redis:?host[host1]&host[host2]'); // Zenstruck\Redis
$proxy = Redis::create('redis:?host[host1]&host[host2]&redis_cluster=1'); // Zenstruck\Redis
```

You can also create a Proxy from an exising instance of `\Redis|\RedisArray|\RedisCluster`:

```
use Zenstruck\Redis;

/** @var \Redis|\RedisArray|\RedisCluster $client */

$proxy = Redis::wrap($client)
```

### Redis *Real* Client Factory

[](#redis-real-client-factory)

An instance of `\Redis|\RedisArray|\RedisCluster` can be created directly:

```
use Zenstruck\Redis;

$client = Redis::createClient('redis://localhost'); // \Redis
$client = Redis::createClient('redis://localhost?redis_sentinel=sentinel_service'); // \Redis (using Redis Sentinel)
$client = Redis::createClient('redis:?host[host1]&host[host2]'); // \RedisArray
$client = Redis::createClient('redis:?host[host1]&host[host2]&redis_cluster=1'); // \RedisCluster
```

### Factory Options

[](#factory-options)

Certain Redis options can be set via your *DSN*'s query parameters or passed as an array to the second parameter of `Zenstruck\Redis::create/createClient()`.

#### Prefix

[](#prefix)

You can set a prefix for all keys:

```
use Zenstruck\Redis;

$proxy = Redis::create('redis://localhost?prefix=app:');
$proxy = Redis::create('redis://localhost', ['prefix' => 'app:']); // equivalent to above
```

#### Serializer Option

[](#serializer-option)

By default, Redis stores all scalar/null values as strings and objects/arrays as "Array"/"Object". In order to store properly typed values and objects/arrays, you must configure a Redis serializer:

```
use Zenstruck\Redis;

// PHP: serialize/unserialize values
$proxy = Redis::create('redis://localhost?serializer=php');
$proxy = Redis::create('redis://localhost', ['serializer' => \Redis::SERIALIZER_PHP]); // equivalent to above

// JSON: json_encode/json_decode values (doesn't work for objects)
$proxy = Redis::create('redis://localhost?serializer=json');
$proxy = Redis::create('redis://localhost', ['serializer' => \Redis::SERIALIZER_JSON]); // equivalent to above
```

**NOTE**: There is a performance trade off when using Redis serialization. Consider creating a separate client for operations/logic that requires serialization.

Redis Proxy API
---------------

[](#redis-proxy-api)

```
/** @var Zenstruck\Redis $proxy */

// call any \Redis|\RedisArray|\RedisCluster method
$proxy->set('mykey', 'value');
$proxy->get('mykey'); // "value"

// get the "real" client
$proxy->realClient(); // \Redis|\RedisArray|\RedisCluster
```

### Sequences/Pipelines and Transactions

[](#sequencespipelines-and-transactions)

The proxy has a fluent, auto-completable API for Redis pipelines and transactions:

```
/** @var Zenstruck\Redis $proxy */

// use \Redis::multi()
$results = $proxy->transaction()
    ->set('x', '42')
    ->incr('x')
    ->get('x')->as('value') // alias the result of this command
    ->del('x')
    ->execute() // the results of the above transaction as an array (keyed by index of command or alias if set)
;

$results['value']; // "43" (result of ->get())
$results[3]; // true (result of ->del())

// use \Redis::pipeline() - see note below about \RedisCluster
$proxy->sequence()
    ->set('x', '42')
    ->incr('x')
    ->get('x')->as('value') // alias the result of this command
    ->del('x')
    ->execute() // the results of the above sequence as an array (keyed by index of command of alias if set)
;

$results['value']; // "43" (result of ->get())
$results[3]; // true (result of ->del())
```

**NOTE:** When using `sequence()` with `\RedisCluster`, the commands are executed atomically as pipelines are not supported.

**NOTE:** When using `sequence()`/`transaction()` with a `\RedisArray` instance, the first command in the sequence/transaction must be a "key-based command" (ie `get()`/`set()`). This is to choose the node the transaction is run on.

### Countable\\Iterable

[](#countableiterable)

`Zenstruck\Redis` is countable and iterable. There are some differences when counting/iterating depending on the underlying client:

- `\Redis`: count is always 1 and iterates over itself once
- `\RedisArray`: count is the number of hosts and iterates over each host wrapped in a proxy.
- `\RedisCluser`: count is the number of *masters* and iterates over each *master*with *node parameters* pre-set. This enables running [node commands](https://github.com/phpredis/phpredis/blob/develop/cluster.markdown#directed-node-commands)on each *master* without passing node parameters to these commands (when iterating)

```
/** @var Zenstruck\Redis $proxy */

$proxy->count(); // 1 if \Redis, # hosts if \RedisArray, # "masters" if \RedisCluster

foreach ($proxy as $node) {
    $proxy->flushAll(); // this is permitted even for \RedisCluster (which typically requires a $nodeParams argument)
}
```

**NOTE:** If running commands that require being run on each host/*master* it is recommended to iterate and run even if using `\Redis`. This allows a seamless transition to `\RedisArray`/`\RedisCluster` later.

Utilities
---------

[](#utilities)

### ExpiringSet

[](#expiringset)

`Zenstruck\Redis\Utility\ExpiringSet` encapsulates the concept of a *Redis expiring set*: a set (unordered list with no duplicates) whose members expire after a time. Each read/write operation on the set *prunes* expired members.

```
/** @var Zenstruck\Redis $client */

$set = $client->expiringSet('my-set'); // redis key to store the set

$set->add('member1', 600); // set add "member1" that expires in 10 minutes
$set->add('member1', new \DateInterval::createFromDateString('5 minutes')); // can use \DateInterval for the TTL
$set->add('member1', new \DateTime('+5 minutes')); // use \DateTimeInterface to set specific expiry timestamp

$set->remove('member1'); // explicitly remove a member

$set->all(); // array - all unexpired members

$set->contains('member'); // true/false

$set->clear(); // clear all items

$set->prune(); // explicitly "prune" the set (remove expired members)

count($set); // int - number of unexpired members

foreach ($set as $member) {
    // iterate over unexpired members
}

// fluent
$set
    ->add('member1', 600)
    ->add('member2', 600)
    ->remove('member1')
    ->remove('member2')
    ->prune()
    ->clear()
;
```

**NOTE**: In order to use complex types (arrays/objects) as members, your redis client must be [configured with a serializer](#serializer-option).

Below is a pseudocode example using this object for tracking active users on a website. When authenticated users login or request a page, their username is added to the set with a 5-minute idle time-to-live (TTL). A user is considered *active*within this time. On logout, they are removed from the set. If a user has not made a request within their last TTL, they are removed from the set.

```
/** @var Zenstruck\Redis $client */

$set = $client->expiringSet('active-users');
$ttl = \DateInterval::createFromDateString('5 minutes');

// LOGIN EVENT:
$set->add($event->getUsername(), $ttl);

// LOGOUT EVENT:
$set->remove($event->getUsername());

// REQUEST EVENT:
$set->add($event->getUsername(), $ttl);

// ADMIN MONITORING DASHBOARD WIDGET
$activeUserCount = count($set);
$activeUsernames = $set->all(); // [user1, user2, ...]

// ADMIN USER CRUD LISTING
foreach ($users as $user) {
    $isActive = $set->contains($user->getUsername()); // bool
    // ...
}
```

Integrations
------------

[](#integrations)

### Symfony Framework

[](#symfony-framework)

Add a supported Redis [DSN](#redis-factory) environment variable:

```
# .env

REDIS_DSN=redis://localhost
```

Configure services:

```
# config/packages/zenstruck_redis.yaml

services:

    # Proxy that is autowireable
    Zenstruck\Redis:
        factory: ['Zenstruck\Redis', 'create']
        arguments: ['%env(REDIS_DSN)%']

    # Separate proxy's that have different prefixes
    redis1:
        class: Zenstruck\Redis
        factory: ['Zenstruck\Redis', 'create']
        arguments: ['%env(REDIS_DSN)%', { prefix: 'prefix1:' }]
    redis2:
        class: Zenstruck\Redis
        factory: ['Zenstruck\Redis', 'create']
        arguments: ['%env(REDIS_DSN)%', { prefix: 'prefix2:' }]

    # Separate proxy that uses PHP serialization
    serialization_redis:
        class: Zenstruck\Redis
        factory: ['Zenstruck\Redis', 'create']
        arguments: ['%env(REDIS_DSN)%', { serializer: php }]

    # expiring set service
    active_users:
        class: Zenstruck\Redis\Utility\ExpiringSet
        factory: ['@Zenstruck\Redis', 'expiringSet']
        arguments:
            - active_users # redis key

    # Specific clients that are autowireable
    Redis:
        class: Redis
        factory: ['Zenstruck\Redis', 'createClient']
        arguments: ['%env(REDIS_DSN)%'] # note REDIS_DSN must be for \Redis client

    RedisArray:
        class: RedisArray
        factory: ['Zenstruck\Redis', 'createClient']
        arguments: ['%env(REDIS_DSN)%'] # note REDIS_DSN must be for \RedisArray client

    RedisCluster:
        class: RedisCluster
        factory: ['Zenstruck\Redis', 'createClient']
        arguments: ['%env(REDIS_DSN)%'] # note REDIS_DSN must be for \RedisCluster client
```

Use `Zenstruck\Redis` for session storage (see [Symfony Docs](https://symfony.com/doc/current/session/database.html#store-sessions-in-a-key-value-database-redis)for more details/options):

```
# config/services.yaml

# Assumes "Zenstruck\Redis" is available as a service and symfony/expression-language is installed
services:
    redis_session_handler:
        class:  Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler
        arguments:
            - "@=service('Zenstruck\\\\Redis').realClient()"

# config/packages/framework.yaml
framework:
    # ...
    session:
        handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler
```

Contributing
------------

[](#contributing)

Running the test suite:

```
composer install
docker compose up -d # setup redis, redis-cluster, redis-sentinel
vendor/bin/phpunit -c phpunit.docker.xml
```

Credit
------

[](#credit)

Much of the code to create php-redis clients from a *DSN* has been taken and modified from the [Symfony Framework](https://github.com/symfony/symfony/blob/8e8207bb72d7f2cb8be355994ad2fcfa97c00f74/src/Symfony/Component/Cache/Traits/RedisTrait.php).

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance79

Regular maintenance activity

Popularity30

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity59

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~476 days

Total

4

Last Release

83d ago

Major Versions

v0.2.0 → v1.0.02025-12-07

PHP version history (2 changes)v0.1.0PHP &gt;=8.0

v0.2.0PHP &gt;=8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/707369cc916e0ea1aacbf077dcba464f611cef879f024d8944311a54a15224b3?d=identicon)[kbond](/maintainers/kbond)

---

Top Contributors

[![kbond](https://avatars.githubusercontent.com/u/127811?v=4)](https://github.com/kbond "kbond (55 commits)")

---

Tags

redis

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/zenstruck-redis/health.svg)

```
[![Health](https://phpackages.com/badges/zenstruck-redis/health.svg)](https://phpackages.com/packages/zenstruck-redis)
```

###  Alternatives

[predis/predis

A flexible and feature-complete Redis/Valkey client for PHP.

7.8k305.7M2.4k](/packages/predis-predis)[snc/redis-bundle

A Redis bundle for Symfony

1.0k39.4M67](/packages/snc-redis-bundle)[clue/redis-protocol

A streaming Redis protocol (RESP) parser and serializer written in pure PHP.

5311.0M13](/packages/clue-redis-protocol)[cache/redis-adapter

A PSR-6 cache implementation using Redis (PhpRedis). This implementation supports tags

523.9M27](/packages/cache-redis-adapter)[enqueue/redis

Message Queue Redis Transport

405.5M25](/packages/enqueue-redis)[rtcamp/nginx-helper

Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also provides cloudflare edge cache purging with Cache-Tags.

23817.0k1](/packages/rtcamp-nginx-helper)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
