Trident HTTP cache for any Symfony application (6.4, 7.x and 8.x; PHP
8.2+, 8.4+ with Symfony 8; Doctrine ORM with DoctrineBundle 2 or 3). The Sylius bundle (qoliber/trident-cache-sylius) builds on it.
What it does:
- Shares pages safely. For the routes you list, the response policy decides
after rendering whether Trident may share the page:
- shared (
public, max-age=0, s-maxage=3600, stale-while-revalidate=86400, plus boundedX-Cache-Tags) only for a GET/HEAD 200 withoutSet-Cookie; - the render must not have read the visitor's session (the session was not started, or it is new and empty);
- the request carries no bypass cookie, and no platform voter refuses.
- Everything else on those routes is
private, no-store.
- shared (
- Durable purges from Doctrine. Each change's tags (from your
EntityTagResolverservices) are recorded as outbox rows in the same transaction as the change:- a rolled-back save leaves nothing;
- a committed one can never lose its purge;
- rows are delivered at the end of the request and removed only when Trident acknowledged them;
- otherwise they are retried with backoff;
- each purge is delivered once more
redeliver_afterseconds later (default 10,0= never). Trident already refuses to store a page whose fetch began before a purge of its tags; the second delivery also covers a render that read the old data from a replica or an application cache.
- FOSHttpCache (optional): the
TridentProxyClient(tags, clear) uses the same outbox. Withfos_proxy_client: auto(default) it becomes FOSHttpCache's client only when the application configured none — an existing Varnish or Symfony client is never replaced;trueforces it,falsenever. - Several Trident instances, each purged and retried on its own.
- Admin screens' logic:
qoliber/trident-php'sAdmin\AdminService(shared with Shopware): dashboard, purge, cached pages, tags, coverage, warmer, launch, reflect, denoisers, bans, backends, discovery, events. Pattern purges and bans are limited to the shop's own hosts. A platform renders the screens and provides aShopAdapter.
Requires Doctrine ORM and DBAL (MySQL/MariaDB, PostgreSQL and SQLite are tested). FOSHttpCacheBundle, DoctrineMigrationsBundle and symfony/scheduler are optional; the bundle boots in a plain kernel without them.
Instances come from the environment first (they describe infrastructure, and differ between staging and production while the database is copied):
TRIDENT_INSTANCES='{"edge-1":{"api_url":"http://10.0.0.11:9301","api_token":"…"},"edge-2":{"api_url":"http://10.0.0.12:9301"}}'
TRIDENT_API_TOKEN=… # the token of an instance that names none
TRIDENT_ALLOWED_API_HOSTS=10.0.0.11,10.0.0.12 # optional allowlist (host or host:port, exact)
TRIDENT_PURGE_MODE=soft # soft (default) or hard
TRIDENT_TAG_PREFIX=shop1_ # several shops on one Trident
TRIDENT_DEBUG_HEADERS=1 # X-Trident-Decision on responses
TRIDENT_TOKEN_KEY=… # optional: key for the sealed admin token (default: kernel.secret)Without any environment instance, the admin screen's single URL and token are used.
# config/packages/trident.yaml
trident:
cacheable_routes: [app_home, app_product_show, app_category]
bypass_cookies: [my_login_marker]
tags:
identity_prefixes: [product_]
overflow_families: { '/^product_\d+$/': product_overflow }Run the migration (doctrine:migrations:migrate): it creates
trident_purge_outbox and trident_settings. Without DoctrineMigrationsBundle,
doctrine:schema:update creates them from the entity mapping, which the bundle
adds to the default entity manager (the short orm.mappings form or
entity_managers:).
If the instance comes from the admin screen and the settings table cannot be
READ (the database is down, not "not migrated yet"), the failure is logged as
critical, purges are still recorded for the default instance (delivered once
the settings read again), and trident:purge:status reports it — a save never
fails because of Trident. Environment instances do not depend on that read.
trident:
redeliver_after: 10 # seconds; 0 = deliver each purge once
fos_proxy_client: auto # auto | true | falseImplement Qoliber\TridentSymfony\Tags\EntityTagResolver for your entities
(autoconfigured), and add tags to pages with ResponseTags::add() or
FOSHttpCache's response tagger.
An application with a login needs Trident to recognise a signed-in visitor before it looks up a page. Otherwise that visitor gets the page an anonymous render put in the cache, with "Sign in" where their name should be. With symfony/security-bundle:
trident:
login_marker: trident_auth- The bundle sets
trident_auth=1(HttpOnly, SameSite=Lax, a session cookie) while a user is signed in, and clears it once nobody is. - A request carrying it is a bypass cookie of the response policy.
- A render for a signed-in user is refused for sharing even when the authentication read no session (remember-me, a stateless token).
List the same cookie in Trident's configuration so the request passes to the application:
[cache.key]
bypass_cookies = ["trident_auth"]trident:purge:drain— deliver due rows (cron every minute). With symfony/scheduler,messenger:consume scheduler_tridentdoes the same.--forceretries failed rows despite their backoff;--nowdelivers every row at once, whatever its due time (second deliveries included) — after an incident, or in tests.trident:purge:status— instances, pending rows, errors; exit 1 when a purge has been pending over 15 minutes (--stale-after=<seconds>to change it), a configuration error exists, or rows are owed to an instance that is no longer configured. A purge owed for a few seconds is normal.trident:purge:forget <instance>— drop the rows of a removed instance.trident:purge [tags…] [--all]— purge by hand, durably.
- The admin-stored token is sealed to its URL (
Security\TokenVault: XChaCha20-Poly1305, the normalised URL as associated data, HKDF ofTRIDENT_TOKEN_KEYor the kernel secret).- It is never returned to the admin screen.
- Pointing the URL elsewhere makes it unusable until re-entered.
- Token routing: the environment token goes only to environment URLs.
- API URLs must be
http(s)://host[:port][/path]. - Refused targets: link-local and cloud-metadata addresses are refused on the
resolved address, which is pinned for the connection (
Http\NetworkGuard). - Screens show only answers from a verified Trident admin API, and error bodies are clipped.
- A client's
Surrogate-Capabilityheader is removed before the application sees it.
Versions follow Trident: this bundle 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 admin screens warn when a connected Trident runs another release line.
qoliber/trident-symfony 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-symfony 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.