モジュールをDBTNG(Drupal 7の新しいデータベース抽象化レイヤー)に対応させる

~ HowTo Convert a module to DBTNG 和訳 ~

DBTNGはDrupal7におけるデータベースの新しい抽象化レイヤーです。これはいくつかのオブジェクト指向の原理を使うことで、Drupal6よりもはるかに多くの機能を提供します。

Drupal6のモジュールをDBTNGで動作させるには、多くのクエリーは変更が必要です。ここではその変更点について、最も一般的なものについて説明します。

シンプルなセレクトクエリ

ほとんどのクエリはさほど多くの作業は必要ありません。通常、2つのことが必要です。

ひとつは、引数の変換です。'%s'、%dなどの引数は、:unique_nameのように変更します。そして引数の配列のキーは、このプレースホルダと同じ名前にします。ここでのルールは次のようなものです。

  • 「'」、「"」といった引用符は使わない。
  • 引数それぞれに、固有の名前(固有のプレースホルダ)を使う。
  • 使用した名前はそのまま引数の配列で使います。(例: :keys)

Example:

<?php
// Old code.
$result = db_query("SELECT n.nid, u.name  FROM {node} n  INNER JOIN {users} u ON u.uid = n.uid WHERE n.type = '%s' AND n.status = %d", array('page', 1));
// DBTNG. Note that multiple arguments should be wrapped on seperate lines.
$result = db_query("SELECT n.nid, u.name  FROM {node} n  INNER JOIN {users} u ON u.uid = n.uid WHERE n.type = :type AND n.status = :status", array(
  ':type' => 'page',
  ':status' => 1,
));
?>

2つめは、データの取り出しの部分を修正することです。単に結果をループするような場合は、シンプルにforeach構文を使うと良いでしょう。デフォルトの結果セットはオブジェクトの形式になります。別の形式が必要な場合は、2つ方法があります。

  1. fetchオプションを指定します。(例: db_query($sql, $params, array('fetch' => PDO::FETCH_ASSOC)))
  2. 結果セットの特定のデータを使用する場合。(foreach ($result->fetchAllAssoc() as $row))

例:

<?php
// Old code, loop over a result.
while ($row = db_fetch_object($result)) { 
}
// DBTNG.
foreach ($result as $row) {
}
// Old code, fetch a single field directly from a query.
$nid = db_result(db_query('...'));
// DBTNG.
$nid = db_query('...')->fetchField();
?>

静的クエリとデータ取得についての詳しい情報はhttp://drupal.org/node/310072 をご覧ください。

インサート、アップデート、デリートクエリ

これらのクエリはdb_insert()db_update() 、そして db_delete()といったDBTNG仕様の構文に修正する必要があります。

アップデートクエリの例:

<?php
// Old code.
db_query("UPDATE {profile_field} SET weight = %d, category = '%s' WHERE fid = %d", $weight, $category, $fid);
// DBTNG.
db_update('profile_field')
  ->fields(array(
    'weight' => $weight,
    'category' => $category,
  ))
  ->condition('fid', $fid)
  ->execute();
?>

クエリのパフォーマンス改善のヒント:

  1. 複数行のインサートを行う場合は、db_insert() のマルチインサート機能を使うことを考えてください。それは複数の配列の値を許可します。データベースがサポートしていれば、1回のクエリで処理が可能です。
  2. 特定のキーのデータが既に存在しているかどうかによって、新しいデータをインサートもしくは既存のデータをアップデートするような場合は、db_merge()を使うことを考えてください。

ダイナミックセレクトクエリ

DBTNGは、次のような操作を行うためのフレキシブルなクエリービルダーを提供します。

  • 他のモジュールがクエリを拡張できるようにする。(db_rewrite_sql() で実装されていた機能)
  • クエリへの条件の追加やJOIN、その他の操作をダイナミックに行う。
  • いわゆるクエリエクステンダー(下記参照)を使用する。

クエリービルダーはDBTNGの非常にパワフルな機能の一つです。これは最初は複雑に思えるかもしれませんが、クエリを文字列として組み立てるよりはるかに堅牢です。ここではクエリービルダーのコンセプトを簡単に説明します。詳しくはダイナミッククエリをご覧ください。

ダイナミッククエリを構築する場合、それは最初から始めるのがベストですが、以下のようなポイントが参考になるでしょう。クエリオブジェクトの作成を最初に行い、最後に実行するのは明らかなことですが、その他の操作、コマンドの順番はクエリ構築には影響せず、変更が可能です。

  1. 最初にクエリオブジェクトを生成します。これは簡単です。
    <?php
    $query = db_select('node', 'n');
    ?>
  2. 次にJOINメソッドを追加できます。JOINや他の多くのメソッド(「add」や「set」など)の返り値はクエリオブジェクトではなく他のもの(多くのケースではエイリアスが返される)なので、メソッドチェーンは出来ません。下の例は'node_comment_statistics'テーブルにJOINしたところです。
    <?php
    $query->join('node_comment_statistics', 'l', 'n.nid = l.nid');
    ?>
  3. ノードテーブルのクエリなので、node_accessタグを追加する必要があります。これはdb_rewrite_sql() の代替となるものです(db_rewrite_sql() はDrupal7では使われません)。リンク先のドキュメントでは、一般的に使用されるタグが含まれていますが、開発者は他のモジュールにクエリの変更を許可したい場合には、いつでもオリジナルのタグを使用することが出来ます。addTag() はメソッドチェーンが可能です。
    <?php
    $query
      ->addTag('node_access')
      ->addTag('custom_tag');
    ?>
  4. チェーンの記述が可能なメソッドを追加していきます。...
    <?php
    $query
      ->fields('n', array('nid', 'title', 'body')) // Select the specified fields from the node table.
      ->orderBy('n.nid', 'DESC') // Adds a ORDER BY n.nid DESC.
      ->condition('n.type', 'page') // Only load nodes with the type page.
      ->range(0, 5); // Load the first 5 rows.
    ?>
  5. クエリを動的に拡張することが出来ます。例えば、特定のユーザーのノードのみを表示してみます。
    <?php
    if (!empty($uid)) {
      $query->condition('n.uid', $uid);
    }
    ?>
  6. 最後に、クエリを実行して結果セットをnidをキーとした配列で受け取ります。
    <?php
    $nodes = $query
      ->execute()
      ->fetchAllAssoc('nid');
    ?>

これまでの手順をまとめて見てみましょう。なるべくメソッドチェーンで記述できるように、操作の順番を考えています。

<?php
$query = db_select('node', 'n');
$query->join('node_comment_statistics', 'l', 'n.nid = l.nid');
if (!empty($uid)) {
  $query->condition('n.uid', $uid);
}
$nodes = $query
  ->addTag('node_access')
  ->addTag('custom_tag')
  ->fields('n', array('nid', 'title', 'body')) // Select the specified fields from the node table.
  ->orderBy('n.nid', 'DESC') // Adds a ORDER BY n.nid DESC.
  ->condition('n.type', 'page') // Only load nodes with the type page.
  ->range(0, 5) // Load the first 5 rows.
  ->execute()
  ->fetchAllAssoc('nid');
?>

pager_query()の代替

pager_query() はDrupal7ではクエリーエクステンダーのページャーデフォルトに置き換わりました。ダイナミッククエリで使用することが出来ます。

クエリーエクステンダーは追加機能によってクエリービルダーを拡張します。クエリーエクステンダーは一度に複数使用することが出来ます。

例えばこのようになります。

  1. クエリを拡張します:
    <?php
    $query = db_select('node', 'n')->extend('PagerDefault');
    ?>
  2. 次に1ページに表示するコンテンツの数を設定します。
    <?php
    $query->limit(10);
    ?>

Important: extend() returns a new query object. Because of that, it is adviced to extend the query object early as shown in the example. If that is not possible, overwrite the existing object reference as shown below:

<?php
$query = $query->extend('PagerDefault');
// Chaining does still work.
$result = $query
  ->extend('PagerDefault')
  ->limit(5)
  ->execute();
?>

tablesort_sql() の代替

For table based sorting, there is another Query extender. As before, first extend the query object and then use the orderByHeader($header) method.テーブルソートには専用のクエリーエクステンダーがあります。前と同じように最初にクエリーオブジェクトを拡張し、それからorderByHeader($header) メソッドを使うようにします。

<?php
$query = db_select('node', 'n')->extend('TableSort');
$query
  ->orderBy('uid', 'DESC') // Sort by uid first.
  ->orderByHeader($header) // Then by what has been selected in the table header
  ->orderBy('changed', 'DESC') // And at last, by the changed date.
?>

コメントをどうぞ。

コア: 
Drupal7