Decoupling your security user from your user model – Matthias Noback

This article shows an example of framework decoupling. You’ll find a more elaborate discussion in my latest book, Recipes for Decoupling.

Why would it be nice to decouple your user model from the framework’s security user or authentication model?

Reason 1: Hexagonal architecture

I like to use hexagonal architecture in my applications, which means among other things that the entities from my domain model stay behind a port. They are never exposed to, for instance, a controller, or a template. Whenever I want to show anything to the user, I create a dedicated view model for it.

We can take the hexagonal approach in any software application, including ones that use a web framework like Symfony or Laravel. However, these frameworks may sometimes require you to expose your internal entities or model objects. One example is the authentication layer. In order to authenticate as a user, the framework wants to know which users you have in your application, and what their hashed passwords are. With Symfony, the standard solution is to equate the security user with the User entity and use the Entity user provider. If you use hexagonal architecture, this isn’t great, since the application now has to expose its entities, even though it wanted to keep them inside.

Reason 2: CQRS

Another reason why you may not like to use your User entity as the security user is that it’s a write model. If you apply CQRS to your application, you don’t want to use objects for both write and read use cases. The User entity is a write model, but the security user required by Symfony is a read model; it won’t be used to make changes to the user’s state (like changing its password), it only exposes data like the user’s email address, hashed password, and their roles. When using CQRS you’re not supposed to reuse the same object for both of these use cases.

Reason 3: Different models

One last reason for not using your User entity as a security user is that they really are a different model, in the sense that they have different properties, offer different behaviors, and just serve different purposes. Furthermore, I’ve found that in real-world applications has a mostly overlapping set of users and security users, but they aren’t per definition the same. For example, you may want some kind of super-admin login, but you don’t necessarily want to have an “admin” User entity because then it also needs an email address, a phone number, a nickname, a public profile, and so on. This demonstrates a typical case of model incompatibility, where we try to fit one into another, and keep pushing until it somewhat works.

Solution: create your own user provider

We can deal with all these possible concerns at once by implementing our own user provider. Symfony has offered this option since the first release of version 2. I actually blogged about it in 2011 which is already 11 years ago. I also contributed to the original cookbook page on the topic, but the best way to find out how to do it today is to look at the current documentation page of course. I must say, it’s very cool that Symfony has this option, because it allows us to keep the user entity behind the port (if we use hexagonal architecture), to return a read model instead of a write model as the security user (if we want to do CQRS), and to return different/fewer/more users than we have in our database. Powerful stuff!

Maybe you don’t work with Symfony, so here’s the outline of the general solution:

  • Instead of passing your actual User entity or model to the framework, pass an object that matches the API that the framework expects (e.g. that implements a SecurityUser interface or offers the expected methods like getPassword() etc.)
  • Define your own implementation for the interface of the service that normally returns the security user to the framework (e.g. SecurityUserRepository), and return your new custom security user object from it instead of the User entity/model.

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)