第5章 Zend_Db

目次

5.1. Zend_Db_Adapter
5.1.1. 導入
5.1.2. SQL インジェクション対策のクォート処理
5.1.3. クエリを直接実行する
5.1.4. トランザクション
5.1.5. 行の挿入
5.1.6. 行の更新
5.1.7. 行の削除
5.1.8. 行の取得
5.2. Zend_Db_Profiler
5.2.1. 導入
5.2.2. プロファイラの使用
5.2.3. プロファイラの高度な使用法
5.3. Zend_Db_Select
5.3.1. 導入
5.3.2. テーブルからのカラムの取得
5.3.3. JOIN されたテーブルからのカラムの取得
5.3.4. WHERE 条件
5.3.5. GROUP BY 句
5.3.6. HAVING 条件
5.3.7. ORDER BY 句
5.3.8. 件数やオフセットによる制限
5.3.9. ページ数や件数による制限
5.4. Zend_Db_Table
5.4.1. 導入
5.4.2. さあ、はじめましょう
5.4.3. テーブル名および主キー
5.4.4. 行の挿入
5.4.5. 行の更新
5.4.6. 行の削除
5.4.7. 主キーによる行の検索
5.4.8. 単一の行の取得
5.4.9. 複数の行の取得
5.4.10. ドメインロジックの追加
5.5. Zend_Db_Table_Row
5.5.1. 導入
5.5.2. 行の取得
5.5.3. 値の変更
5.6. Zend_Db_Table_Rowset
5.6.1. 導入
5.6.2. 行セットの取得
5.6.3. 行セットの順次処理

5.1. Zend_Db_Adapter

5.1.1. 導入

Zend_Db_Adapter は、Zend Framework におけるデータベース API の抽象化レイヤーです。PDO に基づいており、 Zend_Db_Adapter を使用すると、サポートしている SQL データベースへの接続やデータベースに対する操作を同じ API で行えます。 Microsoft SQL Server、MySQL、PostgreSQL、SQLite などをサポートしています。

バックエンドのデータベースに対応した Zend_Db_Adapter のインスタンスを作成するには、接続パラメータの配列とアダプタの名前を渡して Zend_Db::factory() をコールする必要があります。 例えば、ローカルホスト上の MySQL データベース "camelot" に対して "malory" というユーザで接続するには、以下のようにします。

<?php

require_once 'Zend/Db.php';

$params = array ('host'     => '127.0.0.1',
                 'username' => 'malory',
                 'password' => '******',
                 'dbname'   => 'camelot');

$db = Zend_Db::factory('PDO_MYSQL', $params);

?>

同様に、SQLite データベース "camelot.sq3" に接続するには、以下のようにします。

<?php

require_once 'Zend/Db.php';

$params = array ('dbname' => 'camelot.sq3');

$db = Zend_Db::factory('PDO_SQLITE', $params);

?>

同様に、SQLite2 データベース "camelot.sq2" に接続するには、以下のようにします。 メモリ上の sqlite データベースの場合は dsnprefix を指定せず、 dbname に ":memory:" を使用します。

<?php

require_once 'Zend/Db.php';

$params = array ('dbname' => 'camelot.sq2',
                 'dsnprefix' => 'sqlite2');

$db = Zend_Db::factory('pdoSqlite', $params);

?>

どの場合についても、まったく同じ API でデータベースに対する問い合わせを行うことができます。

5.1.2. SQL インジェクション対策のクォート処理

SQL 文で使用する値は、常にクォートしなければなりません。 これにより、SQL インジェクション攻撃を防ぎます。 Zend_Db_Adapter では、値のクォート処理を補助するためのメソッドを 2 つ (元となる PDO オブジェクト経由で) 提供しています。

ひとつめは quote() メソッドです。 これは、データベースアダプタにあわせた適切な形式に、 スカラー値をクォートします。配列をクォートしようとすると、 配列の値をカンマ区切りの文字列に展開して返します。 その際に個々の値は適切にクォートされます (リストパラメータをとる関数などで有用です)。

<?php

// $db オブジェクトを作成します。ここでは Mysql アダプタを使用しているとします。

// スカラーをクォートします
$value = $db->quote('St John"s Wort');
// $value は '"St John\"s Wort"' となります (クォートで囲まれていることに注意しましょう)

// 配列をクォートします
$value = $db->quote(array('a', 'b', 'c');
// $value は '"a", "b", "c"' (カンマ区切りの文字列) となります

?>

ふたつめが quoteInto() メソッドです。 疑問符のプレースホルダを使用した文字列を用意し、 スカラーあるいは配列をクォートしたものをそこに代入します。 これは、クエリや条件を動的に作成する場合に有用です。 スカラーおよび配列の動作は quote() メソッドとまったく同じです。

<?php
    
// $db オブジェクトを作成します。ここでは Mysql アダプタを使用しているとします。

// スカラーをクォートして WHERE 句に使用します
$where = $db->quoteInto('id = ?', 1);
// $where は 'id = "1"' となります (クォートで囲まれていることに注意しましょう)

// 配列をクォートして WHERE 句に使用します
$where = $db->quoteInto('id IN(?)', array(1, 2, 3));
// $where は 'id IN("1", "2", "3")' (カンマ区切りの文字列) となります

?>

5.1.3. クエリを直接実行する

Zend_Db_Adapter のインスタンスを作成すると、 SQL を直接使用したクエリを実行することができます。 Zend_Db_Adapter が、元になっている PDO オブジェクトにクエリを渡し、準備と実行を行い、その結果を PDOStatement オブジェクトとして返します。もし結果が存在すれば、 それを操作することが可能です。

<?php
    
// $db オブジェクトを作成し、適切にクォートした SQL 文で
// データベースに問い合わせます
$sql = $db->quoteInto(
    'SELECT * FROM example WHERE date > ?',
    '2006-01-01'
);
$result = $db->query($sql);

// PDOStatement の $result から、すべての行を配列形式で取得します
$rows = $result->fetchAll();

?>

クエリに対して、データを自動的にバインドすることもできます。 つまり、名前つきのプレースホルダをクエリに設定したうえで、 そのプレースホルダを置き換えるデータを配列として渡すということです。 置き換える際にデータが適切にクォートされるので、 SQL インジェクション攻撃に対してより安全になります。

<?php
    
// $db オブジェクトを作成し、データベースに問い合わせます。
// 今回はプレースホルダのバインドを使用します。
$result = $db->query(
    'SELECT * FROM example WHERE date > :placeholder',
    array('placeholder' => '2006-01-01')
);

// PDOStatement の $result から、すべての行を配列形式で取得します
$rows = $result->fetchAll();

?>

状況によっては、準備したデータを SQL に手動でバインドしたいこともあるでしょう。 そのためには、prepare() メソッドを使用して PDOStatement を準備し、それを直接手動で操作します。

<?php
    
// $db オブジェクトを作成し、データベースに問い合わせます。
// 今回は PDOStatement を用意して手動でバインドします。
$stmt = $db->prepare('SELECT * FROM example WHERE date > :placeholder');
$stmt->bindValue('placeholder', '2006-01-01');
$stmt->execute();

// PDOStatement の $result から、すべての行を配列形式で取得します
$rows = $stmt->fetchAll();

?>

5.1.4. トランザクション

デフォルトでは、PDO は (つまり Zend_Db_Adapter も) 「自動コミット」モードで動作します。 これは、すべてのクエリは実行した時点でコミットされるということを意味します。 トランザクションを使用したい場合には、単純に beginTransaction() メソッドをコールします。 その後に変更内容を commit() あるいは rollBack() します。beginTransaction() が再びコールされるまで、 Zend_Db_Adapter は自動コミットモードに戻ります。

<?php
    
// $db オブジェクトを作成し、トランザクションを開始します。
$db->beginTransaction();

// クエリを実行します。
// 成功した場合には変更内容をコミットし、
// 失敗した場合にはロールバックします。
try {
    $db->query(...);
    $db->commit();
} catch (Exception $e) {
    $db->rollBack();
    echo $e->getMessage();
}

?>

5.1.5. 行の挿入

insert() メソッドを使用すると、 INSERT 文の作成および挿入するデータのバインドを行ってくれます (バインドされるデータは自動的にクォートされるので、 SQL インジェクション攻撃への対策として有用です)。

返される値は、追加した行の ID ではありません。 テーブルには自動インクリメントのカラムがないからです。そのかわりに、 変更された行の数 (通常は 1) が返されます。追加された行の ID を知りたい場合は、lastInsertId() メソッドをコールします。

<?php
    
//
// INSERT INTO round_table
//     (noble_title, first_name, favorite_color)
//     VALUES ("King", "Arthur", "blue");
//

// $db オブジェクトを作成し、そして
// 挿入する行のデータを カラム名 => 値 形式にします
$row = array (
    'noble_title'    => 'King',
    'first_name'     => 'Arthur',
    'favorite_color' => 'blue',
);

// 行を挿入するテーブル
$table = 'round_table';

// 行を挿入し、その行の ID を取得します
$rows_affected = $db->insert($table, $row);
$last_insert_id = $db->lastInsertId();

?>

5.1.6. 行の更新

update() メソッドを使用すると、 UPDATE 文の作成および更新するデータのバインドを行ってくれます (バインドされるデータは自動的にクォートされるので、 SQL インジェクション攻撃への対策として有用です)。

オプションで WHERE 句を指定することで、どの行を更新するのかを指定できます (WHERE 句はバインドパラメータではないことに注意しましょう。 値のクォートは自分で行う必要があります)。

<?php
    
//
// UPDATE round_table
//     SET favorite_color = "yellow"
//     WHERE first_name = "Robin";
//

// $db オブジェクトを作成し、そして
// 更新する新しいデータを カラム名 => 値 形式にします
$set = array (
    'favorite_color' => 'yellow',
);

// 更新するテーブル
$table = 'round_table';

// WHERE 句
$where = $db->quoteInto('first_name = ?', 'Robin');

// テーブルを更新し、更新された行数を取得します
$rows_affected = $db->update($table, $set, $where);

?>

5.1.7. 行の削除

delete() メソッドを使用すると、 DELETE 文の作成を行ってくれます。オプションで WHERE 句を指定することで、どの行を削除するのかを指定できます (WHERE 句はバインドパラメータではないことに注意しましょう。 値のクォートは自分で行う必要があります)。

<?php
    
//
// DELETE FROM round_table
//     WHERE first_name = "Patsy";
//

// $db オブジェクトを作成し、そして
// データを削除するテーブルを指定します
$table = 'round_table';

// WHERE 句
$where = $db->quoteInto('first_name = ?', 'Patsy');

// テーブルを更新し、更新された行数を取得します
$rows_affected = $db->delete($table, $where);

?>

5.1.8. 行の取得

query() メソッドを使用してデータベースに直接問い合わせることもできますが、 普通は単に行を選択して結果を取得できれば十分でしょう。 それを行うのが fetch*() 系のメソッドです。 fetch*() 系のメソッドに対しては、SQL の SELECT 文を渡します。 もし文の中で名前つきプレースホルダを使用した場合は、 そこにバインドする値も配列として渡します。 この値は、クォート処理を行ったあとで文中のプレースホルダと置き換えられます。 fetch*() 系のメソッドには以下のようなものがあります。

  • fetchAll()

  • fetchAssoc()

  • fetchCol()

  • fetchOne()

  • fetchPairs()

  • fetchRow()

<?php
    
// $db オブジェクトを作成し、そして

// すべての行のすべてのカラムを配列として取得します
$result = $db->fetchAll(
    "SELECT * FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// すべての行のすべてのカラムを連想配列として取得します
// 最初のカラムが配列のキーとして使用されます
$result = $db->fetchAssoc(
    "SELECT * FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// すべての行の最初のカラムを取得します
$result = $db->fetchCol(
    "SELECT first_name FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// 最初の値のみを取得します
$result = $db->fetchOne(
    "SELECT COUNT(*) FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// キーと値の組み合わせを順に取得します。最初のカラムが
// 配列のキー、2 番目のカラムが配列の値となります
$result = $db->fetchPairs(
    "SELECT first_name, favorite_color FROM round_table WHERE noble_title = :title",
    array('title' => 'Sir')
);

// 返される最初の行のみを取得します
$result = $db->fetchRow(
    "SELECT * FROM round_table WHERE first_name = :name",
    array('name' => 'Lancelot')
);

?>