Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mercure Laravel Package

symfony/mercure

Symfony Mercure is the PHP publisher component for the Mercure protocol, letting you push real-time updates from your app to browsers and HTTP clients. It handles JWT authentication and dispatching updates to a Mercure hub.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture Fit

The architectural verdict is unchanged: symfony/mercure remains a thin publisher-side client for the Mercure protocol — serialize an Update, POST to a hub, hub fans out over SSE. No hub, no transport, no client JS. Good fit downstream of committed domain events in a Laravel monolith; poor fit for bidirectional concerns (presence, typing, multiplayer); not a queue, not a broadcast driver, not a replacement for Laravel's dispatcher.

What v0.9.0 changes is the framing of the protocol risk. The client now defaults to Mercure protocol 1.0, tracking the stable hub 1.0 line. That materially reduces the "wire format is still moving" concern that previously argued for a conservative posture — the protocol is now the stable thing, and the client is tracking it. But the change lands as a coordinated three-party upgrade (PHP client, hub, browser/EventSource URL), not an independent library bump. The differentiator and the cost are the same as before: HTTP/SSE + standardized protocol, paid for with a second externally-operated service that now must also be version-aligned with the client.

One architecture-level clarification surfaced by this release: the FrankenPHP in-process publish path (mercure_publish()) is only available in worker-mode request context, not in CLI. Since our recommended publish path is a queued job (CLI queue:work), the "remove the network hop" optimization is not compatible with the queue-first design unless queue workers themselves run inside FrankenPHP worker mode. Treat those two decisions as mutually exclusive until proven otherwise.

Integration Feasibility

Still feasible, still not a Laravel package — no service provider, no facade, no publishable config. Hand-wiring set expands by three explicit config decisions:

  • Bind HubInterface as a singleton from hub URL + TokenProviderInterface, and set protocolVersion explicitly rather than relying on the new V1 default. Pin the value in config/mercure.php so an upgrade can't silently flip the wire protocol.
  • Token minting now has two viable shapes. LcobucciFactory (previously recommended) now emits claims under authorization_details and requires iss, aud, sub, and client_id. Alternatively WebTokenFactory now supports the fully-specified Ed25519 algorithm (requires web-token/jwt-library ^4.3, which deprecates polymorphic EdDSA). Either way, our token config is rewritten, not reused.
  • Authorization::setCookie() now issues __Secure-mercure_access_token instead of the legacy name. The __Secure- prefix means Secure attribute + HTTPS is mandatory — local HTTP development will not round-trip this cookie.
  • Frontend subscribe URLs: the Twig helper's query parameter is now match. We don't use Twig, so our hand-written EventSource URLs must be validated against the 1.0 hub's expected parameter — this is the most likely place for a silent, hard-to-diagnose break.

Dependency hygiene is unchanged (symfony/http-client, lcobucci/jwt ^5 coexist cleanly); optionally pulling web-token/jwt-library ^4.3 adds a package to the review surface only if we choose the Ed25519 path.

