Cache - viames/pair GitHub Wiki

Pair framework: Cache

Pair v4 provides a small cache abstraction for framework metadata and application-level cache needs.

The default store is file-backed and dependency-free. APCu and Redis stores are available when the application explicitly configures them.


Main classes

  • Pair\Cache\Cache: resolves the application-wide cache store for the current PHP process.
  • Pair\Cache\CacheStore: small interface implemented by cache drivers.
  • Pair\Cache\FileCacheStore: dependency-free default backed by files under TEMP_PATH/cache.
  • Pair\Cache\ApcuCacheStore: optional APCu-backed store for single-node deployments.
  • Pair\Cache\RedisCacheStore: optional Redis-backed store for shared production deployments.

CacheStore contract

Every store implements:

interface CacheStore {
    public function get(string $key, mixed $default = null): mixed;
    public function set(string $key, mixed $value, ?int $ttlSeconds = null): bool;
    public function has(string $key): bool;
    public function delete(string $key): bool;
    public function clear(): bool;
}

null TTL means no expiration. A non-positive TTL removes the key immediately.

Values should be serializable by the selected backend.


Default file store

use Pair\Cache\Cache;

Cache::store()->set('dashboard.summary', $summary, 300);

$summary = Cache::store()->get('dashboard.summary', []);

Without configuration, Cache::store() returns a FileCacheStore under TEMP_PATH/cache.

The default store can also be configured through .env:

PAIR_CACHE_DRIVER=file
PAIR_CACHE_PATH=
PAIR_CACHE_PREFIX=pair

Supported drivers:

  • file: dependency-free default. Uses PAIR_CACHE_PATH when set, otherwise TEMP_PATH/cache.
  • apcu: uses APCu when the extension is installed and enabled.
  • redis: uses Redis through the Redis PHP extension and the shared REDIS_* settings.

PAIR_CACHE_PREFIX namespaces file and APCu keys. Empty prefixes fall back to pair.


Application-level usage

For normal module or API code, use the static helpers:

use Pair\Cache\Cache;

Cache::set('dashboard.summary', $summary, 300);

$summary = Cache::get('dashboard.summary', []);

$summary = Cache::remember('dashboard.summary', function () {
    return buildDashboardSummary();
}, 300);

Available helpers:

  • Cache::get(string $key, mixed $default = null): mixed
  • Cache::set(string $key, mixed $value, ?int $ttlSeconds = null): bool
  • Cache::has(string $key): bool
  • Cache::delete(string $key): bool
  • Cache::clear(): bool
  • Cache::remember(string $key, callable $resolver, ?int $ttlSeconds = null): mixed

remember() is a convenience helper for ordinary cache-aside reads. It is not a distributed lock; under high concurrency the resolver may run more than once.

When Observability is enabled, the static helpers emit cache spans such as cache.get, cache.set, and cache.remember. Span attributes include a hash of the cache key, not the raw key.


Configure a custom store

use Pair\Cache\Cache;
use Pair\Cache\FileCacheStore;

Cache::setStore(new FileCacheStore(TEMP_PATH . 'cache', 'app'));

Tests and long-running commands can reset the configured store:

Cache::clearStore();

APCu store

use Pair\Cache\ApcuCacheStore;
use Pair\Cache\Cache;

if (ApcuCacheStore::isAvailable()) {
    Cache::setStore(new ApcuCacheStore('pair'));
}

APCu is local to the PHP runtime. It is useful on a single node, but not for shared cache across multiple servers.

APCu can be selected through .env:

PAIR_CACHE_DRIVER=apcu
PAIR_CACHE_PREFIX=pair

Redis store

use Pair\Cache\Cache;
use Pair\Cache\RedisCacheStore;

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

Cache::setStore(new RedisCacheStore($redis, 'pair:cache:'));

The Redis store wraps an existing Redis-compatible client. Pair does not require Redis in the core runtime.

Redis can be selected through .env:

PAIR_CACHE_DRIVER=redis
PAIR_CACHE_REDIS_PREFIX="pair:cache:"
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_TIMEOUT=1

PAIR_CACHE_REDIS_PREFIX namespaces Redis keys independently from the API rate limiter prefix.


Framework consumers

Pair\Api\Idempotency uses CacheStore and defaults to a file-backed store under TEMP_PATH/idempotency.

use Pair\Api\Idempotency;
use Pair\Cache\RedisCacheStore;

Idempotency::setStore(new RedisCacheStore($redis, 'pair:idempotency:'));

RateLimiter still has specialized atomic file/Redis logic because throttling needs sliding-window operations that are stricter than the generic cache contract.


Best practices

  • Use clear, namespaced keys such as module:resource:id.
  • Do not store secrets unless the selected backend is protected appropriately.
  • Use TTLs for data that can become stale.
  • Keep comments and docblocks explanatory only; cache behavior belongs in code and configuration.

See also: Env, Observability, FilesystemMetadata, CrudResourceMetadata, RateLimiter, Idempotency.