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

Jms Messenger Adapter Laravel Package

kunicmarko/jms-messenger-adapter

A Symfony Messenger adapter that lets JMS Serializer handle your message serialization, replacing the default PHP serializer so messages can travel as JSON, XML, or other JMS-supported formats with full metadata and type handling support.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a transport-level adapter: it makes Symfony Messenger serialize message bodies with JMS Serializer instead of Symfony Serializer. It is archived and unmaintained (9 stars, zero dependents), so treat it as a small reference implementation or a fork candidate rather than a long-term dependency. The only recent activity is release 1.1.0, which adds PHP 8 compatibility — a maintenance-only change with no new features, no stamp handling, and no API changes. Practically, that means you can install it on a PHP 8 runtime without syntax-level breakage, but nothing about the adapter's behaviour has improved.

Start by reading vendor/kunicmarko/jms-messenger-adapter/src/Serializer.php. The package has no Symfony bundle and no Flex recipe; you wire it manually. The important surface is one class implementing Symfony\Component\Messenger\Transport\Serialization\SerializerInterface, with encode(Envelope): array and decode(array): Envelope. Confirm two things in that source before designing around it:

  1. Does encode() write anything besides a type header?
  2. Does decode() wrap JMS exceptions in MessageDecodingFailedException and restore any stamps?

Minimal setup:

composer require kunicmarko/jms-messenger-adapter jms/serializer-bundle
# config/services.yaml
services:
    app.messenger.jms_serializer:
        # Class name from vendor/kunicmarko/jms-messenger-adapter/src
        class: Kunicmarko\JMSMessengerAdapter\Serializer
        arguments:
            - '@jms_serializer'
# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                serializer: app.messenger.jms_serializer
            failed:
                dsn: 'doctrine://default?queue_name=failed'
                serializer: app.messenger.jms_serializer

A first message:

namespace App\Message;

use JMS\Serializer\Annotation as JMS;

final class SendWelcomeEmail
{
    /** @JMS\Type("string") */
    private string $userId;

    public function __construct(string $userId)
    {
        $this->userId = $userId;
    }

    public function getUserId(): string
    {
        return $this->userId;
    }
}
$bus->dispatch(new SendWelcomeEmail($user->getId()));
bin/console messenger:consume async -vv

The first useful validation is to inspect the raw transport payload. For AMQP/Redis/Doctrine you should see a JSON body such as {"userId":"123"} and a header type: App\Message\SendWelcomeEmail. If that is all you see, you have seen the whole adapter: it is a thin class-name header plus JMS body serializer.

Use it first for simple, self-contained JSON messages on an async transport. Do not start by migrating a queue full of existing Symfony-serialized messages.

Implementation Patterns

Configure the serializer per transport. This is not a bus middleware or a global serializer replacement. Set serializer: on each transport that needs JMS. Set it on the failure transport too; otherwise failed messages written by one serializer cannot be read by the other.

Keep message DTOs explicit. Messenger messages should be small DTOs, not Doctrine entities. Add JMS metadata for every non-scalar property:

use JMS\Serializer\Annotation as JMS;

final class ReindexProduct
{
    /** @JMS\Type("string") */
    private string $productId;

    /** @JMS\Type("array<App\Message\ReindexLocale>") */
    private array $locales = [];

    /** @JMS\Type("DateTimeImmutable<'Y-m-d H:i:s'>") */
    private \DateTimeImmutable $requestedAt;
}

Without @Type, JMS may decode nested objects as arrays or fail to hydrate collections. Use @SerializedName when the wire name must stay stable even if the PHP property is renamed.

Stabilize the type header. The adapter likely uses get_class($message) as the type header. That means renaming or moving a message class breaks messages already in the queue. Decorate the serializer with a class-to-alias map:

final class StableNameSerializer implements SerializerInterface
{
    private const ALIASES = [
        SendWelcomeEmail::class => 'send_welcome_email',
    ];

    public function encode(Envelope $envelope): array
    {
        $encoded = $this->inner->encode($envelope);
        $encoded['headers']['type'] = self::ALIASES[get_class($envelope->getMessage())];

        return $encoded;
    }

    public function decode(array $encodedEnvelope): Envelope
    {
        $class = array_search($encodedEnvelope['headers']['type'], self::ALIASES, true);

        if ($class === false) {
            throw new MessageDecodingFailedException('Unknown message type.');
        }

        $encodedEnvelope['headers']['type'] = $class;

        return $this->inner->decode($encodedEnvelope);
    }
}

An allow-list like this is also the right place to prevent arbitrary class instantiation from a type header.

Add stamp handling if you need retries or bus routing. Symfony’s own serializer stores sendable stamps in X-Message-Stamp-* headers (BusNameStamp, DelayStamp, RedeliveryStamp, etc.). The JMS adapter is focused on body serialization and may not preserve them. Symptoms include retries resetting or looping, delays being ignored, and messages always going to the default bus after consume. If your source confirms stamps are dropped, wrap encode()/decode() and add a stamp codec. Be careful: stamp classes often do not have JMS metadata, so using JMS for stamps as well can be brittle. A small explicit codec for the stamps you actually use is usually safer. Note that 1.1.0 did not alter the encoding logic — it is still a body-only serializer.

