Domain · Application
Serialization
Let Domain objects own their array state while Application serializers choose a durable string format and reconstruction policy.
Serialization separates a Domain object's stable array representation from the Application policy that encodes and reconstructs it. Implement the Domain contracts on objects that can be round-tripped, then select the canonical Application serializer in the composition root.
Ownership. Serializable and Serializer are Domain contracts. The canonical
Application\Serialization\JsonSerializer and Application\Serialization\PhpSerializer implement
those contracts. The former Domain\Serialization implementations remain only as deprecated 1.x
compatibility surfaces; consumers own format versioning, storage, transport, and migration policy.
Dependencies. Serialization requires PHP 8.5+ and this package; no framework package is required.
Install.
Start with the canonical JSON serializer.
use Fight\Common\Application\Serialization\JsonSerializer;
use Fight\Common\Domain\Serialization\Serializable;
/** @var Serializable $profile */
$serializer = new JsonSerializer();
$payload = $serializer->serialize($profile);
$restored = $serializer->deserialize($payload);
Both formats require an envelope containing @ (the canonical class name) and $ (the array
state), and reject a missing envelope or a class that does not implement Serializable with
DomainException. The class name directs hydration through arrayDeserialize(): use only trusted,
authenticated payloads and keep version or allowlist policy in the consuming application.
PhpSerializer calls PHP unserialize() and is therefore suitable only for trusted PHP-internal
data; prefer JSON for interoperable payloads.
1.x serializer compatibility¶
Fight\Common\Domain\Serialization\JsonSerializer and
Fight\Common\Domain\Serialization\PhpSerializer remain deprecated compatibility classes in 1.x
and are scheduled for removal in 2.0. New code must import the equivalent
Fight\Common\Application\Serialization class; the Domain Serializable and Serializer
interfaces remain the stable contract.
Reference¶
Two cooperating layers keep serialization boundaries explicit: the Domain Serializable interface
lets objects supply and restore their state, while canonical Application Serializer
implementations convert that state to and from string formats. The legacy concrete serializers in
Domain\Serialization are deprecated 1.x compatibility classes, not the recommended API.
Serializable (interface)
│ arraySerialize(): array
│ arrayDeserialize(array $data): static
│
└── YourDomainObject implements Serializable
Serializer (Domain interface)
│ serialize(Serializable $object): string
│ deserialize(string $state): Serializable
│
└── Application\Serialization\JsonSerializer → JSON strings
Application\Serialization\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\Application\Serialization\JsonSerializer
Converts to and from JSON strings. Uses JSON_UNESCAPED_SLASHES as the default encoding.
use Fight\Common\Application\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\Application\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\Application\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 only for trusted PHP-internal use cases where cross-language compatibility is not needed.
Because it calls PHP unserialize(), never use it on untrusted input.
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\Application\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.