How to Create Immutable Objects in PHP

Updated: January 10, 2024 By: Guest Contributor Post a comment

Overview

Immutable objects are objects whose state cannot be modified after they are created. This concept can be beneficial for writing more predictable and reliable software, and you can implement it in PHP using certain principles and techniques. This tutorial will guide you through these techniques, from basic to advanced, to help you create immutable objects in PHP.

Introduction to Immutability

Before diving into the how-to, let’s clarify what immutable objects are and why they are important. An immutable object is an instance of a class that cannot be changed once it’s created. Any method that might alter its state will return a new instance of the class with the changed values. This is in contrast to mutable objects, which can be modified after creation.

Immutability is a core concept in many functional programming languages, and it’s valued because it leads to easier-to-understand code, and reduces the risk of side effects and concurrency issues. In PHP, which is predominantly a mutable language, achieving immutability requires discipline and clear design patterns.

Creating Basic Immutable Objects

To start creating an immutable object, you must design your class so that once an object is constructed, its state can’t change. A basic approach might look something like this:

<?php
class ImmutablePoint {
    private float $x;
    private float $y;

    public function __construct(float $x, float $y) {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX(): float {
        return $this->x;
    }

    public function getY(): float {
        return $this->y;
    }

    public function moveTo(float $newX, float $newY): ImmutablePoint {
        return new self($newX, $newY);
    }
}

// Usage
$point = new ImmutablePoint(1.0, 2.0);
$newPoint = $point->moveTo(3.0, 4.0);
?>

In the above example, ImmutablePoint doesn’t offer any methods that change its state after it’s instantiated. Methods that require a state change return a new instance instead.

Handling Internal Arrays

When dealing with arrays or objects within our immutable class, we must ensure that these too are immutable, or they can lead to inadvertent side effects. One common mistake is to provide direct access to these internal structures, like this:

// Incorrect way to expose internal arrays - MUTABLE
class MutableWrapper {
    private array $data;

    public function __construct(array $data) {
        $this->data = $data;
    }

    public function getData(): array {
        return $this->data; // This is mutable
    }

    ... // Other code
}

// Correct way using immutability pattern
class ImmutableWrapper {
    private array $data;

    public function __construct(array $data) {
        $this->data = $data;
    }

    public function getData(): array {
        return $this->data;
    }

    public function withData(array $newData): ImmutableWrapper {
        return new self(array_merge($this->data, $newData));
    }
}
?>

In the corrected example, any change to data results in a new object being returned with the modified array, leaving the original object’s array untouched.

Advanced Immutability Patterns

For more advanced immutability, you can implement a class that handles its dependencies in an immutable way. This means that not only the properties of the class but also any referenced objects must be immutable. To achieve this, make sure you use immutable objects as dependencies or make defensive copies when necessary.

<?php
class ImmutableDependency {
    private ImmutablePoint $point;

    ... // Other code

    public function __construct(ImmutablePoint $point) {
        // Make a copy to ensure immutability
        $this->point = new ImmutablePoint($point->getX(), $point->getY());
    }

    ... // Other code
}

// Usage
$originalPoint = new ImmutablePoint(1.0, 2.0);
$dependencyObject = new ImmutableDependency($originalPoint);

// $originalPoint remains unchanged even if $dependencyObject changes internally
?>

By using this pattern, you effectively guarantee the immutability of your object’s entire object graph.

Using Immutability in Practice

In practice, immutability can be used to create robust, maintainable, and concurrent software. PHP 8 introduced the readonly keyword, which can make enforcing immutability somewhat easier. Additionally, consider using value objects, embrace returning new instances instead of mutating existing ones, favor composition over inheritance, and when needed, make deep copies of mutable dependencies.

Conclusion

Immutability in PHP is a powerful concept that, when implemented, can make your code more predictable and easier to understand. While PHP does not natively support immutability, you can achieve it through careful class design and object composition. By following the principles illustrated in this tutorial, you’ll be well on your way to writing safer and more reliable PHP applications.