Query alters

Lots of things in Drupal are alterable using various hooks; queries are no different. This means that if a module writes a query such as we've seen before, other modules can alter it by implementing hook_query_alter(). So let's consider an example of how this may work.

Let's assume the following query, which simply returns all player records:

$result = $database->select('players', 'p')
->fields('p')
->execute();

Imagine that another module wants to alter this query and limit the results to find only the players in a specific team. There is one problem. Our query has no markers that can indicate to another module that this is the one that needs to be altered. As you can imagine, there are a bunch of queries that are run in any given request, so identifying queries becomes impossible. Enter query tags.

The previous query would not be alterable because it's not recognizable, and therefore, hook_query_alter() is not even fired on it. In order to make it alterable, we will need to add a query tag and make it identifiable. There is a simple method on the query builder for doing just that: addTag():

$result = $database->select('players', 'p')
->fields('p')
->addTag('player_query')
->execute();

Query tags are simple strings that can be read from inside a hook_query_alter() implementation. So, we could alter the query like this:

/**
* Implements hook_query_alter().
*/
function module_name_query_alter(Drupal\Core\Database\Query\AlterableInterface $query) {
if (!$query->hasTag('player_query')) {
return;
}

// Alter query
}

The only parameter of this hook is the query object onto which we can apply our changes. It also has methods for reading the tags, such as hasTag(), hasAnyTag(), or hasAllTags(). In the previous example we took a defensive approach and simply exited if the query was not about our player_query tagged query. I'll come back to this later on.

Now, let's see how we can alter this query to achieve what we set out to do:

$query->join('teams', 't', 't.id = p.team_id');
$query->addField('t', 'name', 'team_name');
$query->condition('t.name', 'My Team');

As you can see, we are doing a similar thing to what we did before when we built our joined query. We join the team table, add its name field (as a bonus), and set a condition to only return the players in a certain team. Easy peasy.

Let's now return for a second to my remark about the defensive approach we took with this hook implementation. I personally prefer to keep methods short and return early, rather than have a bunch of unintelligible nested conditions. This is typically easy to do in an object-oriented setting. However, with procedural code, it becomes a bit more tedious as you need many private functions that are tricky to name, and even more so with hook implementations into which you might need to add more than one block of code. For example, in our hook_query_alter() implementation, we might need to add an alteration for another query later on. Also, since we return early, we need to add another condition for checking for two tags, and then some more conditions and if statements, and even more conditions (OK, rant over). From a PHP point of view, in this case you'd delegate the actual logic to another function based on the tag of the query, either using a simple switch block or if conditionals. This way, if a new tag comes, a new function can be created for it specifically and called from the switch block. However, we can do one better in this case.

There are a few hooks, particularly alter ones, that have general targeting but also a more specific one. In this example, we also have a hook_query_TAG_alter() hook, which is specific to a given tag. So, instead of us delegating to other functions, we could implement the more specific:

/**
* Implements hook_query_TAG_alter().
*/
function module_name_query_player_query_alter(Drupal\Core\Database\Query\AlterableInterface $query) {
// Sure to alter only the "player_query" tagged queries.
}

So, essentially, the tag itself becomes part of the function name, and we don't need any extra functions.