Search by

therealworld / staticcache-module

therealworld

Certain OXID7 controllers create static caches and may display them.

Package info

bitbucket.org/therealworld/staticcache-module

Homepage

Issues

Type:oxideshop-module

pkg:composer/therealworld/staticcache-module

Statistics

Installs: 623

Dependents: 0

Suggesters: 0

v3.0.5 2026-10-04 13:55 UTC

README

description

Stores the rendered html of a page and serves the next visitor from that copy, so the shop answers without running the controller, the templates or their database queries. Logged-in visitors and visitors with a basket still get the cached page — the parts that must not be shared are replaced with placeholders and reloaded over ajax.

New oe-console commands

  • trw:staticcache:prune [--shop-id=N] [--dry-run]
  • trw:staticcache:status [--url=...] [--lang=N] [--explain]

The commands live in this module, not in the clirun-plugin: only the module knows how its cache is stored, and this way the plugin does not need the module as a dependency. They are registered through the module's services.yaml and appear in oe-console as soon as the module is installed and active.

What is cached, and what is not

A page is served from the cache unless the controller is excluded or the request carries an fnc parameter. A page is stored only when it additionally comes from an anonymous visitor with an empty basket, answers with http 200, is not a redirect, contains a closing document tag, and the volume still has room.

Never cached: everything under account*, the basket, the user, payment, order and thank-you controllers, plus the controllers active modules exclude themselves: through the CacheExcludedControllerContributorInterface decoration chain of the tools-plugin (base class AbstractCacheExcludedControllerContributor, examples: the captcha endpoints, the newsletter web view, the withdrawal form). An exclusion lives in the module's services.yaml and stops applying on its own when the module is deactivated. There is no setting for it: a shop that has to exclude a core or third party controller adds a small contributor of its own.

Every response says what happened

The X-TRW-StaticCache header states the outcome of each request, which is what makes the cache diagnosable at all — and what lets a warmup verify in a single request instead of two:

headermeaning
miss, storedrendered and written to the cache
cache, age=42sserved from the cache
cache+hydration, age=42sserved from the cache, dynamic widgets reloaded over ajax
cache+token, cache+hydration+tokenthe visitor's own csrf token was injected
bypass, reason=adminan admin is looking, always live
miss, reason=controllerthis controller is never cached
miss, reason=user / basketnot an anonymous, empty-basket visitor
miss, reason=functionthe request carries fnc
miss, reason=status / redirectnot a plain 200
miss, reason=nohtmla fragment, not a whole page
miss, reason=diskspacebelow the configured free space minimum
miss, reason=savefailedthe entry could not be written
curl -s -D - -o /dev/null 'https://example.org/a-page/' | grep -i x-trw

Note that OXID counts curl as a search engine (aRobots in config.inc.php), so a plain curl call sees the bot variant of the page. Pass -A 'Mozilla/5.0 ...' for the visitor view.

The cache key

Built from the shop id, the protocol, the language, the request path and those query parameters that are allowed to matter. It deliberately does not contain the session, so one entry serves everybody.

Query parameters: a whitelist, not a blacklist

aTRWCacheKeyAllowedParams lists the parameters that may take part in the key. Anything not listed is stripped from the key — the request is still cached and still served, it just no longer creates an entry of its own.

This is the important setting of the module. With an ignore list alone, every parameter nobody thought of — a new tracking network, an affiliate id, a typo, a bot experimenting — silently multiplies the cache: one crawl once produced 901,349 keys for 23,811 pages and filled the volume.

The price is that the direction of failure flips. A parameter that really does change the page and is missing from the list will make the shop serve the wrong content, where before it only wasted disk. A module that introduces such a parameter therefore declares it itself: through the CacheKeyParamContributorInterface decoration chain of the tools-plugin (base class AbstractCacheKeyParamContributor, example: libriid in the libri-module). Those parameters do not appear in the setting, but apply on top of it while the module is active — and leave the key again on their own when it is deactivated. Leaving the list empty restores the old behaviour (ignore list only) — that is the way back if a shop turns out to depend on something nobody listed.

aTRWCacheKeyIgnoredParams still applies afterwards and keeps session and campaign parameters out of the key even if someone puts a * in the whitelist.

Use trw:staticcache:status --url=... --explain to see what the key building does to a concrete url:

Url          : https://example.org/a-category/?pgNr=2&utm_source=news&whatever=1
State        : warm (expires in 00:59:42)
  normalized : /a-category/?pgNr=2
  params kept: pgNr
  dropped    : utm_source, whatever
Cache key    : 923a5d6205ebeb2b08c5460c390a181c

Housekeeping

Symfony's filesystem cache only removes an expired entry when the same key is read again. A url a bot asked for once and nobody ever asks for again therefore stays on disk forever, which is exactly the shape of the traffic a page cache sees. Without a prune the directory only grows.

trw:staticcache:prune

Drops expired entries and enforces the size limit, evicting the entries with the shortest remaining lifetime until the cache is back to 90% of the limit. Deletes without an extra flag — an expired entry is expired, and a command meant for cron must not need one; use --dry-run to look first.

vendor/bin/oe-console trw:staticcache:prune --dry-run
vendor/bin/oe-console trw:staticcache:prune --shop-id=1

The scheduler task Prune the page cache does the same thing nightly (0 4 * * *). It is installed inactive like every discovered task — switch it on in the backend.

trw:staticcache:status

Reports the directory, the number of entries, their size, the expiry range and the free disk space; with --url it says whether one page is warm and, with --explain, how its key is built. This is also the only honest test that a warmup works: cold before the request, warm after it.

Options

Cache duration

  • iTRWCacheLifetime: how long an entry stays valid, in seconds (default 3600)

Caching rules

  • aTRWCacheKeyAllowedParams: the whitelist above. Empty = old behaviour
  • aTRWCacheKeyIgnoredParams: parameters removed from the key after the whitelist; a trailing * matches by prefix

Admin bypass

  • bTRWBypassCacheForAdminUser: a frontend user with admin rights always sees the live page and never writes to the cache. Costs one database query per logged-in request
  • bTRWBypassCacheForAdminCookie: same for any browser carrying a backend session cookie. Free (no database access) and covers the usual workflow. The cookie is not validated — it only bypasses the cache and grants no rights

Size and disk limits

  • iTRWCacheMaxSizeMb: upper bound per shop, 0 = none. Enforced by the prune run only
  • sTRWCacheMinFreeDisk: below this much free space the cache stops writing but keeps serving (default 2G; accepts 2G, 500M, 5% or plain bytes; empty switches it off). If free space cannot be measured, writing continues — a broken measurement must not switch the cache off

Html minification

  • bTRWMinifyHtml: minify the whole html output, cached or not. <script>, <style>, <textarea> and <pre> are protected

Dynamic hydration

  • bTRWDynamicHydration: serve cached pages to logged-in visitors as well and reload the personalised widgets over ajax
  • aTRWDynamicWidgets: which data-trw-dynamic markers to hydrate

See docs/DYNAMIC_HYDRATION.md for what a theme has to provide, and which widgets cannot be hydrated.

Where the cache lives

<sCompileDir>/static_cache/<shopId>/ — its own directory, next to the compiled templates rather than inside them, so a template deployment and a page cache purge no longer take each other down and the page cache can be cleared, pruned and measured on its own.

trw:clear:cache (clirun-plugin), the cacheclean module and the backend's clear cache still remove it together with everything else — that is what "clear the cache" is expected to mean.