As Drupal module maintainers, we at Nextide need to be constantly updating our modules to add new features or patch issues. Whether your module is available for download or is a custom module for a client site, you can't expect users to uninstall and reinstall it to pick up new features. If you have data or configuration changes, update hooks are mandatory to learn. This post will show how we created a new content entity in a Drupal update hook.
Our Maestro module's first release for Drupal 8 housed the core workflow engine, task console and template builder. It was a rewrite of the core engine's capabilities and was a major undertaking. However, as Drupal 8's core matures, and as our deployments of Maestro continue, new features, patches and bug fixes for Maestro are inevitable. In order to bring those new features to the core product, we had to ensure that anything we added to the module was available to existing users and deployed sites via the Drupal update routine.
One of the first new features of Maestro, not included in the initial release, is the capability to show a linear bird's eye view of the current progression through a process. This feature requires a new content entity to be created. A fresh install of Maestro would install the entity, however, for existing sites, uninstalling and reinstalling Maestro is not an option. A Drupal update hook is required to install the entity. What we found was the available documentation describing how to create a new content entity via an update hook was nearly non-existent. Some of the top Google results showing how others have solved this issue provide outdated and even dangerous methods to solve the problem.
Define Your Content Entity
The first step is to define your entity. Drupal 8's coding structure requires that you place your content entity in your /src/Entity folder. There's a good deal of documentation on how to create a content entity for Drupal 8 on module install. Follow the documentation on creating the entity's required permissions and routes based on your requirements. You can find our Maestro Process Status entity defined in code here: https://cgit.drupalcode.org/maestro/tree/src/Entity/MaestroProcessStatus.php
The Maestro Process Status Entity code in conjunction with the appropriate permissions, routes and access control handlers will install the Maestro Status Entity on module install. However, if you already have Maestro installed, configured and executing workflows, running update.php will not install this entity! Update hooks to the rescue...
Creating a Drupal 8 Update Hook
There's good documentation available on how to write update hooks, but what's missing is how to inject a new content entity for already installed modules. If you use the Google machine, you'll come across many posts and answers to this question showing the following as the (wrong) solution:
/**
* This is an example of WHAT NOT TO DO! DON'T DO THIS!
*
*/
function hook_update_8xxx() {
//For the love of Drupal, do not do this.
\Drupal::entityDefinitionUpdateManager()->applyUpdates(); //No, really, don't do this.
//Are you still trying to do this? Don't.
}
WRONG!!! DON'T DO THIS!!!
This simple update hook will most certainly pick up and install your new entity. It will also apply any other module's entity updates along with yours! This can cause catastrophic issues when modules that have not yet been updated get their entities altered by other modules. What you need to do is tell Drupal to install the new entity explicitly with this less than obvious piece of code which I will explain after showing it:
/**
* Update 8001 - Create maestro_process_status entity.
*/
function maestro_update_8001() {
//check if the table exists first. If not, then create the entity.
if(!db_table_exists('maestro_process_status')) {
\Drupal::entityTypeManager()->clearCachedDefinitions();
\Drupal::entityDefinitionUpdateManager()
->installEntityType(\Drupal::entityTypeManager()->getDefinition('maestro_process_status'));
}
else {
return 'Process Status entity already exists';
}
}
The update hook is found in the maestro.install file and I've removed some of the extra Maestro-specific code to simply show how to get your content entity recognized and installed.
- We do a simple check to see if the maestro_process_status table exists. Since content entities store their data in the database, if the table doesn't exist, our content entity is not installed.
- We clear the cached definitions from the entityTypeManager. This should force Drupal to read in all of the definitions from storage.
- Using the entityDefinitionUpdateManager (also used in the "wrong" example), we use the installEntityType method which takes an entity definition as an input.
- We pass in the maestro_process_status definition using the getDefinition method of the entityTypeManager object.
At this point, Drupal installs the entity based on the definition I showed above. Your content entity is installed, including the database table associated with the entity.