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
Agents
idct/symfony-nats-messenger
claude-code
cursor
messenger
nats-jetstream
symfony-bundle
transport
igbinary-serializer
retry-handler
behat

Symfony Messenger transport for NATS JetStream. PHP 8.2+, Symfony ^7.2 / ^8.0. Uses idct/php-nats-jetstream-client (amphp-based coroutines). Ships IDCTNatsMessengerBundle (Flex auto-enables it), which registers the transport factory and the igbinary serializer - no manual wiring needed. Each...

Install
php artisan boost:add-skill idct/symfony-nats-messenger

Save this content to: AGENTS.md

# Project Guide

## Overview

Symfony Messenger transport for NATS JetStream. PHP 8.2+, Symfony ^7.2 / ^8.0.
Uses `idct/php-nats-jetstream-client` (amphp-based coroutines).
Ships `IDCTNatsMessengerBundle` (Flex auto-enables it), which registers the transport factory
and the igbinary serializer - no manual wiring needed. Each service has one id, its class name:
Symfony wraps a transport's serializer for signing once per id, so a second id would wrap it twice.

## Project Memory

Read [docs/PROJECT_MEMORY.md](docs/PROJECT_MEMORY.md) when starting work for maintainer preferences,
the local client-library relationship, behavioral pitfalls, and a dated issue/environment snapshot.
Refresh changeable facts from source and GitHub before relying on them; update the memory when new
findings affect future work. This guide remains the project instruction source.

## Installation

```bash
composer require idct/symfony-nats-messenger
```

Development setup:
```bash
composer install
composer test              # PHPStan + fast unit tests (run after every change)
```

## Application Structure

```
src/
├── IDCTNatsMessengerBundle.php                # Symfony bundle - auto-registers factory + serializer services
├── NatsTransport.php                          # TransportInterface, MessageCountAwareInterface, SetupableTransportInterface
├── NatsTransportFactory.php                   # TransportFactoryInterface - DSN parsing, transport creation
├── TypeCoercion.php                           # Safe scalar type coercion helpers (final, static)
├── Options/
│   ├── NatsTransportConfiguration.php         # Immutable readonly config value object
│   ├── NatsTransportConfigurationBuilder.php  # DSN + options parsing, validation, builds Configuration
│   ├── RetryHandler.php                       # Enum: SYMFONY (TERM) | NATS (NAK)
│   └── TransportOption.php                    # Enum: all configurable option keys
└── Serializer/
    ├── AbstractEnveloperSerializer.php         # Base serializer with encode/decode envelope wrapping
    └── IgbinarySerializer.php                  # Default serializer (falls back to PhpSerializer)
```

```
tests/
├── bootstrap.php
├── unit/                                       # PHPUnit 11 to 13 - fast, no NATS required
│   ├── NatsTransportTest.php                   # Transport core: setup, send, get, ack, reject, retry
│   ├── NatsTransportFactoryTest.php            # Factory: DSN scheme detection, transport creation
│   ├── Options/
│   │   ├── NatsTransportConfigurationBuilderTest.php  # Builder validation, DSN parsing, option merging
│   │   └── NatsTransportConfigurationTest.php         # Config accessors, type coercion
│   └── Serializer/
│       ├── AbstractEnveloperSerializerTest.php  # Encode/decode round-trips, error cases
│       └── IgbinarySerializerTest.php           # Igbinary serialize/deserialize
├── functional/                                 # Behat - requires running NATS server
│   ├── features/
│   │   ├── nats_setup.feature                  # Stream creation, update, multi-subject, message flow
│   │   ├── nats_stream_limits.feature          # max_bytes, max_msgs, max_msgs_per_subject
│   │   ├── nats_consumer.feature               # Custom consumer names
│   │   ├── nats_batching.feature               # Batch consumption
│   │   ├── nats_nak.feature                    # NAK retry handler
│   │   ├── nats_term.feature                   # TERM failure handler
│   │   ├── nats_delayed.feature                # Scheduled/delayed messages
│   │   ├── nats_tls.feature                    # TLS connections
│   │   └── nats_mtls.feature                   # mTLS with client certificates
│   └── tests/Behat/NatsSetupContext.php        # All step definitions
└── nats/                                       # Docker Compose + NATS configs for testing
    ├── docker-compose.yaml
    ├── nats.conf / nats-tls.conf / nats-mtls.conf
    └── certs/                                  # generate.sh for the test TLS certificates (generated, not committed)
```

