Being a weakly typed dynamic language, PHP has not really had the concept of immutability built into it. We’ve seen the venerable define() and CONSTANTS of course, but they’re limited. Whilst PHP does ship with an immutable class as part of it’s standard library, DateTimeImmutable, there is no immediately obvious method to create custom immutable objects.

This article is part of a series I have written on the topic of immutability in PHP code:

  1. Part one - a discussion of caveats and a simple scalar handling immutable
  2. Part two - improve the process of creating modified copies of the immutable
  3. Part three - objects in immutable data structures and a generalised immutable implementation

Constants

The only immutable data store available in PHP is constants, which are set with define() or the const keyword on a PHP class. There is one important difference between the two options though.

Class constants (const) are set when the code is written and cannot be changed at runtime. This property makes them PHP’s most immutable user defined structure. If you wanted to set a different value conditionally or set a value from another variable you’re out of luck.

class Immutable {
    const TRICK = 'kickflip';
    
    public function __construct() {
        echo static::TRICK;     // kickflip
        static::TRICK = 'HHHH'; // Parse error unexpected '='
    }
}
new Immutable();

Traditional constants, set with define(), can be initialised conditionally and be built from other variables - once set though, they are immutable.

$skater = 'Mullen';

if ($skater === 'Mullen') {
    define('TRICK', $skater . ' created the flatground Ollie');
} else if ($skater === 'Hawk') {
    define('TRICK', $skater . ' invented the Kickflip McTwist');
}
echo TRICK; // Mullen created the flatground Ollie
define('TRICK', 'nothing'); // Notice: Constant TRICK already defined
echo TRICK; // Mullen created the flatground Ollie;

As you can see, if you try to modify the constant then you’ll get a PHP notice - the value though will remain unchanged. You can check if a constant is already defined with the function defined() - note the extra d!

So, that works well for simple scalars. If you want to store any kind of array structure as an immutable though you’ll have to use a class constant or be disappointed by any version of PHP before PHP 7.

define('TRICKS', [
    'Ollie',
    'Darkslide',
    'Heelflip',
    'Nollie'
]);
// Warning: Constants may only evaluate to scalar values

If you’re feeling smug and running PHP7 then, well, I have some bad news - arrays work, sure, but objects not so much!

define('TRICKS', new stdClass());
// Warning: Constants may only evaluate to scalar values or arrays

There are other reasons that you probably don’t want a constant anyway.

In the case of class constants we’ve seen how limited they are with their requirement to be set before runtime. You get a some scoping though with them bound to the class definition in question.

Traditional constants are defined globally. Is that really what you want? Surely you’d rather have a proper variable that you can pass around between functions and that adheres to PHP’s scoping rules.

Both of these methods result in weird syntax usage, where a far simpler variable syntax would be more appropriate and likely expected.

Custom immutable classes

It is possible to write your own immutables using some simple and sneaky PHP techniques though. We’re going to use a simplistic data requirement to make the examples in this article easier to follow. I’ll be using professional skateboarders and the tricks that they brought to the world.

First up, is a simple immutable PHP class that accepts it’s values as parameters to the constructor. I have called the class Immutable, but this is probably a bad idea in non-trivial code as it may become a reserved word in the future. Right now though it has absolutely no significance beyond being a nice name to give the class.

class Immutable {
    private $skater, $trick;

    public function __construct($skater, $trick) {
        $this->skater = $skater;
        $this->trick = $trick;
    }
    
    public function getSkater() {
        return $this->skater;
    }

    public function getTrick() {
        return $this->trick;
    }
}

By using private class properties here we have ensured that the values cannot be changed by code outside of the Immutable class definition.

$x = new Immutable('Hawk', 'Frontside 540');
echo $x->skater = 'Mullen'; // Fatal error: Cannot access private property Immutable::$skater

To allow external access the values bound up into an Immutable object is simply a case of writing a public method that returns the required value. In our example here we have two class properties we want access to so I have added getSkater() to get the skater’s name and getTrick() to get the name of the trick they invented.

This gives you a very simple immutable class that once initialised cannot be changed externally.

Avoiding mutations

It is important that no methods are added to Immutable that will allow values inside the class to be mutated. Obviously you do not want to be writing public methods into the class like setSkater() or setTrick() as these would allow an implementer to mutate our mutable class.

class Immutable {
    ...
    // WRONG: don't do this!
    public function setSkater($skater) {
        $this->skater = $skater;
    }
    ...
}
$x = new Immutable('Mullen', '50-50 Sidewinder');
echo $x->getSkater(); // Mullen
$x->setSkater('Hawk'); // Argh! No! You're MUTATING!
echo $x->getSkater(); // Hawk

Now we have a working example and understanding of an immutable object in PHP it’s time to declare that there are a couple of other pit falls here though too. Sorry! You didn’t really expect it to be that easy did you?

Stopping circumventions

A lot of people out there don’t like immutability as much as you and I. They’ll do everything in their power to circumvent our very specifically designed immutable objects.

Overriding the immutable variable

The easiest way to get around intended immutability in PHP is simply to override the variable the immutable instance is assigned to.

$a = new Immutable('Mullen', 'Casper slide');
$a = new TrickRecord('Mullen', 'Airwalk');

When the code is run $a will be silently overridden by a new TrickRecord instead of throwing an error as we would hope. This is a symptom of PHP’s history and the intended behaviour. Of course being a naughty developer you can be sure they’ve not made TrickRecord immutable!

As of writing there is no way to prevent this from happening in PHP. As we know from before objects can’t be assigned to a constant (only scalars and in PHP 7 arrays). In JavaScript we now have the const keyword that would prevent this from happening, but no such luck in PHP.

The only way that you might be able to prevent this is to use type hints everywhere that you expect an Immutable to be passed in, but as we’ll see there are ways around this too!

