API Index — Namespaces, Packages, Reports, Indices
ExpiresMiddleware
in package
implements
MiddlewareInterface
Expires Middleware (Apache mod_expires equivalent)
Stamps an Expires: header on the response based on its Content-Type.
Modern clients prefer Cache-Control: max-age (see CacheControlMiddleware)
but many legacy proxies and browsers still honour Expires, and the
Apache ExpiresByType image/jpeg "access plus 1 month" idiom is so
common in migrated .htaccess files that the name parity matters.
Apache equivalent:
ExpiresActive On
ExpiresDefault "access plus 5 minutes"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType text/css "access plus 1 year"
Constructor takes a Content-Type prefix => relative-date map. Values are
parsed by strtotime() so any of these forms work:
'+1 year', '+30 days', '+5 minutes', '+86400 seconds'
Base time ('A' vs 'M'):
Pass base: 'A' (default) to compute expiry relative to the current
request time — Apache ExpiresDefault "access plus N" / A base.
Pass base: 'M' to compute expiry relative to the Last-Modified header
value on the response (file modification time) — Apache M base. If no
Last-Modified header is present on the response, the middleware falls
back to access-time (the current request time) and the behaviour is
identical to base: 'A'.
Dual-header emission (emitCacheControl: true):
Apache's set_expiration_fields() always emits both Expires: and
Cache-Control: max-age=N from a single config rule, where max-age is
derived as expires - request_time (mod_expires.c:432–437). Set
$emitCacheControl = true to replicate this atomicity: when Expires is
stamped, a matching Cache-Control: max-age=N, public (or private) is
also emitted with the same delta, keeping both headers in sync to the
second. Existing Cache-Control headers are not overwritten (same
skip-if-present guard as CacheControlMiddleware).
Error-response suppression:
Apache mod_expires never stamps headers on 4xx/5xx responses
(mod_expires.c:455–458). This middleware follows the same rule: responses
with status >= 400 are returned unchanged.
Negative/past expiry clamping:
Apache clamps past expiry to request_time (mod_expires.c:429–431), which
results in max-age=0. This middleware does the same: if the computed
expiry timestamp is in the past, Expires is set to the current time and
Cache-Control: max-age=0 is emitted (when $emitCacheControl = true).
Usage in app.php:
// Access-time base, Expires header only (legacy compat):
$app->addMiddleware(new \ZealPHP\Middleware\ExpiresMiddleware(
byType: [
'image/' => '+30 days',
'text/css' => '+1 year',
'text/javascript' => '+1 year',
'font/' => '+1 year',
],
default: '+5 minutes',
));
// Apache-parity: both Expires + Cache-Control: max-age from one rule:
$app->addMiddleware(new \ZealPHP\Middleware\ExpiresMiddleware(
byType: ['image/' => '+30 days', 'text/css' => '+1 year'],
default: '+5 minutes',
emitCacheControl: true,
));
// M (modification-time) base — expiry relative to Last-Modified:
$app->addMiddleware(new \ZealPHP\Middleware\ExpiresMiddleware(
byType: ['text/html' => '+5 minutes'],
base: 'M',
emitCacheControl: true,
));
Match is by Content-Type prefix (first match wins, longest-prefix-first
for determinism). default applies when no prefix matches; pass null
to skip stamping when nothing matches (Apache ExpiresDefault unset).
Table of Contents
Interfaces
- MiddlewareInterface
Properties
- $base : string
- $byType : array<string, string>
- $default : string|null
- $emitCacheControl : bool
- $publicCache : bool
Methods
- __construct() : mixed
- process() : ResponseInterface
- resolveRelative() : string|null
Properties
$base
private
string
$base
= 'A'
$byType
private
array<string, string>
$byType
sorted longest-prefix-first
$default
private
string|null
$default
= null
$emitCacheControl
private
bool
$emitCacheControl
= false
$publicCache
private
bool
$publicCache
= true
Methods
__construct()
public
__construct([array<string, string> $byType = [] ][, string|null $default = null ][, string $base = 'A' ][, bool $emitCacheControl = false ][, bool $publicCache = true ]) : mixed
Parameters
- $byType : array<string, string> = []
-
CT-prefix => relative-date
- $default : string|null = null
-
Relative-date for unmatched CTs (
null= skip) - $base : string = 'A'
-
'A'= access/request time (default);'M'=Last-Modifiedmtime - $emitCacheControl : bool = false
-
Also emit
Cache-Control: max-age=N(Apache dual-header parity) - $publicCache : bool = true
-
Whether emitted
Cache-Controluses'public'(true) or'private'
process()
public
process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
Parameters
- $request : ServerRequestInterface
- $handler : RequestHandlerInterface
Return values
ResponseInterfaceresolveRelative()
private
resolveRelative(string $ct) : string|null
Parameters
- $ct : string