## Features

- **DSN schemes**: `nats-jetstream://` and `nats-jetstream+tls://`
- **Stream management**: create, update existing (subject merging, config preservation)
- **Retention policies**: `stream_max_age`, `stream_max_bytes`, `stream_max_messages`, `stream_max_messages_per_subject`
- **Storage backends**: `file` or `memory` via `stream_storage`
- **Replication**: `stream_replicas` for HA
- **Consumer strategies**: shared (batching=1) or independent consumers
- **Batching**: configurable `batching` count and `max_batch_timeout`
- **Retry handling**: `retry_handler` - `symfony` (TERM, default) or `nats` (NAK)
- **Scheduled messages**: `scheduled_messages` flag, `DelayStamp` → NATS schedule headers
- **TLS/mTLS**: full TLS config options including client certificates
- **Authentication**: username/password, token, JWT, NKey
- **Serialization**: igbinary (default with fallback) or custom via `AbstractEnveloperSerializer`
- **Publish validation**: JetStream publish responses are parsed and validated
- **Connection checks**: a lost connection is dialled again; `ping_after_idle` PINGs a connection that sat idle, or that an operation failed on, before reuse, and before the NAK or TERM of a failed delivery
- **Timeouts**: `connection_timeout` (the dial), `request_timeout` (replies from the server), `max_batch_timeout` (pulls)
- **Duplicate protection**: the `deduplicate` option and `DeduplicationIdStamp`, sent as `Nats-Msg-Id`; with `reconnect: true`, `resend_on_reconnect` sends such a publish again on the recovered connection
- **Keepalive**: `messenger:consume --keepalive`, an in-progress acknowledgement queued from the signal handler

## Testing

### After every code change
```bash
composer test                    # PHPStan (level max) + fast unit tests
```

### Unit tests with coverage
```bash
composer test:unit               # Coverage report → clover.xml + coverage/
composer coverage:check          # Verify ≥ 90% statement coverage
```

### Functional tests (require Docker)
```bash
composer test:functional:setup   # Install Behat dependencies (first time)
composer nats:start              # Generate the test TLS certs, then start NATS in Docker
composer test:functional         # Run Behat scenarios
composer examples                # Run examples/*.php against the test NATS (needs nats:start)
composer nats:stop               # Stop NATS
```

### Key testing patterns
- Unit tests use `TestableNatsTransport` (overrides `connect()` to skip live NATS)
- `RuntimeTestableNatsTransport` allows injecting `JetStreamContext` and `NatsClient` test doubles via setters
- The unit suite runs on PHPUnit 11 to 13: PHP 8.2 installs 11, PHP 8.3 installs 12 and PHP 8.4 or later
  installs 13 (CI runs 11 and 13). Make a test double that sets no expectations with `createStub()`, not
  `createMock()`: PHPUnit 12 and later report a notice for a mock without expectations, and CI's PHP 8.5 job
  fails on it. Keep `createMock()` where the test checks calls, for example `expects(self::never())`.
- Behat step definitions and hooks are **attributes** (`#[Given]`, `#[When]`, `#[Then]`, `#[AfterScenario]`), and the
  Behat configuration is `tests/functional/behat.php`: Behat 4 reads neither doc-comment annotations nor YAML. The
  configuration is strict, so a scenario with an undefined step fails the run instead of being skipped.
- Mock `Future::complete()` / `Future::error()` for async return values
- `self::callback()` for complex assertion on method arguments (e.g., stream options arrays)

## Documentation Maintenance

### Test Coverage Map (`docs/TESTS.md`)
- Keep `docs/TESTS.md` up to date whenever tests are added, removed, or renamed.
- When a new feature is implemented, add it to the appropriate table with the tests that cover it.
- When a test method is renamed or deleted, update the corresponding entry.

### Changelog (`docs/CHANGELOG.md`)
- Every release must have an entry in `docs/CHANGELOG.md`.
- Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
- Add new items under `## [Unreleased]` as work progresses; move them to a versioned heading at release time.
- Categorize changes as Added, Changed, Fixed, Deprecated, Removed, or Security.

### PR Merge Documentation (`docs/PRs/`)
- When a PR is merged or its features are adapted, add a file to `docs/PRs/` named `PR-NNN-short-description.md`.
- Include: what the PR proposed, how the features were implemented, and which tests cover them.

Project Guide

Overview

Symfony Messenger transport for NATS JetStream. PHP 8.2+, Symfony ^7.2 / ^8.0. Uses idct/php-nats-jetstream-client (amphp-based coroutines). Ships IDCTNatsMessengerBundle (Flex auto-enables it), which registers the transport factory and the igbinary serializer - no manual wiring needed. Each service has one id, its class name: Symfony wraps a transport's serializer for signing once per id, so a second id would wrap it twice.

Project Memory

Read docs/PROJECT_MEMORY.md when starting work for maintainer preferences, the local client-library relationship, behavioral pitfalls, and a dated issue/environment snapshot. Refresh changeable facts from source and GitHub before relying on them; update the memory when new findings affect future work. This guide remains the project instruction source.

Installation

composer require idct/symfony-nats-messenger

Development setup:

composer install
composer test              # PHPStan + fast unit tests (run after every change)

Application Structure

src/
├── IDCTNatsMessengerBundle.php                # Symfony bundle - auto-registers factory + serializer services
├── NatsTransport.php                          # TransportInterface, MessageCountAwareInterface, SetupableTransportInterface
├── NatsTransportFactory.php                   # TransportFactoryInterface - DSN parsing, transport creation
├── TypeCoercion.php                           # Safe scalar type coercion helpers (final, static)
├── Options/
│   ├── NatsTransportConfiguration.php         # Immutable readonly config value object
│   ├── NatsTransportConfigurationBuilder.php  # DSN + options parsing, validation, builds Configuration
│   ├── RetryHandler.php                       # Enum: SYMFONY (TERM) | NATS (NAK)
│   └── TransportOption.php                    # Enum: all configurable option keys
└── Serializer/
    ├── AbstractEnveloperSerializer.php         # Base serializer with encode/decode envelope wrapping
    └── IgbinarySerializer.php                  # Default serializer (falls back to PhpSerializer)
tests/
├── bootstrap.php
├── unit/                                       # PHPUnit 11 to 13 - fast, no NATS required
│   ├── NatsTransportTest.php                   # Transport core: setup, send, get, ack, reject, retry
│   ├── NatsTransportFactoryTest.php            # Factory: DSN scheme detection, transport creation
│   ├── Options/
│   │   ├── NatsTransportConfigurationBuilderTest.php  # Builder validation, DSN parsing, option merging
│   │   └── NatsTransportConfigurationTest.php         # Config accessors, type coercion
│   └── Serializer/
│       ├── AbstractEnveloperSerializerTest.php  # Encode/decode round-trips, error cases
│       └── IgbinarySerializerTest.php           # Igbinary serialize/deserialize
├── functional/                                 # Behat - requires running NATS server
│   ├── features/
│   │   ├── nats_setup.feature                  # Stream creation, update, multi-subject, message flow
│   │   ├── nats_stream_limits.feature          # max_bytes, max_msgs, max_msgs_per_subject
│   │   ├── nats_consumer.feature               # Custom consumer names
│   │   ├── nats_batching.feature               # Batch consumption
│   │   ├── nats_nak.feature                    # NAK retry handler
│   │   ├── nats_term.feature                   # TERM failure handler
│   │   ├── nats_delayed.feature                # Scheduled/delayed messages
│   │   ├── nats_tls.feature                    # TLS connections
│   │   └── nats_mtls.feature                   # mTLS with client certificates
│   └── tests/Behat/NatsSetupContext.php        # All step definitions
└── nats/                                       # Docker Compose + NATS configs for testing
    ├── docker-compose.yaml
    ├── nats.conf / nats-tls.conf / nats-mtls.conf
    └── certs/                                  # generate.sh for the test TLS certificates (generated, not committed)