Wrap decoding failures. Messenger expects decode() to throw Symfony\Component\Messenger\Exception\MessageDecodingFailedException for bad payloads. If JMS exceptions bubble out, the worker can crash instead of rejecting the message. A decorator is the safest place:

try {
    return $this->inner->decode($encodedEnvelope);
} catch (\Throwable $e) {
    throw new MessageDecodingFailedException($e->getMessage(), 0, $e);
}

Validate after deserialization. JMS commonly bypasses the constructor when hydrating objects, so constructor validation will not run. Use @JMS\PostDeserialize for invariant checks, or run validation in a middleware after the message is decoded. Keep post-deserialize hooks free of side effects: they run on every decode, including failure-transport retries.

Plan rolling deployments carefully. Existing messages encoded by Symfony’s serializer have headers like Content-Type and X-Message-Stamp-*; JMS-adapter messages have type. During migration, either drain queues before switching, or use a dispatching serializer that detects the header shape and delegates to the old or new serializer:

if (isset($encodedEnvelope['headers']['type'])) {
    return $this->jmsSerializer->decode($encodedEnvelope);
}

return $this->symfonySerializer->decode($encodedEnvelope);

Test with a real serializing transport. in-memory:// passes envelopes directly and does not exercise encode()/decode(). Use Doctrine, AMQP, or Redis for integration tests. A useful test suite is: encode a message with stamps, assert the body and headers, decode it again, and assert the message and required stamps survive.

Gotchas and Tips

  • Archived status is the biggest risk. No dependents, and 1.1.0 is a one-line compatibility fix rather than a sign of renewed maintenance. Composer constraints may still block Symfony 6/7 or newer JMS versions. If you adopt it, fork it, add tests, and pin versions. Do not expect upstream fixes for stamp handling, exception wrapping, or security concerns.
  • 1.1.0 is PHP 8 compatibility only. Good news: you are less likely to hit a hard syntax/constraint failure on PHP 8 runtimes. Bad news: it says nothing about JMS 3.x features (attributes, backed enums, readonly properties) or newer Symfony Messenger stamp formats. Verify your JMS version specifically, not just the PHP version.
  • No bundle or recipe. You define the service yourself. Because the class implements Messenger’s SerializerInterface, global autowiring aliases can accidentally route every transport through JMS. Use an explicit service id and set it only where intended.
  • The type header is executable surface. A queue producer that can set type may be able to make JMS instantiate arbitrary classes. Always allow-list message class names if the queue is not fully trusted.
  • Verify stamp support before relying on retries. Encode an envelope with BusNameStamp, DelayStamp, and RedeliveryStamp, dump the headers, and decode it. If those stamps are absent, implement a stamp codec or do not use this adapter for transports that depend on retry/delay metadata. Re-test on 1.1.0 — but expect the same result.
  • Failure transport must match. If async uses JMS but failed uses Symfony Serializer, messenger:failed:show/retry cannot decode the message. Configure both.
  • Constructor bypass and typed properties. JMS may hydrate private properties without calling the constructor. Required typed properties can remain uninitialized until accessed, producing Error: Typed property ... must not be accessed before initialization. Test with the real JSON payload, not just PHP objects.
  • Explicit types for nested data. Collections, enums, dates, and nested DTOs need JMS type metadata. Backed enums and readonly properties depend on your JMS version, not on this adapter; 1.1.0 does not add support for them.
  • Groups and serialization context are likely not exposed. If your application uses JMS groups for HTTP payloads, Messenger messages will not automatically use them. Decorate the adapter and pass SerializationContext::create()->setGroups([...]) explicitly if needed.
  • Headers must remain scalar for AMQP. Do not put arrays or objects into headers. Keep an eye on broker header size limits.
  • Format is probably hardcoded to JSON. If you need XML, CSV, or another format, expect to fork or wrap.
  • Upgrade check when moving to 1.1.0. Diff the released tag against what you vendored (composer diff or a git diff in a fork). Confirm the PHP 8 change is limited to compatibility and does not touch encode()/decode() behaviour your decorators depend on.
  • Debugging steps that pay off: bin/console debug:container app.messenger.jms_serializer confirms wiring; a temporary decorator that dumps $encodedEnvelope shows exactly what goes on the wire; messenger:failed:show -vv shows failure-transport decoding; raw AMQP/Redis inspection confirms the type header and JSON body. Use messenger:consume --limit=1 -vv while iterating.
  • If you are choosing today: a PHP 8 compatibility release does not change the verdict. Low adoption and archived state still make this a poor default. Prefer Symfony Serializer, or fork this adapter and own the stamp handling, exception wrapping, allow-listing, and version constraints. If the only goal is “JMS annotations on messages,” a small in-house serializer class is likely less risky than depending on an abandoned nine-star package.
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
shitoudev/permissionmanager