Multiple calls to the constructor

Another simple way of frustrating the immutable is to simply call the constructor again with different parameters.

$x = new Immutable('Hawk', 'Frontside 540');
echo $x->getSkater(); // Hawk
$x->__construct('Song', 'Frontside 540');
echo $x->getSkater(); // Song

Obviously, this is a pain and highly undesirable. Luckily, we can work around this problem by including a flag in our class to prevent mutations. I’d just like to mention here that Daewon Song is a great skater - it’s just that he didn’t invent the frontside 540 so we cannot allow it.

class Immutable {
    private $skater, $trick;
    private $mutable = true;

    public function __construct($skater, $trick) {
        if (false === $this->mutable) {
            throw new \BadMethodCallException('Constructor called twice.');
        }
        $this->skater = $skater;
        $this->trick = $trick;
        $this->mutable = false;
    }
    
    public function getSkater() {
        return $this->skater;
    }

    public function getTrick() {
        return $this->trick;
    }
}

With this change multiple calls to the constructor will not be allowed and throw a fatal exception.

$x = new Immutable('Hawk', 'Frontside 540');
echo $x->getSkater(); // Hawk
$x->__construct('Song', 'Darkslide');
// Fatal error: Uncaught BadMethodCallException: Constructor called twice.

Extending the Immutable class

It is also possible for a developer to write a class that extends our Immutable. This would allow them to overload our constructor with their own.

Once they have their own constructor they can assign $skater and $trick to their own public class properties. This breaks the immutability of the class as public properties can be changed. Similarly they could add new methods to set the value of their properties too.

class NaughtyDev extends Immutable {
    public $mySkater, $myTrick;

    public function __construct($skater, $trick) {
        $this->mySkater = $skater;
        $this->myTrick = $trick;
    }

    public function setTrick($trick) {
        $this->myTrick = $trick;
    }
}

As NaughtyDev extends Immutable any type checking done on an instance will pass. Here a sneaky developer is passing us an instance of NaughtyDev where our code wants an Immutable, but we’re none the wiser.

$x = new NaughtyDev('Hawk', '900');
$x instanceof Immutable; // true

function onlyGiveMeAnImmutable(Immutable $z) {
    return $z;
}
onlyGiveMeAnImmutable($x); // does not throw a type error

It is all perfectly valid code and the expected behaviour of PHP. We’re doing something strange by insisting on an immutable. So that’s a fun little caveat to the whole process.

There is a way around this one with PHP’s final keyword though. Final tells the parser that the class in question is complete and must not be extended with other functionality.

With one small change to our Immutable class we can prevent this attack on the immutable.

final class Immutable {
    private $skater, $trick;
    private $mutable = true;

    public function __construct($skater, $trick) {
        if (false === $this->mutable) {
            throw new \BadMethodCallException('Constructor called twice.');
        }
        $this->skater = $skater;
        $this->trick = $trick;
        $this->mutable = false;
    }
    
    public function getSkater() {
        return $this->skater;
    }

    public function getTrick() {
        return $this->trick;
    }
}

class NaughtyDev extends Immutable {

}
// Fatal error: Class NaughtyDev may not inherit from final class (Immutable)

So now we know that we need a class that is declared final and uses private properties to store our data. It is important to ensure that no methods in Immutable allow changes to any values after the class is instantiated.

Finally, a more complete example

To finish off let’s go through a final working example of an immutable object in PHP. In this example PHP 7 type hinting has been added as well.

declare(strict_types=1);

final class SkateboardTrick {
    private $inventor, $trickName;
    private $mutable = true;
    
    public function __construct(string $skater, string $trick) {
        if (false === $this->mutable) {
            throw new \BadMethodCallException('Constructor called twice.');
        }
        $this->inventor = $skater;
        $this->trickName = $trick;
        $this->mutable = false;
    }

    public function getInventor(): string {
        return $this->inventor;
    }

    public function getTrickName(): string {
        return $this->trickName;
    }
}

The strict type declaration and the use of function argument hinting further fortifies the immutable class. We don’t unexpectedly get given an object for instance. This would be bad because that object could be altered elsewhere, which would change the contents of our immutable - bad! More on this in a later article.

Conclusion

The methods and properties defined in the final class SkateboardTrick ensure that the object is immutable and cannot be extended. This is, of course, our goal.

$x = new SkateboardTrick('Mullen', '540 Shove-it');
echo $x->getInventor(); // Mullen
echo $x->getTrickName(); // 540 Shove-it

$x->inventor = 'Hawk'; // Fatal error: Cannot access private property SkateboardTrick::$inventor

$x->__construct('Hawk', $x->getTrickName());
// Fatal error: Uncaught BadMethodCallException: Constructor called twice.

This means that if you need to alter a value in SkateboardTrick you’ll have to create a new instance with the modified values.

$x = new SkateboardTrick('Mullen', 'Ollie');
echo $x->getInventor(); // Mullen
echo $x->getTrickName(); // Ollie

$z = new SkateboardTrick(
    $x->getInventor(),
    $x->getTrickName() . ' fingerflip'
);
echo $z->getInventor(); // Mullen
echo $z->getTrickName(); // Ollie fingerflip

In my next article I will cover how we can optimise the process of creating new instances with modified information contained within them. They’ll still be immutable of course, just with a little more sugar to make things a touch easier.

This article is part of a series I have written on the topic of immutability in PHP code:

  1. Part one - a discussion of caveats and a simple scalar handling immutable
  2. Part two - improve the process of creating modified copies of the immutable
  3. Part three - objects in immutable data structures and a generalised immutable implementation

If you like this article then you might get a kick out of writing functional php code as taught in the Functional Programming in PHP book that I wrote.