Features

  • DSN schemes: nats-jetstream:// and nats-jetstream+tls://
  • Stream management: create, update existing (subject merging, config preservation)
  • Retention policies: stream_max_age, stream_max_bytes, stream_max_messages, stream_max_messages_per_subject
  • Storage backends: file or memory via stream_storage
  • Replication: stream_replicas for HA
  • Consumer strategies: shared (batching=1) or independent consumers
  • Batching: configurable batching count and max_batch_timeout
  • Retry handling: retry_handler - symfony (TERM, default) or nats (NAK)
  • Scheduled messages: scheduled_messages flag, DelayStamp → NATS schedule headers
  • TLS/mTLS: full TLS config options including client certificates
  • Authentication: username/password, token, JWT, NKey
  • Serialization: igbinary (default with fallback) or custom via AbstractEnveloperSerializer
  • Publish validation: JetStream publish responses are parsed and validated
  • Connection checks: a lost connection is dialled again; ping_after_idle PINGs a connection that sat idle, or that an operation failed on, before reuse, and before the NAK or TERM of a failed delivery
  • Timeouts: connection_timeout (the dial), request_timeout (replies from the server), max_batch_timeout (pulls)
  • Duplicate protection: the deduplicate option and DeduplicationIdStamp, sent as Nats-Msg-Id; with reconnect: true, resend_on_reconnect sends such a publish again on the recovered connection
  • Keepalive: messenger:consume --keepalive, an in-progress acknowledgement queued from the signal handler

Testing

After every code change

composer test                    # PHPStan (level max) + fast unit tests

Unit tests with coverage

composer test:unit               # Coverage report → clover.xml + coverage/
composer coverage:check          # Verify ≥ 90% statement coverage

Functional tests (require Docker)

composer test:functional:setup   # Install Behat dependencies (first time)
composer nats:start              # Generate the test TLS certs, then start NATS in Docker
composer test:functional         # Run Behat scenarios
composer examples                # Run examples/*.php against the test NATS (needs nats:start)
composer nats:stop               # Stop NATS

Key testing patterns

  • Unit tests use TestableNatsTransport (overrides connect() to skip live NATS)
  • RuntimeTestableNatsTransport allows injecting JetStreamContext and NatsClient test doubles via setters
  • The unit suite runs on PHPUnit 11 to 13: PHP 8.2 installs 11, PHP 8.3 installs 12 and PHP 8.4 or later installs 13 (CI runs 11 and 13). Make a test double that sets no expectations with createStub(), not createMock(): PHPUnit 12 and later report a notice for a mock without expectations, and CI's PHP 8.5 job fails on it. Keep createMock() where the test checks calls, for example expects(self::never()).
  • Behat step definitions and hooks are attributes (#[Given], #[When], #[Then], #[AfterScenario]), and the Behat configuration is tests/functional/behat.php: Behat 4 reads neither doc-comment annotations nor YAML. The configuration is strict, so a scenario with an undefined step fails the run instead of being skipped.
  • Mock Future::complete() / Future::error() for async return values
  • self::callback() for complex assertion on method arguments (e.g., stream options arrays)

Documentation Maintenance

Test Coverage Map (docs/TESTS.md)

  • Keep docs/TESTS.md up to date whenever tests are added, removed, or renamed.
  • When a new feature is implemented, add it to the appropriate table with the tests that cover it.
  • When a test method is renamed or deleted, update the corresponding entry.

Changelog (docs/CHANGELOG.md)

  • Every release must have an entry in docs/CHANGELOG.md.
  • Follow Keep a Changelog format.
  • Add new items under ## [Unreleased] as work progresses; move them to a versioned heading at release time.
  • Categorize changes as Added, Changed, Fixed, Deprecated, Removed, or Security.

PR Merge Documentation (docs/PRs/)

  • When a PR is merged or its features are adapted, add a file to docs/PRs/ named PR-NNN-short-description.md.
  • Include: what the PR proposed, how the features were implemented, and which tests cover them.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource