第9章 Zend_Db

目次

9.1. Zend_Db_Adapter
9.1.1. 導入
9.1.2. SQL インジェクション対策のクォート処理
9.1.3. クエリを直接実行する
9.1.4. トランザクション
9.1.5. 行の挿入
9.1.6. 行の更新
9.1.7. 行の削除
9.1.8. 行の取得
9.2. Zend_Db_Profiler
9.2.1. 導入
9.2.2. プロファイラの使用
9.2.3. プロファイラの高度な使用法
9.3. Zend_Db_Select
9.3.1. Select オブジェクトの概要
9.3.2. Select オブジェクトの作成
9.3.3. Select クエリの作成
9.3.4. Select クエリの実行
9.3.5. その他のメソッド
9.4. Zend_Db_Table
9.4.1. テーブルクラスについて
9.4.2. テーブルクラスの定義
9.4.3. テーブルのインスタンスの作成
9.4.4. テーブルへの行の挿入
9.4.5. テーブルの行の更新
9.4.6. テーブルからの行の削除
9.4.7. 主キーによる行の検索
9.4.8. 行セットの問い合わせ
9.4.9. 単一の行の問い合わせ
9.4.10. テーブルのメタデータ情報の取得
9.4.11. テーブルのメタデータのキャッシュ
9.4.12. テーブルクラスのカスタマイズおよび拡張
9.5. Zend_Db_Table_Row
9.5.1. 導入
9.5.2. 行の取得
9.5.3. データベースへの行の書き込み
9.5.4. 行のシリアライズと復元
9.5.5. 行クラスの拡張
9.6. Zend_Db_Table_Rowset
9.6.1. 導入
9.6.2. 行セットの取得
9.6.3. 行セットからの行の取得
9.6.4. 行セットの配列としての取得
9.6.5. 行セットのシリアライズと復元
9.6.6. 行セットクラスの拡張
9.7. Zend_Db_Table Relationships
9.7.1. 導入
9.7.2. リレーションの定義
9.7.3. 従属行セットの取得
9.7.4. 親の行の取得
9.7.5. 多対多のリレーションを使用した行セットの取得
9.7.6. 書き込み操作の連鎖

9.1. Zend_Db_Adapter

9.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 でデータベースに対する問い合わせを行うことができます。

9.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")' (カンマ区切りの文字列) となります

?>

9.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();

?>

9.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();
}

?>

9.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();

?>

9.1.6. 行の更新

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

オプションで 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);

?>

UPDATE クエリで複数の WHERE 句を適用する必要がある場合は、 それらを配列形式で update() に渡すことができます。 渡した句は AND 演算子で連結されます。OR 演算子で連結する場合は手動で設定しなければなりません。

<?php
$set = array('favorite_color' => 'yellow');

// 複数の条件を AND で組み合わせます
$where   = array(
    $db->quoteInto('first_name = ?', $firstName),
    $db->quoteInto('noble_title = ?', $nobleTitle)
    );
$count   = $db->update('round_table', $set, $where);

// 複数の条件を OR で組み合わせます
$firstNameOne = $db->quote($firstNameOne);
$firstNameTwo = $db->quote($firstNameTwo);
$where        = "first_name = $firstNameOne OR first_name = $firstNameTwo";
$count        = $db->update('round_table', $set, $where);

?>
[注意] 注意

WHERE 句の値や識別子が自動的にクォートされることはありません。 クォートが必要な値を使用する場合は、それを自分自身でクォートしなければなりません。 データベースアダプタのメソッド quote()quoteInto() あるいは quoteIdentifier() を使用しましょう。

9.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);

?>

update() メソッドと同様、 WHERE 句の配列を delete() メソッドの引数 $where に指定することができます。

[注意] 注意

WHERE 句の値や識別子が自動的にクォートされることはありません。 クォートが必要な値を使用する場合は、それを自分自身でクォートしなければなりません。 データベースアダプタのメソッド quote()quoteInto() あるいは quoteIdentifier() を使用しましょう。

9.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')
);

?>