Extenders

Last updated on
19 February 2018

Select queries support the concept of "extenders". An extender is a way to add functionality to a Select query at runtime. That functionality could be additional methods or altering the behavior of existing methods.

For those familiar with object-oriented design patterns, extenders are an implementation of the Decorator Pattern. They attach additional responsibilities to an object dynamically by providing a flexible alternative to subclassing for extending functionality.

Using an Extender

To use an extender, you must first have a query object. From the query object, the extend() method will return a new object that should be used in place of the query object. For example:

$query = $query->extend('PagerDefault');

The above line takes a select query, creates a new PagerDefault query object that contains the original select query, and returns the new object. $query may now be used as if it was the original query object but with additional methods now available.

Note that $query is not altered in place. The new object is returned from extend(), and if it is not saved to a variable it will be lost. For example, the following will not do what you expect:

$query = db_select('node', 'n');
$query
  ->fields('n', array('nid', 'title'))
  ->extend('PagerDefault')   // This line returns a new PagerDefault object.
  ->limit(5);               // This line works, because the PagerDefault object is what is called.

// The return from extend() was never saved to a variable, so $query is still just the Select object.
$query->orderBy('title');

// This line executes the Select object, not the extender.  The extender no longer exists.
$result = $query->execute();

To avoid this problem, the recommended convention for extending a Select query is to do so when the query is first declared.

$query = db_select('node', 'n')->extend('PagerDefault')->extend('TableSort');
$query->fields(...);
// ...

That ensures that $query is the fully extended object right from the beginning.

Also note that while extenders may be stacked (as in the example above), not all extenders are compatible with other extenders and the order may matter. For example, a query that is extended with both a pager and table-sort behavior must be extended with PagerDefault first.

Creating new extenders

An extender is simply a class that implements the SelectQueryInterface, and takes two parameters in its constructor: A select query (or rather, another object that implements SelectQueryInterface) and a DatabaseConnection object. It must then re-implement the methods of SelectQueryInterface and pass them through to query object specified in the constructor, returning itself where appropriate.

In the vast majority of cases, all of that can be done by extending the SelectQueryExtender class which handles all of that internally. In practice, therefore, an extender is any class that extends SelectQueryExtender. The name of the class is what should be specified in the extend() call from a query object.

It is up to the extender class, then, to add or override methods as appropriate. Any method that it does not override will be transparently passed through to the wrapped query object. When overriding a method, the extender may or may not call the underlying query object but it must return the same value that is expected from the SelectQuery interface. In most cases, that is the query object itself, or for the extender the extender object itself.

The following example should make that clearer.

class ExampleExtender extends SelectQueryExtender {

  /**
   * Override the normal orderBy behavior.
   */
  public function orderBy($field, $direction = 'ASC') {
    return $this;
  }

  /**
   * Add a new method of our own.
   */
  public function orderByForReal($field, $direction = 'ASC') {
    $this->query->orderBy($field, $direction);
    return $this;
  }
}

The example above overrides the orderBy() method of a query to do nothing, but adds another method, orderByForReal(), that implements actual ordering behavior. (Naturally this is a rather pointless example, but it does serve to illustrate how extenders work.) Note that in both methods, the $this being returned is the extender object itself. That ensures that the extender doesn't "get lost" by returning the query object.

Any module may declare an Extender. Core ships with two that are generally useful: PagerDefault and TableSort. See the API documentation for those classes for how to leverage them in your own code.

Supporting multiple database types

The extend method works the same way as db_select, in that it searches for a class name that is suffixed with the database driver.

Therefore, you can define a class ExampleExtender_pgsql as well as ExampleExtender and the former will be used if applicable.

Extenders that ships with Drupal 7 Core

SearchQuery

Do a query on the full-text search index for a word or words.

This function is normally only called by each module that supports the indexed search (and thus, implements hook_update_index()).

Results are retrieved in two logical passes. However, the two passes are joined together into a single query. And in the case of most simple queries the second pass is not even used.

The first pass selects a set of all possible matches, which has the benefit of also providing the exact result set for simple "AND" or "OR" searches.

The second portion of the query further refines this set by verifying advanced text conditions (such as negative or phrase matches).

The used query object has the tag 'search_$module' and can be further extended with hook_query_alter().
 

PagerDefault

Query extender for pager queries.

This is the "default" pager mechanism.  It creates a paged query with a fixed number of entries per page.
 

TableSort

Query extender class for tablesort queries.

MigrateConnectionQuery

Query extender for retrieving the connection used on the query.