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 Messenger Laravel Package

bref/symfony-messenger

Bridge for running Symfony Messenger on AWS Lambda with Bref. Dispatch messages to SQS, SNS, or EventBridge and let Lambda workers consume them, with setup documented on the Bref website.

View on GitHub
Deep Wiki
Context7

Getting Started

What it actually is: a Symfony Messenger transport/consumer bridge for Bref. It gives you three TransportInterface implementations (sqs://, sns://, eventbridge://) for sending, and three Lambda handlers (SqsConsumer, SnsConsumer, EventBridgeConsumer) that turn a Lambda event batch into $bus->dispatch() calls. It is Symfony-only — a Laravel app cannot use it with Laravel's queue system (see Gotchas).

Minimal path:

  1. composer require bref/bref bref/symfony-messenger symfony/messenger (Symfony 5.4+/6/7). Flex/bundle registration wires the transport factories and consumer services; if you're on a non-Flex app, register the bundle manually.
  2. Route a message to a Bref transport in config/packages/messenger.yaml:
    framework:
        messenger:
            transports:
                async:
                    dsn: 'sqs://'
                    options:
                        queue_name: 'orders'
            routing:
                'App\Message\SendInvoice': async
    
    (SNS/EventBridge DSNs take topic ARN / event bus name instead — the only real documentation is at bref.sh/docs/symfony/messenger, not in the repo README. Check option names there before guessing.)
  3. Create a second Lambda function whose handler is the consumer, triggered by the queue:
    # serverless.yml
    functions:
      web:
        handler: public/index.php
        runtime: php-83-fpm
        events: [{ httpApi: '*' }]
      worker:
        handler: bin/worker.php
        runtime: php-83
        timeout: 30
        events:
          - sqs:
              arn: !GetAtt OrdersQueue.Arn
              batchSize: 10
    
    <?php // bin/worker.php
    use Bref\Symfony\Messenger\Service\Sqs\SqsConsumer;
    require __DIR__.'/../vendor/autoload.php';
    (new Symfony\Component\Dotenv\Dotenv())->bootEnv(__DIR__.'/../.env');
    $kernel = new App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
    $kernel->boot();
    return $kernel->getContainer()->get(SqsConsumer::class);
    
  4. First use case: HTTP Lambda dispatches SendInvoice → SQS → worker Lambda runs #[AsMessageHandler] SendInvoiceHandler. That's the whole loop; everything else is configuration.

Implementation Patterns

Dispatch vs. consume are separate functions. Treat the HTTP function and the worker function as two deployables. The web function only ever send()s; the worker function only ever handles records. Don't try to do both in one Lambda.

Pick the transport by fan-out semantics.

  • sqs:// — single work queue, one consumer, redrive policy for retries. This is your Laravel queue:work equivalent.
  • sns:// — pub/sub fan-out to multiple SQS queues or Lambdas; use when several services care about one event.
  • eventbridge:// — event bus with content-based routing rules; use for domain events crossing service boundaries.

Handlers are ordinary Messenger handlers, so keep the business logic out of the Lambda layer:

#[AsMessageHandler]
final class SendInvoiceHandler
{
    public function __construct(private InvoiceMailer $mailer) {}
    public function __invoke(SendInvoice $message): void { $this->mailer->send($message->id); }
}

One consumer class per Lambda. SqsConsumer, SnsConsumer, EventBridgeConsumer each expect a specific record shape; create three functions (worker-sqs, worker-sns, worker-events) rather than branching on payload.

Local development. The Bref transports are effectively send-only — consumption happens through the Lambda consumer, so bin/console messenger:consume async against sqs:// is not your dev loop. Instead:

  • swap the transport to in-memory:// or doctrine:// in config/packages/dev/messenger.yaml and consume locally as normal;
  • or point at LocalStack if you need real SQS semantics.

Testing.

// config/packages/test/messenger.yaml → dsn: 'in-memory://'
$transport = $container->get('messenger.transport.async');
$bus->dispatch(new SendInvoice(42));
self::assertCount(1, $transport->getSent());

For the consumer, hydrate a Bref event from a fixture and invoke it directly — no AWS needed:

$consumer->handle(new SqsEvent(['Records' => [$record]]), new Context());

Infrastructure stays yours. The package does not create queues, topics, subscriptions, or DLQs. Declare them in serverless.yml resources / Terraform / CDK alongside the function, and reference ARNs via env vars.

Extension point: implement TransportInterface + a Messenger transport factory for anything else in AWS (Kinesis, MSK), or decorate a consumer to add logging/tracing around the dispatch loop.

Gotchas and Tips

  • Not Laravel. MessageBusInterface ≠ Bus::dispatch, handlers ≠ Jobs, there is no queue:work, no failed_jobs table, no Horizon. A Laravel shop should use bref/laravel-bridge with Laravel's native SQS driver; only reach for this package in a Symfony codebase (or a polyglot monorepo where the worker is Symfony).
  • Retries are SQS redrive, not Messenger's retry_strategy. Messenger's retry_strategy and failure_transport are dead configuration here — the consumer throws, Lambda marks the batch failed, SQS re-delivers after the visibility timeout until maxReceiveCount, then the redrive policy sends it to the DLQ. Set the redrive policy explicitly or you get an infinite retry loop on poison messages.
  • Batch failure = whole-batch retry = duplicates. By default one bad record fails all records in the invocation. If the consumer supports SQS ReportBatchItemFailures, enable it and return the failed item identifiers; otherwise make every handler idempotent (dedupe on a business key or the Messenger envelope's message id, not the SQS MessageId alone).
  • Visibility timeout ≥ Lambda timeout (AWS recommends ~6×). If the function timeout exceeds the visibility timeout, SQS re-delivers while the first invocation is still running and you get concurrent duplicate processing.
  • 256 KB SQS payload ceiling. The default PHP serializer plus a Messenger envelope and base64-ish encoding eats a big chunk of that fast. Switch to the JSON serializer for interop and smaller payloads, and use a claim-check (payload in S3, reference in the message) for anything large.
  • Warm containers carry state. The kernel boots once and the function object is reused across invocations. Keep handlers stateless, watch static caches and Doctrine's identity map, and lean on Symfony's services resetter between messages.
  • Cold starts are boot time. bin/worker.php boots the full kernel on every cold start. Run composer dump-env prod and composer install --classmap-authoritative --no-dev in the build to shave hundreds of ms.
  • Credentials come from the execution role. No AWS keys in env vars; grant sqs:SendMessage/ReceiveMessage/DeleteMessage (or sns:Publish, events:PutEvents) on the exact ARNs.
  • SNS/EventBridge shapes differ. The SNS consumer sees the notification envelope (Records[].Sns), the EventBridge consumer sees detail. A rule with a too-narrow event pattern or an SNS subscription filter will silently drop messages you thought you were sending — verify end-to-end with a real deploy before trusting it.
  • Debugging: serverless invoke local (or bref local) for the consumer, CloudWatch Logs for the real thing (the Context object gives you the request id to correlate). A message that "disappeared" is almost always a routing rule, a filter policy, or a DLQ — check the queue's metrics before the code.
  • Maturity: 72 stars and a stub README mean the Bref website is your source of truth and the surface area is small. Pin the version in composer.json, and read the CHANGELOG on upgrade — there is not much community content to fall back on.
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.
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder
ideatica/config-writer
keaton-php/pipe-dream
jacksonwebservices/laravel-user-suite
maksimru/conditional-laravel-debugbar
gismatul/nova-support-chat
gismatul/laravel-support-chat
networkrailbusinesssystems/oracle-api
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
refinery29/php-cs-fixer-config
parsnick/steak