Null Users and System Users in Drupal

Have you ever needed to have a 'special user' to perform tasks on your Drupal site, such as performing actions based on an API request, or for sending an internal site message?

If you just create a new user, how do you identify that user going forward? Do you hard-code the 'magic' user ID in your custom code? What if the user has a different ID on different environments of your site? You could declare it in each environment's settings file and retrieve it from there, but what then if you need to do the same on another site? That would mean some duplication of code - and something that could have been abstracted and re-used.

I had to do this recently, and rather than just duplicate the code I decided to make it into it's own module - which then became two modules.

System users

The System User module provides a re-usable, generic way to denote users as 'system users', which is not specific to a certain site or environment as this is value is stored against each individual user in the database.

'System user' is a term used in Linux, which I thought also applies well to this scenario.

From https://www.ssh.com/iam/user/system-account:

A system account is a user account that is created by an operating system during installation and that is used for operating system defined purposes. System accounts often have predefined user ids. Examples of system accounts include the root account in Linux.

A system user isn't an account that we'd expect a person to log in with and perform routine tasks like updating content, but rather for the system (site) to use to perform tasks like the earlier examples.

Declaring a user as a system user

System User module adds a base field to Drupal's User entity, which determines whether or not each user is a system user - i.e. if this field is TRUE, that user is a system user. This means that users can easily be queried to identify which are system users, without having to rely on magic, environment and site specific user IDs. This also means that we can have multiple system users, if needed.

{.border .p-1}

In the Drupal 8 version of the module, a SystemUser is a custom entity, that contains it's own create method for creating new system users. This is a essentially a wrapper around User::create() that automatically sets the value of the system user field as part of the creation.

The original intention is that system users would always be created manually in an custom install or update hook, however since releasing the module, I've also added an install hook to the module to automatically create a new system user when the module is installed, basing the username on the site name.

There is also an open issue to add a Drush command to create a new system user, and I'd imagine I'll also add a Drupal Console command too.

Retrieving system users

Whilst you could easily write your own query that retrieves users based on the value of the system user field, but the module contains a SystemUserManager service that contains methods to do so. It also provides a static helper class that determines if a specified user is a system user by checking the value of the system user field.

// Retrieve the first system user.
$system_user = $this->systemUserManager->getFirst();

// Is the specified user a system user?
$is_system_user = SystemUserManager::isSystemUser($user);

But what do we return if there are no system users? You could return NULL or FALSE, but I decided to take a different approach, which became the second module.

Null users

The Null User module is an implementation of the null object pattern for users in Drupal 8. In this case, a NullUser is an extension of Drupal's AnonymousUserSession, which means that it inherits sensible defaults to return for a non-existent User. Though, through inheritance, the id, getRoles and hasPermission methods are overridden to return relevant values.

use Drupal\Core\Session\AnonymousUserSession;

class NullUser extends AnonymousUserSession {
...
}

Null User module is a dependency of System User in Drupal 8, so When no system user is found from the getFirst() method, a NullUser is returned. Whilst I could alternatively have returned NULL or FALSE, we then would need to check if the returned value was an object or not before calling methods on it.

$system_user = $this->systemUserManager->getFirst(); // Returns NULL or FALSE.

// Need to check if a user was returned or not.
if (!$system_user) {
return;
}

if ($system_user->isActive()) {
...
}

Because instead we're returning a NullUser, which through class inheritance has the same methods and properties as a regular user, there is no need to do the additional check as you will always receive a relevant object, and the expected methods will always be present.

$system_user = $this->systemUserManager->getFirst(); // Returns a NullUser.

if ($system_user->isActive()) {
...
}

This means we have less code, which also is simpler and more readable.

System User module is the only one that I'm aware of that makes use of Null User, but I've added a list to the project page so let me know if you can think of any others.

Resources

Null object pattern

Null User module

System User module