Summary
Replace the object duck typing used for Redis clients in the storage backends with a dedicated RedisClientInterface plus thin adapters for phpredis (Redis) and Predis (Predis\Client).
Motivation
The Redis-backed storages currently accept private object $client and call methods like ->setex()/->eval() on it. This deviates from two of the library's stated core principles:
- Type safety (PHPStan Level 8, Psalm Level 1) — the calls cannot be statically verified.
- No suppressions — each call requires
@phpstan-ignore method.notFound / @psalm-suppress Mixed*.
A typed interface removes every such suppression in these classes and restores full static analysis.
Why it wasn't done initially (constraints to preserve)
- Optional dependencies can't be type-hinted.
ext-redis and predis/predis are suggest, not require — a class from an absent package cannot appear in a type hint. The interface + adapter pattern solves this: the adapter is only instantiated when the client exists.
- phpredis and Predis differ in signatures/returns (
del variadic vs. array, eval argument order, false vs. null returns). The adapter should normalise these, which is a correctness win, not just cosmetics.
Scope
PDO-based storages already use the concrete PDO type — no change needed.
Breaking change
Constructors change from object $client to RedisClientInterface $client. Callers passing a raw Redis/Predis\Client must wrap it in the matching adapter. Hence 2.0.0.
A BC-preserving auto-wrap in 1.x (detecting the client type at runtime and wrapping internally) was considered but rejected: it adds runtime client-detection magic, which is undesirable in a security library where explicitness is preferred.
Acceptance criteria
- No
@phpstan-ignore/@psalm-suppress for Redis client calls remain in the storage classes.
- PHPStan L8 + Psalm L1 pass without those suppressions.
- Adapters covered by tests; 100% MSI maintained.
Summary
Replace the
objectduck typing used for Redis clients in the storage backends with a dedicatedRedisClientInterfaceplus thin adapters for phpredis (Redis) and Predis (Predis\Client).Motivation
The Redis-backed storages currently accept
private object $clientand call methods like->setex()/->eval()on it. This deviates from two of the library's stated core principles:@phpstan-ignore method.notFound/@psalm-suppress Mixed*.A typed interface removes every such suppression in these classes and restores full static analysis.
Why it wasn't done initially (constraints to preserve)
ext-redisandpredis/predisaresuggest, notrequire— a class from an absent package cannot appear in a type hint. The interface + adapter pattern solves this: the adapter is only instantiated when the client exists.delvariadic vs. array,evalargument order,falsevs.nullreturns). The adapter should normalise these, which is a correctness win, not just cosmetics.Scope
RedisClientInterface(get,set,setex,del,keys,eval, …) +PhpRedisAdapter,PredisAdapterCsrf\Storage\RedisCsrfStorageRateLimiting\Storage\RedisStorageRateLimiting\Storage\AtomicRedisStorage@phpstan-ignore method.notFound/@psalm-suppress Mixed*from the aboveRateLimiting\Storage\MemcachedStoragethe same interface treatment. It currently uses the concreteMemcachedtype — typed and suppression-free, so lower priority and a different motivation.PDO-based storages already use the concretePDOtype — no change needed.Breaking change
Constructors change from
object $clienttoRedisClientInterface $client. Callers passing a rawRedis/Predis\Clientmust wrap it in the matching adapter. Hence 2.0.0.A BC-preserving auto-wrap in 1.x (detecting the client type at runtime and wrapping internally) was considered but rejected: it adds runtime client-detection magic, which is undesirable in a security library where explicitness is preferred.
Acceptance criteria
@phpstan-ignore/@psalm-suppressfor Redis client calls remain in the storage classes.