エクステンダ

~ Extenders 和訳 ~

セレクトクエリは「エクステンダ」という概念をサポートします。エクステンダはセレクトクエリに動的に機能を追加する方法です。これによりメソッドを新たに追加したり、既存のメソッドの振る舞いを変更したりといったことが出来ます。

オブジェクト指向のデザインパターンになぞらえるなら、エクステンダはDecoratorパターンの実装です。機能拡張のためのサブクラス化の柔軟な選択肢を提供することにより、動的にオブジェクトに追加の役割をもたせます。

エクステンダの使用

エクステンダを使用するには、対象のクエリオブジェクトの extend() メソッドを使います。これは代わりに使用される新しいオブジェクトを返します。例:

<?php
$query = $query->extend('PagerDefault');
?>

上はセレクトクエリの拡張を示しています。この場合、エクステンダはオリジナルのセレクトクエリを内包する新たなPagerDefaultクエリオブジェクトを生成し、新たなオブジェクトを返します。$queryはオリジナルのクエリと同様に使うことができますが、追加のメソッドも使えるようになっています。

$queryはその場で変更されないことに注意してください。extend() によって返された新しいオブジェクトは、変数に格納されなければそのまま失われます。例えば、次のような場合は期待する動作になりません。

<?php
$query = db_select('node', 'n');
$query
  ->fields('n', array('nid', 'title')
  ->extend('PagerDefault')   // このラインで新しい PagerDefault オブジェクトが返されます。
  ->limit(5);               // 返り値が PagerDefault オブジェクトなので、この行は動作します。

// extend() から返されたオブジェクトが変数に格納されていません。この時点で$queryは通常のセレクトクエリのままです。
$query->orderBy('title');

// これはセレクトクエリを実行しています。エクステンダではありません。エクステンダはこの時点でもう存在しません。
$result = $query->execute();
?>

この場合の解決策としては、推奨されるやり方は、セレクトクエリの最初の宣言のときに、エクステンドしてしまいます。

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

これなら間違いなく $query は最初から完全に拡張されたオブジェクトということになります。

もうひとつ、注意しておきたいのは、エクステンダは上の例のように複数組み合わせて使うことができますが、すべてのエクステンダが互いに互換性があるわけではなく、記述する順番によってはうまく動かない場合があります。例えば、ページャーのエクステンダとテーブルソートのエクステンダの両方を使うときは、ページャーのほうを最初に記述します。

エクステンダの作成

エクステンダはシンプルに SelectQueryInterface を実装するクラスです。コンストラクタには2つのパラメータ(セレクトクエリオブジェクトもしくは SelectQueryInterface を実装する他のオブジェクトと、データベースコネクションオブジェクト)を持ちます。そして SelectQueryInterface のメソッドを再実装し、コンストラクタにそれらのパラメータを渡し、必要に応じて自身を返します。

ほとんどの場合は、このように処理したいものすべてを内部に含む SelectQueryExtender クラスを継承することによって、要求を満たすことが出来るでしょう。SelectQueryExtender を継承するクラスであるゆるものが作れます。それがエクステンダです。クラスの名前は、クエリオブジェクトの extend() のコールの引数として使われます。

エクステンダクラスを作成するところまでを説明しました。では実際にメソッドを操作するにはどうすれば良いのでしょうか。メソッドで特にオーバーライドを行わないものは、内部に渡されたクエリオブジェクトを通してそのまま引き継がれます。メソッドをオーバーライドする際、元になるクエリオブジェクトをコールすることがありますが、そこでは SelectQuery インターフェースの実装と同じものが返されます。ほとんどの場合、それはクエリオブジェクトそのものであり、エクステンダにとっては、エクステンダオブジェクトそのものです。

具体的には次のようになります。

<?php
class ExampleExtender extends SelectQueryExtender {

  /**
   * 通常の orderBy の動作をオーバーライド
   */
  public function orderBy($field, $direction = 'ASC') {
    return $this;
  }

  /**
   * 自身の新しいメソッドを追加。
   */
  public function orderByForReal($field, $direction = 'ASC') {
    $this->query->orderBy($field, $direction);
    return $this;
  }
}
?>

上記の例では、エクステンダの orderBy() メソッドを無効化し、代わりにそれと同じ振る舞いをする orderByForReal() というメソッドを追加しています(もっともこれではあまり意味のある操作ではないかもしれませんが、エクステンダの振る舞いを理解するのには役立つでしょう)。どちらのメソッドも $this を返り値とすることによってエクステンダ自身を返していることに注目してください。これによってエクステンダはクエリを失うことなく確実に動作します。

すべてのモジュールは独自のエクステンダを作成できます。コアには一般的に有用とされる2つのエクステンダ、「ページャデフォルト(PagerDefault)」と「テーブルソート(TableSort)」があります。これらのクラスのAPIドキュメントでその活用方法を説明していますので、参考にしてください。

複数のデータベースのサポート

エクステンダは db_select と同じように動作しますので、接尾語としてデータベースドライバ名を指定すれば、MYSQL以外のデータベースでも動作します。

たとえばクラス名を ExampleExtender_pgsql とすれば、使用しているデータベースが PostgreSQL の場合はそれが ExampleExtender の代わりに使われます。

コア: 
Drupal7