php-http/cache-plugin
PSR-6 cache plugin for HTTPlug that adds transparent HTTP response caching to your client. Plug it into the HTTPlug plugin client to cache and reuse responses, reducing network calls and improving performance.
Install the package and a PSR-6 cache adapter (e.g., Redis or Symfony Cache):
composer require php-http/cache-plugin symfony/cache symfony/redis-cache
Configure a PSR-6 cache pool (e.g., in config/cache.php or a service provider):
use Symfony\Component\Cache\Adapter\RedisAdapter;
$cachePool = RedisAdapter::createConnection('redis://localhost:6379')->getPool();
Wrap your HTTPlug client (e.g., Guzzle or Symfony HttpClient) with the CachePlugin:
use Http\Client\Common\Plugin\CachePlugin;
use Http\Client\Common\Plugin\CachePlugin\CacheKeyGenerator\SimpleGenerator;
use Http\Client\Common\Plugin\CachePlugin\CachePolicy;
$client = new \Http\Client\Common\Plugin\Client\BaseClient();
$cachePlugin = new CachePlugin(
$cachePool,
new SimpleGenerator(),
new CachePolicy() // Customize TTL, blacklisted paths, etc.
);
$cachedClient = $cachePlugin->attachTo($client);
First use case: Cache API responses for a Laravel service:
$response = $cachedClient->sendRequest(new \Http\Message\Request('GET', 'https://api.example.com/data'));
$data = json_decode($response->getBody(), true);
CachePlugin to any HTTPlug-compatible client (e.g., Guzzle, Symfony HttpClient) without modifying existing logic.// Laravel service provider
public function register()
{
$this->app->singleton('http.cached_client', function ($app) {
$client = $app->make(\Http\Client\Common\Plugin\Client\BaseClient::class);
$cachePool = $app->make(\Psr\Cache\CacheItemPoolInterface::class);
return (new CachePlugin($cachePool))
->attachTo($client);
});
}
use Http\Client\Common\Plugin\CachePlugin\CacheKeyGenerator\HeaderCacheKeyGenerator;
$cachePlugin = new CachePlugin(
$cachePool,
new HeaderCacheKeyGenerator(['Authorization']) // Include auth headers in cache key
);
/auth, /webhooks) from caching.$cachePolicy = new CachePolicy();
$cachePolicy->setBlacklistedPaths(['/auth', '/webhooks']);
$cachePlugin = new CachePlugin($cachePool, new SimpleGenerator(), $cachePolicy);
X-Cache: HIT).use Http\Client\Common\Plugin\CachePlugin\CacheListener\AddHeaderCacheListener;
$listener = new AddHeaderCacheListener();
$cachePlugin = new CachePlugin($cachePool, new SimpleGenerator(), [], [$listener]);
ETag headers (e.g., for immutable resources).use Http\Client\Common\Plugin\CachePlugin\EtagCachePlugin;
$etagCachePlugin = new EtagCachePlugin($cachePool);
$cachedClient = $etagCachePlugin->attachTo($client);
use Psr\Cache\CacheItemPoolInterface;
public function update()
{
// Business logic...
$this->invalidateCache();
}
protected function invalidateCache()
{
$cachePool = app(CacheItemPoolInterface::class);
$cachePool->deleteItem('api.example.com/data');
}
Stream Detachment Issues:
CachePlugin::clientCache() or CachePlugin::serverCache() factory methods (v1.3+), which handle stream detachment automatically.Cache Key Collisions:
SimpleGenerator may produce identical keys for similar but distinct requests (e.g., /users?page=1 vs. /users?page=2).HeaderCacheKeyGenerator or extend CacheKeyGenerator to include query params:
$generator = new class implements CacheKeyGenerator {
public function generateKey(RequestInterface $request): string
{
return sha1($request->getUri() . $request->getBody());
}
};
TTL Misconfigurations:
default_ttl to null or 0 may lead to unexpected behavior (e.g., infinite cache or no caching).3600 for 1 hour):
$cachePolicy = new CachePolicy();
$cachePolicy->setDefaultTtl(3600);
Blacklist Regex Overlap:
blacklisted_paths may block legitimate endpoints.preg_match() first:
if (preg_match('/blacklisted_pattern/', '/path/to/endpoint')) {
// Will be blocked
}
ETag CachePlugin Quirks:
EtagCachePlugin always revalidates cached responses, which may increase API calls.Enable Cache Headers:
Add AddHeaderCacheListener to inspect X-Cache headers in responses:
$cachePlugin = new CachePlugin($cachePool, new SimpleGenerator(), [], [
new AddHeaderCacheListener()
]);
Log Cache Hits/Misses:
Extend CacheListener to log events:
$listener = new class implements CacheListener {
public function onCacheHit(ResponseInterface $response): void
{
Log::debug('Cache HIT', ['url' => $response->getEffectiveUri()]);
}
public function onCacheMiss(ResponseInterface $response): void
{
Log::debug('Cache MISS', ['url' => $response->getEffectiveUri()]);
}
};
Validate Cache Entries: Manually check cached items for corruption:
$item = $cachePool->getItem('cache_key');
if ($item->isHit()) {
$response = unserialize($item->get());
// Verify response integrity
}
Custom Cache Policy:
Extend CachePolicy to implement business-specific rules (e.g., cache only during off-peak hours):
class TimeBasedCachePolicy extends CachePolicy {
public function shouldCache(RequestInterface $request): bool
{
return parent::shouldCache($request) &&
now()->hour >= 0 && now()->hour < 6; // Cache only at night
}
}
PSR-6 Cache Adapter: Swap adapters (e.g., Redis → APCu) without changing client code:
$cachePool = new \Cache\Adapter\ApcuAdapter();
Middleware Integration: Combine with Laravel middleware to conditionally enable caching:
$client = $request->hasHeader('X-Cache-Enabled')
? $cachePlugin->attachTo($baseClient)
: $baseClient;
Service Container Binding:
Bind the cached client in AppServiceProvider:
$this->app->bind(\Http\Client\HttpClient::class, function ($app) {
$client = new \Http\Client\Common\Plugin\Client\BaseClient();
$cachePool = $app->make(\Psr\Cache\CacheItemPoolInterface::class);
return (new CachePlugin($cachePool))->attachTo($client);
});
Queue Job Caching:
Avoid caching responses in queue jobs (e.g., HandleWebhook) where idempotency isn’t guaranteed.
**Testing
How can I help you explore Laravel packages today?