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+. Add to a HandlerStack to transparently cache responses and speed up API calls. Supports PSR-7 and multiple backends: 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 Client Integration:

    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;
    
    // Create a PSR-6 cache pool (in-memory for demo)
    $cachePool = new ArrayCachePool();
    
    // Initialize storage and strategy
    $storage = new Psr6CacheStorage($cachePool);
    $strategy = new PrivateCacheStrategy($storage);
    
    // Create and configure Guzzle client
    $stack = HandlerStack::create();
    $stack->push(new CacheMiddleware($strategy), 'cache');
    
    $client = new Client(['handler' => $stack]);
    
  3. First Use Case:

    // Cache will automatically handle GET requests to this URL
    $response = $client->get('https://api.example.com/data');
    
    // Subsequent identical requests will return cached response
    $cachedResponse = $client->get('https://api.example.com/data');
    

Where to Look First

  • Laravel Integration: Check the Laravel cache example in the README.
  • Strategy Patterns: Review the strategy examples for PrivateCacheStrategy, PublicCacheStrategy, and GreedyCacheStrategy.
  • Request Matching: Explore the delegate caching section for dynamic strategy assignment.

Implementation Patterns

Core Workflow

  1. Middleware Placement: Always push the CacheMiddleware to the top of the HandlerStack to ensure it processes requests before other middleware (e.g., retries, logging).

    $stack->push(new CacheMiddleware($strategy), 'cache');
    
  2. Strategy Selection:

    • Use PrivateCacheStrategy for user-specific or sensitive data (e.g., authenticated API calls).
    • Use PublicCacheStrategy for shared, non-sensitive data (e.g., public APIs).
    • Use GreedyCacheStrategy for APIs with poor caching headers or when you need to enforce a TTL.
  3. Storage Integration: Laravel developers should leverage LaravelCacheStorage for seamless integration with Laravel's cache drivers (Redis, Memcached, etc.):

    $storage = new LaravelCacheStorage(Cache::store('redis'));
    
  4. Dynamic TTL with Greedy Strategy: Override TTL per request using headers:

    $request = $client->get('https://api.example.com/data', [
        'headers' => [
            GreedyCacheStrategy::HEADER_TTL => 3600, // 1 hour
        ],
    ]);
    

Advanced Patterns

1. Delegate Caching for API-Specific Rules

Assign different strategies based on the request domain or endpoint:

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

2. Cache Invalidation

Manually invalidate cached entries for specific requests:

$client->get('https://api.example.com/data', [
    'cache' => [
        'invalidate' => true, // Force cache invalidation
    ],
]);

Or globally clear the cache:

$storage->clear(); // For PSR-6/PSR-16 storage
Cache::forget('guzzle-cache-key'); // For Laravel storage

3. Conditional Caching

Disable caching for specific requests using the cache option:

$client->get('https://api.example.com/uncached-data', [
    'cache' => [
        'enabled' => false,
    ],
]);

4. Laravel Service Provider Integration

Register the client in a Laravel service provider:

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

5. Testing Cached Responses

Mock the cache storage in tests:

$mockStorage = $this->createMock(Psr6CacheStorageInterface::class);
$mockStorage->method('fetch')->willReturn($cachedResponse);
$strategy = new PrivateCacheStrategy($mockStorage);

Gotchas and Tips

Pitfalls

  1. Cache Key Collisions:

    • Issue: Different requests with identical URLs but varying headers (e.g., Authorization) may generate the same cache key, leading to incorrect caching.
    • Fix: Use GreedyCacheStrategy with KeyValueHttpHeader to include dynamic headers in the cache key:
      new GreedyCacheStrategy($storage, 3600, new KeyValueHttpHeader(['Authorization']))
      
  2. Binary Data Truncation:

    • Issue: Binary responses (e.g., images, PDFs) may be truncated if not handled properly.
    • Fix: Ensure your storage adapter supports binary data (e.g., Flysystem with LocalFilesystemAdapter). Use Psr6CacheStorage with a binary-safe cache pool like Cache\Adapter\Filesystem\FilesystemCachePool.
  3. TTL Misconfiguration:

    • Issue: Incorrect TTL values (e.g., 0 or negative) can cause cache entries to expire immediately or behave unpredictably.
    • Fix: Validate TTL values in GreedyCacheStrategy or use PrivateCacheStrategy/PublicCacheStrategy to rely on HTTP headers.
  4. Laravel Cache Driver Quirks:

    • Issue: Laravel's cache drivers (e.g., file, database) may not handle binary data or large payloads well.
    • Fix: Prefer redis or memcached for production. For file-based caching, use Flysystem:
      new FlysystemStorage(new LocalFilesystemAdapter(storage_path('app/guzzle-cache')))
      
  5. Stale-While-Revalidate:

    • Issue: Responses marked as stale-while-revalidate may not be cached correctly.
    • Fix: Ensure your storage adapter supports allowStaleWhileRevalidate (most PSR-6 adapters do).
  6. Middleware Order:

    • Issue: Placing CacheMiddleware after other middleware (e.g., retries, logging) may lead to inconsistent caching behavior.
    • Fix: Always push CacheMiddleware to the top of the stack:
      $stack->push(new CacheMiddleware($strategy), 'cache');
      
  7. DST and Timezone Issues:

    • Issue: Daylight Saving Time (DST) transitions can cause cache entries to expire prematurely.
    • Fix: Use PSR-20 Clock (supported since v8.0.0) for deterministic time management:
      use Kevinrob\GuzzleCache\Clock\SystemClock;
      $strategy = new PrivateCacheStrategy($storage, new SystemClock());
      

Debugging Tips

  1. Inspect Cache Entries: Use the inspectAll utility to debug cached entries:

    use Kevinrob\GuzzleCache\Utils;
    Utils::inspectAll($storage); // Lists all cached entries
    
  2. Log Cache Hits/Misses: Extend CacheMiddleware to log cache events:

    class LoggingCacheMiddleware extends CacheMiddleware
    {
        public function __construct(StrategyInterface $strategy)
        {
            parent::__construct($strategy);
        }
    
        protected function handle(RequestInterface $request, $next)
        {
            $response = parent::handle($request, $next);
            \Log::info('Cache ' . ($this->isCacheHit ? 'hit' : 'miss'), [
                'request' => $request->getUri(),
                'method' => $request->getMethod(),
            ]);
            return $response;
        }
    }
    
  3. Validate Cache Headers: Ensure responses include proper caching headers (Cache-Control, ETag, Last-Modified). Use tools like HTTP Header Live Viewer to verify.

  4. **Check for Corrupted Entries

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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport