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

lendable/symfony-messenger-polyfill

A lightweight polyfill that provides Symfony Messenger-compatible classes and interfaces, letting Laravel and PHP projects use Messenger-style message buses without requiring the full Symfony component. Useful for package authors who need interoperability while keeping dependencies minimal.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first. This polyfill exists to let you install symfony/messenger:4.2 in a project still running Symfony 3.4/4.1, where the framework.messenger config key and the Messenger bundle wiring do not exist. It is archived (last release Jan 2019), has no dependents, and targets a Symfony branch that went EOL in mid-2020. Treat it as a stopgap for a legacy app you cannot upgrade yet, or as a worked example of how the 4.2 Messenger internals fit together — not as a foundation for new work. If you can move to Symfony 4.3+/5.x, do that instead; the polyfill's whole reason to exist evaporates and you get the real integration.

Minimal steps

  1. Pin the Messenger component explicitly. The polyfill only makes sense against 4.2, so do not let Composer drift you into 4.3/4.4 where the shimmed classes collide with the real ones:
{
  "require": {
    "symfony/messenger": "4.2.*",
    "lendable/symfony-messenger-polyfill": "^1.0"
  }
}
  1. Register the bundle manually. Pre-Flex Symfony has no auto-discovery, so add the bundle class shipped by the package (check the package src/ and README for the exact FQCN) to AppKernel::registerBundles(). Order matters — register it before FrameworkBundle so its compiler passes can run ahead of the framework's.

  2. Define your bus yourself. In a 4.2-era Symfony the framework will not build messenger.bus.default for you, so declare the bus, the middleware stack, and the handler tag in services.yml.

  3. First use case: a single command bus, one handler, one transport. Dispatch a RegisterUser message, route it to a doctrine transport, run bin/console messenger:consume in a worker. That loop is the whole point of the package — prove it end-to-end before adding a second bus or an event bus.

Where to look first: the package's DependencyInjection/ folder (that is where the polyfilled config and compiler passes live), then symfony/messenger 4.2's own MessengerPass so you understand which tags the polyfill is emulating. The README is short; the code is the documentation.

Implementation Patterns

Hand-wire the bus as a service. Because framework.messenger isn't available, you build the bus explicitly. The canonical 4.2 shape:

services:
    _defaults:
        autowire: false
        autoconfigure: false

    messenger.middleware.add_bus_name_stamp:
        class: Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware
        arguments: ['command_bus']

    messenger.bus.command:
        class: Symfony\Component\Messenger\MessageBus
        arguments:
            - !tagged_iterator messenger.middleware
        tags: [{ name: messenger.bus, bus: command_bus }]

Note !tagged_iterator is a 4.0+ feature; on 3.4 you'll need a compiler pass or Symfony\Component\DependencyInjection\Argument\IteratorArgument built in a pass. The polyfill's own bundle is a good reference for exactly this.

Register handlers with the tag the polyfill understands. Handlers are plain services implementing __invoke(YourMessage $message):

    App\Handler\RegisterUserHandler:
        tags:
            - { name: messenger.message_handler, bus: command_bus }

If a handler is silently never called, this tag is almost always the culprit — the bus attribute must match the bus service name you tagged, not the transport name.

Use HandleTrait for synchronous query-style calls. Symfony\Component\Messenger\HandleTrait (4.2) lets you dispatch on a bus and pull the handler's return value out of the HandledStamp, which is how you keep controllers from knowing about handlers:

use Symfony\Component\Messenger\HandleTrait;

final class UserController
{
    use HandleTrait;

    public function __construct(MessageBusInterface $queryBus)
    {
        $this->messageBus = $queryBus;
    }

    public function __invoke(string $id): Response
    {
        $user = $this->handle(new FindUser($id)); // returns the handler's value
    }
}

Separate buses by intent, not by transport. The 4.2 middleware is where behaviour lives: dispatch_after_current_bus for events raised inside handlers, failed_message_processing_middleware around the whole bus for retries, SendMessageMiddleware only on the bus that should actually enqueue. Keep a synchronous query_bus with no SendMessageMiddleware at all — sending queries to a transport and waiting for a reply is a trap you'll debug for a day.

Transports: config lives in your own service definitions. Doctrine, AMQP and Redis transports all exist in 4.2. Register them as services implementing TransportInterface and tag the factory with messenger.transport_factory if you add a custom one. For the serializer, messenger.transport.native_php_serializer is fine for single-app deployments; symfony_serializer is what you want the moment messages cross a version boundary or another language.

Testing. There is no TestTransport in 4.2 (that arrived in 4.3), so use the in-memory transport and assert on its getSent() / getAcknowledged() in kernel tests. For unit tests of handlers, instantiate the handler directly — do not boot a bus just to test business logic.

Integration tip: because everything is hand-wired, keep a single messenger.yml imported into config.yml, and let the polyfill's bundle own only the parts it must (compiler passes, tag aliases). Everything application-specific stays in your config so the day you drop the polyfill, the diff is confined to bootstrapping.

Gotchas and Tips

  • Pin 4.2.*, not ^4.2. Composer will happily resolve Messenger 4.3+/4.4, which ships real versions of the classes the polyfill shims. You get either a "cannot redeclare class" fatal or, worse, an autoload race where the shim wins and you're running stale interfaces against new internals. An exact constraint avoids a very confusing afternoon.
  • Remove the polyfill the same commit you upgrade Symfony. It is not harmless once the real classes exist. Grep the package's src/ for the class list and confirm each is native before deleting, then drop the bundle from AppKernel.
  • Bundle ordering is load-bearing. Registered after FrameworkBundle, the polyfill's compiler passes can run after the container has already resolved tagged iterators, and your transport middleware silently disappears from the bus. When a message is dispatched but never sent, check bundle order before anything else.
  • Don't fight the config validator. Attempting framework: messenger: ... in a 3.4/4.1 app throws an "unrecognized option" error at cache warmup. All Messenger config is service definitions, full stop. Same for framework.messenger.transports.* examples you copy from modern docs — they will not work here.
  • Cache is your enemy during wiring changes. Compiler-pass and tagged-iterator issues survive cache:clear surprisingly often because of the var/cache/dev/Container*/ app kernel cache. Use rm -rf var/cache/dev when tagging changes don't take effect.
  • Serialization drift is the real production risk. With native_php_serializer, a message class that gains a constructor argument breaks consumers still running the old deploy. Consumers and producers deploy at different times; use symfony_serializer with an explicit envelope/MessageSerializer, or version your message classes and keep the old shape readable.
  • Retries must be configured explicitly. There's no framework default here. Wrap the bus with failed_message_processing_middleware plus a RetryStrategy (max retries, delay, multiplier) or a failure_transport, otherwise a throwing handler drops the message on the floor and messenger:consume moves on.
  • Debugging tools are scarcer than you expect. debug:messenger exists in 4.2 but reflects what the framework wired — which is nothing, in your setup. Debug at the container level instead: debug:container --tag=messenger.message_handler and debug:container --tag=messenger.middleware tell you truthfully what the polyfill produced.
  • Extension points worth knowing: decorate MessageBusInterface for cross-cutting concerns (logging, correlation IDs) instead of adding middleware late in the stack; implement TransportInterface + TransportFactoryInterface and tag messenger.transport_factory to add a transport (SQS, GCP Pub/Sub shims are the common case); implement MiddlewareInterface for anything that needs to short-circuit dispatch.
  • The archive is the biggest gotcha. No security fixes, no 4.3 features (TestTransport, messenger:failed:* commands, --time-limit on consumers), and no one to ask when a compiler pass interacts badly with another bundle's. Budget the Symfony upgrade as the actual deliverable and treat this package as scaffolding with a demolition date.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource