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

Messenger Laravel Package

ibexa/messenger

Ibexa DXP Messenger is part of the Ibexa DXP package set, adding Symfony Messenger integration so Ibexa applications can dispatch and handle messages asynchronously through queues and transports. Install it as part of a full Ibexa DXP setup; licensed under Ibexa BUL or GPL v2.

View on GitHub
Deep Wiki
Context7

Getting Started

ibexa/messenger is a thin integration layer that wires Symfony Messenger into Ibexa DXP: it registers the buses, Ibexa‑specific middleware, and the transports that Ibexa's own subsystems (search indexing, notifications, connectors, background recalculation) dispatch onto. In practice you are not learning a new API — you are learning which Symfony Messenger surface Ibexa has already configured for you, and then reusing it for your own messages.

Preconditions (check these before writing code):

  • A working Ibexa DXP installation (composer show ibexa/dxp). This package is pulled in by the metapackage; you rarely require it standalone.
  • PHP 8.1+, symfony/messenger at the version pinned by your DXP release.
  • A transport you can actually run: Doctrine (default, zero infra), AMQP, or Redis. Doctrine is fine for dev, painful at scale.
  • License check. The README is explicit: the code is dual‑licensed under the Ibexa Business Use License (requires a valid DXP subscription) and GPL‑2.0. composer reports NOASSERTION, so automated license scanners will flag it. Get this resolved with legal before production, not after.

Minimal steps to first message:

# 1. Confirm the package and the bus wiring are present
composer show ibexa/messenger
bin/console debug:messenger                 # lists buses, handlers, routing
bin/console debug:container --tag=messenger.message_handler

# 2. Configure a transport if one isn't already available
# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                retry_strategy:
                    max_retries: 3
                    delay: 1000
                    multiplier: 2
                failure_transport: failed
            failed:
                dsn: 'doctrine://default?queue_name=failed'
        routing:
            'App\Message\ReindexContent': async
# 3. Make sure a worker can run, then consume
MESSENGER_TRANSPORT_DSN=doctrine://default bin/console messenger:consume async -vv

Where to look first (in this order):

  1. vendor/ibexa/messenger/ — grep -rn "messenger" src/ to see exactly which buses, middleware and tags the package injects. This is the authoritative answer; the README is not.
  2. bin/console debug:config framework messenger and bin/console debug:messenger — the effective configuration after Ibexa's compiler passes have run. Hand-written YAML lies; these commands don't.
  3. Your project's config/packages/messenger.yaml for whatever the DXP release already routes to async.

First realistic use case: move a slow content side‑effect off the request. E.g. a controller fires a ContentPublishedEvent handler that calls an external API; wrap that call in a message, route it to async, and the editorial save becomes fast again.

Implementation Patterns

1. Dispatch from application code, not from a controller

Inject the bus rather than MessageBusInterface::dispatch() from the container; keep the controller thin and let a domain service decide whether the operation is async.

final class ReindexContent
{
    public function __construct(
        public readonly int $contentId,
        public readonly int $versionNo,
        public readonly string $languageCode,
    ) {}
}
final class ContentSubscriber implements EventSubscriberInterface
{
    public function __construct(private MessageBusInterface $bus) {}

    public static function getSubscribedEvents(): array
    {
        return [ContentEvents::ON_CONTENT_PUBLISHED => 'onPublished'];
    }

    public function onPublished(ContentEvent $event): void
    {
        $content = $event->getContent();
        $this->bus->dispatch(new ReindexContent(
            $content->id,
            $content->getVersionInfo()->versionNo,
            $content->getVersionInfo()->initialLanguageCode,
        ));
    }
}

Pass scalars and value objects; never pass Content, ContentInfo, Location or entities (see Gotchas).

2. Handlers: explicit tags, explicit return

Use #[AsMessageHandler] or the tag — but be deliberate about bus: if the project has more than one bus, otherwise a handler silently attaches to the wrong one.

#[AsMessageHandler]
final class ReindexContentHandler
{
    public function __construct(
        private ContentService $contentService,
        private SearchService $searchService,
    ) {}

    public function __invoke(ReindexContent $message): void
    {
        $content = $this->contentService->loadContent(
            $message->contentId,
            [$message->languageCode],
            $message->versionNo,
        );
        $this->searchService->indexContent($content);
    }
}

3. Establish context inside the handler

The consumer has no request, no session, no siteaccess, no logged-in user. Two patterns that cover 95% of cases:

// A. Impersonate a known service user for privileged background work
$repository->setCurrentUser($this->userService->loadUserByLogin('admin'));

// B. Carry the actor in the message and re-hydrate it
$repository->setCurrentUser($this->userService->loadUser($message->userId));

For siteaccess-dependent work, set the siteaccess explicitly at the top of the handler (SiteAccessService / SiteAccessResolver) — never rely on a default, because worker siteaccess differs from web siteaccess.

4. Routing strategies

