Content · Web Development · PHP

Building Immutable Domain Models

Building Immutable Domain Models explained through concise examples, edge cases, and practical PHP implementation guidance. Learn how to apply it with examples and common mistakes.

1 views Published Oct 17, 2026 Updated Oct 17, 2026 admin

Building Immutable Domain Models shows how to apply a focused PHP technique using understandable, runnable examples and clearly defined constraints.

What Problem Does This Solve?


Model domain invariants in constructors and methods; separate expected validation failures from infrastructure errors and avoid hiding exceptions.

Core Concepts


  • Identify the data entering the operation and the expected output.
  • Model domain invariants in constructors and methods; separate expected validation failures from infrastructure errors and avoid hiding exceptions.
  • Handle expected failures and test boundary conditions rather than assuming success.

Example 1: A Small Working Example


Start with a small input so the behavior is easy to follow.

<?php

readonly class MoneyAmount { public function __construct(public int $cents) { if ($cents < 0) { throw new InvalidArgumentException("Negative amount"); } } }
$price = new MoneyAmount(1299);
echo $price->cents;

The object validates its state at construction and prevents later modification of promoted readonly properties. This makes invalid domain values harder to create accidentally.

Example 2: A Different Case


The second example changes the task or data while using the same general technique.

<?php

readonly class EmailAddress { public function __construct(public string $value) { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException("Invalid email"); } } }
echo (new EmailAddress("[email protected]"))->value;

The object validates its state at construction and prevents later modification of promoted readonly properties. This makes invalid domain values harder to create accidentally.

Practical Example


This practical example is a complete standalone PHP snippet to adapt to a real application.

<?php

readonly class EmailAddress { public function __construct(public string $value) { if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException("Invalid email"); } } }
echo (new EmailAddress("[email protected]"))->value;
// Inspect the next distinct state after the operation.
echo "\nSecond scenario complete";

The object validates its state at construction and prevents later modification of promoted readonly properties. This makes invalid domain values harder to create accidentally.

Important Behavior and Edge Cases


  • Validate XML size and disable untrusted external resource loading. XMLReader is forward-only; DOM materializes a tree and can consume much more memory.
  • Model domain invariants in constructors and methods; separate expected validation failures from infrastructure errors and avoid hiding exceptions.
  • Test a malformed input, a missing dependency and a boundary value when those cases apply.

Comparison With Related Approaches


ApproachTrade-off
Building Immutable Domain ModelsModel domain invariants in constructors and methods; separate expected validation failures from infrastructure errors and avoid hiding exceptions.
Related PHP approachImmutable value objects protect validated state; mutable arrays are more flexible but cannot enforce invariants automatically.

Common Beginner Mistakes


MistakeCorrect Approach
Skipping a relevant boundary or security checkValidate XML size and disable untrusted external resource loading. XMLReader is forward-only; DOM materializes a tree and can consume much more memory.
Choosing a technique without considering its trade-offImmutable value objects protect validated state; mutable arrays are more flexible but cannot enforce invariants automatically.
Assuming sample data behaves like production inputTest missing fields, unexpected types, failures and the application’s actual trust boundaries.

Key Takeaways


  • Model domain invariants in constructors and methods; separate expected validation failures from infrastructure errors and avoid hiding exceptions.
  • Immutable value objects protect validated state; mutable arrays are more flexible but cannot enforce invariants automatically.
  • Write tests around the inputs and failure cases most likely in your application.

Use these examples as a starting point and adapt the validations and boundaries to the specific application.

Share this resourceSend this page to someone who may find it useful.