Skip to content

refactor: replace Redis client duck typing with a typed RedisClientInterface #67

@marcstraube

Description

@marcstraube

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)

  1. 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.
  2. 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

  • New RedisClientInterface (get, set, setex, del, keys, eval, …) + PhpRedisAdapter, PredisAdapter
  • Csrf\Storage\RedisCsrfStorage
  • RateLimiting\Storage\RedisStorage
  • RateLimiting\Storage\AtomicRedisStorage
  • Remove all @phpstan-ignore method.notFound / @psalm-suppress Mixed* from the above
  • Optional (consistency/testability, not duck typing): give RateLimiting\Storage\MemcachedStorage the same interface treatment. It currently uses the concrete Memcached type — typed and suppression-free, so lower priority and a different motivation.

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.

Metadata

Metadata

Assignees

Labels

breaking-changeBackwards-incompatible change; targets a major releaseeffort:mMedium (30 min - 2h)refactorCode refactoring

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions