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. Installation:

    composer require kevinrob/guzzle-cache-middleware
    
  2. Basic Integration (Laravel example):

    use GuzzleHttp\Client;
    use GuzzleHttp\HandlerStack;
    use Kevinrob\GuzzleCache\CacheMiddleware;
    use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
    use Kevinrob\GuzzleCache\Storage\LaravelCacheStorage;
    
    $stack = HandlerStack::create();
    $stack->push(
        new CacheMiddleware(
            new PrivateCacheStrategy(
                new LaravelCacheStorage(app('cache.store'))
            )
        ),
        'cache'
    );
    
    $client = new Client(['handler' => $stack]);
    
  3. First Use Case: Cache API responses for a Laravel controller:

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

Where to Look First

  • Documentation: Focus on the README for storage adapters (Laravel, PSR6, Flysystem).
  • Key Classes:
    • CacheMiddleware: Core middleware.
    • PrivateCacheStrategy/PublicCacheStrategy: Default strategies.
    • GreedyCacheStrategy: For custom TTLs.
    • DelegatingCacheStrategy: For request-specific rules.

Implementation Patterns

1. Strategy Selection

  • Default: Use PrivateCacheStrategy for non-shared data (e.g., user-specific API calls).
  • Shared: Use PublicCacheStrategy for shared resources (e.g., public API endpoints).
  • Greedy: Override TTLs or ignore server headers:
    $stack->push(
        new CacheMiddleware(
            new GreedyCacheStrategy(
                new LaravelCacheStorage(app('cache.store')),
                ttl: 3600, // Custom TTL in seconds
                headers: ['Authorization'] // Headers affecting cache key
            )
        )
    );
    

2. Request-Specific Caching

Use DelegatingCacheStrategy to apply rules per domain/endpoint:

$strategy = new DelegatingCacheStrategy(new NullCacheStrategy());
$strategy->registerRequestMatcher(
    new class implements RequestMatcherInterface {
        public function matches(RequestInterface $request) {
            return str_contains($request->getUri()->getHost(), 'api.example.com');
        }
    },
    new PublicCacheStrategy(new LaravelCacheStorage(app('cache.store')))
);

3. Integration with Laravel Services

Wrap Guzzle clients in a service provider:

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

4. Cache Invalidation

Manually clear cache for specific requests:

$client->get('https://api.example.com/data', [
    'on_stats' => function (TransferStats $stats) {
        if ($stats->getHandlerStats('cache')['cache_hit']) {
            app('cache')->forget($stats->getHandlerStats('cache')['cache_key']);
        }
    }
]);

5. Testing

Mock the cache layer:

$mockCache = $this->createMock(Psr6CacheStorage::class);
$mockCache->method('fetch')->willReturn(null);
$middleware = new CacheMiddleware(new PrivateCacheStrategy($mockCache));

Gotchas and Tips

Pitfalls

  1. Binary Data Truncation:

    • Ensure stream_get_contents() is used for binary responses (fixed in v7.0.0).
    • Workaround: Use GuzzleHttp\Psr7\Stream explicitly:
      $stream = new Stream(fopen('php://temp', 'r+'));
      $stream->write($response->getBody()->getContents());
      
  2. DST Timezone Bugs:

    • Use PSR-20 Clock (v8.0.0+) for deterministic timestamps:
      use Kevinrob\GuzzleCache\Clock\SystemClock;
      $strategy = new PrivateCacheStrategy($storage, new SystemClock());
      
  3. Authorization Headers in Public Cache:

    • Public cache ignores Authorization headers by default (RFC 9111 compliance).
    • Fix: Exclude them from cache keys in GreedyCacheStrategy:
      new GreedyCacheStrategy($storage, ttl: 3600, headers: ['Authorization'])
      
  4. Corrupted Cache Entries:

    • Invalid entries may persist. Use CacheMiddleware::INVALIDATE_ALL (v8.0.0+) to purge:
      CacheMiddleware::invalidateAll($storage);
      
  5. Laravel Cache Duration Changes:

    • Laravel’s Cache::put() TTL overrides middleware settings. Use GreedyCacheStrategy for consistency.

Debugging Tips

  • Inspect Cache Hits/Misses:
    $client->get('...', [
        'on_stats' => function (TransferStats $stats) {
            dump($stats->getHandlerStats('cache'));
        }
    ]);
    
  • Log Cache Keys: Extend CacheMiddleware to log keys:
    class DebugCacheMiddleware extends CacheMiddleware {
        public function __construct($strategy) {
            parent::__construct($strategy);
            $this->strategy->setLogger(function ($key, $method) {
                \Log::debug("Cache $method: $key");
            });
        }
    }
    

Extension Points

  1. Custom Storage: Implement Kevinrob\GuzzleCache\Storage\StorageInterface for new backends (e.g., Redis clusters):

    class RedisClusterStorage implements StorageInterface {
        public function fetch($key) { /* ... */ }
        public function save($key, $entry, $ttl) { /* ... */ }
        public function delete($key) { /* ... */ }
    }
    
  2. Dynamic TTLs: Override GreedyCacheStrategy to fetch TTLs from headers:

    class DynamicTTLCacheStrategy extends GreedyCacheStrategy {
        protected function getTtl(RequestInterface $request) {
            return (int) $request->getHeaderLine('X-Cache-TTL') ?: parent::getTtl($request);
        }
    }
    
  3. Request Matching: Extend RequestMatcherInterface for complex rules (e.g., path patterns):

    class PathMatcher implements RequestMatcherInterface {
        public function matches(RequestInterface $request) {
            return preg_match('/^\/api\/v\d+\//', $request->getUri()->getPath());
        }
    }
    

Performance Quirks

  • PSR6 vs. Laravel Cache: PSR6 (Psr6CacheStorage) is slower for high-throughput apps due to adapter overhead. Use Laravel’s native cache (LaravelCacheStorage) for better performance.
  • Memory Usage: Greedy caching with large TTLs may bloat storage. Monitor with:
    app('cache.store')->getStore()->getStats();
    

Configuration Quirks

  • Default TTL: PrivateCacheStrategy uses 0 (no cache) by default. Always specify a TTL:
    new PrivateCacheStrategy($storage, ttl: 300) // 5 minutes
    
  • Vary Headers: The middleware respects Vary headers but ignores Cookie/Authorization in public cache (RFC 7234). Explicitly include them in GreedyCacheStrategy if needed.
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.
nasirkhan/laravel-sharekit
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony