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.
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 predefiend 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.
I'm an Acquia-certified Drupal Triple Expert with 18
years of experience, an open-source software maintainer and Drupal core contributor, public speaker, live streamer, and host of the Beyond Blocks podcast.