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

Also available in Русский (Russian):

  1. Часть 1 - PHP и неизменяемость. Часть 1
  2. Часть 2 - PHP и неизменяемость: экземпляры, которые могут быть изменены. Часть 2
  3. Часть 3 - PHP и неизменяемость: объекты и обобщение. Часть 3

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.

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

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.

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

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.

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

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.

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.

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.

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.

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

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.

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.

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.

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.

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.

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

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

Also available in Русский (Russian):

  1. Часть 1 - PHP и неизменяемость. Часть 1
  2. Часть 2 - PHP и неизменяемость: экземпляры, которые могут быть изменены. Часть 2
  3. Часть 3 - PHP и неизменяемость: объекты и обобщение. Часть 3

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.