PHPackages                             methorz/http-cache-middleware - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. methorz/http-cache-middleware

ActiveLibrary[HTTP &amp; Networking](/categories/http)

methorz/http-cache-middleware
=============================

PSR-15 HTTP caching middleware with ETag support and RFC 7234 compliance

v1.0.0(5mo ago)049MITPHPPHP ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0CI passing

Since Dec 1Pushed 4mo agoCompare

[ Source](https://github.com/MethorZ/http-cache-middleware)[ Packagist](https://packagist.org/packages/methorz/http-cache-middleware)[ RSS](/packages/methorz-http-cache-middleware/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (10)Versions (2)Used By (0)

MethorZ HTTP Cache Middleware
=============================

[](#methorz-http-cache-middleware)

**PSR-15 HTTP caching middleware with ETag support and RFC 7234 compliance**

[![CI](https://github.com/MethorZ/http-cache-middleware/actions/workflows/ci.yml/badge.svg)](https://github.com/MethorZ/http-cache-middleware/actions/workflows/ci.yml)[![codecov](https://camo.githubusercontent.com/43bb4448507c33f6dbe88680a404d40a7a8ff9e167fc13a12145262e8990b019/68747470733a2f2f636f6465636f762e696f2f67682f4d6574686f725a2f687474702d63616368652d6d6964646c65776172652f67726170682f62616467652e737667)](https://codecov.io/gh/MethorZ/http-cache-middleware)[![PHPStan](https://camo.githubusercontent.com/1bc07920f0d36e55c17e1d38b1caa132cc605f51a82b388c962870b9a747b898/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e2e737667)](https://phpstan.org/)[![PHP Version](https://camo.githubusercontent.com/c9f64f714c636ba27a3bba6dfd52f98426832db1262747efa54b212d16943651/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253545382e322d626c7565)](https://php.net)[![License](https://camo.githubusercontent.com/f8df3091bbe1149f398a5369b2c39e896766f9f6efba3477c63e9b4aa940ef14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e)](LICENSE)

Automatic HTTP caching for PSR-15 applications with ETag generation, 304 Not Modified responses, and Cache-Control header management. Zero configuration, production-ready.

---

✨ Features
----------

[](#-features)

- 🏷️ **Automatic ETag Generation** - MD5/SHA256/custom algorithm support
- 🚀 **304 Not Modified** - Automatic conditional request handling
- 📋 **Cache-Control Builder** - Fluent interface for RFC 7234 directives
- ✅ **RFC Compliant** - RFC 7234 (caching) &amp; RFC 7232 (conditional requests)
- 🎯 **Conditional Requests** - `If-None-Match`, wildcard support
- 💪 **Strong &amp; Weak ETags** - Full support for both ETag types
- 🔧 **Zero Configuration** - Sensible defaults, works out-of-the-box
- 🎨 **Highly Customizable** - Control caching behavior per-route
- 📦 **Framework Agnostic** - Works with any PSR-15 application

---

📦 Installation
--------------

[](#-installation)

```
composer require methorz/http-cache-middleware
```

---

🚀 Quick Start
-------------

[](#-quick-start)

### **Basic Usage**

[](#basic-usage)

```
use MethorZ\HttpCache\Middleware\CacheMiddleware;

// Add to middleware pipeline
$app->pipe(new CacheMiddleware());
```

That's it! All `GET` and `HEAD` requests will now have:

- Automatic ETag generation
- 304 Not Modified responses
- Proper caching headers

---

📖 Detailed Usage
----------------

[](#-detailed-usage)

### **With Cache-Control Directives**

[](#with-cache-control-directives)

```
use MethorZ\HttpCache\Middleware\CacheMiddleware;
use MethorZ\HttpCache\Directive\CacheControlDirective;

$cacheControl = CacheControlDirective::create()
    ->public()
    ->maxAge(3600)
    ->mustRevalidate();

$middleware = new CacheMiddleware(cacheControl: $cacheControl);
```

**Generated Headers**:

```
ETag: "5d41402abc4b2a76b9719d911017c592"
Cache-Control: public, max-age=3600, must-revalidate

```

### **Configuration Options**

[](#configuration-options)

```
$middleware = new CacheMiddleware(
    enabled: true,                  // Enable/disable caching
    cacheControl: $directive,       // Cache-Control directive
    useWeakEtag: false,            // Use weak ETags (W/)
    etagAlgorithm: 'md5',          // Hash algorithm (md5, sha256, etc.)
    cacheableMethods: ['GET', 'HEAD'], // Cacheable HTTP methods
    cacheableStatuses: [200, 203], // Cacheable status codes
);
```

### **Development Mode (Disable Caching)**

[](#development-mode-disable-caching)

For development, you want fresh data on every request. Simply disable the middleware:

```
// Option 1: Conditionally add middleware based on environment
if (getenv('APP_ENV') !== 'development') {
    $app->pipe(new CacheMiddleware());
}

// Option 2: Disable via constructor parameter
$middleware = new CacheMiddleware(
    enabled: getenv('APP_ENV') !== 'development'
);

// Option 3: Don't add middleware to pipeline in development
// (recommended - cleanest approach)
```

**Recommended approach**: Only add `CacheMiddleware` to your production pipeline configuration, not in development.

---

🎯 Cache-Control Directive Builder
---------------------------------

[](#-cache-control-directive-builder)

Fluent interface for building Cache-Control headers:

### **Common Patterns**

[](#common-patterns)

**Public, cacheable for 1 hour**:

```
CacheControlDirective::create()
    ->public()
    ->maxAge(3600)
    ->mustRevalidate();
// Output: "public, max-age=3600, must-revalidate"
```

**Private, no caching**:

```
CacheControlDirective::create()
    ->private()
    ->noCache();
// Output: "private, no-cache"
```

**Immutable assets (images, CSS, JS)**:

```
CacheControlDirective::create()
    ->public()
    ->maxAge(31536000) // 1 year
    ->immutable();
// Output: "public, max-age=31536000, immutable"
```

**API responses with shared cache**:

```
CacheControlDirective::create()
    ->public()
    ->maxAge(300)      // Browser cache: 5 minutes
    ->sMaxAge(3600)    // CDN cache: 1 hour
    ->staleWhileRevalidate(60);
// Output: "public, max-age=300, s-maxage=3600, stale-while-revalidate=60"
```

### **All Directives**

[](#all-directives)

MethodDescriptionExample`public()`Cache may be stored by any cache`public``private()`Cache only for single user`private``noCache()`Must revalidate before use`no-cache``noStore()`Must not be stored anywhere`no-store``maxAge(int)`Maximum freshness time`max-age=3600``sMaxAge(int)`Shared cache max age`s-maxage=7200``mustRevalidate()`Must revalidate when stale`must-revalidate``proxyRevalidate()`Proxy must revalidate`proxy-revalidate``noTransform()`Cache must not transform response`no-transform``staleWhileRevalidate(int)`Serve stale while fetching fresh`stale-while-revalidate=60``staleIfError(int)`Serve stale if origin errors`stale-if-error=120``immutable()`Response will never change`immutable`---

🏷️ ETag Generation
------------------

[](#️-etag-generation)

### **Automatic ETag Generation**

[](#automatic-etag-generation)

```
use MethorZ\HttpCache\Generator\ETagGenerator;

// Strong ETag (exact match required)
$etag = ETagGenerator::generate($response);
// Output: "5d41402abc4b2a76b9719d911017c592"

// Weak ETag (semantic equality)
$weakEtag = ETagGenerator::generateWeak($response);
// Output: W/"5d41402abc4b2a76b9719d911017c592"

// Custom algorithm
$sha256Etag = ETagGenerator::generateWithAlgorithm($response, 'sha256');
```

### **ETag Utilities**

[](#etag-utilities)

```
// Check if ETag is weak
ETagGenerator::isWeak('W/"abc"'); // true
ETagGenerator::isWeak('"abc"');   // false

// Extract hash value
ETagGenerator::extractHash('"abc123"');   // "abc123"
ETagGenerator::extractHash('W/"abc123"'); // "abc123"

// Compare ETags
ETagGenerator::matches('"abc"', 'W/"abc"', weakComparison: true);  // true
ETagGenerator::matches('"abc"', 'W/"abc"', weakComparison: false); // false
```

---

🔄 How It Works
--------------

[](#-how-it-works)

### **1. First Request (Cache Miss)**

[](#1-first-request-cache-miss)

```
Client → GET /api/items
Server → 200 OK
         ETag: "abc123"
         Cache-Control: public, max-age=3600
         Body: {...}

```

**Client caches response with ETag**

### **2. Subsequent Request (Cache Validation)**

[](#2-subsequent-request-cache-validation)

```
Client → GET /api/items
         If-None-Match: "abc123"
Server → 304 Not Modified
         ETag: "abc123"
         Cache-Control: public, max-age=3600
         (empty body)

```

**Benefits**:

- ⚡ **Faster**: No body transmission (~95% bandwidth reduction)
- 💰 **Cheaper**: Reduced server CPU &amp; network costs
- 🌍 **Better UX**: Instant responses for unchanged resources

---

🎯 Use Cases
-----------

[](#-use-cases)

### **1. Static Asset Caching**

[](#1-static-asset-caching)

```
// For images, CSS, JS with content hashing in filename
$middleware = new CacheMiddleware(
    cacheControl: CacheControlDirective::create()
        ->public()
        ->maxAge(31536000) // 1 year
        ->immutable(),
);
```

### **2. API Response Caching**

[](#2-api-response-caching)

```
// Cache API responses for 5 minutes
$middleware = new CacheMiddleware(
    cacheControl: CacheControlDirective::create()
        ->public()
        ->maxAge(300)
        ->mustRevalidate(),
);
```

### **3. Dynamic Content with Validation**

[](#3-dynamic-content-with-validation)

```
// Always validate with server, but use weak ETags
$middleware = new CacheMiddleware(
    useWeakEtag: true,
    cacheControl: CacheControlDirective::create()
        ->public()
        ->noCache() // Always revalidate
        ->maxAge(0),
);
```

### **4. Private User Data**

[](#4-private-user-data)

```
// Cache in browser only, not in shared caches
$middleware = new CacheMiddleware(
    cacheControl: CacheControlDirective::create()
        ->private()
        ->maxAge(300),
);
```

### **5. CDN Integration**

[](#5-cdn-integration)

```
// Different cache times for browser vs CDN
$middleware = new CacheMiddleware(
    cacheControl: CacheControlDirective::create()
        ->public()
        ->maxAge(300)      // Browser: 5 minutes
        ->sMaxAge(3600)    // CDN: 1 hour
        ->staleWhileRevalidate(60),
);
```

---

🔧 Configuration Examples
------------------------

[](#-configuration-examples)

### **Mezzio / Laminas**

[](#mezzio--laminas)

```
// config/autoload/middleware.global.php
use MethorZ\HttpCache\Middleware\CacheMiddleware;
use MethorZ\HttpCache\Directive\CacheControlDirective;

return [
    'dependencies' => [
        'factories' => [
            CacheMiddleware::class => function (): CacheMiddleware {
                return new CacheMiddleware(
                    cacheControl: CacheControlDirective::create()
                        ->public()
                        ->maxAge(3600),
                );
            },
        ],
    ],
];

// config/pipeline.php
$app->pipe(CacheMiddleware::class);
```

### **Per-Route Configuration**

[](#per-route-configuration)

```
// Apply different caching strategies per route
$publicCaching = new CacheMiddleware(
    cacheControl: CacheControlDirective::create()->public()->maxAge(3600),
);

$privateCaching = new CacheMiddleware(
    cacheControl: CacheControlDirective::create()->private()->maxAge(300),
);

$app->get('/api/public', [$publicCaching, PublicHandler::class]);
$app->get('/api/user/profile', [$privateCaching, ProfileHandler::class]);
```

---

📊 HTTP Headers Reference
------------------------

[](#-http-headers-reference)

### **Request Headers (Client → Server)**

[](#request-headers-client--server)

HeaderDescriptionExample`If-None-Match`Conditional request with ETag`"abc123"` or `W/"abc123"` or `*``If-Modified-Since`Conditional request with date`Wed, 21 Oct 2015 07:28:00 GMT`### **Response Headers (Server → Client)**

[](#response-headers-server--client)

HeaderDescriptionExample`ETag`Entity tag for resource version`"abc123"` or `W/"abc123"``Cache-Control`Caching directives`public, max-age=3600``Expires`Absolute expiration time`Wed, 21 Oct 2025 07:28:00 GMT``Last-Modified`Resource modification time`Wed, 21 Oct 2024 07:28:00 GMT`---

🧪 Testing
---------

[](#-testing)

```
# Run tests
composer test

# Static analysis
composer analyze

# Code style
composer cs-check
composer cs-fix
```

**Test Coverage**: 37 tests, 57 assertions, 100% passing

---

⚡ Performance Impact
--------------------

[](#-performance-impact)

### **Bandwidth Savings**

[](#bandwidth-savings)

```
Without caching:
GET /api/items → 200 OK (10 KB body) → 10 KB transferred

With caching (subsequent requests):
GET /api/items (If-None-Match: "abc123") → 304 Not Modified → ~500 bytes transferred

Savings: ~95% bandwidth reduction

```

### **Server Load Reduction**

[](#server-load-reduction)

- ✅ 304 responses skip expensive body serialization
- ✅ ETag comparison is instant (simple hash check)
- ✅ Reduces database queries when responses haven't changed
- ✅ Lower CPU usage for repeated identical requests

---

🔒 Security Considerations
-------------------------

[](#-security-considerations)

### **Private vs Public**

[](#private-vs-public)

```
// ❌ Don't cache sensitive user data publicly
CacheControlDirective::create()->public(); // Bad for /api/user/profile

// ✅ Use private for user-specific data
CacheControlDirective::create()->private(); // Good for /api/user/profile
```

### **Cache Invalidation**

[](#cache-invalidation)

This middleware handles **validation** (304 responses), not **invalidation**. For cache invalidation:

- Change resource content → new ETag → cache miss → fresh response
- Use `no-cache` directive → always revalidate with server
- Use CDN purge APIs for immediate invalidation

---

📚 Resources
-----------

[](#-resources)

- [RFC 7234: HTTP Caching](https://tools.ietf.org/html/rfc7234)
- [RFC 7232: Conditional Requests](https://tools.ietf.org/html/rfc7232)
- [MDN: HTTP Caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching)
- [MDN: Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)

---

🔗 Related Packages
------------------

[](#-related-packages)

This package is part of the MethorZ HTTP middleware ecosystem:

PackageDescription**[methorz/http-dto](https://github.com/methorz/http-dto)**Automatic HTTP ↔ DTO conversion with validation**[methorz/http-problem-details](https://github.com/methorz/http-problem-details)**RFC 7807 error handling middleware**[methorz/http-cache-middleware](https://github.com/methorz/http-cache-middleware)**HTTP caching with ETag support (this package)**[methorz/http-request-logger](https://github.com/methorz/http-request-logger)**Structured logging with request tracking**[methorz/openapi-generator](https://github.com/methorz/openapi-generator)**Automatic OpenAPI spec generationThese packages work together seamlessly in PSR-15 applications.

---

📄 License
---------

[](#-license)

MIT License. See [LICENSE](LICENSE) for details.

---

🤝 Contributing
--------------

[](#-contributing)

Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

---

🔗 Links
-------

[](#-links)

- [Changelog](CHANGELOG.md)
- [Contributing](CONTRIBUTING.md)
- [Security](SECURITY.md)
- [Issues](https://github.com/MethorZ/http-cache-middleware/issues)

###  Health Score

37

—

LowBetter than 82% of packages

Maintenance77

Regular maintenance activity

Popularity8

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

159d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b0233a9072fdc1746031fd96b487fce9b0d5f55c0124b70fc51ac2bb833bb906?d=identicon)[MethorZ](/maintainers/MethorZ)

---

Tags

304-not-modifiedcache-controlcomposerconditional-requestsetagframework-agnostichttp-cachemiddlewarepackagistphpphp8psr-15rfc-7234httpmiddlewarecachepsr-15cache-controlEtagconditional-requestsrfc-7234

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/methorz-http-cache-middleware/health.svg)

```
[![Health](https://phpackages.com/badges/methorz-http-cache-middleware/health.svg)](https://phpackages.com/packages/methorz-http-cache-middleware)
```

###  Alternatives

[kevinrob/guzzle-cache-middleware

A HTTP/1.1 Cache for Guzzle 6. It's a simple Middleware to be added in the HandlerStack. (RFC 7234)

43117.4M104](/packages/kevinrob-guzzle-cache-middleware)[mezzio/mezzio

PSR-15 Middleware Microframework

3883.6M97](/packages/mezzio-mezzio)[middlewares/utils

Common utils for PSR-15 middleware packages

503.4M93](/packages/middlewares-utils)[mezzio/mezzio-authentication-oauth2

OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications.

28483.0k2](/packages/mezzio-mezzio-authentication-oauth2)[mezzio/mezzio-authentication

Authentication middleware for Mezzio and PSR-7 applications

121.6M26](/packages/mezzio-mezzio-authentication)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
