Utopia Cache
A dependency-free PHP caching library with a single Cache facade and swappable adapters for filesystem, Redis, Memcached, Hazelcast, sharding, pooling, and circuit-breaking.
Repository Health
Technical Analysis
Utopia Cache is a lightweight PHP library for storing, loading, and purging application cache data behind one consistent API. Instead of coupling your code to a specific cache backend, you construct a Cache object around any Adapter implementation — Filesystem, Memory, Redis, RedisCluster, Memcached, Hazelcast, or None — and every caller uses the same load(), save(), touch(), list(), purge(), flush(), ping(), and getSize() methods regardless of what’s underneath.
Beyond simple key/value adapters, the library ships composable wrapper adapters: Sharding distributes keys across multiple backing adapters by hashing, Pool checks an adapter out of a utopia-php/pools connection pool per call, and CircuitBreaker wraps any adapter so a failing cache backend stops being hammered with requests. A Leasable feature interface adds generation-token leases (getGeneration() / saveWithLease()) that close the classic cache-aside read-after-write race, and a Telemetry feature interface lets adapters emit OpenTelemetry-style duration histograms and hit/miss counters via utopia-php/telemetry.
Built and maintained by the Appwrite team, it’s part of the broader Utopia Framework but has no hard dependency on it — it works standalone in any PHP 8.4+ project. It also ships a dedicated Redis\Multiplexing adapter for Swoole applications that need to serve many concurrent coroutines from a single Redis TCP connection instead of a sized connection pool.
What You Get
- A minimal
Cachefacade wrapping anyAdapter(load/save/touch/list/purge/flush/ping/getSize) with automatic key-case normalization - Ready-made adapters for
Filesystem,Memory,Redis,RedisCluster,Memcached,Hazelcast, and a no-opNoneadapter for disabling caching - Composable resilience adapters —
CircuitBreakerto stop calling a failing backend,Poolto check a connection out of autopia-php/poolspool per call, andShardingto spread keys across several adapters by hash - Generation-token leasing (
Leasableinterface:getGeneration()/saveWithLease()) that closes the cache-aside read-after-write race condition - Built-in OpenTelemetry-style instrumentation (
cache.operation.durationhistogram,cache.load.totalhit/miss counter) via theTelemetryfeature interface, opt-in throughsetTelemetry() - A dedicated
Redis\Multiplexingadapter for Swoole apps, serving many coroutines from one Redis TCP connection instead of a sized pool
Common Use Cases
- Caching expensive API responses or computed data behind a TTL, with the option to swap the backing store later without touching call sites
- Building a self-hosted PHP application (like Appwrite) that needs to support several deployment sizes — file cache for small installs, Redis or a sharded Redis cluster for larger ones
- Wrapping an unreliable or rate-limited upstream cache with
CircuitBreakerso application requests degrade gracefully instead of piling up on a dead backend - Running cache traffic through Swoole coroutines at high concurrency via the
Redis\Multiplexingadapter instead of tuning a connection pool - Turning caching off entirely in tests or local development with the
Noneadapter while keeping the same call sites as production
Under The Hood
Architecture
The library centers on a strategy pattern: Cache is a thin facade that normalizes key casing and records telemetry, then delegates every operation to an injected Adapter implementation (src/Cache/Cache.php, src/Cache/Adapter.php). Optional capabilities are added through marker interfaces under src/Cache/Feature/ — Leasable, Telemetry, Retryable — that adapters implement only when they support them, and callers check with instanceof rather than relying on a bloated base interface (e.g. Cache::getGeneration() returns '0' when the adapter isn’t Leasable). Several adapters are decorators over another Adapter: CircuitBreaker forwards every call through utopia-php/circuit-breaker, falling back to safe defaults when the breaker is open; Sharding picks a target adapter per key via crc32($key) % count; Pool checks an adapter out of a utopia-php/pools pool for the duration of one call. This composition lets a caller stack CircuitBreaker(Sharding([...])) without any of the pieces knowing about each other. The blast radius of changing the core Adapter interface is wide, though — every concrete adapter (Filesystem, Memory, Redis, RedisCluster, Memcached, Hazelcast, Sharding, Pool, CircuitBreaker) has to implement each method, even as a no-op.
Tech Stack
PHP 8.4+ with strict typing (declare(strict_types=1)), PSR-4 autoloaded via Composer (Utopia\Cache\ → src/Cache). Direct dependencies are all sibling Utopia packages: utopia-php/circuit-breaker (^0.4) for the CircuitBreaker adapter, utopia-php/pools (^2.0) for the Pool adapter, and utopia-php/telemetry (^0.4) for the metrics abstraction — the core library itself has no hard runtime dependency on Redis, Memcached, or Swoole; those are optional PHP extensions listed under Composer’s suggest block. swoole/ide-helper is a dev-only dependency for IDE support on the coroutine-based Redis\Multiplexing adapter. Tests run on PHPUnit, split into unit and e2e testsuites (phpunit.xml), with the e2e suite spinning up Redis, sharded Redis, Memcached, and Hazelcast containers via docker-compose.yml.
Code Quality
Tests are organized around a shared abstract Base.php conformance suite that concrete adapter tests extend, ensuring every adapter honors the same load/save/touch/purge contract, plus focused unit tests for CircuitBreaker, Pool, telemetry emission, and Redis internals (client, envelope framing, Lua-script fallback), and e2e tests per real backend (Redis, RedisCluster, Memcached, Hazelcast, Sharding, and three Multiplexing scenarios). Typing is strict and consistent throughout, with PHPDoc generics used for array shapes (array<int|string, mixed>) that PHP’s type system can’t express natively. Error handling favors feature-detection (instanceof) and try/finally telemetry recording over exceptions for optional-capability paths — CircuitBreaker::getName(), for instance, catches \Throwable and falls back to a sentinel name rather than propagating. Static analysis runs through PHPStan (phpstan.neon, extending a shared config from the parent monorepo). This repository is a read-only mirror of a package inside the utopia-php/monorepo, so the only workflow present here is a mirror.yml sync job — actual CI (test/lint gates) runs upstream in the monorepo and isn’t directly visible from this mirror.
API Design
The public surface is deliberately small — eight methods on Adapter, one Cache facade wrapping any of them — so adopting a new backend means writing one class, not learning a new API. The generation-lease pattern (getGeneration() + saveWithLease()) is a genuinely useful addition beyond typical PSR-16/PSR-6-style cache abstractions: it gives callers a cheap way to detect “did someone purge this key between my read and my write” without adding locking, and it degrades transparently to a plain save() on adapters that don’t implement Leasable. Documentation is solid for a small library — the README covers every adapter in a table, and docs/multiplexing.md is a genuinely thorough writeup of when to reach for the multiplexing adapter over a pool, complete with tuning guidance for readTimeout vs livenessTimeout.