Technical Risk

  • 0.9.0 is a breaking release on a 0.x line — exactly the pattern the previous assessment flagged. It breaks in three independent places: protocol default, cookie name, JWT claim shape. Pin exactly; budget the upgrade as a project, not a bump.
  • Silent protocol mismatch. A V1-default client against a 0.x hub (or an un-updated hub in a second environment) fails wholesale with no application-level trace. Mitigation: explicit ProtocolVersion config value + hub version pinned in IaC + a smoke test in CI.
  • Cookie rename is a lockstep dependency. Client library, Authorization::setCookie(), hub config, and any frontend cookie-reading code must move together. A half-upgrade yields 401s that look identical to "real-time is broken."
  • JWT claim contract tightened. authorization_details requires iss/aud/sub/client_id; missing any is a hard failure at mint time. This fails fast (good) but invalidates existing token config and any runbook that documents the old claim shape.
  • FrankenPHP publish path throws under CLI. FrankenPhpHub::publish() now throws an explicit Symfony\Component\Mercure\Exception\RuntimeException instead of "Call to undefined function." Better diagnostics, same constraint: do not select FrankenPhpHub for a queue-worker publish path. Either confirm queue workers run in FrankenPHP worker mode, or use the HTTP Hub. Note this is a new hard failure where previously it was a confusing one — worth a deploy-time check.
  • Hub remains the system of record for delivery. Unchanged: SSE is a latency optimization, not a guarantee; bounded history; mandatory REST resync on reconnect.
  • Auth boundary mismatch (Laravel guards don't apply to the hub) — unchanged, and now compounded by the __Secure- cookie requiring same-site HTTPS topology to be settled before the spike, not during it.
  • EdDSA deprecation is a forward-looking item: if we adopt WebTokenFactory, plan for Ed25519 rather than inheriting EdDSA.
  • Ecosystem signal unchanged in substance — an officially maintained Symfony subpackage, low continuity risk, thin community troubleshooting corpus. The 0.9.0 release is evidence of active maintenance.

Key Questions

  1. What feature are we buying latency for — and would Reverb (first-party, already in our stack) satisfy it with less new infrastructure?
  2. Which hub version are we pinning, and is it ≥1.0? If not, who signs off on ProtocolVersion::Legacy, and when does it get upgraded?
  3. Who owns the hub (self-hosted vs. managed), what is its SLA, and what is our documented degraded mode when it is down?
  4. Token contract: do we stay on LcobucciFactory (and can we supply iss/aud/sub/client_id from existing config), or move to WebTokenFactory + Ed25519 and take on web-token/jwt-library ^4.3?
  5. Topic taxonomy: does it map 1:1 to authorizable resources, and are per-topic publish claims enforced in review rather than wildcards?
  6. Are private updates required? If so, app and hub are same-site over HTTPS (required by the __Secure- cookie), or we accept token-in-query-string.
  7. Does any part of our publish path run in FrankenPHP worker mode? If not, FrankenPhpHub is off the table and the network hop stays.
  8. What is the resync contract on reconnect (endpoint, cursor)?

Integration Approach

Stack Fit

Laravel 11/12 on PHP 8.2–8.4; symfony/mercure ^0.9 with symfony/http-client and lcobucci/jwt ^5, protocolVersion set explicitly in config. Seam unchanged: domain event → queued listener/job → HubInterface::publish(); observers never call the hub directly. Frontend consumes EventSource against the hub URL — subscribe parameter now match; our URLs must be verified against the 1.0 hub. Use MockHub for publish-path tests so unit tests don't need a hub.

Migration Path

No longer purely additive at the wire level. Adding Mercure to a greenfield area is still additive versus polling/Echo, because both hang off the same domain events — but the v0.9.0 client is a coordinated migration: client, hub, token claims, cookie name, and frontend URLs move as one unit. Start with a single read-only feature (notification badge, order status, import progress) where staleness on failure is tolerable. Do not begin with collaborative editing or anything needing ordering guarantees.

Compatibility

Five gates now, up from three:

  1. Hub resolves a PSR-18 client — install symfony/http-client to avoid discovery surprises.
  2. Egress through corporate proxy: HTTP_PROXY/VERIFY conventions don't automatically apply to a self-constructed symfony/http-client instance.
  3. Protocol alignment: hub ≥1.0 with client V1, or both on Legacy. Pin hub image tag and client version together; assert in CI.
  4. Cookie viability: __Secure-mercure_access_token requires HTTPS + Secure and same-site topology. Local dev needs a TLS-terminating proxy or the Legacy path.
  5. JWT claims: iss/aud/sub/client_id present for LcobucciFactory; token serialization via authorization_details understood by the hub version we pin. Verify on staging before pilot.

Sequencing

  1. Decide hub version + protocol first (half day): 1.0/V1 is the target; document the fallback if infrastructure can't move yet.
  2. Spike (1 week): local hub via Docker over HTTPS, publish from a queued job, consume in a throwaway page using match. This validates cookie/CORS/protocol behavior on our actual domain topology — the three things most likely to break.
  3. Wire-up: singleton binding, explicit protocolVersion, per-topic JWT claims under the new contract, config in config/mercure.php, feature flag off by default. Confirm Hub vs FrankenPhpHub against the queue-worker runtime.
  4. Pilot (one feature): queued publish, metrics, documented fallback to polling on hub failure.
  5. Hub productionization: TLS, CORS allowlist, key rotation runbook, health checks, alerting on 401 rate and protocol-version mismatch.
  6. Broaden: only after publish-latency and failure-rate dashboards exist.
  7. Decide on Reverb: if bidirectional needs surface during the pilot, converge on one real-time stack rather than operating two.

Operational Impact

Maintenance

Low code maintenance, moderate — and now more tightly coupled — infrastructure maintenance. The upgrade story is the change: client, hub, and frontend must be versioned as a set. Maintain a compatibility matrix (client version ↔ protocol version ↔ hub image tag ↔ cookie name ↔ claim shape) and treat hub upgrades as rehearsed changes with a rollback image tag. Token authority config now carries four required registered claims, so config drift is a functional break, not a cosmetic one.

Support

No vendor to call for a self-hosted hub; support is upstream maintainers plus docs. In-house, name an owner for the JWT authority (who can mint publish claims) — a privileged capability, treated like a signing key, with rotation and least-privilege topic scoping. Add a second named owner for the token claim contract (iss/aud/sub/client_id, authorization_details), since that is now the most likely cause of a total real-time outage after any upgrade.

Scaling

Unchanged. Publish volume scales as ordinary outbound HTTP from queue workers; the bound is worker concurrency, not the component. Hub scales horizontally subject to the persistence/transport options in the pinned version. Topic cardinality remains the real lever: one topic per user/tenant keeps fan-out cheap; a global topic with client-side filtering will not scale and leaks data if private is misused.

Failure Modes

  • Protocol-version mismatch (V1 client, pre-1.0 hub) → 100% failure, no app-level trace. Detect via explicit config + CI assertion + a synthetic publish in health checks.
  • Cookie-name mismatch / non-HTTPS origin → private updates silently 401. Alert on 401 rate and assert Secure cookie behavior in staging.
  • Missing JWT claim (iss/aud/sub/client_id) → tokens rejected hub-side; fails at mint time, which is the good case — make sure the error isn't swallowed by the queue.
  • FrankenPhpHub used from CLI → clear Symfony\Component\Mercure\Exception\RuntimeException per this release. Prefer the HTTP Hub for queue workers; if the exception appears in production logs, the runtime assumption was wrong.
  • Hub unavailable → queued job retries with backoff; after N attempts log and drop (SSE best-effort, REST is truth). Never let hub failure fail the user's write.
  • Synchronous publish → hub latency in the request path. Avoid entirely.
  • Silent client disconnect → stale UI. Mandatory resync-on-reconnect.
  • Wildcard publish scope in prod → any compromised instance can spoof any topic. Enforce per-topic claims in review.
  • Duplicate broadcasts on retry → deterministic Update ids plus client dedupe.

Ramp-Up

Slightly higher than before, concentrated on the token story rather than the protocol. A mid-level engineer still publishes their first update in an afternoon; the learning curve is now topic design plus the JWT claim contract and cookie model (1–2 weeks to fluency, add a few days for the new claims). Frontend reconnect/resync is where teams underestimate effort. MockHub keeps unit tests cheap. Plan a short internal write-up covering topic taxonomy, JWT scoping, and the client↔hub version matrix before the second team adopts it, or you will get divergent topic and token conventions that are expensive to unify later.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource