Using Value Objects to Protect Domain Invariants 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
| Approach | Trade-off |
|---|---|
| Using Value Objects to Protect Domain Invariants | Model domain invariants in constructors and methods; separate expected validation failures from infrastructure errors and avoid hiding exceptions. |
| Related PHP approach | Immutable value objects protect validated state; mutable arrays are more flexible but cannot enforce invariants automatically. |
Common Beginner Mistakes
| Mistake | Correct Approach |
|---|---|
| Skipping a relevant boundary or security check | Validate 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-off | Immutable value objects protect validated state; mutable arrays are more flexible but cannot enforce invariants automatically. |
| Assuming sample data behaves like production input | Test 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.