Approach When to use
routing: in messenger.yaml per message class Default. Explicit, greppable.
async: true on the AddMessageToBus/DispatchAfterCurrentBus middleware Rare; avoid.
SendMessageMiddleware + bus: command_bus Only if you introduce your own buses.

Prefer per-class routing. A wildcard 'App\Message\*': async reads nicely until someone adds a message that must be sync and spends an afternoon debugging.

5. Events → messages → handlers is the happy path

The pattern that pays off daily in Ibexa: keep the synchronous event subscriber cheap (build a message and dispatch), and push all I/O into the handler. This automatically gives you retries, a failure queue, and horizontal scaling for the expensive part. It also means a slow third‑party API can no longer take down the editorial UI.

6. Deployment and operations

Run consumers under Supervisor or systemd, with bounded lifetimes so leaks don't accumulate:

command=php bin/console messenger:consume async --time-limit=3600 --memory-limit=256M -vv
numprocs=2
autostart=true
autorestart=true
stopwaitsecs=3600

--time-limit should be slightly less than Supervisor's stopwaitsecs, otherwise you get SIGKILLed mid-message and a duplicate delivery.

7. Testing

Set framework.messenger.transports.async.dsn: 'in-memory://' in config/packages/test/messenger.yaml. The message is then handled within the same process, so a functional test can assert the side effect directly without running a worker. For unit tests, use Symfony\Component\Messenger\Transport\InMemory\InMemoryTransport and assert getDispatchedMessages() / getAcknowledged().

Gotchas and Tips

Serialization is the #1 footgun. The default PhpSerializer serializes the whole object graph. Ibexa domain objects (Content, ContentInfo, Location, User) hold service references, closures in some versions, and huge cached structures — they either fail to serialize or produce multi‑megabyte payloads. Always send IDs and re‑load in the handler. If you must send richer DTOs, use messenger.transport.symfony_serializer explicitly and write normalizers; don't assume it works because the test passed with a tiny object.

EntityManager and repository caches go stale. A long‑running consumer keeps a single Doctrine connection and Ibexa keeps in-memory caches. Call $entityManager->clear() at the end of each message (a small middleware is the cleanest place), and re‑fetch content by ID per message rather than holding references. Symptoms of skipping this: EntityNotFoundException, detached entity errors, and handlers acting on version n‑1 of content.

At‑least‑once delivery, not exactly‑once. A worker killed after the handler commits but before the ack will replay the message. Every handler must be idempotent — upserts, not inserts; check-then-act guarded by a unique constraint or a processed‑message table.

Failure transport is not optional. Without failure_transport: failed, a message that exhausts retries is dropped silently. Configure it, and add bin/console messenger:failed:show / messenger:failed:retry to your on‑call runbook. Note that messenger:failed:retry re‑runs with the current code, which is usually what you want but occasionally re‑breaks things.

Middleware ordering matters. Doctrine's DoctrineTransactionMiddleware wraps a handler in a transaction; if your handler also makes HTTP calls to a third party, you're holding a DB transaction open across the network. Either don't wrap those handlers, or move the HTTP call out. Similarly, middleware registered by Ibexa (context/permission-related) runs in an order you did not choose — inspect debug:messenger before adding your own, and prefer appending over re‑ordering.

Retries multiply side effects. With max_retries: 3 and a multiplier: 2, a failing external API call is attempted four times. That's fine for reads, dangerous for "send email"/"charge card". For non‑idempotent actions, either set max_retries: 0 and route to failed, or make the handler idempotent with a dedupe key.

Consumers don't see config changes. After changing messenger.yaml in a deploy you must restart workers — they boot once. Add bin/console messenger:stop-workers to your deploy script so old code doesn't keep consuming new message shapes. This bites hardest during a rolling deploy where message class constructors changed.

CLI config and siteaccess differ. bin/console runs with a default siteaccess and no user; if a handler depends on either, it will pass in a web request and fail in the worker. The reverse also happens. Test the handler in the worker, always.

Debugging playbook: debug:messenger (is the handler wired to the bus?) → debug:config framework messenger (is routing right?) → messenger:consume async -vv (does the middleware chain blow up?) → check var/log/ for the transport exception. If a message vanishes, check messenger_messages in the DB — Doctrine transport leaves rows behind on handler failure, which is often mistaken for "the worker isn't running".

Extension points: the package's own DependencyInjection extension and compiler passes are the place to look if you need a custom middleware inserted into the Ibexa bus rather than a new one. Register it with the messenger.middleware tag and let the bus's middleware: list reference it by id, so you stay compatible with whatever Ibexa adds in a patch release.

Pin your DXP version. This package is versioned in lockstep with the DXP release, not independently (note the 2026-08-20 last‑release date — it's a release train artefact, not a mature standalone library with zero stars). Treat it as an internal component: don't composer require it standalone, and read the DXP changelog for messenger changes during upgrades rather than expecting a semantic‑versioning signal.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log