Effective immutability with PHPStan – Matthias Noback

This article is about a topic that didn’t make the cut for my latest book, Recipes for Decoupling. It still contains some useful ideas I think, so here it is anyway!

DateTimeImmutable is mutable

I don’t know where I first heard it, but PHP’s DateTimeImmutable is not immutable:

<?php $dt = new DateTimeImmutable('now');
echo $dt->getTimestamp() . "\n"; $dt->__construct('tomorrow');
echo $dt->getTimestamp() . "\n";

The result:

1656927919
1656972000

Indeed, DateTimeImmutable is not really immutable because its internal state can be modified after instantiation. After calling __construct() again, any existing object reference will have the modified state as well. But an even bigger surprise might be that if a constructor is public, it’s just another method that you can call. You’re not supposed to, but you can. Which is another reason to make the constructor of non-service objects private and add a public static constructor to a class that you want to be immutable:

// If we could fix `DateTimeImmutable` ;) final class DateTimeImmutable
{ private function __construct(string $datetime) { // ... } public static function fromString(string $datetime = 'now'): self { return new self($datetime); }
}

Effective immutability

Of course, we can’t change DateTimeImmutable itself. And maybe we don’t have to either. Because, and I think most developers will agree here: nobody calls the __construct() method anyway. This is exactly the point I want to make in this article: DateTimeImmutable is already effectively immutable because nobody mutates its state. Technically, instantiating the DateTimeImmutable class results in a mutable object, but as long as we don’t use any of the state-mutating methods, we won’t experience a difference between a truly immutable object and one that is actually immutable because of its class definition.

The advantage of making an object truly immutable is that we can be certain that it never gets modified, so we won’t have to deal with any mistakes (bugs) related to unexpectedly modified state. The downside is that we have to do more work on the classes to make their instances truly immutable.

If we could “demote” from true to effective immutability it would save us a lot of work. And it would even enable us to keep using, for instance, the DateTime class, which is the mutable (and much older) alternative for DateTimeImmutable. The only problem is that effective immutability relies on developer discipline. And I’ve always found that a bad thing to rely on. We aren’t machines and we will forget one instance, make one mistake, and in the long run the code quality goes down again.

Using PHPStan to prevent calls to __construct

As programmers we are lucky to live in the Age of Static Analysis, using which we can tackle many issues related to developer discipline. In PHP there are several static analysis tools. Maybe the most commonly known ones will be Psalm and PHPStan. Since most of my experience is with PHPStan I use that one. It will analyze your code, and show you errors for anything that doesn’t look right. E.g. a call to a method that doesn’t exist, a missing argument, an argument of the wrong type, and so on. PHPStan can be extended with your own checks, called rules. One of the rules that could be quite helpful in the context of effective immutability is a rule that says you can’t call __construct() explicitly.

Considering the example we saw earlier, we want to trigger an error for calls like $dt->__construct(), but ignore any other methods calls like getTimestamp():

<?php $dt = new DateTimeImmutable('now');
echo $dt->getTimestamp() . "\n"; $dt->__construct('tomorrow');
echo $dt->getTimestamp() . "\n";

I won’t go into the details of custom rule development and the accompanying tests and test fixtures. You’ll find a step-by-step instruction in Recipes for Decoupling, or in the PHPStan documentation. Here’s the code for a rule that does the trick. I’ve added a few comments to explain what’s going on:

<?php
declare(strict_types=1); namespace Utils\PHPStan; use PhpParser

Truncated by Planet PHP, read more at the original (another 2263 bytes)

Deploy dockerized PHP Apps to production on GCP via docker compose [Tutorial Part 9] – Pascal Landau

In the ninth part of this tutorial series on developing PHP on Docker we will
deploy our dockerized PHP application to a production environment (a GCP Compute Instance VM)
and run it via docker compose as a proof of concept.