Query alteration (tagging)

Last updated on
14 October 2016

An important feature of dynamic select queries is the ability of other modules to alter them on the fly. That allows other modules to inject their own restrictions into the query, either to alter a module's behavior or to apply runtime restrictions on the query, such as node access restrictions. There are three components to query alteration: tagging, meta data, and hook_query_alter().

Only queries that have been tagged by the original creator can be altered later. Most of Drupals everyday small queries are not explicitly tagged and therefore not alterable in this way.


Tagging

Any dynamic Select query may be "tagged" with one or more strings. These tags serve to identify the type of query it is, which in turn allows alter hooks to determine if they need to take action. Tags should be an alphanumeric lowercase string, following the same rules as a PHP variable. (That is, letters, numbers, and underscores only and must begin with a letter.) To add a tag to a query, use the addTag() method:

$query->addTag('node_access');

To determine if a given query object has been tagged with a given tag, there are three methods available:

// TRUE if this query object has this tag.
$query->hasTag('example');

// TRUE if this query object has every single one of the specified tags.
$query->hasAllTags('example1', 'example2');

// TRUE if this query object has at least one of the specified tags.
$query->hasAnyTag('example1', 'example2');

Both hasAllTags() and hasAnyTag() take an arbitrary number of parameters with each tag as its own parameter. Order does not matter.

There is no hard restriction on what tags may be used, but certain standard tags are commonly used. A partial list of standardized tags is listed below:

node_access
This query should have node access restrictions placed on it; all queries that retrieve a list of nodes (or node IDs) for display to users should have this tag. However, note that when the Node module alters queries with this tag, it does not check the published/unpublished status of nodes, so your base query is responsible for ensuring that unpublished nodes are not displayed to inappropriate users.
entity_field_access
This query should have entity field access restrictions placed on it.
translatable
This query should have translatable columns.
term_access
This query should have taxonomy term-based restrictions placed on it; all queries that retrieve a list of taxonomy terms for display to users should have this tag.
views
This query is generated by the views module.
views_<view_name>
The name of the view creating this query.


Meta data

Queries may also have meta data attached to them to provide additional context to alter hooks. Meta data may be any PHP variable, and is keyed by a string.

$node = node_load($nid);
// ... Create a $query object here.
$query->addMetaData('node', $node);

Meta data has no intrinsic meaning, and on its own has no effect on the query object. It exists only to provide additional information to alter hooks, and generally only apply when the query has certain tags.

To access a given piece of meta data on a query, use the getMetaData() method.

$node = $query->getMetaData('node');

If no meta data has been assigned with that key, NULL will be returned.


hook_query_alter()

Neither tagging nor meta data do anything on their own. Both exist solely to provide information to hook_query_alter(), which can take virtually any action on a Select query.

All Dynamic Select query objects are passed through hook_query_alter() by the execute() method, immediately before the query string is compiled. That gives modules the opportunity to manipulate the query as desired. hook_query_alter() accepts a single parameter: the Select query object itself.

/**
 * Implementation of hook_query_alter().
 */
function example_query_alter(QueryAlterableInterface $query) {
  // ...
}

There is also a tag-specific alter hook, hook_query_TAG_NAME_alter(), that is called for every tag on a given Select query, after the generic one has been called. The following example is called for queries that have the tag 'sort_by_weight':

/**
 * implements hook query alter to allow ordering by weight
 * @param QueryAlterableInterface $query
 */
function MYMODULE_query_sort_by_weight_alter(QueryAlterableInterface $query) {
  $query->join('weight_weights', 'w', 'node.nid = w.entity_id');
  $query->fields('w', array('weight'));
  $query->orderBy('w.weight', 'ASC');
}

(The example above can be used in combination with the weight module.

There are two important observations to be made regarding hook_query_alter().

  1. The $query parameter is not passed by reference. Because it is an object, the object will not be duplicated anyway due to the way PHP 5 and later handles objects. Passing by reference is therefore unnecessary. The alter hook also has no return value.
  2. The parameter type is explicitly specified as QueryAlterableInterface. While not strictly necessary, explicitly specifying the parameter type provides slightly better runtime protection against passing in the wrong type of variable. The type is also specified as QueryAlterableInterface rather than simply SelectQuery to better provide forward compatibility.

The alter hook may take any action on the query object it wishes, except executing the query again as that would result in an infinite loop. The alter hook may make use of the tags and meta data associated with a query to determine what if any action to take. Module developers may either call additional methods on the query object as listed above to add additional fields, joins, conditionals, etc. to the query, or may request access to the query object's internal data structures to manipulate them directly. The former is preferred for adding new information to a query while the latter allows the alter hook to remove information from a query or manipulate instructions already queued up.

$fields =& $query->getFields();
$expressions =& $query->getExpressions();
$tables =& $query->getTables();
$order =& $query->getOrderBy();
$where =& $query->conditions();
$having =& $query->havingConditions();

It is important to note that all of the above must be returned by reference (=&) so that the alter hook is accessing the same data structure as the object. All of the above methods return an array, the general structure of which is documented in the inline documentation of SelectQuery in includes/database/select.inc.

For example, to remove an existing sort order:


$order =& $query->getOrderBy();
unset($order['n.nid']);