mikespub / micheh-psr7-cache
Cache and conditional request helpers for PSR-7 HTTP Messages
Requires
- php: >=8.1
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.5
README
This library provides an easy way to either add cache relevant headers to a PSR-7 HTTP message implementation, or to extract cache and conditional request information from a PSR-7 message (e.g. if a response is cacheable).
It also provides a Cache-Control
value object to provide an object oriented interface for the manipulation of the Cache-Control
header.
Installation of this fork (PHP 8.1+)
Install this library using Composer:
$ composer require mikespub/micheh-psr7-cache
Note: the original package is still available at micheh/psr7-cache but it is no longer maintained
Quickstart
To enable caching of HTTP responses, create an instance of CacheUtil
, call the method withCache
and provide your PSR-7 response.
/** @var \Psr\Http\Message\ResponseInterface $response */ $util = new \Micheh\Cache\CacheUtil(); $response = $util->withCache($response);
This will add the header Cache-Control: private, max-age=600
to your response.
With this header the response will only be cached by the client who sent the request and will be cached for 600 seconds (10 min).
During this time the client should use the response from the cache and should not make a new request to the application.
Cache Validators
The application should also add Cache Validators to the response: An ETag
header (and Last-Modified
header if you know when the resource was last modified).
This way the client will also include the ETag
and Last-Modified
information in the request and the application can check if the client still has the current state.
/** @var \Psr\Http\Message\ResponseInterface $response */ $util = new \Micheh\Cache\CacheUtil(); $response = $util->withCache($response); $response = $util->withETag($response, 'my-etag'); $response = $util->withLastModified($response, new \DateTime());
Revalidate a response
To determine if the client still has a current copy of the page and the response is not modified, you can use the isNotModified
method.
Simply add the cache headers to the response and then call the method with both the request and the response.
The method will automatically compare the If-None-Match
header of the request with the ETag
header of the response (and/or the If-Modified-Since
header of the request with the Last-Modified
header of the response if available).
If the response is not modified, return the empty response with the cache headers and a status code 304
(Not Modified).
This will instruct the client to use the cached copy from the previous request, saving you CPU/memory usage and bandwidth.
Therefore it is important to keep the code before the isNotModified
call as lightweight as possible to increase performance.
Don't create the complete response before this method.
/** @var \Psr\Http\Message\RequestInterface $request */ /** @var \Psr\Http\Message\ResponseInterface $response */ $util = new \Micheh\Cache\CacheUtil(); $response = $util->withCache($response); $response = $util->withETag($response, 'my-etag'); $response = $util->withLastModified($response, new \DateTime()); if ($util->isNotModified($request, $response)) { return $response->withStatus(304); } // create the body of the response
Conditional request with unsafe method
While the procedure described above is usually optional and for safe methods (GET and HEAD), it is also possible to enforce that the client has the current resource state.
This is useful for unsafe methods (e.g. POST, PUT, PATCH or DELETE), because it can prevent lost updates (e.g. if another client updates the resource before your request).
It is a good idea to initially check if the request includes the appropriate headers (If-Match
for an ETag
and/or If-Unmodified-Since
for a Last-Modified
date) with the hasStateValidator
method.
If the request does not include this information, abort the execution and return status code 428
(Precondition Required) or status code 403
(Forbidden) if you only want to use the original status codes.
/** @var \Psr\Http\Message\RequestInterface $request */ $util = new \Micheh\Cache\CacheUtil(); if (!$util->hasStateValidator($request)) { return $response->withStatus(428); }
If the state validators are included in the request, you can check if the request has the current resource state and not an outdated version with the method hasCurrentState
.
If the request has an outdated resource state (another ETag
or an older Last-Modified
date), abort the execution and return status code 412
(Precondition Failed).
Otherwise you can continue to process the request and update/delete the resource.
Once the resource is updated, it is a good idea to include the updated ETag
(and Last-Modified
date if available) in the response.
/** @var \Psr\Http\Message\RequestInterface $request */ /** @var \Psr\Http\Message\ResponseInterface $response */ $util = new \Micheh\Cache\CacheUtil(); if (!$util->hasStateValidator($request)) { return $response->withStatus(428); } $eTag = 'my-etag' $lastModified = new \DateTime(); if (!$util->hasCurrentState($request, $eTag, $lastModified)) { return $response->withStatus(412); } // process the request
Available helper methods
References
- RFC7234: Caching
- RFC7232: Conditional Requests (Cache Validation)
- RFC5861: Cache-Control Extensions for Stale Content
License
The files in this archive are licensed under the BSD-3-Clause license. You can find a copy of this license in LICENSE.md.