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 Azure Service Bus Messenger Bundle Laravel Package

halloverden/symfony-azure-service-bus-messenger-bundle

This Symfony bundle integrates Azure Service Bus with Symfony Messenger, providing a transport for sending and receiving messages via Azure queues or topics so Symfony apps can use Azure-backed asynchronous messaging.

View on GitHub
Deep Wiki
Context7

Getting Started

First, reset expectations: this is not a Laravel package. It registers a TransportFactory with Symfony Messenger's transport registry (symfony/messenger). Laravel's queue system is a different programming model (Illuminate\Contracts\Queue\Queue, connectors via Queue::extend, workers via queue:work/Horizon). There is no shared extension point, so you cannot composer require this and have dispatch() work. Before anything else, clone the repo and read three files:

  • composer.json — which Azure SDK it wraps (azure-php-sdk? microsoft/azure-service-bus?) and whether it speaks AMQP 1.0 or falls back to the REST API. This single fact determines latency, per-operation billing, and whether lock renewal is manual.
  • Transport.php / TransportFactory.php — how the DSN is parsed, how queues vs. topics/subscriptions are selected.
  • The receiver/consumer class — how get() settles messages (complete/abandon/deadLetter), where DeliveryCount is read, and whether peek-lock is renewed.

Where to look first for a Laravel team: stand up a throwaway Symfony 6/7 skeleton (symfony/messenger + this bundle), point it at a real Service Bus namespace, and send one JSON message end to end. That ten-minute exercise answers more than the README: you see header/property conventions on the wire, you see what messenger:consume actually does per message, and you get a message shape you can reproduce from Laravel.

First real use case: a dedicated consumer microservice. A small Symfony app running messenger:consume against Service Bus, which calls your Laravel API (or writes to a shared Redis/DB) for each message. This is the only pattern that lets you use the package as-authored without dragging symfony/messenger into a Laravel app as a second runtime.

If you want Service Bus inside Laravel natively, don't install this — read it and port it. A Illuminate\Queue\Queue subclass plus Queue::extend('azure', ...) wrapping the same Azure SDK is roughly 300–500 lines and inherits failed_jobs, backoff config, and Horizon-adjacent tooling for free.

Implementation Patterns

Pattern A — Reference implementation → custom Laravel connector (recommended). Use the package as a spec, not a dependency. Copy the semantics into a Laravel queue connector:

// AppServiceProvider::boot()
Queue::extend('azure', fn () => new AzureServiceBusQueue(
    $azureClient, config('queue.connections.azure')
));

Then dispatch(new OrderPlaced($order)) works with ->onConnection('azure')->onQueue('orders'), and php artisan queue:work azure consumes. Reproduce the package's settlement logic (complete on success, abandon on handler exception so DeliveryCount increments, deadLetter after N attempts) and map Service Bus DeliveryCount onto Laravel's $this->attempts().

Pattern B — Sidecar Symfony consumer, boundary-only integration. Laravel never talks to Service Bus directly. Laravel publishes intent to an outbox table (or Redis stream); a small Symfony relay worker — built on this bundle — reads the outbox and pushes to Service Bus; a Symfony consumer pulls from Service Bus and calls back into Laravel over HTTP with a signed payload. Two processes, one contract, no dual messaging runtime inside Laravel.

Pattern C — Use only the consumer half. Symfony worker with messenger:consume handles inbound Service Bus messages; Laravel handles everything internal on Redis/SQS. Wire it in config/packages/messenger.yaml:

framework:
    messenger:
        transports:
            azure_in:
                dsn: '%env(MESSENGER_AZURE_DSN)%'
                options:
                    entity_path: 'orders'
                    subscription: 'laravel-consumer'
                retry_strategy:
                    max_retries: 5
                    delay: 1000
                    multiplier: 2
                    max_delay: 60000

Outbox for anything you can't lose. Write the message row in the same DB transaction as the domain change, relay asynchronously. This holds regardless of which pattern you choose and is the single best defense against the "consumer died silently for 40 minutes" incident.

Wrap it behind your own interface. Whether you go Pattern A or B, expose one internal contract (MessageBus::publish(string $topic, array $payload): void) and let the Azure implementation sit behind it. That's the difference between a contained swap and a rewrite when this package stalls.

Contract tests against a real namespace. Before production, cover: publish → consume round trip, handler exception → retry → dead-letter after max deliveries, topic fan-out to two subscriptions, malformed payload, and a long-running handler that exceeds lock duration. Run these on every Azure SDK bump.

Routing and topology. Keep entity names configuration-driven (entity_path per transport), one transport per logical queue/topic. Don't multiplex unrelated message types onto one queue — Service Bus dead-lettering and filtering are far more useful when entities map 1:1 to bounded contexts.

Gotchas and Tips

The AMQP trap. Service Bus speaks AMQP 1.0; ext-amqp is 0.9.1 and incompatible. If the package resolves to a REST-based client, you inherit poll-based latency, per-operation billing, and likely manual peek-lock renewal. Verify this before you assume throughput. Test with a handler that sleeps 60s against a 5-minute lock and watch for redelivery.

Composer version conflicts bite late. Laravel already pins many symfony/* packages. Adding symfony/messenger alongside a Laravel app — if you go that route — surfaces constraint conflicts at composer update, in CI, usually on a Friday. Pin exact versions and add composer why-not symfony/messenger <version> to CI.

Two retry models don't compose. Symfony Messenger's retry strategy and failure transport are not Laravel's tries/backoff/failed_jobs. Blending them produces ambiguous failure behavior under load — the worst possible time to discover it. If you run both runtimes, draw a hard line: one system owns retries for a given queue, always.

Dead letters are invisible to Horizon. Service Bus dead letters do not appear in failed_jobs and Horizon will not see them. Build a small scheduled command (az servicebus ... or SDK call) that reports dead-letter counts and message IDs into your existing alerting. Same for consumer liveness — there is no Horizon supervisor equivalent; add a heartbeat.

Lock renewal for long handlers. If your handler can take longer than the entity's lock duration, you need renewal or you get duplicate delivery while the original is still processing. Check whether the receiver renews; if not, cap handler time and set the lock duration accordingly. Either way: handlers must be idempotent. Retry plus at-least-once delivery guarantees duplicates.

Serializer mismatch. Symfony's PhpSerializer produces application/x-php-serialized-message; Laravel's default is JSON. If both sides touch the same queue, set the Symfony transport serializer: messenger.transport.symfony_serializer with JSON, and make the Laravel side produce matching envelope shape. Mismatched serializers fail at consume time with a cryptic decode error.

Credentials and silent stalls. Managed identity is preferable to SAS keys, but both can expire mid-run and manifest as a consumer that just stops pulling with no error. Log the connection state and alert on consumer lag, not on exceptions.

Topics, subscriptions, and filters. Topic fan-out works, but correlation/SQL filters on subscriptions and scheduled (deferred) messages may not be exposed by a thin transport. If your design depends on ordering, note that sessions serialize consumption per session and will cap throughput — confirm support before designing anything order-dependent.

Extension points are thin. With 2 stars and one maintainer, expect no customization hooks. Fork early, keep your fork minimal and documented, and budget a small maintenance allowance per Symfony major and Azure SDK release. Treat upstream as a starting point you own, not a dependency you passively consume.

Debugging. messenger:consume -vv shows transport-level activity; correlate with Service Bus message ID in Application Insights. When a message "disappears," check dead-letter first, then DeliveryCount, then the consumer's connection — in that order.

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.
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo