Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Guzzle Cache Middleware Laravel Package

kevinrob/guzzle-cache-middleware

RFC 7234–compliant HTTP cache middleware for Guzzle 6+ using a HandlerStack. Improve API call performance with transparent caching. Supports PSR-7 and multiple storages: Laravel cache, Flysystem, PSR-6/16, and WordPress object cache.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require kevinrob/guzzle-cache-middleware
    
  2. Basic Guzzle integration (for Laravel or standalone PHP):

    use GuzzleHttp\Client;
    use GuzzleHttp\HandlerStack;
    use Kevinrob\GuzzleCache\CacheMiddleware;
    use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
    use Kevinrob\GuzzleCache\Storage\Psr6CacheStorage;
    use Cache\Adapter\PHPArray\ArrayCachePool; // Fallback for testing
    
    $stack = HandlerStack::create();
    $stack->push(
        new CacheMiddleware(
            new PrivateCacheStrategy(
                new Psr6CacheStorage(new ArrayCachePool())
            )
        ),
        'cache'
    );
    
    $client = new Client(['handler' => $stack]);
    
  3. First use case: Cache API responses for a Laravel controller:

    public function fetchData()
    {
        $response = $this->client->get('https://api.example.com/data');
        return response()->json(json_decode($response->getBody(), true));
    }
    

Key Files to Review

  • src/CacheMiddleware.php: Core middleware logic.
  • src/Strategy/: Cache strategies (PrivateCacheStrategy, PublicCacheStrategy, GreedyCacheStrategy).
  • src/Storage/: Storage adapters (Laravel, PSR6, Flysystem, etc.).
  • tests/: Real-world usage examples and edge cases.

Implementation Patterns

1. Laravel-Specific Workflows

Cache API Responses with Redis

use Illuminate\Support\Facades\Cache;
use Kevinrob\GuzzleCache\Storage\LaravelCacheStorage;

$stack->push(
    new CacheMiddleware(
        new PrivateCacheStrategy(
            new LaravelCacheStorage(Cache::store('redis'))
        )
    ),
    'api-cache'
);

Dynamic Cache TTL per Request

$request = $this->client->getAsync('https://api.example.com/data')
    ->then(function (ResponseInterface $response) {
        return $response->getBody();
    });

// Override TTL for greedy caching
$request->withHeader('X-Cache-TTL', 3600); // 1 hour

2. Strategy Selection

Strategy Use Case Headers Respected
PrivateCacheStrategy User-specific data (e.g., dashboards, personalized APIs). Cache-Control: private
PublicCacheStrategy Shared data (e.g., public APIs, static assets). Cache-Control: public
GreedyCacheStrategy APIs with missing/inconsistent Cache-Control headers. Custom TTL (ignores headers)
NullCacheStrategy Disable caching for specific endpoints (via Delegate strategy). None

Example: Delegate Strategy for Mixed APIs

$delegateStrategy = new DelegatingCacheStrategy(new NullCacheStrategy());
$delegateStrategy->registerRequestMatcher(
    new class implements RequestMatcherInterface {
        public function matches(RequestInterface $request) {
            return str_contains($request->getUri()->getHost(), 'internal-api.example.com');
        }
    },
    new PrivateCacheStrategy(new Psr6CacheStorage($cachePool))
);

$stack->push(new CacheMiddleware($delegateStrategy), 'delegate-cache');

3. Integration with Laravel Services

Cache HTTP Clients in Service Providers

public function register()
{
    $this->app->singleton('api.client', function ($app) {
        $stack = HandlerStack::create();
        $stack->push(
            new CacheMiddleware(
                new PrivateCacheStrategy(
                    new LaravelCacheStorage($app['cache.store'])
                )
            ),
            'api-cache'
        );
        return new Client(['handler' => $stack, 'base_uri' => 'https://api.example.com']);
    });
}

Cache Middleware for Routes

Route::get('/dashboard', function () {
    $client = app('api.client');
    $response = $client->get('/user-data');
    return view('dashboard', ['data' => json_decode($response->getBody(), true)]);
})->middleware('cache.headers'); // Optional: Add custom middleware to validate headers.

4. Testing Patterns

Mocking Cache Responses

public function testCachedResponse()
{
    $cachePool = $this->createMock(Psr6CacheItemPoolInterface::class);
    $cachePool->method('getItem')
        ->willReturn($this->createMock(Psr6CacheItemInterface::class));

    $strategy = new PrivateCacheStrategy(new Psr6CacheStorage($cachePool));
    $middleware = new CacheMiddleware($strategy);

    $request = new Request('GET', 'https://api.example.com/data');
    $response = new Response(200, [], '{"cached": true}');

    $this->assertEquals('cached', $middleware->__invoke($request, $response)->getBody());
}

Validating Cache Hits/Misses

public function testCacheMiss()
{
    $cachePool = $this->createMock(Psr6CacheItemPoolInterface::class);
    $cachePool->method('getItem')
        ->willReturnCallback(function ($key) {
            throw new CacheItemNotFoundException();
        });

    $strategy = new PrivateCacheStrategy(new Psr6CacheStorage($cachePool));
    $middleware = new CacheMiddleware($strategy);

    $request = new Request('GET', 'https://api.example.com/data');
    $response = $this->createMock(ResponseInterface::class);

    $this->assertFalse($middleware->__invoke($request, $response)->isCached());
}

5. Performance Optimization

Prioritize Fast Storage Backends

  • Redis/Memcached: Lowest latency for high-throughput APIs.
  • File System (Flysystem): Fallback for simplicity (avoid for high-frequency requests).
  • Database (PSR6): Avoid unless necessary (high overhead).

Disable Caching for Specific Endpoints

$stack->push(
    new CacheMiddleware(
        new DelegatingCacheStrategy(new NullCacheStrategy())
    ),
    'no-cache'
);

// Override for allowed endpoints
$delegateStrategy->registerRequestMatcher(
    new class implements RequestMatcherInterface {
        public function matches(RequestInterface $request) {
            return str_contains($request->getUri()->getPath(), '/public');
        }
    },
    new PublicCacheStrategy(new Psr6CacheStorage($cachePool))
);

Gotchas and Tips

1. Common Pitfalls

Binary Data Truncation

  • Issue: Large binary responses (e.g., images, PDFs) may be truncated if not handled properly.
  • Fix: Ensure the storage adapter supports binary data (e.g., Flysystem with LocalFilesystemAdapter).
    $storage = new FlysystemStorage(
        new LocalFilesystemAdapter('/path/to/cache', ['disable_asserts' => true])
    );
    

Cache Key Collisions

  • Issue: Different requests may generate the same cache key if Vary headers are ignored.
  • Fix: Use GreedyCacheStrategy with custom headers or ensure Vary is respected:
    $strategy = new PrivateCacheStrategy(
        new Psr6CacheStorage($cachePool),
        ['Authorization', 'X-User-ID'] // Headers to include in cache key
    );
    

TTL Misconfiguration

  • Issue: Incorrect TTL values (e.g., 0 or null) may cause unexpected cache behavior.
  • Fix: Validate TTLs in GreedyCacheStrategy:
    $strategy = new GreedyCacheStrategy($storage, 3600); // Default TTL: 1 hour
    $strategy->setDefaultTTL(3600); // Explicitly set default
    

Laravel Cache Duration Quirks

  • Issue: Laravel’s cache duration may not align with HTTP Cache-Control directives.
  • Fix: Use PrivateCacheStrategy with explicit TTL:
    $strategy = new PrivateCacheStrategy(
        new LaravelCacheStorage(Cache::store('redis')),
        300 // Override TTL in seconds
    );
    

2. Debugging Tips

Inspect Cache Entries

use Kevinrob\GuzzleCache\Utils;

$cacheEntry = Utils::inspectAll($storage->getItem('cache-key'));
dd($cacheEntry); // Debug cache metadata (TTL, headers, etc.)

Log Cache Hits/Misses

$middleware = new CacheMiddleware($strategy);
$middleware->setLogger($this->app->make(LoggerInterface::class));

// Logs cache operations to Laravel's log channel.

Clear Corrupted Cache

// Manually delete corrupted entries (e.g., truncated files).
$storage->delete('corrupted-cache-key');

3. Advanced Configuration

Custom Cache Key Generation

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope