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

Symfony Nats Messenger Laravel Package

idct/symfony-nats-messenger

A Symfony Messenger transport for NATS that lets PHP applications publish and consume messages over NATS subjects with native Symfony integration, configuration, and worker support for event-driven architectures.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

1. Messaging backbone selection (build vs. buy vs. keep) This package is the deciding artifact in a "NATS JetStream vs. RabbitMQ vs. Redis Streams vs. SQS/EventBridge" evaluation for a PHP estate. It makes NATS a first-class, configurable option for Symfony Messenger rather than a bespoke integration project — DSN-driven, bundle-registered, no glue code. Concrete decisions it unblocks:

  • Consolidate two systems into one: NATS covers queue semantics (work queue via pull consumers) and streaming/persistence (JetStream), plus request/reply. That's a headcount and runbook reduction, not just a vendor swap.
  • Keep durable messages without a separate broker cluster — stream_storage: file, stream_replicas, stream_max_bytes/messages/max_age give you retention policy as configuration, so capacity planning becomes a config review instead of an infra project.
  • Delay/schedule without bolting on a scheduler service: scheduled_messages + Symfony DelayStamp → NATS schedule headers removes a cron/Lambda/Redis-ZSET component from the architecture diagram.

2. Delivery-guarantee and idempotency posture This is the highest-leverage PM decision here, because it changes product-visible behaviour:

  • retry_handler: symfony (TERM, default) keeps retry/DLQ logic inside Symfony Messenger → your existing retry strategy, failure transport, and messenger:failed workflow stay authoritative. Choose this to preserve current on-call tooling.
  • retry_handler: nats (NAK) pushes redelivery to the broker → lower app complexity, but your DLQ and "poison message" story now lives in NATS. This is an operations ownership decision, not a library setting; make it explicitly.
  • deduplicate + DeduplicationIdStamp (Nats-Msg-Id) gives broker-side duplicate suppression in a defined window. This is the lever for "do we need consumer-side idempotency keys?" — often the answer becomes "publisher-side dedupe ID plus a cheap idempotency check," which is materially less work than a full exactly-once design.
  • Scope the honest limit: this is at-least-once. Anything money-, inventory-, or notification-critical still needs idempotent handlers. Put that in the acceptance criteria, not the appendix.
  • As of 5.7, a 409 Server Shutdown or 409 Leadership Change on a pull is an empty batch, not a missing-consumer error. That turns the routine "broker node restarted / JetStream leader moved" event from a worker crash into a no-op poll — a direct reduction in pages, and a reason the HA story is credible rather than aspirational. Note the remaining edge: an ack_sync acknowledgement that lands inside an outage still fails after request_timeout.

3. Operational footprint and HA decisions

  • stream_replicas lets you decide "single node is acceptable for this workload" per stream instead of per cluster — useful for tiering: replicas=1 for analytics/low-value events, replicas=3 for order events.
  • stream_storage: memory vs file is a per-stream durability decision you can now make in a PR rather than a platform ticket.
  • New in 5.7 — reconnect: true (default false) changes the failure mode of a worker from "process exits on connection loss" to "process polls through the outage and resumes." max_reconnect_attempts (default: client default of 10, ≈109 s against a refusing server) is now the knob that expresses "how long a deploy or broker restart may last before we accept worker restarts." That is a product availability setting you can put in a config review: it is the difference between a rolling NATS upgrade being invisible to the queue and being an incident. With auto_setup, provisioning is re-verified after reconnect and a publish that hits a stream-less server gets one setup() plus one replay of the same frame — worth deciding whether you want that self-healing or prefer the failure.
  • Cost to price: a reconnect that gives up discards buffered acknowledgements (logged at error level) and the messages reappear after ack_wait — i.e. expect duplicate deliveries around long outages. That is consistent with the at-least-once posture, but it belongs in the runbook.

4. Throughput/latency trade-off, expressed as config batching + max_batch_timeout and consumer_strategies (shared vs. independent) let you tune per-transport. Practical PM framing: batch the analytics and email pipelines for cost; keep batching=1 for user-facing, latency-sensitive paths. Because Messenger supports multiple transports, you can run mixed strategies in one app and measure. The reconnect bounds are expressed in the same currency — request_timeout per request, max_batch_timeout + 1 s per pull — so latency budgets and outage budgets are tuned together rather than by folklore.

5. Migration and coexistence Multiple transports in Symfony Messenger means you can stand NATS up beside the incumbent broker and canary one queue, one consumer group at a time — reversible by removing one DSN line. This converts a "big bang broker migration" into a roadmap of small, individually shippable slices. Reconnect mode improves this specifically: during the overlap period a flaky incumbent or a NATS node bounce no longer forces a worker restart that muddies your comparison data.

6. TLS/mTLS and auth as a compliance gate, not a feature nats-jetstream+tls://, client certificates, JWT/NKey/token auth mean security review isn't a blocker. If you're in a regulated environment, the presence of mTLS + per-stream retention config is often what turns "no" into "yes with conditions." 5.7's PSR-3 wiring (connection lifecycle at info, errors at error, under the messenger channel) also means connection-level events land in the existing audit/log pipeline instead of a library-specific sink — one less exemption to argue for.

7. Test/quality posture affecting risk appetite The 5.7 release raises this materially: 669 unit tests, 99.78% statement coverage, and mutation testing at 96.9% (1008/1040 mutants killed) with the 32 survivors individually documented. Mutation score is the signal a coverage gate can't give you — it says the assertions bite on the risky branches. The reconnect work itself ships with a dedicated test that drives the real client over a scripted connection (silent, failed write, server-side close, held and refused dials) plus Behat scenarios that restart the server under a live worker and under a sender. For a package of this size that is a disproportionately strong engineering signal, and it is now a specific signal about the newly added code path rather than a general one. That justifies a pilot rather than a blanket rejection on popularity.

8. Where it does not help — a decision you should make early This is a Symfony Messenger transport, not a Laravel queue driver. If the product is genuinely Laravel-first, adopting it means adopting symfony/messenger as your bus, or writing a bridge — you would forfeit Laravel's queue ergonomics (Horizon, failed_jobs, queue:work semantics, job middleware). Make that call explicitly: "we are standardising on Symfony Messenger inside Laravel" is a legitimate architectural decision, but it is a decision, not an install. Related: observability is now logging-complete but still not metrics-complete — 5.7 hands the client your logger, which is not the same as tracing or dashboards.

When to Consider This Package

Adopt (pilot or direct) when:

  • You are on Symfony 7.2/8.0 with PHP 8.2+ and already use (or are committed to) Symfony Messenger as the bus.
  • You already run, or have decided to run, NATS JetStream — this package is then the cheapest correct path; the alternative is hand-rolling a transport.
  • You need durable, replayable messaging with retention limits, delayed delivery, and HA without adding RabbitMQ/Kafka operational surface.
  • You need workers to survive broker restarts and rolling upgrades without restart storms — reconnect: true plus the 409-as-empty-batch behaviour is the feature that makes this a viable production posture rather than a demo one.
  • You need mTLS / JWT / NKey auth for a security review.
  • You want connection lifecycle and error events in your existing PSR-3/Monolog pipeline rather than a separate collector. (New direct dependency: psr/log ^3.0.)
  • Your team can tolerate one small transport library with a single maintainer and would be comfortable vendoring/forking it (MIT license makes this free).
  • The message volume/value justifies per-stream durability tuning rather than a flat managed-queue tier.

