PHPackages                             happyr/message-serializer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. happyr/message-serializer

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

happyr/message-serializer
=========================

Serialize classes the good way.

1.0.0(1y ago)81507.1k↓44%12[2 issues](https://github.com/Happyr/message-serializer/issues)[1 PRs](https://github.com/Happyr/message-serializer/pulls)MITPHPPHP ^7.3 || ^8.0CI passing

Since Jun 20Pushed 1mo ago3 watchersCompare

[ Source](https://github.com/Happyr/message-serializer)[ Packagist](https://packagist.org/packages/happyr/message-serializer)[ GitHub Sponsors](https://github.com/Nyholm)[ RSS](/packages/happyr-message-serializer/feed)WikiDiscussions master Synced 3d ago

READMEChangelog (10)Dependencies (3)Versions (15)Used By (0)

Message serializer
==================

[](#message-serializer)

[![Latest Version](https://camo.githubusercontent.com/a25d3426e80a376ade75deb8a6480e257b1be77e1e92c90301b894233d97c828/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f4861707079722f6d6573736167652d73657269616c697a65722e7376673f7374796c653d666c61742d737175617265)](https://github.com/Happyr/message-serializer/releases)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![Code Coverage](https://camo.githubusercontent.com/4e9f6ece6275a7c77cf94cdd9acbfc0bef3743a786b79e9b42d9d130c2369ef5/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f636f7665726167652f672f4861707079722f6d6573736167652d73657269616c697a65722e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/Happyr/message-serializer)[![Quality Score](https://camo.githubusercontent.com/9d6d6c3d7876fd78778f15dea161399678c73fc3c43a51917a9bf4d24156e7e0/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f4861707079722f6d6573736167652d73657269616c697a65722e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/Happyr/message-serializer)[![Total Downloads](https://camo.githubusercontent.com/30fea105482048157c7eb82f91e8a482d84575eb3409a9885738513eb9d6b7e1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6861707079722f6d6573736167652d73657269616c697a65722e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/happyr/message-serializer)

This package contains some interfaces and classes to help you serialize and deserialize a PHP class to an array. The package does not do any magic for you but rather help you to define your serialization rules yourself.

Install
-------

[](#install)

```
composer require happyr/message-serializer

```

See integration with [Symfony Messenger](#integration-with-symfony-messenger).

The Problem
-----------

[](#the-problem)

When you serialize a PHP class to show the output for a different user or application there is one thing you should really keep in mind. That output is part of a public contract that you cannot change without possibly breaking other applications.

Consider this example:

```
class Foo {
    private $bar;

    public function getBar()
    {
        return $this->bar;
    }

    public function setBar($bar)
    {
        $this->bar = $bar;
    }
}

$x = new Foo();
$x->setBar('test string');

$output = serialize($x);
echo $output;
```

This will output:

```
O:3:"Foo":1:{s:8:"Foobar";s:11:"test string";}

```

Even if you doing something smart with `json_encode` you will get:

```
{"bar":"test string"}
```

This might seem fine at first. But if you change the `Foo` class slightly, say, rename the private property or add another property, then your output will differ and you have broken your contract with your users.

The solution
------------

[](#the-solution)

To avoid this problem we need to separate the class from the plain representation. The way we do that is to use a `Transformer` to take a class and produce an array.

```
use Happyr\MessageSerializer\Transformer\TransformerInterface;

class FooTransformer implements TransformerInterface
{
    public function getVersion(): int
    {
        return 1;
    }

    public function getIdentifier(): string
    {
        return 'foo';
    }

    public function getPayload($message): array
    {
        return [
            'bar' => $message->getBar(),
        ];
    }

    public function supportsTransform($message): bool
    {
        return $message instanceof Foo;
    }
}
```

This transformer is only responsible to convert a `Foo` class to an array. The reverse operation is handled by a `Hydrator`:

```
use Happyr\MessageSerializer\Hydrator\HydratorInterface;

class FooHydrator implements HydratorInterface
{
    public function toMessage(array $payload, int $version)
    {
        $object = new Foo();
        $object->setBar($payload['bar']);

        return $object;
    }

    public function supportsHydrate(string $identifier, int $version): bool
    {
        return $identifier === 'foo' && $version === 1;
    }
}
```

With transformers and hydrators you are sure to never accidentally change the output to the user.

The text representation of `Foo` when using the `Transformer` above will look like:

```
{
    "version": 1,
    "identifier": "foo",
    "timestamp": 1566491957,
    "payload": {
        "bar": "test string"
    },
    "_meta": []
}
```

### Manage versions

[](#manage-versions)

If you need to change the output you may do so with help of the version property. As an example, say you want to rename the key `bar` to something differently. Then you create a new `Hydrator` like:

```
use Happyr\MessageSerializer\Hydrator\HydratorInterface;

class FooHydrator2 implements HydratorInterface
{
   public function toMessage(array $payload, int $version)
   {
       $object = new Foo();
       $object->setBar($payload['new_bar']);

       return $object;
   }

   public function supportsHydrate(string $identifier, int $version): bool
   {
       return $identifier === 'foo' && $version === 2;
   }
}
```

Now you simply update the transformer to your new contract:

```
use Happyr\MessageSerializer\Transformer\TransformerInterface;

class FooTransformer implements TransformerInterface
{
    public function getVersion(): int
    {
        return 2;
    }

    public function getIdentifier(): string
    {
        return 'foo';
    }

    public function getPayload($message): array
    {
        return [
            'new_bar' => $message->getBar(),
        ];
    }

    public function supportsTransform($message): bool
    {
        return $message instanceof Foo;
    }
}
```

### Differentiate between "I cant hydrate message" and "Wrong version"

[](#differentiate-between-i-cant-hydrate-message-and-wrong-version)

Sometimes it is important to know the difference between "*I dont not want this message*" and "*I want this message, but not this version*". An example scenario would be when you have multiple applications that communicate with each other and you are using a retry mechanism when a message failed to be delivered/handled. You **do not want** to retry a message if the application is not interested but you **do want** to retry if the message has wrong version (like it would be when you updated the sender app but not the receiver app).

So lets update `FooHydrator2` from previous example:

```
use Happyr\MessageSerializer\Hydrator\Exception\VersionNotSupportedException;
use Happyr\MessageSerializer\Hydrator\HydratorInterface;

class FooHydrator2 implements HydratorInterface
{
   // ...

   public function supportsHydrate(string $identifier, int $version): bool
   {
       if ('foo' !== $identifier) {
           return false;
       }

       if (2 === $version) {
           return true;
       }

       // We do support the message, but not the version
       throw new VersionNotSupportedException();
   }
}
```

SerializerRouter
----------------

[](#serializerrouter)

If you dispatch/consume messages serialized with `Happyr\MessageSerializer\Serializer`and default Symfony messenger to same transport you might wanna use `Happyr\MessageSerializer\SerializerRouter`. This serializer will decide whether it will use `Happyr\MessageSerializer\Serializer` to decode/encode your message or the default one from Symfony messenger.

```
use Happyr\MessageSerializer\SerializerRouter;

$serializerRouter = new SerializerRouter($happyrSerializer, $symfonySerializer);
```

Integration with Symfony Messenger
----------------------------------

[](#integration-with-symfony-messenger)

To make it work with Symfony Messenger, add the following service definition:

```
# config/packages/happyr_message_serializer.yaml

services:
  Happyr\MessageSerializer\Serializer:
    autowire: true

  Happyr\MessageSerializer\Transformer\MessageToArrayInterface: '@happyr.message_serializer.transformer'
  happyr.message_serializer.transformer:
    class: Happyr\MessageSerializer\Transformer\Transformer
    arguments: [!tagged happyr.message_serializer.transformer]

  Happyr\MessageSerializer\Hydrator\ArrayToMessageInterface: '@happyr.message_serializer.hydrator'
  happyr.message_serializer.hydrator:
    class: Happyr\MessageSerializer\Hydrator\Hydrator
    arguments: [!tagged happyr.message_serializer.hydrator]

  # If you want to use SerializerRouter
  Happyr\MessageSerializer\SerializerRouter:
    arguments:
      - '@Happyr\MessageSerializer\Serializer'
      - '@Symfony\Component\Messenger\Transport\Serialization\SerializerInterface'
```

If you automatically want to tag all your Transformers and Hydrators, add this to your main service file:

```
# config/services.yaml
services:
    # ...

    _instanceof:
        Happyr\MessageSerializer\Transformer\TransformerInterface:
            tags:
                - 'happyr.message_serializer.transformer'

        Happyr\MessageSerializer\Hydrator\HydratorInterface:
            tags:
                - 'happyr.message_serializer.hydrator'
```

Then finally, make sure you configure your transport to use this serializer:

```
# config/packages/messenger.yaml

framework:
    messenger:
        transports:
            amqp: '%env(MESSENGER_TRANSPORT_DSN)%'

            to_foobar_application:
              dsn: '%env(MESSENGER_TRANSPORT_FOOBAR)%'
              serializer: 'Happyr\MessageSerializer\Serializer'

            # If you use SerializerRouter
            from_foobaz_application:
              dsn: '%env(MESSENGER_TRANSPORT_FOOBAZ)%'
              serializer: 'Happyr\MessageSerializer\SerializerRouter'
```

### Note about Envelopes

[](#note-about-envelopes)

When using Symfony Messenger you will get an `Envelope` passed to `TransformerInterface::getPayload()`. You need to handle this like:

```
use Happyr\MessageSerializer\Transformer\TransformerInterface;

class FooTransformer implements TransformerInterface
{
    // ...

    public function getPayload($message): array
    {
        if ($message instanceof Envelope) {
            $message = $message->getMessage();
        }

        return [
            'bar' => $message->getBar(),
        ];
    }

    public function supportsTransform($message): bool
    {
        if ($message instanceof Envelope) {
            $message = $message->getMessage();
        }

        return $message instanceof Foo;
    }
}
```

Pro tip
-------

[](#pro-tip)

You can let your messages implement both `HydratorInterface` and `TransformerInterface`:

```
use Happyr\MessageSerializer\Hydrator\HydratorInterface;
use Happyr\MessageSerializer\Transformer\TransformerInterface;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Messenger\Envelope;

class CreateUser implements HydratorInterface, TransformerInterface
{
    private $uuid;
    private $username;

    /** Constructor must be public and empty. */
    public function __construct() {}

    public static function create(UuidInterface $uuid, string $username): self
    {
        $message = new self();
        $message->uuid = $uuid;
        $message->username = $username;

        return $message;
    }

    public function getUuid(): UuidInterface
    {
        return $this->uuid;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function toMessage(array $payload, int $version): self
    {
        return self::create(Uuid::fromString($payload['id']), $payload['username']);
    }

    public function supportsHydrate(string $identifier, int $version): bool
    {
        return $identifier === 'create-user' && $version === 1;
    }

    public function getVersion(): int
    {
        return 1;
    }

    public function getIdentifier(): string
    {
        return 'create-user';
    }

    public function getPayload($message): array
    {
        if ($message instanceof Envelope) {
            $message = $message->getMessage();
        }

        return [
            'id' => $message->getUuid()->toString(),
            'username' => $message->getUsername(),
        ];
    }

    public function supportsTransform($message): bool
    {
        if ($message instanceof Envelope) {
            $message = $message->getMessage();
        }

        return $message instanceof self;
    }
}
```

Just note that we cannot use a constructor to this class since it will work both as a value object and a service.

###  Health Score

57

—

FairBetter than 98% of packages

Maintenance71

Regular maintenance activity

Popularity50

Moderate usage in the ecosystem

Community20

Small or concentrated contributor base

Maturity72

Established project with proven stability

 Bus Factor1

Top contributor holds 69.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~217 days

Recently: every ~478 days

Total

11

Last Release

401d ago

Major Versions

0.5.2 → 1.0.02025-05-29

PHP version history (3 changes)0.4.1PHP ^7.2

0.4.2PHP ^7.3

0.5.1PHP ^7.3 || ^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/401ccc5eea13c60cf807ae982af00e368e2166e2f26d8eb541dcd881a57385bc?d=identicon)[Nyholm](/maintainers/Nyholm)

---

Top Contributors

[![Nyholm](https://avatars.githubusercontent.com/u/1275206?v=4)](https://github.com/Nyholm "Nyholm (41 commits)")[![aradoje](https://avatars.githubusercontent.com/u/30075502?v=4)](https://github.com/aradoje "aradoje (10 commits)")[![carlos-ea](https://avatars.githubusercontent.com/u/5512089?v=4)](https://github.com/carlos-ea "carlos-ea (2 commits)")[![demyan112rv](https://avatars.githubusercontent.com/u/6883337?v=4)](https://github.com/demyan112rv "demyan112rv (1 commits)")[![peter279k](https://avatars.githubusercontent.com/u/9021747?v=4)](https://github.com/peter279k "peter279k (1 commits)")[![smoench](https://avatars.githubusercontent.com/u/183530?v=4)](https://github.com/smoench "smoench (1 commits)")[![tyx](https://avatars.githubusercontent.com/u/245494?v=4)](https://github.com/tyx "tyx (1 commits)")[![ddeboer](https://avatars.githubusercontent.com/u/89267?v=4)](https://github.com/ddeboer "ddeboer (1 commits)")[![Deamon](https://avatars.githubusercontent.com/u/560209?v=4)](https://github.com/Deamon "Deamon (1 commits)")

### Embed Badge

![Health badge](/badges/happyr-message-serializer/health.svg)

```
[![Health](https://phpackages.com/badges/happyr-message-serializer/health.svg)](https://phpackages.com/packages/happyr-message-serializer)
```

###  Alternatives

[symfony/lock

Creates and manages locks, a mechanism to provide exclusive access to a shared resource

514139.2M692](/packages/symfony-lock)[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.7k38.9k](/packages/matomo-matomo)[ecotone/ecotone

Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.

564576.7k53](/packages/ecotone-ecotone)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

751291.4k43](/packages/civicrm-civicrm-core)[illuminate/broadcasting

The Illuminate Broadcasting package.

7127.2M208](/packages/illuminate-broadcasting)[logiscape/mcp-sdk-php

Model Context Protocol SDK for PHP

368116.8k12](/packages/logiscape-mcp-sdk-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
