mfonte / redisw
An expressive, strictly-typed Redis client built on top of the phpredis PECL extension, with first-class caching helpers.
Requires
- php: ^8.1
- ext-redis: >=5.3
- psr/simple-cache: ^2.0 || ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.40
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.5 || ^11.0
Suggests
- ext-igbinary: Faster and more compact serialization (serializer: igbinary)
- ext-msgpack: MessagePack serialization (serializer: msgpack)
Provides
- psr/simple-cache-implementation: 2.0 || 3.0
Conflicts
None
Replaces
None
README
An expressive, strictly-typed wrapper around the phpredis extension, designed for caching:
- lazy connection, with configurable retries;
- a default TTL on every write, with random jitter to avoid cache stampedes;
- safe round trip of
null,trueandfalse, which phpredis can't tell apart from a missing key; - serialization (
php,igbinary,json,msgpack) and compression (lzf,zstd,lz4); - consistent exceptions, plus a
wrap()helper when you'd rather getnullthan an exception; - hit/miss counters and a "force revalidate" switch for stale-while-revalidate strategies;
- a PSR-16
CacheInterfaceadapter; - Redis 6 ACL users, TLS with custom SSL context options.
Requirements
- PHP >= 8.1
ext-redis>= 5.3- optional, recommended:
ext-igbinary, for faster and more compact serialization
Installation
composer require mfonte/redisw
Usage
<?php use Mfonte\Redisw\Exception\RedisException; use Mfonte\Redisw\Exception\RedisKeyNotFoundException; use Mfonte\Redisw\RedisClient; $client = RedisClient::instance([ 'host' => '127.0.0.1', 'port' => 6379, 'serializer' => 'igbinary', 'cache_ttl' => 300, ]); // no connection is opened until the first command $client->set('user:42', ['name' => 'Ada', 'admin' => true]); try { $user = $client->get('user:42'); } catch (RedisKeyNotFoundException) { $user = null; // the key does not exist (or has expired) } catch (RedisException $e) { // connection or server error } // don't want exceptions? wrap() returns null instead $user = $client->wrap('get', 'user:42');
Per-call TTL
$client->set('session:abc', $data, 3600); // expires in exactly 1 hour (no jitter) $client->set('config', $config, 0); // never expires $client->mset(['a' => 1, 'b' => 2], 120); // same TTL for every key
PSR-16 (Simple Cache)
use Mfonte\Redisw\Cache\SimpleCache; $cache = new SimpleCache($client); // a Psr\SimpleCache\CacheInterface $cache->set('user.42', $user, new DateInterval('PT10M')); $user = $cache->get('user.42', default: null);
A null TTL applies the client's cache_ttl (including its jitter), and a TTL <= 0 deletes the item, as required by PSR-16. clear() deletes only the keys of the configured key_prefix; without a prefix, it flushes the current database.
TLS and ACL users
$client = RedisClient::instance([ 'host' => 'redis.example.com', 'port' => 6380, 'ssl' => true, 'ssl_context' => [ 'cafile' => '/etc/ssl/certs/redis-ca.pem', 'verify_peer_name' => true, ], 'username' => 'app', // Redis >= 6 ACL user 'auth' => getenv('REDIS_PASSWORD'), ]);
Caching with stale-while-revalidate
if ($client->has('report')) { return $client->get('report'); } $report = buildExpensiveReport(); $client->set('report', $report); return $report;
Setting force_revalidate to true (for example from a "refresh" request) makes has() and exists() return false, so that values are rebuilt and overwritten. getHits() and getMisses() count the results of has() / exists().
Configuration
| Key | Type | Default | Description |
|---|---|---|---|
host |
string | - | Required. Hostname, IP address or unix socket path. |
port |
int | - | Required. TCP port (use 0 for unix sockets). |
connect_timeout |
float | 0 |
Connection timeout, in seconds. 0 means no timeout. |
connect_tries |
int | 1 |
Number of connection attempts before giving up. |
read_timeout |
float | 0 |
Read timeout, in seconds. 0 uses the default_socket_timeout ini setting. |
persistent |
bool | false |
Use persistent connections (pconnect). |
db_index |
int | null |
Database to SELECT after connecting. |
cache_ttl |
int | 60 |
TTL, in seconds, applied to every write. 0 means no expiration. |
cache_ttl_jitter |
bool | true |
Add a random 10-99 seconds jitter to every TTL, to avoid cache stampedes. |
auth |
string | - | Password for AUTH. auth_password is accepted as an alias. |
username |
string | - | ACL username (Redis >= 6). Requires auth. |
ssl |
bool | false |
Connect over TLS. |
ssl_context |
array | [] |
SSL context options, used when ssl is true. |
key_prefix |
string | '' |
Prefix for every key. It is normalized to a lowercase slug (My App becomes my-app). |
serializer |
string | none |
One of none, php, igbinary, json, msgpack. |
compression |
string | none |
One of none, lzf, zstd, lz4 (availability depends on how phpredis was built). |
force_revalidate |
bool | false |
Make has(), exists() and allKeys() behave as if the database were empty. |
Unknown keys and invalid values throw an InvalidArgumentException. Every option also has a fluent setter (setHost(), setCacheTTL(), ...) that can be called at any time: connection parameters drop the current connection, while the database, prefix, serializer and compression are applied to the live connection immediately.
Note: without a serializer, phpredis stores values as plain strings, so arrays and objects can't be stored. Use a serializer if you store anything other than strings and numbers. Conversely,
incr(),decr()andappend()work on raw values: don't use them on keys written byset()with a serializer.
API
Strings: get, set, mget, mset, setnx, msetnx, getset, incr, decr, append, strlen, getbit, setbit
Keys: exists, has, delete, deleteAll (keys of the current prefix), rename, renamenx, expire (milliseconds), expireat (unix timestamp), ttl (milliseconds), persist, allKeys, allValues
Sets: sadd, srem, spop, srandmember, scard, sismember, smembers, sdiffstore, sinterstore, sunionstore
Scripting: evaluate, evalSha
Server: dbSize, flushDb, flushAll, flushAllAsync, disconnect
allKeys() and allValues() use SCAN and MGET, so they don't block the server, but they still walk the whole database: avoid them on large datasets.
Exceptions
All exceptions extend Mfonte\Redisw\Exception\RedisException:
| Exception | Thrown when |
|---|---|
RedisNotConfiguredException |
A command is run before host and port are configured. |
RedisConnectionException |
The connection can't be configured (e.g. SELECT fails). |
RedisTriesOverConnectException |
The connection failed connect_tries times (a RedisConnectionException). |
RedisKeyNotFoundException |
get() is called on a missing key. |
RedisImpossibleValueException |
Redis returns an unexpected reply (e.g. incr() on a non-integer). |
RedisScriptExecutionException |
A Lua script fails. |
Cache\CacheException |
A SimpleCache operation fails (implements PSR-16 CacheException). |
Testing
composer install docker run --rm -d -p 6379:6379 redis:7-alpine # if you don't have a Redis server composer check # coding style, static analysis and tests
The integration tests only use databases 15 and 14 (configurable through REDIS_DB and REDIS_DB_ALT). See CONTRIBUTING.md for details.
Changelog, contributing and security
- CHANGELOG.md lists the changes of every release, and how to upgrade.
- CONTRIBUTING.md explains how to set up the project and send a pull request.
- SECURITY.md explains how to report a vulnerability privately.
Credits
A big thank you goes to alxmsl for the base implementation on alxmsl/Redis, and to ukko for the phpredis autocomplete stubs on ukko/phpredis-phpdoc, which made this package easier to write.
License
Released under the Apache License 2.0.