Look elsewhere (or plan for significant internal work) when:

  • The stack is Laravel-first and you want to stay idiomatic. Prefer a native Laravel queue driver (SQS/Redis) or a Laravel-specific NATS package. Don't adopt Symfony Messenger purely to reach NATS unless the bus is a deliberate strategic choice.
  • You need vendor-backed support and an SLA. 27 stars, 0 dependents, single-maintainer bus factor. There is no commercial backstop; if this transport breaks during an incident, you are the maintainer.
  • You are on Symfony ≤7.1 or PHP <8.2. The ^7.2 / ^8.0 constraint forces a framework upgrade — price that in; it will dominate the timeline.
  • You need first-class observability out of the box. Logging is now covered; metrics and tracing are still not. Budget OpenTelemetry or Messenger middleware instrumentation as a real work item. This bullet narrowed in 5.7 but did not close.
  • Exactly-once or strict ordering is a hard requirement. At-least-once plus dedupe windows, with shared consumers, does not give you either for free. Reconnect mode adds a second duplicate window (buffered acks discarded after a failed recovery, redelivered after ack_wait) — still correct, still not exactly-once. If ordering is per-entity, design partition keys or pick a different tool.
  • Your team has no NATS operational experience and no appetite to gain it. The library lowers the integration cost; it does not lower the operate a NATS cluster cost. Reconnect mode reduces the blast radius of an outage; it does not remove the outage.
  • You rely on PhpSerializer fallback across rolling deploys. The default is igbinary with PHP-serialize fallback; serialized payload compatibility between old and new app versions during a deploy needs an explicit plan (versioned message contracts), or you'll ship a deploy-window incident.
  • You need acknowledgement guarantees strictly stronger than at-least-once and cannot tolerate the known edge. A fire-and-forget ack whose write fails while the client still reports the connection open can block for a whole attempt loop (upstream client behaviour, tracked as ideaconnect/php-nats-jetstream-client#244). If that latency is unacceptable on your critical path, use ack_sync and accept request_timeout as the bound, or look elsewhere.

Practical pilot gate before committing roadmap: one non-critical queue, one sprint, success criteria = publish validation surfaces failures; NAK/TERM semantics behave as documented; worker memory is flat over 24h; one deliberately poisoned message reaches a DLQ via the chosen retry handler; and a docker stop / rolling restart of the broker with reconnect: true leaves the worker alive and consuming, with max_reconnect_attempts sized against your longest realistic outage (the default 10 attempts ≈109 s). If those five pass, adopt; if worker memory or reconnect behaviour is shaky at your traffic shape, stop and reconsider.

How to Pitch It (Stakeholders)

To executives (one paragraph): "We can run our background job processing on NATS JetStream using a small MIT-licensed, well-tested Symfony transport instead of standing up another broker cluster. It gives us durable messaging, delayed jobs, HA and mTLS through configuration, which removes a component from our architecture and a scheduler from our roadmap. The latest release closes the operational objection we raised: workers now ride out a broker restart or rolling upgrade instead of exiting, and connection events land in our existing logging. The commercial risk remains that the library is small and single-maintainer — mitigated by MIT licensing (we can fork and own it), a test suite that includes mutation testing at ~97% and scripted-failure tests of the exact reconnect path, and a two-week pilot on one non-critical queue before any commitment. Expect the real cost to be a Symfony/PHP version alignment and metrics/tracing instrumentation, not the integration itself."

To engineering (the honest version):

  • "It's a Messenger transport + bundle, not a framework. Install is composer require, Flex registers the factory and igbinary serializer, and you configure it entirely by DSN and options. You'll be productive in an afternoon."
  • "The parts that usually hurt are already handled: stream create/update with subject merging, publish-response validation, reconnect-on-idle via ping_after_idle, explicit connection_timeout / request_timeout, and --keepalive in-progress acks queued from the signal handler. Those are the details you'd otherwise discover in production."
  • "5.7 is a minor release: reconnect is off by default and there is nothing to change on upgrade. Two behaviour changes apply either way — a 409 Server Shutdown / 409 Leadership Change pull now returns an empty batch instead of erroring (and auto_setup stops re-provisioning against a dying server), and if you have a logger service the bundle hands it to the NATS client. New direct dependency: psr/log ^3.0."
  • "Turn reconnect: true on deliberately and size max_reconnect_attempts against your real outage budget. Per-operation bounds are documented: request_timeout for a request, max_batch_timeout + 1 s for a pull. Know the trade: a recovery that gives up drops buffered acknowledgements (logged at error level) and the messages come back after ack_wait — so a long outage buys you duplicates, not loss. ack_sync inside an outage still fails at request_timeout; that's the operation that doesn't get better."
  • "Constraints you must respect: PHP 8.2+, Symfony ^7.2/^8.0 only; one service id per class name (Symfony wraps a transport's serializer for signing per id, so a second id double-wraps it); amphp coroutine client underneath, so validate worker memory and event-loop behaviour under load in the pilot."
  • "Choose retry_handler deliberately: symfony/TERM keeps retries and the failure transport inside Messenger; nats/NAK moves them to the broker. Don't leave it at default by accident — it defines who owns the DLQ."
  • "Quality signals worth trusting, restated for this release: 669 unit tests, 99.78% statement coverage, 96.9% mutation score with all 32 survivors justified in docs/TESTS.md, PHPUnit on 8.2–8.5, a reconnect test that drives the real client over a scripted silent/failing/closing/refusing connection, and Behat features against a real NATS for NAK, TERM, batching, delays, TLS, mTLS, and server restarts under a live worker and sender. That's the test matrix you'd have asked for, aimed at the new code."
  • "One gap still to budget: no metrics or tracing hooks — logging is now covered, dashboards are not. Message-serialization compatibility across rolling deploys still needs a versioning convention. Both are tractable, neither is free."

Framing for the buy-vs-build conversation: building this transport internally is roughly "DSN parsing, stream lifecycle, consumer modes, retry semantics, TLS/mTLS, delayed messages, dedupe headers, reconnect-with-bounded-recovery semantics, plus the test matrix above." The reconnect work alone — per-operation budgets, re-verifying setup after recovery, replaying a publish exactly once, and deciding what to do with buffered acks — is the kind of thing that takes a quarter to get subtly wrong. Buying it (at the cost of a fork risk) is clearly the better trade — provided the pilot clears the five gates and someone is named as the internal owner of the fork.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle