symfony/mercure-bundle
Symfony bundle that integrates the Mercure Component into full-stack Symfony applications, making it easy to add real-time push updates over Server-Sent Events. Provides configuration, dependency injection setup, and seamless hub integration for publishing and subscribing to live updates.
symfony/mercure-bundle (now v0.6.1), not a Laravel package. For a Laravel/PHP target stack, the realistic integration surface is the framework-agnostic symfony/mercure client (permitted at ^0.9) wired through a Laravel service provider — the bundle's autowiring, config tree, and FrankenPHP factory logic are Symfony-kernel features that do not carry over. Value is moderate for Laravel, high for Symfony services in a mixed estate.Hub and FrankenPhpHub at runtime via a factory, with url/public_url defaulting to null unconditionally. Good for Docker Compose / 12-factor deployments and enables FrankenPHP worker mode; but a missing env var silently selects the embedded hub instead of failing loudly. Unchanged in v0.6.1 and still a misconfiguration class we must guard against.url to the default aud claim of protocol 1.0 publisher tokens. Practically, this means a hub reached at an internal URL (app → hub) while exposed at a different public URL (browser → hub) no longer requires the resource_identifier workaround to accept publications. This is a direct follow-through on the null-default/hub-factory shift in v0.6.0: the framework now models the split-URL reality most containerised deployments actually have. Architectural fit improves for our standard deployment shape; no design change required.composer require symfony/mercure:^0.9 plus a singleton binding for HubInterface / RemoteHubInterface built from config('mercure.*'). Accept no bundle config tree, no autowiring, no profiler panel — we own the wiring. Estimated 0.5–1 day. v0.6.1 does not change this effort; if anything it removes one config branch (resource_identifier) from the provider.RemoteHubInterface is autowirable — globally for a remote default hub, and per-hub via named arguments (RemoteHubInterface $fooHub). Per-hub publisher / subscriber options allow distinct signing keys.http_client per-hub option lets us bind a dedicated, short-timeout, connection-pooled HTTP client for publishing, isolating Mercure publish latency from the app's default HTTP client. Unchanged by v0.6.1 and still the highest-value runtime lever in the release line.iss / sub / client_id in jwt.claims for jwt.secret hubs. v0.6.1 removes aud from the list of claims we must hand-configure for dual-URL hubs — the default now carries the hub url. Net effect: less bespoke JWT configuration per environment, which is where most of the upgrade cost sat.protocol_version: 1.0 means hub and client must move together. The aud-includes-url behaviour is a 1.0-only change; hubs pinned to 0.x via the bridge flag will not see it. The escape hatch (protocol_version: 0.x, or the ProtocolVersion enum) remains a short-lived bridge.jwt.secret, iss, sub, and client_id must be present in jwt.claims or the container fails to compile. Hard boot-time failure, not a runtime warning. Still the single largest risk in this release line and still mandates a per-environment config audit before deploy.Publisher / PublisherInterface / TraceablePublisher and StaticJwtProvider; consumers must move to Hub / HubInterface / TraceableHub and StaticTokenProvider. Any injected PublisherInterface becomes an unresolvable-service error at boot. v0.6.1 does not restore or alias any of these — the rename remains mandatory for anyone upgrading from v0.4.x.aud Claim Behaviour Change (NEW in v0.6.1): The default aud for protocol 1.0 publisher tokens now contains the hub url. Risks are narrow but real:
aud validation: a hub configured to expect a specific aud (e.g. public URL, or a custom resource_identifier-derived value) may now receive a token with an additional/different aud entry. aud is a list, so permissive hubs are unaffected; strictly-scoped hubs must be checked before rollout.aud in jwt.claims overrides, merges with, or is appended to the new default. Assume merge until verified — that is the safest planning assumption and the one that could surface duplicate or unexpected audience entries.url: null case: with v0.6.0's unconditional null default, confirm the aud claim is omitted or empty rather than containing a null/empty value. An aud of "" would be rejected by most hub validators.
No boot-time failure class here; failure mode is 401 on publish at runtime, which is cheaper to detect but easy to miss in staging if staging shares a single URL for internal and public access.__Secure-mercure_access_token, dropping to mercure_access_token when kernel.debug. Custom JS, reverse proxies, or hub config referencing the old name must update in lockstep, and local HTTP dev will diverge from production naming.MERCURE_URL reliably selects FrankenPHP's built-in hub, so a typo'd variable in a non-FrankenPHP deployment does not throw. Still requires an explicit startup assertion. Note the interaction with v0.6.1: an empty url also affects the aud claim, compounding the diagnosis difficulty of a single misconfigured variable.MercureDataCollector::getPublishers() is removed; internal dashboards, test helpers, or profiler extensions calling it break. Grep before upgrade.default_hub Misconfiguration: Now raises a configuration error instead of a TypeError — a DX improvement that shortens incident diagnosis.publisher / subscriber keys mean compromised publisher credentials can no longer mint subscriber tokens. The v0.6.1 aud default is narrowly audience-tightening in effect (it binds tokens to the hub URL they are sent to) and does not widen the attack surface.protocol_version: 0.x bridge per environment? v0.6.1's aud change only applies to 1.0 hubs — a mixed fleet will behave inconsistently on this claim.aud Validation (NEW): Does any hub enforce a specific aud value, or use resource_identifier to accept publications at a secondary URL? If resource_identifier was our workaround for a dual-URL setup, v0.6.1 lets us retire it — but only after confirming the hub accepts the new default.aud explicitly in jwt.claims, does the v0.6.1 default merge or override? Verify in staging before it reaches a hub with strict audience checks.jwt.secret vs. publisher/subscriber keys? Every jwt.secret hub needs iss / sub / client_id defined before the deploy lands.PublisherInterface, TraceablePublisher, StaticJwtProvider, or getPublishers() references? (Grep the full repo, not just src/.)TraceableHub cross-request memory fix are material — and whether the empty-url behaviour is a feature or a trap.url/public_url config must be correct.http_client for Mercure publishing with a short timeout and its own circuit breaker, so a slow hub cannot exhaust the app's HTTP connection pool?symfony/mercure:^0.9 standalone. Bind HubInterface and RemoteHubInterface as singletons; map config/mercure.php to url, public_url, jwt or publisher/subscriber, http_client, protocol_version. v0.6.1 simplifies this config — no resource_identifier branch needed for dual-URL hubs. No bundle, no autowiring, no profiler.RemoteHubInterface (global default plus per-hub named arguments), per-hub http_client, and the runtime Hub / FrankenPhpHub factory.__Secure- prefixed name in production. Note: the v0.6.1 change is publisher-side only — no frontend impact.Treat this as a three-stage program, not a version bump. v0.6.1 is a patch that folds into stage 3, not a new stage.
aud / resource_identifier usage; grep for removed APIs (PublisherInterface, TraceablePublisher, StaticJwtProvider, mercure.hub.*.jwt_provider, getPublishers()); grep for the old cookie name across JS, proxy, and hub configs; enumerate environments relying on implicit JWT claims.protocol_version) — Where hub upgrades lag, set protocol_version: 0.x, and land the jwt.claims additions (iss/sub/client_id) plus the PublisherInterface → HubInterface rename in the same change. The bridge keeps the upgrade reversible without a full rollback. The v0.6.1 aud default does not apply under the 0.x bridge — so bridge environments carry the old audience behaviour and must be revisited at cut-over.protocol_version pin to take the 1.0 default. On v0.6.1, this is also when the aud-includes-url default takes effect: retire resource_identifier, verify publication auth on every dual-URL environment, and adopt publisher / subscriber keys plus per-hub http_client where warranted.protocol_version: 0.x; both string and ProtocolVersion enum accepted.aud Claim (NEW): Protocol 1.0 publisher tokens now carry the hub url in the default aud. resource_identifier is no longer required for dual-URL hubs. Hubs enforcing a fixed audience must be re-validated. Explicit jwt.claims.aud interaction must be verified in staging — do not assume override.symfony/mercure: ^0.9 permitted — verify the standalone client in Laravel's composer.lock matches what the Symfony services resolve. v0.6.1 is a bundle-side default change, so the two do not need to move in lockstep on this specific item.__Secure-mercure_access_token by default; mercure_access_token when kernel.debug is true. A frontend test matrix item.iss / sub / client_id to jwt.claims for every jwt.secret hub, before deploying. This is the only change that can prevent the container from booting; it is independent of code and can be merged early.PublisherInterface → HubInterface, TraceablePublisher → TraceableHub, StaticJwtProvider → StaticTokenProvider; drop any getPublishers() usage.protocol_version per environment while hubs are upgraded, then remove the pin.aud Behaviour (v0.6.1) — In staging, with a dual-URL hub and protocol 1.0, confirm: (a) publication succeeds without resource_identifier; (b) any explicit aud config merges as expected; (c) a null url does not emit an empty audience. One environment, one hub, before any shared config lands.resource_identifier — Only after step 4 passes, and only on environments whose hubs are on 1.0.__Secure- name in non-debug environments.MERCURE_URL is non-empty unless the runtime is genuinely FrankenPHP; this also protects the aud claim from being built off an unintended URL.http_client (or the equivalent Guzzle client in Laravel) for the publish path.symfony/mercure 0.5 noise from container boot logs and profiler traces).resource_identifier workaround for dual-URL hubs — one fewer per-environment special case to document and drift-check. Counterweight: per-hub http_client, publisher/subscriber keys, and explicit jwt.claims remain, so a documented config contract plus a config-lint step in CI is still worthwhile — now with an added assertion on url correctness, since it feeds the audience claim.iss/sub/client_id in jwt.claims for a jwt.secret hub. Top of the runbook.PublisherInterface → incomplete HubInterface migration. Explicit and greppable.aud mismatch. Causes: hub enforcing a fixed audience that the new default does not satisfy; an explicit jwt.claims.aud colliding with the new default; or url pointing somewhere the hub does not recognise as itself. First check url vs. public_url, then hub-side aud config. Note the inverse of the usual pattern: after v0.6.1 this class of ticket should get rarer, not more common — if it spikes, suspect misconfigured url, not the upgrade.__Secure-mercure_access_token mismatch against the hub's configured cookie. Reproduces inversely in debug (mercure_access_token), so do not trust local success.MERCURE_URL silently selecting the embedded hub. Fixed by the boot-time assertion above.TraceableHub), hub logs (JWT/audience errors), Mercure.js console (connection/subscription). The TraceableHub memory fix from v0.6.0 means profiler output is trustworthy in Octane/FrankenPHP worker mode.default_hub pointing at an undefined hub produces a configuration error rather than a TypeError, shortening triage.http_client remains the most directly useful scaling lever in this release line — a short timeout plus a bounded connection pool on the publish path prevents a degraded hub from consuming app workers' HTTP capacity.url, we can now place the hub behind an internal service address for app traffic while exposing a different public URL to browsers without a bespoke claim workaround per environment. This is directly useful at scale — internal routing, service mesh, and per-region hub endpoints stop leaking into JWT configuration.| Component | Failure Scenario | Mitigation |
|---|---|---|
| Config (JWT) | Container fails to compile — jwt.secret hub missing iss/sub/client_id |
Pre-merge config audit per environment; CI config lint; rollback gate on boot |
| Config (Services) | PublisherInterface / StaticJwtProvider unresolvable after removal |
Complete HubInterface / StaticTokenProvider migration in the same change; grep gate |
| Config (Hub URL) | Empty MERCURE_URL silently selects FrankenPHP embedded hub — now also produces a wrong/absent aud |
Boot-time assertion requiring non-empty URL outside FrankenPHP |
Auth (aud) (v0.6.1) |
Publish returns 401 on a dual-URL hub — audience mismatch after the default aud change, or explicit jwt.claims.aud colliding |
Staging verification on a dual-URL 1.0 hub before shared config lands; confirm merge-vs-override semantics; keep resource_identifier until proven |
Auth (aud) (v0.6.1) |
aud emitted as null/empty when url is unset, rejected by hub validator |
Assert non-empty url at boot; inspect a decoded token in staging |
| Protocol | Client on 1.0 default hitting a 0.x hub | Pin protocol_version: 0.x as a bridge; upgrade hubs before removing the pin. Note: bridged hubs do not get the aud fix |
| Auth Cookie | __Secure-mercure_access_token mismatch between app, hub, and browser |
Align cookie name across JS, proxy, hub config; test over HTTPS, not just kernel.debug |
| Mercure Hub | Hub downtime | Managed hub with SLA or multi-region / multi-instance hub |
| Network | Firewall or legacy proxy blocks HTTP/2 | Documented long-polling fallback |
| Publish Path | Slow hub exhausts app HTTP client | Dedicated http_client with short timeout and bounded pool |
| App Worker | App crashes mid-publish | Queue publishes; retry with backoff |
| Client | Browser tab closed / dropped SSE | Client-side reconnection logic with backoff |
| Observability | TraceableHub message bleed in worker mode (fixed in v0.6.0) |
Verify profiler accuracy post-deploy |
resource_identifier branch for dual-URL hubs.RemoteHubInterface and named-argument per-hub injection shorten this relative to prior releases.aud verification pass. This is not a drop-in patch; it is a scheduled migration with a rollback plan. v0.6.1 adds roughly 30–60 minutes of verification and no additional code change.jwt.claims (iss/sub/client_id), PublisherInterface → HubInterface, StaticJwtProvider → StaticTokenProvider, getPublishers() removal, protocol_version pinning, cookie rename, and aud/resource_identifier retirement.url vs. public_url should be in each — this is now load-bearing for publish auth, not just for embedded-hub selection.url selection rule.__Secure-) vs. kernel.debug (mercure_access_token).How can I help you explore Laravel packages today?