At the beginning of this chapter, we defined two tables using hook_schema() which got installed together with the module. To reiterate, if the module had already been installed, we could have triggered the schema installation using the drupal_install_schema() function. However, what if we needed to add another column later on, say to the teams table? Our module is installed, and so is the schema; so we cannot exactly uninstall it on production just to trigger the schema creation again, not to mention losing the data. Luckily, there is a system in place for this, namely update hooks— hook_update_N()—where N represents the schema version. These are sequentially named hook implementations that go inside the module *.install file and that are triggered when running the updates, either via going to /update.php or by using the drush updatedb command.
The main purpose of these update hooks is making schema alterations to existing database tables. However, partly due to the weak configuration management system in earlier versions of Drupal, they have evolved—through developer creativity—into a mechanism for updating various types of configuration or performing tasks (even content-related) upon a deployment to the next environment. Helping out with this is the $sandbox argument passed to the hook implementations, which can be used to batch these operations (to prevent an execution timeout). We will not cover this aspect here, but will instead talk about the standalone Batch API in a future chapter, lessons from which you'll be able to apply here as well. Instead, we will see how to implement such a hook to perform schema updates.
As mentioned, these hook implementations go into the *.install file. Let's see an example:
/**
* Update hook for performing an update task.
*/
function my_module_update_8001(&$sandbox) {
// Do stuff
}
The DocBlock of this hook implementation should contain a description of what it does. It is displayed when running the updates (either via the UI or using Drush).
The name of the function is one of its most important aspects. It starts with the module name, followed by update, and finally, by the module's schema version (the next one if we want this update hook to actually run); but what is a module's schema version?
When installed, Drupal sets each module a schema version: 8000. In Drupal 7, it was 7000, and in 6, it was 6000. You get the difference between the major versions of Drupal. When an update hook runs, Drupal sets that module's schema version to the number found in the update hook. So, in the previous example, it would be 8001. This is to keep track of all the update hooks and to not run them more than once. By convention, but not necessity, the second digit from the left in the schema version represents the major version number of the module itself. For example, for an 8.x-1.x version, it would be 8101.
Let's now see how we can alter our teams database table with an update hook and add a column to store a location string field. The first thing we want to do is update our hook_schema() implementation and add this information there as well. This won't do anything in our case; however, due to the way update hooks work, we need to add it there as well. What I mean by this is that if a module is first installed and it has update hooks in it already, those update hooks do not run, but the module's schema version gets set as the number of the last update hook found in it. So, if we do not add our new column inside hook_schema(), installing this module on another site (or even on the current one after an uninstall) will not get our new column in. So, we need to account for both situations.
In the field definition of our teams table schema, we can add the following column definition:
'location' => [
'description' => 'The team location.',
'type' => 'varchar',
'length' => 255,
],
It's as simple as that. Next, we can implement an update hook and add this field to the table:
/**
* Adds the "location" field to the teams table.
*/
function sports_update_8001(&$sandbox) {
$field = [
'description' => 'The team location.',
'type' => 'varchar',
'length' => 255,
];
$schema = \Drupal::database()->schema();
$schema->addField('teams', 'location', $field);
}
Here, we used the same field definition, loaded the database connection service, and used its schema object to add that field to the table. The code itself is pretty self-explanatory, but it's also worth mentioning that this is an example in which we cannot inject the service, hence we have to use it statically. So, don't feel bad about situations like this.
Next, we can use Drush to run the updates:
Sure enough, the teams table now has a new column. If you try to run the updates again, you'll note that there are none to be run because Drupal has set the schema version of the sports module to 8001. So, the next one in line to be run has to have 8002 at the end (or, something greater than 8001 and lower than 9000, in any case).
In the previous example, we added a new field to an existing table. However, we might need to create a new table entirely, or even delete one. The schema object on the database connection service has the relevant methods to do so. The following are a few examples, but I recommend that you check out the base Drupal\Core\Database\Schema class for the available methods:
$schema->createTable('new_table', $table_definition);
$schema->addField('teams', 'location', $field);
$schema->dropTable('table_name');
$schema->dropField('table_name', 'field_to_delete');
$schema->changeField('table_name', 'field_name_to_change', 'new_field_name', $new_field_definition);
There are a few cautionary aspects you need to consider when using update hooks. For example, you cannot be sure of the state of the environment before the hooks actually run, so ensure that you account for this. I recommend you check out the documentation (https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension%21module.api.php/function/hook_update_N/8.2.x) about hook_update_N() and carefully read the section about the function body.