Extenders

Last updated on
21 December 2016

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.