UnmarshalMapper
Unmarshalling means converting structured data into an object. Mappers::marshal() is the inverse operation.
Basic usage
Mappers::unmarshal($typeName) takes the target class as its argument. That class must provide a static method annotated with #[Unmarshal] which returns a Mapper — the mapper that actually converts the structured data into an object of that type.
An example is a simple Email value object:
class Email implements StringValueObject, \Stringable {
function __construct(private readonly string $value) {
IllegalValueException::assertTrue(ValidationUtils::isEmail($this->value),
'Invalid email: ' . $this->value);
}
#[Unmarshal]
static function unmarshalMapper(): Mapper {
return Mappers::pipe(Mappers::email(), Mappers::valueIfNotNull(fn (string $email) => new self($email)));
}
}
The pipe first validates and normalizes the input as an email, then wraps it in an Email instance. valueIfNotNull leaves null untouched.
$values = [];
Bind::values('test@email.ch', null)
->map(Mappers::unmarshal(Email::class))
->toArray($values)
->exec();
var_dump($values);
Output:
array(2) {
[0]=>
object(Email) (1) {
["value":"Email":private]=>
string(13) "test@email.ch"
}
[1]=>
NULL
}
The UnmarshalMapper verifies that the configured mapper actually produces an instance of the target class (or null); if a bindable ends up holding any other type, it throws a n2n\bind\mapper\impl\valobj\err\ValueObjectMapperCorruptedException.
ObjectUnmarshalTrait
For objects built from several properties (rather than a single scalar value), ObjectUnmarshalTrait provides a ready-made #[Unmarshal] mapper. It is based on Mappers::subPropsForClass() — it maps each property of the class by name and merges the results into a new instance (Mappers::subMergeToObject()).
class SomeObject {
use ObjectUnmarshalTrait;
public string $name;
public int $age;
}
$result = Bind::attrs(['name' => 'John Doe', 'age' => 30])
->root(Mappers::unmarshal(SomeObject::class))
->toValue()
->exec();
$obj = $result->get(); // SomeObject with $name = 'John Doe' and $age = 30
If some properties require special mapping, pass additional mappers to unmarshal(...). They run before the default property mapping. To keep using the trait while overriding the #[Unmarshal] method, alias it:
class SomeObject {
use ObjectUnmarshalTrait {
unmarshal as traitUnmarshal;
}
public string $name;
public int $age;
#[Unmarshal]
static function unmarshal(): Mapper {
return self::traitUnmarshal(Mappers::subProps()->prop('age', Mappers::value(fn ($v) => 999999)));
}
}
Here age is mapped with a custom mapper (always 999999), while name keeps the default mapping.
TypedArrayUnmarshalTrait
TypedArrayUnmarshalTrait provides an #[Unmarshal] mapper for n2n\util\col\TypedArray subclasses. It unmarshals each element using the unmarshaler of the value type declared with #[ValueType(...)], then constructs the typed array.
#[ValueType(Email::class)]
class EmailArray extends TypedArray {
use TypedArrayUnmarshalTrait;
}
$result = Bind::attrs(['emails' => ['a@b.ch', 'c@d.ch']])
->prop('emails', Mappers::unmarshal(EmailArray::class))
->toValue()
->exec();
$obj = $result->get(); // EmailArray containing two Email instances
The value type (here Email) must itself provide an unmarshaler (e.g. via #[Unmarshal]). Each element is unmarshalled with Mappers::unmarshal(...) of the value type, and mandatory validation is applied to elements when the value type is non-nullable.