Skip to main content

PipeMapper

Chains multiple mappers (and validators) so they run in sequence on the same bindables — each mapper sees the values produced by the previous one. Mappers::pipe() itself counts as a single mapper.

Mappers::pipe() accepts Mapper or Validator arguments; validators are wrapped automatically. Mappers can also be appended with add(), and pipes can be nested.

If a mapper returns a not-ok MapResult — for instance a closure mapper returning false — the pipe stops and later mappers are not run. Mappers that only record validation errors still return ok, so the pipe continues; the first failing validation is the one reported per bindable.

Example: chaining mappers​

$result = Bind::values(' test@email.ch ', null)->map(Mappers::pipe(
Mappers::email(),
Mappers::valueIfNotNull(fn (string $v) => strtoupper($v)),
))->toArray()->exec();
var_dump($result->get());

Output:

array(2) {
[0]=>
string(13) "TEST@EMAIL.CH"
[1]=>
NULL
}

Mappers::email() normalizes and validates the input, then Mappers::valueIfNotNull() transforms the validated value. null passes through both. This validate-then-construct pattern is also how value objects are built — see UnmarshalMapper.

Example: validation in a pipe​

$result = Bind::attrs(['email1' => 'not-an-email', 'email2' => 'also-bad'])
->props(['email1', 'email2'], Mappers::pipe(
Mappers::email(),
Mappers::valueIfNotNull(fn (string $v) => strtoupper($v)),
))
->toArray()
->exec();

// result will be invalid with error messages provided for all email properties.
var_dump($result->isValid()); // false
var_dump($result->getErrorMap()->getChild('email1')->isEmpty()); // false due to invalid email
var_dump($result->getErrorMap()->getChild('email2')->isEmpty()); // false due to invalid email