How It Works
This page explains the caching gap that Acorn MilliCache fills, how the StoreResponse middleware pipeline works, and how cached responses are served.
The Caching Gap#Copied!
MilliCache's standard caching flow works like this:
- A request arrives →
advanced-cache.phpchecks Redis for a cached response - On HIT → the cached page is served immediately (WordPress never loads)
- On MISS → WordPress loads, MilliCache hooks
template_redirectto start output buffering, and the finished response is stored in Redis
The problem: Acorn custom routes are resolved during parse_request and send their response directly — before WordPress reaches template_redirect. MilliCache's output-buffering hook never fires, so Acorn route responses are never cached.
flowchart TD
A[Request arrives] --> B{advanced-cache.php<br/>Redis lookup}
B -->|HIT| C[Serve cached page<br/>~5-15 ms]
B -->|MISS| D[WordPress loads]
D --> E[parse_request]
E -->|Acorn route| F[Laravel router handles request]
E -->|WordPress route| G[template_redirect]
G --> H[MilliCache output buffering]
H --> I[Store in Redis ✓]
F --> J[Response sent]
J --> K[template_redirect never fires]
K --> L[Not cached ✗]
style C fill:#d4edda
style I fill:#d4edda
style L fill:#f8d7da
How StoreResponse Fills the Gap#Copied!
The StoreResponse middleware runs inside Acorn's Laravel router — exactly where WordPress hooks cannot reach. It captures the finished response and stores it in Redis using MilliCache's own API.
Middleware Pipeline#Copied!
flowchart TD
A[Request enters middleware] --> B{millicache function<br/>exists?}
B -->|No| C[Run controller only]
C --> D[Return response]
B -->|Yes| E[Run inner pipeline]
E --> F[Response ready]
F --> G{check_cache_decision?}
G -->|No| H[Return response]
G -->|Yes| I{Content available?}
I -->|No| H
I -->|Yes| J[Add cache flags]
J --> K[Store in Redis]
K --> H
style K fill:#d4edda
The middleware follows this sequence:
- Check MilliCache is active —
function_exists('millicache'). If MilliCache isn't loaded (e.g. deactivated), the middleware becomes a no-op. - Run the inner pipeline —
$next($request)passes the request through any inner middleware (including Acorn MilliRules'ExecuteRulesmiddleware, if installed) and into your controller. - Check the cache decision —
millicache()->check_cache_decision(). By this point, both MilliCache's PHP bootstrap rules and any WordPress-aware rules from Acorn MilliRules have executed. If any rule calleddo_cache(false)(e.g. for logged-in users), the check returnsfalseand the response is returned without storing. MilliCache handles bypass and reason headers internally. - Capture the response — the middleware reads the response content, headers, and status code.
- Add cache flags — adds a
route:{name}flag for named routes, or a barerouteflag for unnamed routes (see Cache Flags below). - Store in Redis — delegates to
millicache()->response()->store(), which handles hash generation, flag collection, compression, and writing the cache entry.
is_user_logged_in()) have already executed. Acorn MilliRules can disable caching based on logged-in users, specific routes, or any custom condition.What Gets Stored#Copied!
The middleware passes these values to MilliCache's ResponseProcessor:
| Value | Source | Description |
|---|---|---|
| Content | $response->getContent() |
The full response body |
| Headers | $response->headers->all() |
All response headers in Key: Value format |
| Status code | $response->getStatusCode() |
HTTP status code (e.g. 200) |
| TTL | millicache()->options()->get_ttl() |
Cache lifetime from MilliCache config |
| Grace | millicache()->options()->get_grace() |
Stale-while-revalidate grace period |
Writer::validate_headers() automatically filters out Set-Cookie and X-MilliCache-* headers before writing. The middleware does not need to handle this.Cache Flags#Copied!
MilliCache uses cache flags for targeted invalidation — e.g. purging all entries tagged with a specific flag. For WordPress pages, MilliCache automatically adds flags like post:123 or archive:category:5 via its RequestFlags rules. Since Acorn routes bypass that hook, this package adds its own flags before storing.
Automatic Flags#Copied!
The middleware adds a cache flag based on the route name:
| Route | Flag | Example |
|---|---|---|
| Named | route:{name} |
route:products:index |
| Unnamed | route |
route |
The Laravel route name is converted from dots to colons to match MilliCache's flag convention (products.index → route:products:index). Unnamed routes receive a bare route fallback flag.
In addition, MilliCache automatically adds a url:{hash} flag for every cache entry (both WordPress and Acorn).
Use the route* wildcard to target all Acorn route caches at once, or a specific flag like route:products:index for targeted invalidation.
route* or individually via their url:{hash}.Custom Flags#Copied!
You can add custom flags to Acorn route cache entries using Acorn MilliRules. Define a rule with an add_flag action that targets your route by name, controller, or any other condition.
Cache Clearing#Copied!
WP-CLI#Copied!
MilliCache provides WP-CLI commands for cache management. These work for all cached entries, including Acorn routes:
1# Clear all cached pages
2wp millicache clear
3
4# Clear all Acorn route caches
5wp millicache clear --flag=route*
6
7# Clear a specific route's cache
8wp millicache clear --flag=route:products:index
See the MilliCache WP-CLI documentation for the full command reference.
Automatic Clearing#Copied!
Acorn MilliCache automatically clears cache entries when certain Artisan commands run. The mapping between commands and flag patterns is configurable:
1// config/millicache.php
2'clear' => [
3 'optimize:clear' => 'route*',
4 'route:clear' => 'route*',
5 'route:cache' => 'route*',
6],
By default, optimize:clear, route:clear, and route:cache all clear Acorn route caches (route*). WordPress page caches are not affected.
wp millicache clear --flag=route:products:index via WP-CLI.Cache Serving (HIT Path)#Copied!
Once a response is stored, subsequent requests are served by MilliCache's advanced-cache.php drop-in. This runs before WordPress loads:
advanced-cache.phpcalculates the request hash- Looks up the hash in Redis
- On HIT → sends the cached headers, status code, and body directly
- WordPress, Acorn, and Laravel are never loaded (~5–15 ms response time)
This package has no role in the HIT path. It only handles MISS storage.
Error Handling#Copied!
Cache storage is wrapped in a try/catch block. If Redis is unavailable or any storage step fails:
- The original response is returned to the visitor unchanged
- The error is logged via
error_log()with an[acorn-millicache]prefix - No exception propagates to the user
Full Request Lifecycle#Copied!
sequenceDiagram
participant Browser
participant AdvancedCache as advanced-cache.php
participant WordPress
participant Acorn as Acorn Router
participant Middleware as StoreResponse
participant Controller
participant Redis
Browser->>AdvancedCache: GET /acorn-route
AdvancedCache->>Redis: Lookup hash
Redis-->>AdvancedCache: MISS
AdvancedCache->>WordPress: Continue loading
WordPress->>Acorn: parse_request (route matched)
Acorn->>Middleware: Enter middleware stack
Middleware->>Middleware: Check millicache() exists ✓
Middleware->>Controller: $next($request) (runs ExecuteRules + controller)
Controller-->>Middleware: Response (200, HTML, headers)
Middleware->>Middleware: check_cache_decision() ✓
Middleware->>Middleware: Add flag (route:{name})
Middleware->>Redis: Store via millicache()->response()->store()
Middleware-->>Browser: Return response
Note over Browser,Redis: Next request — HIT path
Browser->>AdvancedCache: GET /acorn-route
AdvancedCache->>Redis: Lookup hash
Redis-->>AdvancedCache: HIT
AdvancedCache-->>Browser: Cached response (~5-15 ms)
Further Reading#Copied!
- MilliCache — How Caching Works — the full cache lifecycle including output buffering, compression, and stale-while-revalidate
- MilliCache — Configuration — TTL, grace period, and other cache settings
- Acorn MilliRules — route-aware cache rules and conditions