webman/cache
Cache component for the Webman framework, providing a PSR-16 simple cache API with multiple drivers including file, Redis, APCu, and in-memory array storage. Works with Workerman's event loop for fast, reliable caching in high-performance PHP applications.
Treat it as first-party, not as a 3‑star package. webman/cache is the official cache component for the webman framework and provides the support\Cache facade the docs use everywhere. The GitHub star count and "0 dependents" reflect how PHP packages are consumed (via Packagist inside webman skeletons), not the actual footprint.
Minimal setup:
composer require webman/cache
config/cache.php. It defines default plus a stores map (typically file, redis, array). If your skeleton doesn't ship it, copy the sample from the repo README — every behaviour you care about is decided in this file.vendor/webman/cache/src — the manager that maps driver strings to Symfony cache adapters. That file is the source of truth for which options each driver accepts (path, connection, prefix/namespace, lifetime).support\Cache is a thin static wrapper over a PSR‑16 (Psr\SimpleCache\CacheInterface) store. If you know PSR‑16, you already know 90% of it.First use case — protect an expensive call:
use support\Cache;
$stats = Cache::get('dashboard:stats');
if ($stats === null) {
$stats = $repo->expensiveAggregate(); // 800ms query
Cache::set('dashboard:stats', $stats, 60); // 60s TTL
}
return json_encode($stats);
Sanity check after wiring it up: hit the route once and confirm the artefact appears (runtime/cache/ for the file driver, or scan keys in Redis for the redis driver). That one check catches 80% of misconfiguration.
Static facade for the common case, named stores for the exceptions. Use Cache::* for the default store and Cache::store('redis') when a code path needs a different lifetime or backend (e.g. array in tests, redis for anything shared across workers).
Cache::store('redis')->set('lock:report', 1, 30);
Cache::store('array')->set('k', 'v'); // per-process, ideal for tests
Cache-aside with a remember helper. PSR‑16 has no remember, so own it in your app layer rather than scattering get/set pairs:
function remember(string $key, int $ttl, callable $cb, ?string $store = null)
{
$cache = $store ? Cache::store($store) : Cache::store();
$value = $cache->get($key, $sentinel = new stdClass());
if ($value !== $sentinel) {
return $value;
}
$value = $cb();
$cache->set($key, $value, $ttl);
return $value;
}
The sentinel matters — see the gotchas below on null values.
Key conventions before you write a single key. Pick a scheme on day one: {app}:{version}:{entity}:{id}:{variant}, e.g. shop:v2:product:814:detail. Version bumps (v2) let you invalidate an entire shape of cached payloads without clear(). Namespace per tenant if you're multi-tenant on a shared Redis DB.
Batch operations. Use getMultiple / setMultiple / deleteMultiple for warming lists (product grids, config maps) — one round trip instead of N on Redis.
$keys = array_map(fn($id) => "shop:v2:product:$id", $ids);
$missing = array_diff_key(array_flip($keys), Cache::getMultiple($keys));
Invalidation on write, TTL as backstop. Delete specific keys inside the write path (Cache::delete("shop:v2:product:$id")), and let TTL handle anything you forgot. Do not reach for clear() in production.
Driver by environment. file is fine for single-host dev and cheap read-mostly data; redis (via your existing config/redis.php connection name) is the correct production default under Workerman, because every worker process needs to see the same cache. array is for tests and request-scoped scratch data only.
Testability. Bind Psr\SimpleCache\CacheInterface in the container to Cache::store('array'), or swap in an in-memory fake. Code that type-hints the interface instead of calling support\Cache statically is trivially testable — worth the small extra indirection on services you actually unit test.
Warm-up strategy. If you warm caches in onWorkerStart, remember every worker runs it; either accept the duplicate work (it's usually cheap) or gate it to a single worker with a Redis SETNX so you don't stampede the DB on deploy.
{ } ( ) / \ @ : in keys and throws InvalidArgumentException. Raw URLs and emails will blow up. Hash or sanitise: 'url:' . md5($url).null vs "not found". Cache::get($k, $default) returns $default both when the key is missing and when the stored value is null. If you cache nulls or falsy payloads, use Cache::has($k) to disambiguate, or the sentinel-object pattern above.clear() is a blunt instrument. Depending on the adapter it wipes the whole store/namespace — on Redis that can mean dropping keys belonging to other apps sharing the DB. Prefer explicit deletes; if you must bulk-invalidate, use a versioned key prefix you can abandon.0/negative TTLs as "expire immediately" or fall back to the adapter's default lifetime depending on version. Use a positive int for seconds and null for "adapter default"; don't rely on 0 meaning "forever".RedisAdapter prefixes and marshals values, so redis-cli GET user:1 won't show what you expect. Debug with redis-cli --scan --pattern '*<yourprefix>*' and inspect through the cache API, not by hand.runtime_path()/cache needs to be writable by the worker user — a classic Docker/first-deploy failure. Conversely, a deploy that wipes runtime/ wipes your cache: usually desirable, and it hides the "cached object shape changed after release" class of bugs only if you store arrays rather than serialised objects.array store is per-process. Under Workerman each worker has its own; it is not a cache in any meaningful production sense.webman/redis, symfony/lock) around the recompute.set() returns false on failure rather than throwing. Anywhere the cache is load-bearing (rate limits, idempotency keys), branch on the return value.symfony/cache; adapter behaviour and constructor signatures shift across Symfony majors. Pin and upgrade deliberately, and re-read vendor/webman/cache/src when you do — it's ~200 lines and answers every "which option does this driver take?" question faster than the README.Psr\SimpleCache\CacheInterface and wrap your own client, then expose it through your app's service layer rather than patching the manager — that keeps the upgrade path clean.How can I help you explore Laravel packages today?