Search by

yadddl / value-object

cnastasi

Yet another Value Object library — functional validation (PHP 8.5 pipes) and structural serialize/deserialize

Package info

github.com/yadddl/value-object

pkg:composer/yadddl/value-object

Statistics

Installs: 1 430

Dependents: 1

Suggesters: 1

Stars: 5

Open Issues: 0

v2.0.1 2026-11-06 17:59 UTC

This package is auto-updated.

Last update: 2026-11-06 17:59:51 UTC


README

Yet Another DDD Library — reusable PHP value objects with functional validation (PHP 8.5 pipes) and structural serialize/deserialize in one package.

Requires PHP 8.5+.

composer require yadddl/value-object

Why

  • If a value object instance exists, it is valid
  • Validation returns T|ErrorsBag (union types — no Either monad)
  • Errors accumulate; pipelines do not stop at the first failure
  • The serializer does not validate — it calls domain create() factories
  • No application-framework dependencies

Inspired by the Value Objects in PHP 8 series, especially the functional approach.

Quick start

Define a value object

use Yadddl\ValueObject\Contracts\ValidatedValue;
use Yadddl\ValueObject\Error\ErrorsBag;
use Yadddl\ValueObject\Error\Failable;
use Yadddl\ValueObject\Error\FailableSuccess;
use Yadddl\ValueObject\Validators\IntegerValue;

readonly final class Age implements Failable, ValidatedValue
{
    use FailableSuccess;

    private function __construct(public int $value) {}

    public static function create(mixed $value): Age|ErrorsBag
    {
        $context = $value
            |> IntegerValue::from(...)
            |> IntegerValue::min(0, 'Age cannot be negative')
            |> IntegerValue::max(150, 'Age cannot exceed 150');

        return $context->isValid()
            ? new self($context->getValue())
            : $context->getErrors();
    }
}

Use it

$result = Age::create($_POST['age'] ?? null);

if ($result instanceof Age) {
    echo $result->value;
} else {
    foreach ($result->getMessages() as $message) {
        echo $message, PHP_EOL;
    }
}

// Trusted boundary:
$age = Age::create(25)->orFail();

Composite with field errors

$context = MultipleValidationContext::setup(
    name: Name::create($name),
    email: Email::create($email),
    age: Age::create($age),
);

return $context->isValid()
    ? new Person(...$context->getValues())
    : $context->getErrors(); // MultipleFieldErrorsBag

Backed enums in the validation pipeline

use Yadddl\ValueObject\Validators\BackedEnumValue;

enum ListingStatus: string
{
    case Published = 'published';
    case Draft = 'draft';
}

$status = BackedEnumValue::create(ListingStatus::class, $raw);
// ListingStatus|ErrorsBag — no throw on invalid input

$context = MultipleValidationContext::setup(
    status: $status,
    // …
);
// On success, getValues()['status'] is the enum case (not the backing string)

Serialize / deserialize

use Yadddl\Serializer\StructuralConverter;

$converter = StructuralConverter::create();

$converter->toArray($age);                      // 25
$converter->fromArray(25, Age::class);          // Age|ErrorsBag

$payload = $converter->toArray($person);
// ['name' => …, 'email' => …, 'age' => …]

$person = $converter->fromArray($payload, Person::class);
// Person|MultipleFieldErrorsBag

Documentation

Guide Contents
Getting started Install, first VO, primitives, converter
Building value objects Canonical shape, password-like rules, examples
Composites MultipleValidationContext, form errors
Validators IntegerValue / StringValue / ArrayValue / BackedEnumValue API
Errors DomainError, InvalidField / InvalidEntity, ErrorsBag, orFail(), entry-point mapping
Primitives & dates Index + summary table of all shipped types; Text/Integer/Email/Url/Uuid/Date families
Serializer Conventions, round-trips, nested graphs
Migration from 0.8 Breaking changes from YAVOL / YASL
Changelog Release notes

Runnable samples live under examples/.

Package layout

Yadddl\ValueObject\…   validators, errors, primitives, composites
Yadddl\Serializer\…    structural object ↔ array (same Composer package)

Domain code should depend on Yadddl\ValueObject only. Adapters may also use Yadddl\Serializer.

Development

composer install
composer test
composer test-coverage   # Xdebug; enforces 100% coverage
composer psalm
composer cs-check

License

MIT — see LICENSE.