Serializers¶
Two cooperating layers: the Serializable interface lets domain objects opt in to being serialized, and Serializer implementations convert those objects to and from string formats.
Serializable (interface)
│ arraySerialize(): array
│ arrayDeserialize(array $data): static
│
└── YourDomainObject implements Serializable
Serializer (interface)
│ serialize(Serializable $object): string
│ deserialize(string $state): Serializable
│
├── JsonSerializer → JSON strings
└── PhpSerializer → PHP-serialized strings
Table of Contents¶
- The Serializable Interface
- The Serializer Interface
- Envelope Format
- JsonSerializer
- PhpSerializer
- Complete Example
The Serializable Interface¶
Fight\Common\Domain\Serialization\Serializable
Domain objects implement this interface to declare they can be serialized. It has two methods:
interface Serializable
{
public static function arrayDeserialize(array $data): static;
public function arraySerialize(): array;
}
arraySerialize()returns the object's state as a plain associative array.arrayDeserialize()is a named constructor that reconstructs the object from that array. It may throwDomainExceptionif the data is invalid.
Implement it on any domain object — value objects, entities, or configuration models:
use Fight\Common\Domain\Serialization\Serializable;
final readonly class Coordinate implements Serializable
{
private function __construct(
public float $lat,
public float $lng
) {}
public static function fromString(string $value): static
{
// ...
}
public static function arrayDeserialize(array $data): static
{
return new static($data['lat'], $data['lng']);
}
public function arraySerialize(): array
{
return ['lat' => $this->lat, 'lng' => $this->lng];
}
}
The Serializer Interface¶
Fight\Common\Domain\Serialization\Serializer
The mechanism that converts Serializable objects to and from strings:
interface Serializer
{
public function serialize(Serializable $object): string;
public function deserialize(string $state): Serializable;
}
serialize()takes anySerializableobject and returns a string representation.deserialize()takes that string and returns a new instance of the original object.
The Serializer is format-agnostic — two implementations ship with the library.
Envelope Format¶
Both serializers wrap the object's data in a structured envelope with two keys:
[
'@' => 'App.Dto.Coordinate', // canonical class name (dots, not backslashes)
'$' => ['lat' => 48.85, 'lng' => 2.35], // the arraySerialize() result
]
@— identifies which class to reconstruct during deserialization, stored as a canonical (dot-separated) class name.$— the object's serialized state, exactly as returned byarraySerialize().
During deserialization, the serializer reads @, resolves the class via ClassName::full(), verifies it implements Serializable, and calls $class::arrayDeserialize($data['$']).
JsonSerializer¶
Fight\Common\Domain\Serialization\JsonSerializer
Converts to and from JSON strings. Uses JSON_UNESCAPED_SLASHES as the default encoding.
use Fight\Common\Domain\Serialization\JsonSerializer;
$serializer = new JsonSerializer();
// Serialize
$json = $serializer->serialize($coordinate);
// '{"@":"App.Dto.Coordinate","$":{"lat":48.85,"lng":2.35}}'
// Deserialize
$restored = $serializer->deserialize($json);
// Coordinate instance
Suitable for API payloads, message queues, database JSON columns, or any interop scenario where the string needs to be human-readable and language-independent.
Throws DomainException when:
- The JSON string cannot be parsed.
- The envelope is missing the @ or $ key.
- The resolved class does not implement Serializable.
PhpSerializer¶
Fight\Common\Domain\Serialization\PhpSerializer
Uses PHP's native serialize() and unserialize() with the same envelope format. The output is PHP-specific and more compact.
use Fight\Common\Domain\Serialization\PhpSerializer;
$serializer = new PhpSerializer();
$serialized = $serializer->serialize($coordinate);
// a:2:{s:1:"@";s:18:"App.Dto.Coordinate";s:1:"$";a:2:{s:3:"lat";d:48.85;s:3:"lng";d:2.35;}}
$restored = $serializer->deserialize($serialized);
// Coordinate instance
Suitable for PHP-internal use cases: cache backends, session storage, or any context where cross-language compatibility is not needed.
Same validation as JsonSerializer — missing keys or non-Serializable classes throw DomainException.
Complete Example¶
A richer domain object that composes another Serializable and handles nested deserialization:
use Fight\Common\Domain\Serialization\JsonSerializer;
use Fight\Common\Domain\Serialization\Serializable;
final readonly class CustomerProfile implements Serializable
{
public function __construct(
public string $name,
public EmailAddress $email,
public int $loyaltyTier
) {}
public static function arrayDeserialize(array $data): static
{
return new static(
$data['name'],
EmailAddress::fromString($data['email']),
$data['loyaltyTier']
);
}
public function arraySerialize(): array
{
return [
'name' => $this->name,
'email' => $this->email->toString(),
'loyaltyTier' => $this->loyaltyTier,
];
}
}
// --- Usage ---
$serializer = new JsonSerializer();
$profile = new CustomerProfile(
'Alice',
EmailAddress::fromString('alice@example.com'),
3
);
$json = $serializer->serialize($profile);
// '{"@":"App.Dto.CustomerProfile","$":{"name":"Alice","email":"alice@example.com","loyaltyTier":3}}'
$restored = $serializer->deserialize($json);
// CustomerProfile instance, $restored->email is an EmailAddress value object
The round-trip preserves the original class and its data. The serializer handles the envelope and class resolution; the domain object controls what data goes in and how it is reconstructed.