Tools for building rich domain primitives in PHP.
composer require fasano/phprimitivesPHP has no base class for scalars - so there's nowhere to hook validation, enforce invariants, or express domain meaning. PHPrimitives fills that gap with 4 base classes:
Simply extend the base classes, and implement the validate method. Throw an InvalidArgumentException if the value is invalid.
readonly class Email extends AbstractString
{
protected static function validate(string $value): void
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email address: ' . $value);
}
}
}Primitives enforce invariants at the edges of your application. Validate at boundaries, trust everywhere else. Primitives convert to/from scalars at your application edges:
# Input boundary - scalar to primitive
$age = new Age($request->input('age')); # Throws if invalid
$email = new Email($request->input('email')); # Throws if invalid# Everywhere else - work with primitives
function createUser(Email $email, Age $age): User
{
# Type system guarantees correctness
# No defensive checks required
}# Output boundary - primitive to scalar
$repository->save([
'email' => $email->value,
'age' => $age->value,
]);Without primitives, validation scatters throughout your codebase:
function createUser(string $email, int $age): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { /* ... */ }
if ($age < 0 || 120 < $age) { /* ... */ }
# Repeat in every function that touches these values
}Key benefits:
- Invariants enforced at construction — Invalid data cannot enter your domain
- Type safety through nominal typing — Can't pass
AgewhereTemperatureis expected, even though both wrap integers - Domain concepts expressed clearly —
Emailis more meaningful thanstring
Primitives implement JsonSerializable out of the box:
echo json_encode(new Person(
new Name('John'),
new Age(18),
new Email('john@example.com'),
));
// {"name":"John","age":18,"email":"john@example.com"}The wrapping and unwrapping of values into primitive objects can be automated fairly trivially. You can find very simple libs to do so in Symfony and Doctrine below, along with a full example project that uses no scalars anywhere.
| Package | Description |
|---|---|
| phprimitives-doctrine | Doctrine DBAL type mappings |
| phprimitives-symfony | Symfony Serializer integration |
| phprimitives-example | An in-context example usage of PHPrimitives |
