qoliber / trident-cache-prestashop
PrestaShop 9 module for the Trident HTTP cache: full-page caching of anonymous catalogue pages, cache tags, durable purges (outbox, retried until acknowledged), personal content from the browser, and the shared admin screens.
Package info
github.com/qoliber/trident-cache-prestashop
Type:prestashop-module
pkg:composer/qoliber/trident-cache-prestashop
Requires
- php: ~8.1.0||~8.2.0||~8.3.0||~8.4.0||~8.5.0
- ext-sodium: *
- guzzlehttp/guzzle: ^7.5
- guzzlehttp/psr7: ^2.4
- qoliber/trident-php: ~1.8.2
Requires (Dev)
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-08 19:26:36 UTC
README
The tridentcache module puts a PrestaShop 9 shop behind the Trident HTTP cache:
catalogue pages are cached and invalidated by tag, every purge is delivered
durably, and the visitor's own bits (cart, greeting) come from the browser — a
cached page costs the shop nothing.
- Module:
tridentcache/— Composer packageqoliber/trident-cache-prestashop, on the shared libraryqoliber/trident-php~1.8.2 (the corrected SDK candidate must be published before public installation). - Sample Trident config:
trident.toml. - Live e2e stack:
tests/prestashop-e2e.
Tested: PrestaShop 9.1.5, PHP 8.3, MariaDB 10.11, Trident 1.8.0.
The technical minimum is PrestaShop 9.0.3. Its post-country-selection hook lets the module keep a first cookieless visitor in the shop's default country before currency selection. PrestaShop 9.0.0–9.0.2 do not emit this hook. This minimum is distinct from the tested profile above.
PrestaShop's native compatibility check refuses a fresh installation below 9.0.3. Upgrading or enabling an already-installed module does not repeat that fresh-install check: on an older shop, upgrade PrestaShop to 9.0.3 or later before deploying or updating this module, then use the normal module update procedure. An upgrade or enable success, or a registered hook name, does not validate this requirement.
Install
- Copy
tridentcache/tomodules/tridentcache/, runcomposer install --no-devinside it, thenbin/console prestashop:module install tridentcache. - Tell the module where Trident is — deployment configuration wins (as
env.phpdoes for Magento). Inapp/config/parameters.php:'trident_instances' => '{"default":{"api_url":"http://127.0.0.1:9301","api_token":"…"}}', 'trident_purge_mode' => 'soft',
or the same asTRIDENT_*environment variables (TRIDENT_INSTANCES,TRIDENT_API_URL+TRIDENT_API_TOKEN,TRIDENT_PURGE_MODE,TRIDENT_TAG_PREFIX,TRIDENT_ALLOWED_API_HOSTS,TRIDENT_DEBUG_HEADERS) — php-fpm clears the environment unless its pool passes them. A one-server shop can use Advanced Parameters → Trident Cache → Settings instead (URL + token). - Trident: start from
trident.toml, replace everyCHANGE ME, then generate the never-cache paths for YOUR shop and paste them over itspass_patterns— cart/checkout/account slugs are translated per language:bin/console trident:config. - A cron is REQUIRED (PrestaShop has no scheduler): every minute,
bin/console trident:purge:drain, or a request to the cron URL shown on the Settings screen with the token in theX-Trident-Cron-Tokenheader (or a POSTtoken— never in the URL). It retries what failed, delivers scheduled purges (a price starting at midnight) and — because a change made from the CLI, the database or the webservice is only noticed by the next request that reaches PHP, and a warm cache sends few — is what makes such changes appear.
How it decides
A render is shared (public, s-maxage, its cache tags, no cookie) only when
it is what any visitor in the same price context would get — decided on the final
HTML (actionOutputHTMLBefore): a GET of a catalogue page (home, category,
product, CMS, brand, supplier, listing pages, search), answered 200, for a visitor
who is not logged in and has an empty cart, with no notification on the page, and
whose price-context marker matches the render. Everything else is
private, no-store; Trident still serves such visitors the shared page of their
context, and never stores their own render.
PrestaShop writes its visitor cookie (PrestaShop-<hash>) and starts a PHP
session on every render, a guest's first included. A shared render drops every
Set-Cookie and stops the cookie from being written; a guest gets it as soon as
they act (add to cart, log in), on a response that is never shared. The guest form
token (static_token) is identical for every guest (computed for customer 0).
Price context (trident_ctx). Currency, cart and login live in ONE encrypted
cookie, so the module sets a marker when prices differ from the shop default —
another currency, another tax country (a logged-in customer's address), or a group
with prices or category access of its own — and Trident keys by it (vary_cookies). A render is
stored only when the marker the visitor sent matches the computed one, so a forged
marker never stores one context's prices under another's key. (A visitor with
no marker and one with d are two cache variants of the same default context —
harmless.) The marker is renewed on every response that is not shared.
First visit and the country. With PS_DETECT_COUNTRY (the default)
PrestaShop would pick the country — and its currency — from the browser's
Accept-Language; but Trident serves the default context's page to every
visitor without a marker. So the module keeps the shop's default country
for a visitor who has not chosen a currency or country, is not a known
customer and has no address on their cart: anonymous visitors see the default
country's prices until they choose, log in or enter an address. (To make that
explicit, disable "Set customer country from browser language" in
International → Localization.) The marker then lives as long as PrestaShop's
own cookie (PS_COOKIE_LIFETIME_FO), HttpOnly, Secure on https; the default
context's marker is d.
Personal content. Shared pages carry empty placeholders; the library's
trident-sections.js fills the cart badge and the greeting from localStorage and
calls /module/tridentcache/sections only when the version cookie trident_pv
changed (cart save, login, logout). No cookie, no request.
Tags and purges
Every page: all, menu; product pp_<id> + p_<id>; category / CMS / brand /
supplier c_ / cms_ / m_ / s_<id>; home home; plus p_<id> of every
product shown (data-id-product). Product save → p_<id> + its categories;
stock → p_<id> only when availability flips (an order does not purge listings);
category / CMS save → its page + parent, menu only when a field the menu shows
changed; specific price → its product; price rule or currency rate → all.
Bounded to 200 tags / 4 KiB with per-kind overflow tags.
Also purged: combinations, images, features and values, attributes and groups,
taxes and tax rules, customer groups, currencies, brands, suppliers, module
content (ps_imageslider, ps_customtext, link blocks) — all through the generic
actionObject* hooks — and the prices-drop / new-products listings on every
product change. A scheduled specific price or catalogue price rule is purged
again when it starts and when it ends (on PrestaShop's minute).
Durable (X02): each purge is recorded per instance in
PREFIX_trident_purge_outbox and removed only on acknowledgement; refusals and
outages are retried with backoff (cron, cron URL, and after back-office requests);
each purge goes out once more ~10 s later. trident:purge:status [--stale-after]
exits 1 when a purge is owed too long; trident:purge:forget <instance>.
Back office
Advanced Parameters → Trident Cache: the shared screens (dashboard, purge, cached pages, tags, coverage, warmer, launch, reflect, denoisers, bans, backends, discovery, events) and Settings. View permission to read, edit to act; every action is a POST with a CSRF token (HMAC of the employee's session, keyed with the shop secret); actions that change what everyone is served need an explicit confirmation, checked on the server; host/pattern purges are limited to the shop's hosts.
Security
- The back-office token is sealed to its URL (
TokenVault, key from_COOKIE_KEY_): editing the URL cannot send it elsewhere; it is never shown. - A deployment token goes only to deployment URLs. Instance URLs are
http(s)://host[:port][/path]; link-local/metadata addresses are refused on the resolved, pinned address; no environment proxy. SetTRIDENT_ALLOWED_API_HOSTSin production. - A client-sent
Surrogate-Capabilityis dropped; the cron token is compared in constant time.
Modules that print per-visitor HTML and are NOT detected
The module keeps a render private for its own rules and for ps_viewedproduct, ps_googleanalytics and PrestaShop's notifications. Other modules that write something about THE VISITOR into the page are not known to it, for example:
- cookie-consent banners rendered server-side (the visitor's choice in the HTML);
- ps_facebook (the Pixel
event_id), psxmarketingwithgoogle, GTM / enhanced-ecommercedataLayermodules that print cart or customer data; - ps_checkout express buttons with a per-cart configuration;
- third-party "recently viewed", A/B testing and geo-IP modules;
- blockwishlist shows empty hearts on cached pages (its state loads per visitor).
Before going live with such a module, check it like cases 58 and 80 do (two
visitors, the second must not see the first one's data), and keep its pages
private if needed (trident_is_private_page-style rules in your own module, or
a pass rule in trident.toml).
Known limits
- PrestaShop saves are not transactional. Before every ObjectModel save the module records a backstop purge due in 120 s (deduplicated); the After hook purges at once. If the process dies between the save and its After hook, the backstop still purges the change.
- Changes made by direct SQL (imports that bypass ObjectModel, custom scripts) are never seen: purge afterwards (Purge → Everything, or tags).
- A shopper's own cold render stays private; they get the pages anonymous visitors cached.
- The release zip should prefix its vendored libraries (Guzzle, the library) as the WooCommerce plugin does with Strauss; not done yet.
- Trident's warmer uses a browser
Accept-Encoding(part of the key): warmed pages are HITs for browsers, not forcurl. - Settings saved through
Configurationare watched only for the keys that change every page (shop enable, catalogue mode, tax display, default currency/country/language, sorting, the main menu, featured products, …); any other setting a theme or module reads needs a manual purge.
Versioning
Versions follow Trident: this module 1.8.x works with Trident 1.8. MAJOR.MINOR moves
with the engine (every Trident X.Y.0 release is also a release of this package,
changed or not); the PATCH number is this package's own. The 1.8 line is published as 1.8.0-beta.1 (the module file itself says 1.8.0: PrestaShop stores module versions in a varchar(8) column). The
admin screens warn when a connected Trident runs another release line.
This repository is a mirror
qoliber/trident-cache-prestashop is developed in the Trident repository together with the
shared library qoliber/trident-php
and the live end-to-end test stacks, and published to
github.com/qoliber/trident-cache-prestashop automatically:
every commit there is a "Sync from trident-cache@…" snapshot. Please open
issues there; pull requests against the mirror cannot be merged, because the
next sync would overwrite them. Releases are the tags of that repository.