Small, strictly typed value objects for PHP applications. The package provides immutable wrappers for common scalar, identifier, date/time, and person-domain values, with validation close to the data it protects.
A primitive like int or string says nothing about what it holds. A value object gives the value a name, validates it once, and makes illegal states unrepresentable - the core idea behind value objects in Domain-Driven Design.
// Primitive obsession: any int slips through, and it is easy to swap arguments.
function register(string $firstname, string $lastname, int $age): void {}
register('Lovelace', 'Ada', -5); // compiles, runs, corrupts data
// Value objects: the type system and constructors guard the domain rules.
function register(Name $name, Age $age): void {}
register(
new Name(Firstname::fromString('Ada'), Lastname::fromString('Lovelace')),
Age::fromInt(-5), // throws ValueObjectException before reaching the domain
);Value objects are compared by value, not identity ($a->equals($b)), and never change after creation, so they can be passed around and shared safely.
- PHP 8.3, 8.4 or 8.5
ext-mbstring- Composer 2
composer require slpxxv/value-objectAll value objects are immutable, implement Stringable, and expose their native value through value(). Invalid input throws Slpxxv\ValueObject\Exception\ValueObjectException or one of its subclasses.
use Slpxxv\ValueObject\Boolean;
use Slpxxv\ValueObject\Double;
use Slpxxv\ValueObject\NonNegativeInteger;
use Slpxxv\ValueObject\PositiveInteger;
use Slpxxv\ValueObject\StringLiteral;
$enabled = Boolean::fromString('yes');
$ratio = Double::fromFloat(1.5);
$offset = NonNegativeInteger::fromInt(0);
$page = PositiveInteger::fromInt(1);
$label = StringLiteral::fromString('Slpxxv');
$enabled->value(); // true
(string) $page; // "1"
$label->length(); // 6Boolean accepts 1, true, yes, y, and on, plus their false equivalents (0, false, no, n, off). String input is case-insensitive and surrounding whitespace is ignored. Double rejects NAN and infinite values.
use Slpxxv\ValueObject\Identifier\Uuid;
$id = Uuid::generate();
$sameId = Uuid::fromString($id->value());
$binary = $id->toBytes();
$id->equals($sameId); // true
Uuid::fromBytes($binary); // equivalent UUIDUUID strings are validated and normalized to their canonical lowercase representation.
use Slpxxv\ValueObject\DateTime\DateTime;
$createdAt = DateTime::fromString('2026-08-12T12:00:00+00:00');
$expiresAt = $createdAt->modify('+30 days');
$createdAt->lessThan($expiresAt); // true
$createdAt->format(); // DateTimeInterface::ATOM by defaultThe wrapped value is a DateTimeImmutable; modifying it always creates a new value object.
use Slpxxv\ValueObject\Person\Age;
use Slpxxv\ValueObject\Person\Female;
use Slpxxv\ValueObject\Person\Firstname;
use Slpxxv\ValueObject\Person\Lastname;
use Slpxxv\ValueObject\Person\Name;
$name = new Name(
Firstname::fromString('Ada'),
Lastname::fromString('Lovelace'),
);
$age = Age::fromInt(36); // accepted range: 0..150
$gender = Female::create();
(string) $name; // "Ada Lovelace"First names support up to 50 characters and last names up to 100 characters. Available gender values are created with Female::create(), Male::create(), and Unspecified::create().
Install dependencies and run the complete quality suite:
composer install
composer checkIndividual commands are also available:
composer test # PHPUnit
composer analyse # PHPStan and Psalm
composer cs-check # coding-style check
composer cs-fix # apply coding-style fixesReleased under the MIT License.