Implementing Multi-Layer Caching in PHP with the Decorator Pattern

Why Use Multi-Layer Caching?

Consider a PHP application where each database query takes about 1 second—users experience slow page loads. Adding caching helps, but is a single cache layer sufficient?

  • In-memory cache is fast but volatile—it disappears when the service restarts.
  • Redis offers persistence but incurs network latency.
  • File-based cache is durable but limited by disk I/O speed.

The multi-layer caching strategy prioritizes speed while ensuring reliability:

  1. Check in-memory cache first (fastest)
  2. Fall back to Redis if not found (moderate speed)
  3. Finally query file system or database (slowest but most reliable)

This reduces load on slower storage layers while maintaining data availability.

Why the Decorator Pattern?

To build a chain like: Memory → Redis → File → Database, traditional inheritance leads to rigid, deeply nested classes:

// Problematic inheritance approach
class MemoryRedisFileCache extends FileCache { /* ... */ }

The decerator pattern offers a composable alternative—like nesting dolls:

$dataSource = new DatabaseAdapter();

$cacheStack = new InMemoryCache(
    new RedisCache(
        new FilesystemCache($dataSource, '/tmp/cache', 3600),
        ['host' => '127.0.0.1', 'port' => 6379],
        'app_',
        true
    ),
    300,   // TTL in seconds
    1024   // Max items
);

Benefits include:

  1. Flexible composition: reorder layers as needed
  2. Single-responsibility classes: each handles only its own logic
  3. Easy extension: add new cache types without modifying existing code

Usage Example

After installing via Cmoposer (composer require hejunjie/tools):

use Hejunjie\Tools\Cache\Decorators;

$source = new UserDatabaseGateway();

$multiCache = new Decorators\InMemoryCache(
    new Decorators\RedisCache(
        new Decorators\FilesystemCache(
            $source,
            '/var/cache/app',
            7200
        ),
        ['host' => 'redis.local', 'port' => 6379],
        'user_data:',
        false
    ),
    600,
    500
);

// Transparent read-through
$user = $multiCache->get('profile_456');

// Write-through: updates all layers
$multiCache->set('profile_456', ['name' => 'Li Hua']);

The get() method traverses layers from fastest to slowest, returning immediately on hit and propagating the result back up the chain. The set() method writes to all layers simultaneously.

Performance Comparison

Metric No Cache Single-Layer Three-Layer
Read Latency 1.2s 0.3s 0.05ms
DB Load 100% 30% <5%
Post-Restart Availability Full Lost File-backed recovery

Key Advantages

Modular Design: Easily reconfigure layers:

// Skip Redis during maintenance
$cache = new Decorators\InMemoryCache(
    new Decorators\FilesystemCache($db, '/cache', 3600),
    600,
    500
);

Robustness Features:

  • File cache uses advisory locking to prevent race conditions
  • Redis client includes automatic reconnection logic
  • In-memory cache enforces maximum item limits to avoid OOM errors

Operational Visibility: Built-in metrics:

print_r($cache->getMetrics());
/* Output:
[
    'hits' => 2953,
    'misses' => 47,
    'hit_ratio' => 0.984,
    'stored_items' => 1024
]
*/

Ideal Use Cases

  • Frequently accessed read-heavy data (e.g., product catalogs)
  • Low-latency API endpoints
  • Systems under high database load
  • Applications requiring graceful degradation after restarts

Tags: PHP decorator-pattern Caching Redis in-memory-cache

Posted on Sun, 02 Aug 2026 16:02:20 +0000 by captain_scarlet87