Backend Development 12 min read

Using Value Objects in PHP 8.1/8.2 to Improve Code Quality

The article explains how the Value Object pattern, combined with PHP 8.1/8.2 features like readonly properties and named arguments, can eliminate primitive‑type validation duplication, prevent parameter‑order mistakes, and ensure immutability, thereby making PHP code more robust, maintainable, and self‑documenting.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Using Value Objects in PHP 8.1/8.2 to Improve Code Quality

In the coding world, keeping code clean and robust is crucial. The Value Object pattern is a design pattern that can significantly improve code quality, making it more robust and maintainable.

This article explains how to implement the pattern using PHP 8.1 and PHP 8.2, leveraging the newest language features to add a bit of "syntactic sugar".

Common problems with primitive types

Simple data types lack built‑in validation, which can lead to unexpected values. For example, an age represented as an integer should not be negative, should not exceed a realistic maximum, and may need to be at least 18 in certain domains. Email strings must contain an @ symbol and a domain. Because validation logic is often duplicated throughout the code, inconsistencies can arise.

<code>function logic1(int $age): void {
    ($age >= 18) or throw InvalidAge::adultRequired($age);
    // Do stuff
}

function logic2(int $age): void {
    ($age >= 0) or throw InvalidAge::lessThanZero($age);
    ($age <= 120) or throw InvalidAge::matusalem($age);
    // Do stuff
}
</code>

Using a value object solves these problems by encapsulating validation and ensuring data consistency.

<code>readonly final class Age {
    public function __construct(public int $value) {
        ($value >= 18) or throw InvalidAge::adultRequired($value);
        ($value <= 120) or throw InvalidAge::matusalem($value);
    }
}
</code>
<code>function logic1(Age $age): void {
    // Do stuff
}

function logic2(Age $age): void {
    // Do stuff
}
</code>

When an Age instance exists, it is guaranteed to be valid everywhere in the code without repeated checks.

2. Parameter order confusion

When functions accept parameters of similar types, it is easy to swap their order, causing subtle bugs. For example, logic1() expects $name then $surname , while logic2() expects the opposite.

<code>function logic1(string $name, string $surname): void {
    // Logic error, $name is switched with $surname, unintentionally
    logic2($name, $surname);
}

function logic2(string $surname, string $name): void {
    // Do stuff
}
</code>

PHP 8.0 introduced named arguments, which can eliminate this class of errors.

<code>function logic1(string $name, string $surname): void {
    logic2(name: $name, surname: $surname);
}
</code>

3. Unexpected modification

Passing primitive values by reference can unintentionally modify the original variable.

<code>function logic1(int &$age): void {
    if ($age = 42) { // BUG: assignment instead of comparison
        echo "That's the answer\n";
    }
    echo "Your age is $age\n"; // Always prints 42
}
</code>

Using immutable value objects prevents such side effects.

<code>final readonly class Age {
    public function __construct(public int $value) {
        // validation
    }
}

function logic1(Age $age): void {
    if ($age->value = 42) { // will trigger a runtime error because property is readonly
        echo "That's the answer\n";
    }
    echo "Your age is {$age->value}\n"; // prints original value
}
</code>

Classes as Types

Value objects treat classes as types, encapsulating data and behavior, which gives developers better control and validation over the data.

Key Characteristics of Value Objects

1. Immutability

Once created, a value object's internal data must not change. Prior to PHP 8.1 this was achieved with private properties and getters only. PHP 8.1 introduced the readonly keyword, simplifying immutable property declarations.

<code>class Age // PHP 8.1
{
    public function __construct(public readonly int $value)
    {
        ($value >= 18) or throw InvalidAge::adultRequired($value);
        ($value <= 120) or throw InvalidAge::matusalem($value);
    }
}
</code>
<code>class Age // PHP < 8.1
{
   private int $value;

    public function __construct(int $value)
    {
        ($value >= 18) or throw InvalidAge::adultRequired($value);
        ($value <= 120) or throw InvalidAge::matusalem($value);
        $this->value = $value;
    }

    public function value(): int { return $this->value; }
}
</code>

2. Comparability

Value objects should be easy to compare for equality.

<code>// Money
public function equals(Money $money): bool {
    return $this->amount === $money->amount && $this->currency === $money->currency;
}

$thousandYen = new Money(1000, Currency::YEN);
$thousandEuro = new Money(1000, Currency::EURO);
$thousandYen->equals($thousandEuro); // false
</code>

3. Consistent Valid State

Validation should be performed inside the constructor so that an instance is always in a valid state. When using deserialization libraries, a validate method can be called after construction (e.g., via a #[PostLoad] attribute).

<code>public function __construct(public string $value) {
    $this->validate();
}

private function validate(): void {
    // perform specific validation
}
</code>

4. Debuggability

Implementing __toString() for simple value objects or toArray() for complex ones provides easy debugging output.

<code>final readonly class Name {
    public function __construct(public string $value) {}
    public function __toString(): string { return $this->value; }
}

final readonly class Person {
    public function __construct(public Name $name, public Surname $surname) {}
    public function __toString(): string { return "{$this->name} {$this->surname}"; }
    public function toArray(): array { return ['name' => (string)$this->name, 'surname' => (string)$this->surname]; }
}
</code>

Using the Value Object pattern in PHP 8.2 can dramatically improve code quality, making applications more robust, maintainable, and less error‑prone.

design patternType SafetyImmutableValue Object
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.