Skip to content

Application · Adapter

Cache

Load reusable values through an application cache port, then select the cache store at the composition boundary.

Application code owns keys, TTL intent, and fallback behavior; the adapter owns store access.

Cache is an optimization boundary, not storage. The portable Cache contract owns read-through loading; MutableCache adds explicit deletion and clearing where a use case genuinely needs invalidation. Select PSR-6, PSR-16, Laravel, or CodeIgniter at the composition root.

A cache-through abstraction that fetches a value by key and invokes a loader callback on miss. The Application layer defines read-only and mutable ports; adapters wrap PSR-6, PSR-16, Laravel, or CodeIgniter caches.

Application\Cache
├── Cache (interface)       — read(string $key, callable $loader, int $ttl): mixed
├── MutableCache            — Cache plus delete(string $key) and clear()
└── Exception\
    └── CacheException       — extends SystemException

Adapter\Cache
├── Psr6\Psr6Cache          — canonical Cache → PSR-6 CacheItemPoolInterface adapter
├── Psr16\Psr16Cache        — MutableCache → PSR-16 CacheInterface adapter
├── Laravel\LaravelCache    — MutableCache → Laravel cache repository adapter
├── CodeIgniter\CodeIgniterCache — MutableCache → CodeIgniter cache adapter
└── PsrCache                — deprecated 1.x compatibility path

Table of Contents

  1. Cache (Interface)
  2. Psr6Cache
  3. Deprecated PsrCache Compatibility
  4. CacheException
  5. Symfony Configuration
  6. Usage Examples

Cache (Interface)

Fight\Common\Application\Cache\Cache

A single-method port that implements the cache-through pattern: if a value is cached, return it; otherwise invoke $loader(), store the result, and return it.

interface Cache
{
    /**
     * Fetches data from cache or loader function
     *
     * Callback signature:
     * function (): mixed {}
     *
     * @throws CacheException When an error occurs
     */
    public function read(string $key, callable $loader, int $ttl): mixed;
}
Parameter Type Description
$key string Cache key
$loader callable Invoked on cache miss to produce the value
$ttl int Time-to-live in seconds

Psr6Cache

Fight\Common\Adapter\Cache\Psr6\Psr6Cache

Wraps any PSR-6 CacheItemPoolInterface and a PSR-3 LoggerInterface. This is the canonical PSR-6 adapter implementation.

final readonly class Psr6Cache implements MutableCache
{
    public function __construct(
        private CacheItemPoolInterface $cachePool,
        private LoggerInterface $logger
    ) {}
}

Read flow

  1. $cachePool->getItem($key) — fetch from pool
  2. Cache hit → log Cache HIT: "<key>" at DEBUG, return $cacheItem->get()
  3. Cache miss → log Cache MISS: "<key>" at DEBUG
  4. Invoke $loader() to produce the value
  5. $cacheItem->set($results) — store the value
  6. $cacheItem->expiresAfter($ttl) — set TTL
  7. $cachePool->save($cacheItem) — persist
  8. Return $cacheItem->get()

All exceptions are caught and wrapped in CacheException.


Deprecated PsrCache Compatibility

Fight\Common\Adapter\Cache\PsrCache remains a silent compatibility path throughout 1.x and delegates to Psr6Cache. New integrations should use Fight\Common\Adapter\Cache\Psr6\Psr6Cache. The legacy class will be removed in 2.0.

Mutable and framework adapters

Psr16Cache, LaravelCache, and CodeIgniterCache implement MutableCache, which adds delete() and clear() to the read-through contract. All three preserve cached null values instead of confusing them with a miss, and wrap provider failures in CacheException.

Laravel's CacheServiceProvider binds MutableCache and aliases Cache to the same singleton. CodeIgniter applications compose CacheServices::mutableCache() or CacheServices::cache() from their configured native cache. A generic PSR-16 application constructs Psr16Cache with its cache and PSR-3 logger. No Yii cache adapter is shipped; bind another supported implementation explicitly.

clear() flushes the selected underlying store. If that store is shared with unrelated features, use a dedicated namespace or store, or avoid broad clearing in application use cases.


CacheException

Fight\Common\Application\Cache\Exception\CacheException

class CacheException extends SystemException {}

An empty exception class. Thrown when any error occurs during cache read (pool failure, loader failure, logger failure).


Symfony Configuration

# config/packages/common_cache.yaml

services:
    _defaults:
        autowire: true
        autoconfigure: true

    # --- PSR-6 cache pool (example: Symfony Cache) ---
    Symfony\Component\Cache\Adapter\AdapterInterface:
        class: Symfony\Component\Cache\Adapter\FilesystemAdapter
        arguments:
            - 'app.cache'
            - 0
            - '%kernel.cache_dir%/pools'

    Psr\Cache\CacheItemPoolInterface:
        alias: Symfony\Component\Cache\Adapter\AdapterInterface

    # --- Canonical PSR-6 adapter ---
    Fight\Common\Adapter\Cache\Psr6\Psr6Cache:
        arguments:
            - '@Psr\Cache\CacheItemPoolInterface'
            - '@logger'

    # --- Interface alias ---
    Fight\Common\Application\Cache\Cache:
        alias: Fight\Common\Adapter\Cache\Psr6\Psr6Cache

Usage Examples

Caching a Database Query

use Fight\Common\Application\Cache\Cache;

class UserRepository
{
    public function __construct(
        private Cache $cache,
        private Connection $db
    ) {}

    public function findById(int $id): ?array
    {
        return $this->cache->read(
            sprintf('user.%d', $id),
            fn () => $this->db->fetchAssociative('SELECT * FROM users WHERE id = ?', [$id]) ?: null,
            300  // 5 minutes
        );
    }
}

Caching an API Response

class WeatherService
{
    public function __construct(private Cache $cache, private HttpService $http) {}

    public function getForecast(string $city): array
    {
        return $this->cache->read(
            "weather.{$city}",
            function () use ($city) {
                $response = $this->http->send(
                    $this->http->createRequest('GET', "/weather/{$city}")
                );
                return json_decode((string) $response->getBody(), true);
            },
            600  // 10 minutes
        );
    }
}

Testing with ArrayAdapter

use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Fight\Common\Adapter\Cache\Psr6\Psr6Cache;

$pool   = new ArrayAdapter();
$logger = new NullLogger();
$cache  = new Psr6Cache($pool, $logger);

// First call — invokes loader
$result = $cache->read('key', fn () => 'computed', 60);
self::assertSame('computed', $result);

// Second call — returns cached value; loader is NOT invoked
$loader = $this->createMock(Callable::class);
$loader->expects($this->never())->method('__invoke');
$result = $cache->read('key', $loader, 60);
self::assertSame('computed', $result);