第9章 Zend_Db

目次

9.1. Zend_Db_Adapter
9.1.1. アダプタを使用したデータベース接続
9.1.2. サンプルデータベース
9.1.3. クエリ結果の読み込み
9.1.4. Writing Changes to the Database
9.1.5. Quoting Values and Identifiers
9.1.6. Controlling Database Transactions
9.1.7. Listing and Describing Tables
9.1.8. Closing a Connection
9.1.9. Running Other Database Statements
9.1.10. Notes on Specific Adapters
9.2. Zend_Db_Statement
9.2.1. ステートメントの作成
9.2.2. ステートメントの実行
9.2.3. SELECT 文からの結果の取得
9.3. Zend_Db_Profiler
9.3.1. 導入
9.3.2. プロファイラの使用
9.3.3. プロファイラの高度な使用法
9.4. Zend_Db_Select
9.4.1. Select オブジェクトの概要
9.4.2. Select オブジェクトの作成
9.4.3. Select クエリの作成
9.4.4. Select クエリの実行
9.4.5. その他のメソッド
9.5. Zend_Db_Table
9.5.1. テーブルクラスについて
9.5.2. テーブルクラスの定義
9.5.3. テーブルのインスタンスの作成
9.5.4. テーブルへの行の挿入
9.5.5. テーブルの行の更新
9.5.6. テーブルからの行の削除
9.5.7. 主キーによる行の検索
9.5.8. 行セットの問い合わせ
9.5.9. 単一の行の問い合わせ
9.5.10. テーブルのメタデータ情報の取得
9.5.11. テーブルのメタデータのキャッシュ
9.5.12. テーブルクラスのカスタマイズおよび拡張
9.6. Zend_Db_Table_Row
9.6.1. 導入
9.6.2. 行の取得
9.6.3. データベースへの行の書き込み
9.6.4. 行のシリアライズと復元
9.6.5. 行クラスの拡張
9.7. Zend_Db_Table_Rowset
9.7.1. 導入
9.7.2. 行セットの取得
9.7.3. 行セットからの行の取得
9.7.4. 行セットの配列としての取得
9.7.5. 行セットのシリアライズと復元
9.7.6. 行セットクラスの拡張
9.8. Zend_Db_Table Relationships
9.8.1. 導入
9.8.2. リレーションの定義
9.8.3. 従属行セットの取得
9.8.4. 親の行の取得
9.8.5. 多対多のリレーションを使用した行セットの取得
9.8.6. 書き込み操作の連鎖

9.1. Zend_Db_Adapter

Zend_Db とその関連クラス群は、Zend Framework において SQL データベースとのインターフェイスを担当します。 Zend_Db_Adapter は、PHP アプリケーションから RDBMS に接続する際に使用する基本クラスです。 RDBMS の種類に応じて、それぞれ個別のアダプタクラスがあります。

Zend_Db のアダプタは、 ベンダ固有の PHP 拡張モジュールを共通インターフェイスにとりまとめる役割を果たします。 これにより、いちど書いた PHP アプリケーションが ほんの少しの手間で複数 RDBMS に対応するようになります。

アダプタクラスのインターフェイスは、 PHP Data Objects 拡張モジュールのインターフェイスと似ています。 Zend_Db では、次の RDBMS 用の PDO ドライバに対するアダプタクラスを用意しています。

  • MySQL

  • Microsoft SQL Server

  • Oracle

  • PostgreSQL

  • SQLite

さらに、Zend_Db では、 以下の RDBMS 用の拡張モジュールを使用するアダプタクラスも用意しています。

  • MySQL (mysqli を使用します)

  • Oracle (oci8 を使用します)

  • IBM DB2 (ibm_db2 を使用します)

[注意] 注意

Zend_Db のアダプタは、どれも PHP の拡張モジュールを使用しています。 Zend_Db のアダプタを使用するには、 対応する PHP 拡張モジュールが使用できる環境でなければなりません。 たとえば、PDO 系の Zend_Db アダプタを使用するのなら、 PDO 拡張モジュールが必要で、 また使用する RDBMS 用の PDO ドライバも必要となります。

9.1.1. アダプタを使用したデータベース接続

ここでは、データベースアダプタのインスタンスを作成する方法を説明します。 これは、PHP アプリケーションから RDBMS サーバへの接続を確立することに対応します。

9.1.1.1. Zend_Db アダプタのコンストラクタの使用

コンストラクタを使用して、アダプタのインスタンスを作成することができます。 アダプタのコンストラクタが受け取る引数はひとつで、 接続を確立するために必要なパラメータを配列で渡します。

例 9.1. アダプタのコンストラクタの使用

<?php

require_once 'Zend/Db/Adapter/Pdo/Mysql.php';

$db = new Zend_Db_Adapter_Pdo_Mysql(array(
    'host'     => '127.0.0.1',
    'username' => 'webuser',
    'password' => 'xxxxxxxx',
    'dbname'   => 'test'
));

9.1.1.2. Zend_Db のファクトリの使用

コンストラクタを直接使用する以外の方法として、静的メソッド Zend_Db::factory() を使用することもできます。 このメソッドは、必要に応じて Zend_Loader::loadClass() を使用して動的にアダプタクラスを読み込みます。

最初の引数には、アダプタクラスのベース名を文字列で指定します。 たとえば、文字列 'Pdo_Mysql' は Zend_Db_Adapter_Pdo_Mysql クラスに対応します。その次の引数は、 アダプタのコンストラクタに指定するのと同じ形式の配列となります。

例 9.2. ファクトリメソッドの使用

<?php

require_once 'Zend/Db.php';

// 自動的に Zend_Db_Adapter_Pdo_Mysql クラスを読み込み、そのインスタンスを作成します
$db = Zend_Db::factory('Pdo_Mysql', array(
    'host'     => '127.0.0.1',
    'username' => 'webuser',
    'password' => 'xxxxxxxx',
    'dbname'   => 'test'
));
[注意] 注意

独自に Zend_Db_Adapter_Abstract の派生クラスを作成し、そのクラス名のプレフィックスが "Zend_Db_Adapter" でない場合は、factory() でそのアダプタを読み込むことはできません。 自分でクラスを読み込み、 コンストラクタを使用してインスタンスを作成する必要があります。

9.1.1.3. アダプタのパラメータ

Zend_Db のアダプタクラスで使用できるパラメータを以下にまとめます。

  • host: データベースサーバのホスト名あるいは IP アドレス。 データベースが PHP アプリケーションと同じホスト上で動いている場合は、 'localhost' あるいは '127.0.0.1' を指定します。

  • username: RDBMS サーバへの接続時に使用する認証用アカウントの ID。

  • password: RDBMS サーバへの接続時に使用する認証用パスワード。

  • dbname: RDBMS サーバ上のデータベースインスタンス名。

  • port: RDBMS サーバによっては、管理者が指定した ポート番号によるネットワーク接続を許可しているものもあります。 このパラメータを使用すると、PHP アプリケーションが接続時に使用するポート番号を指定できます。 これは RDBMS サーバの設定にあわせなければなりません。

  • options: このパラメータは、すべての Zend_Db_Adapter クラスで共通のオプションを連想配列で指定します。

  • driver_options: このパラメータは、各データベース拡張モジュールに固有の 追加オプションを連想配列で指定します。 典型的な使用例としては、PDO ドライバの属性などがあります。

例 9.3. ファクトリでの大文字小文字変換オプションの指定

このオプションは、定数 Zend_Db::CASE_FOLDING で指定します。 これは、PDO や IBM DB2 データベースドライバにおける ATTR_CASE 属性に対応するもので、 結果セットの文字列キーの大文字小文字変換を指定します。 設定できる値は Zend_Db::CASE_NATURAL (デフォルト)、 Zend_Db::CASE_UPPER および Zend_Db::CASE_LOWER のいずれかです。

<?php

$options = array(
    Zend_Db::CASE_FOLDING => Zend_Db::CASE_UPPER
);

$params = array(
    'host'           => '127.0.0.1',
    'username'       => 'webuser',
    'password'       => 'xxxxxxxx',
    'dbname'         => 'test',
    'options'        => $options
);

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

例 9.4. ファクトリでの自動クォートオプションの指定

このオプションは、定数 Zend_Db::AUTO_QUOTE_IDENTIFIERS で指定します。 この値が true (デフォルト) の場合は、 アダプタが生成する SQL 文中のテーブル名やカラム名、 エイリアスといった識別子をすべてクォートします。これにより、 SQL のキーワードや特殊文字を含む識別子を使用できるようになります。 この値が false の場合は、 識別子の自動クォートは行いません。 クォートすべき文字を使用している場合は、自分で quoteIdentifier() メソッドをコールする必要があります。

<?php

$options = array(
    Zend_Db::AUTO_QUOTE_IDENTIFIERS => false
);

$params = array(
    'host'           => '127.0.0.1',
    'username'       => 'webuser',
    'password'       => 'xxxxxxxx',
    'dbname'         => 'test',
    'options'        => $options
);

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

例 9.5. ファクトリでの PDO ドライバのオプションの指定

<?php

$pdoParams = array(
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
);

$params = array(
    'host'           => '127.0.0.1',
    'username'       => 'webuser',
    'password'       => 'xxxxxxxx',
    'dbname'         => 'test',
    'driver_options' => $pdoParams
);

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

echo $db->getConnection()->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY);

9.1.1.4. 遅延接続の管理

アダプタクラスのインスタンスを作成した時点では、 まだ RDBMS サーバへの接続は行われません。 接続用のパラメータを内部で保持しておき、 実際にクエリを実行することになった時点で初めて接続を確立します。 これにより、アダプタオブジェクトをすばやく作成できるようになっています。 つまり、そのリクエストの中で 実際にデータベースクエリを発行するかどうかが確定していなくても、 とりあえずアダプタのインスタンスを作成しておくということもできます。

強制的に RDBMS との接続を確立する必要がある場合は、 getConnection() メソッドを使用します。 このメソッドは、対応する PHP 拡張モジュール用の接続オブジェクトを返します。 たとえば、PDO ドライバ系のアダプタクラスを使った場合は、 getConnection() は データベースとの接続を確立してから PDO オブジェクトを返します。

データベースへの接続時に発生する例外、 すなわち ID やパスワードの間違いなどを捕捉したい場合に、 これは役立つでしょう。 実際に接続を行うまで例外はスローされないので、 どこか一か所に例外処理をまとめておいたほうが アプリケーションがシンプルになって便利でしょう。

例 9.6. 接続時の例外処理

<?php

try {
    $db = Zend_Db::factory('Pdo_Mysql', $parameters);
    $db->getConnection();
} catch (Zend_Db_Adapter_Exception $e) {
    // ID かパスワードが間違っている、あるいは RDBMS が起動していないなど……
} catch (Zend_Exception $e) {
    // factory() が指定したアダプタクラスを読み込めなかったなど……
}

9.1.2. サンプルデータベース

Zend_Db クラスのドキュメントでは、 クラスやメソッドの使用法を説明するために単純なデータベースを使用します。 これは、とあるソフトウェア開発プロジェクトにおけるバグ管理を想定したものです。 次の 4 つのテーブルで構成されています。

  • accounts テーブルには、 バグ管理データベースを使用するユーザについての情報を格納します。

  • products テーブルには、 バグを記録する対象となる製品についての情報を格納します。

  • bugs テーブルには、 バグについての情報を格納します。 バグの状態や報告者、修正担当者、検証担当者などの情報が含まれます。

  • bugs_products テーブルには、 バグと製品の関連付け情報を格納します。 これは、いわゆる多対多のリレーションシップを実装するものです。 ひとつのバグが複数の製品に関連するものであることもあれば、 当然ひとつの製品には複数のバグが存在するからです。

このサンプルデータベースを作成するための SQL の擬似コードは、次のようになります。 これらのテーブルは、Zend_Db 自体のユニットテストでも使用します。

CREATE TABLE accounts (
  account_name      VARCHAR(100) NOT NULL PRIMARY KEY
);

CREATE TABLE products (
  product_id        INTEGER NOT NULL PRIMARY KEY,
  product_name      VARCHAR(100)
);

CREATE TABLE bugs (
  bug_id            INTEGER NOT NULL PRIMARY KEY,
  bug_description   VARCHAR(100),
  bug_status        VARCHAR(20),
  reported_by       VARCHAR(100) REFERENCES accounts(account_name),
  assigned_to       VARCHAR(100) REFERENCES accounts(account_name),
  verified_by       VARCHAR(100) REFERENCES accounts(account_name)
);

CREATE TABLE bugs_products (
  bug_id            INTEGER NOT NULL REFERENCES bugs,
  product_id        INTEGER NOT NULL REFERENCES products,
  PRIMARY KEY       (bug_id, product_id)
);
        

また、bugs テーブルには accounts テーブルを指す外部キー参照が複数含まれることにも注意しましょう。 それぞれの外部キーは、そのバグに対して accounts テーブルの別々の行を参照することもあります。

サンプルデータベースの物理データモデルは、次の図のようになります。

9.1.3. クエリ結果の読み込み

ここでは、SELECT クエリを実行してその結果を取得するための アダプタのメソッドについて説明します。

9.1.3.1. 結果セット全体の取得

SQL の SELECT クエリの実行とその結果の取得を一度に行うには fetchAll() メソッドを使用します。

このメソッドの最初の引数には、SELECT 文を文字列で指定します。 あるいは、文字列ではなく Zend_Db_Select クラスのオブジェクトを指定することもできます。 オブジェクトを渡した場合は、アダプタの内部でそれを自動的に SELECT 文の文字列に変換します。

fetchAll() でその次に指定する引数は、 SQL 文中のパラメータプレースホルダを置換する値の配列となります。

例 9.7. fetchAll() の使用

<?php

$sql = 'SELECT * FROM bugs WHERE bug_id = ?';

$result = $db->fetchAll($sql, 2);

9.1.3.2. フェッチモードの変更

デフォルトでは、fetchAll() は行の配列を返します。各行のデータは連想配列となります。 連署配列のキーは、列名かあるいは SELECT クエリで指定した列の別名となります。

返り値の形式を別のものにするには setFetchMode() メソッドを使用します。 対応しているモードは、以下の定数で表されるものです。

  • Zend_Db::FETCH_ASSOC: データを、連想配列の配列として返します。 配列のキーは、カラム名を文字列で表したものとなります。 これは、Zend_Db_Adapter クラスのデフォルトのフェッチモードです。

    select で取得する一覧の中に同名のカラムが含まれている場合 (たとえば複数テーブルを JOIN した場合など) は、その名前のエントリはひとつしか含まれません。 FETCH_ASSOC モードを使用する場合は、 SELECT クエリでカラムの別名を指定するなどして、 結果の配列におけるキーが一意になるようにしなければなりません。

    デフォルトでは、これらの文字列はデータベースドライバから返されるものをそのまま使用します。 通常は、これは RDBMS サーバでのカラム名となるでしょう。 大文字小文字を指定するには、Zend_Db::CASE_FOLDING オプションを使用します。これは、 アダプタのインスタンスを作成する際に使用します。 例 9.3. 「ファクトリでの大文字小文字変換オプションの指定」 を参照ください。

  • Zend_Db::FETCH_NUM: データを、配列の配列で返します。 配列は数値添字形式となり、クエリで指定した順番での位置がキーとなります。

  • Zend_Db::FETCH_BOTH: データを、配列の配列で返します。 配列のキーは、FETCH_ASSOC モードで用いる文字列と FETCH_NUM モードで用いる数値の両方を含んだものとなります。 配列の要素数が、FETCH_ASSOC や FETCH_NUM の場合の倍になることに注意しましょう。

  • Zend_Db::FETCH_COLUMN: データを、値の配列で返します。 配列の各要素の値は、結果セットのあるひとつのカラムの値となります。 デフォルトでは、これは最初の (0 番目の) カラムとなります。

  • Zend_Db::FETCH_OBJ データを、オブジェクトの配列で返します。 デフォルトのクラスは、PHP の組み込みクラス stdClass となります。結果セットのカラムは、このクラスのプロパティとしてアクセスできます。

例 9.8. setFetchMode() の使用

<?php

$db->setFetchMode(Zend_Db::FETCH_OBJ);

$result = $db->fetchAll('SELECT * FROM bugs WHERE bug_id = ?', 2);

// $result はオブジェクトの配列となります
echo $result[0]->bug_description;

9.1.3.3. 連想配列形式での結果セットの取得

fetchAssoc() メソッドは、 フェッチモードの設定にかかわらず、 結果のデータを連想配列の配列で返します。

例 9.9. fetchAssoc() の使用

<?php

$db->setFetchMode(Zend_Db::FETCH_OBJ);

$result = $db->fetchAssoc('SELECT * FROM bugs WHERE bug_id = ?', 2);

// $result は、フェッチモードの指定とは関係なく連想配列の配列となります
echo $result[0]['bug_description'];

9.1.3.4. 結果セットの単一のカラムの取得

fetchCol() メソッドは、 フェッチモードの設定にかかわらず、 結果のデータを値の配列で返します。 これは、クエリの最初のカラムのみを返します。 それ以外のカラムの内容は破棄されます。 別のカラムが必要な場合は 項9.2.3.4. 「結果セットからの単一のカラムの取得」 を参照ください。

例 9.10. fetchCol() の使用

<?php

$db->setFetchMode(Zend_Db::FETCH_OBJ);

$result = $db->fetchCol('SELECT bug_description, bug_id FROM bugs WHERE bug_id = ?', 2);

// bug_description を含み、bug_id は含みません
echo $result[0];

9.1.3.5. 結果セットからの キー/値 のペアの取得

fetchPairs() メソッドは、データを キー/値 のペア (連想配列) の配列で返します。 この連想配列のキーは、SELECT クエリが返す最初のカラムの値となります。 また、連想配列の値は、SELECT クエリが返す二番目のカラムの値となります。 クエリから返されるその他のカラムは破棄されます。

SELECT クエリをうまく設計し、最初のカラムの値が一意になるようにしなければなりません。 もし最初のカラムに重複する値があれば、連想配列のエントリが上書きされてしまいます。 overwritten.

例 9.11. fetchPairs() の例

<?php

$db->setFetchMode(Zend_Db::FETCH_OBJ);

$result = $db->fetchAAssocssoc('SELECT bug_id, bug_status FROM bugs');

echo $result[2];

9.1.3.6. 結果セットからの単一の行の取得

fetchRow() メソッドは、 結果のデータを現在のフェッチモードで返します。 ただ、返すのは結果セットから取得した最初の行のみです。

例 9.12. fetchRow() の使用

<?php

$db->setFetchMode(Zend_Db::FETCH_OBJ);

$result = $db->fetchRow('SELECT * FROM bugs WHERE bug_id = 2');

// $result はオブジェクトの配列ではなく、単なるオブジェクトとなります
echo $result->bug_description;

9.1.3.7. 結果セットからの単一のスカラー値の取得

fetchOne() メソッドは fetchRow()fetchCol() を組み合わせたようなものです。 結果セットからの最初の行から、最初のカラムの値のみを返します。 したがって、このメソッドの返り値は配列やオブジェクトではなく単一のスカラー値となります。

例 9.13. fetchOne() の使用法

<?php

$result = $db->fetchOne('SELECT bug_status FROM bugs WHERE bug_id = 2');

// this is a single string value
echo $result;

9.1.4. Writing Changes to the Database

You can use the Adapter class to write new data or change existing data in your database. This section describes methods to do these operations.

9.1.4.1. Inserting Data

You can add new rows to a table in your database using the insert() method. The first argument is a string that names the table, and the second argument is an associative array, mapping column names to data values.

例 9.14. Inserting to a table

<?php

$data = array(
    'created_on'      => '2007-03-22',
    'bug_description' => 'Something wrong',
    'bug_status'      => 'NEW'
);

$db->insert('bugs', $data);

Columns you exclude from the array of data are not specified to the database. Therefore, they follow the same rules that an SQL INSERT statement follows: if the column has a DEFAULT clause, the column takes that value in the row created, otherwise the column is left in a NULL state.

By default, the values in your data array are inserted using parameters. This reduces risk of some types of security issues. You don't need to apply escaping or quoting to values in the data array.

You might need values in the data array to be treated as SQL expressions, in which case they should not be quoted. By default, all data values passed as strings are treated as string literals. To specify that the value is an SQL expression and therefore should not be quoted, pass the value in the data array as an object of type Zend_Db_Expr instead of a plain string.

例 9.15. Inserting expressions to a table

<?php

$data = array(
    'created_on'      => new Zend_Db_Expr('CURDATE()'),
    'bug_description' => 'Something wrong',
    'bug_status'      => 'NEW'
);

$db->insert('bugs', $data);

9.1.4.2. Retrieving a Generated Value

Some RDBMS brands support auto-incrementing primary keys. A table defined this way generates a primary key value automatically during an INSERT of a new row. The return value of the insert() method is not the last inserted ID, because the table might not have an auto-incremented column. Instead, the return value is the number of rows affected (usually 1).

If your table is defined with an auto-incrementing primary key, you can call the lastInsertId() method after the insert. This method returns the last value generated in the scope of the current database connection.

例 9.16. Using lastInsertId() for an auto-increment key

<?php

$db->insert('bugs', $data);

// return the last value generated by an auto-increment column
$id = $db->lastInsertId();

Some RDBMS brands support a sequence object, which generates unique values to serve as primary key values. To support sequences, the lastInsertId() method accepts two optional string arguments. These arguments name the table and the column, assuming you have followed the convention that a sequence is named using the table and column names for which the sequence generates values, and a suffix "_seq". This is based on the convention used by PostgreSQL when naming sequences for SERIAL columns. For example, a table "bugs" with primary key column "bug_id" would use a sequence named "bugs_bug_id_seq".

例 9.17. Using lastInsertId() for a sequence

<?php

$db->insert('bugs', $data);

// return the last value generated by sequence 'bugs_bug_id_seq'.
$id = $db->lastInsertId('bugs', 'bug_id');

// alternatively, return the last value generated by sequence 'bugs_seq'.
$id = $db->lastInsertId('bugs');

If the name of your sequence object does not follow this naming convention, use the lastSequenceId() method instead. This method takes a single string argument, naming the sequence literally.

例 9.18. Using lastSequenceId()

<?php

$db->insert('bugs', $data);

// return the last value generated by sequence 'bugs_id_gen'.
$id = $db->lastSequenceId('bugs_id_gen');

For RDBMS brands that don't support sequences, including MySQL, Microsoft SQL Server, and SQLite, the arguments to the lastInsertId() method are ignored, and the value returned is the most recent value generated for any table by INSERT operations during the current connection. For these RDBMS brands, the lastSequenceId() method always returns null.

[注意] Why not use "SELECT MAX(id) FROM table"?

Sometimes this query returns the most recent primary key value inserted into the table. However, this technique is not safe to use in an environment where multiple clients are inserting records to the database. It is possible, and therefore is bound to happen eventually, that another client inserts another row in the instant between the insert performed by your client application and your query for the MAX(id) value. Thus the value returned does not identify the row you inserted, it identifies the row inserted by some other client. There is no way to know when this has happened.

Using a strong transaction isolation mode such as "repeatable read" can mitigate this risk, but some RDBMS brands don't support the transaction isolation required for this, or else your application may use a lower transaction isolation mode by design.

Furthermore, using an expression like "MAX(id)+1" to generate a new value for a primary key is not safe, because two clients could do this query simultaneously, and then both use the same calculated value for their next INSERT operation.

All RDBMS brands provide mechanisms to generate unique values, and to return the last value generated. These mechanisms necessarily work outside of the scope of transaction isolation, so there is no chance of two clients generating the same value, and there is no chance that the value generated by another client could be reported to your client's connection as the last value generated.

9.1.4.3. Updating Data

You can update rows in a database table using the update() method of an Adapter. This method takes three arguments: the first is the name of the table; the second is an associative array mapping columns to change to new values to assign to these columns.

The values in the data array are treated as string literals. See 項9.1.4.1. 「Inserting Data」 for information on using SQL expressions in the data array.

The third argument is a string containing an SQL expression that is used as criteria for the rows to change. The values and identifiers in this argument are not quoted or escaped. You are responsible for ensuring that any dynamic content is interpolated into this string safely. See 項9.1.5. 「Quoting Values and Identifiers」 for methods to help you do this.

The return value is the number of rows affected by the update operation.

例 9.19. Updating rows

<?php

$data = array(
    'updated_on'      => '2007-03-23',
    'bug_status'      => 'FIXED'
);

$n = $db->update('bugs', $data, 'bug_id = 2');

If you omit the third argument, then all rows in the database table are updated with the values specified in the data array.

If you provide an array of strings as the third argument, these strings are joined together as terms in an expression separated by AND operators.

例 9.20. Updating rows using an array of expressions

<?php

$data = array(
    'updated_on'      => '2007-03-23',
    'bug_status'      => 'FIXED'
);

$where[] = "reported_by = 'goofy'";
$where[] = "bug_status = 'OPEN'";

$n = $db->update('bugs', $data, $where);

// Resulting SQL is:
//  UPDATE "bugs" SET "update_on" = '2007-03-23', "bug_status" = 'FIXED'
//  WHERE ("reported_by" = 'goofy') AND ("bug_status" = 'OPEN')

9.1.4.4. Deleting Data

You can delete rows from a database table using the delete() method. This method takes two arguments: the first is a string naming the table.

The second argument is a string containing an SQL expression that is used as criteria for the rows to delete. The values and identifiers in this argument are not quoted or escaped. You are responsible for ensuring that any dynamic content is interpolated into this string safely. See 項9.1.5. 「Quoting Values and Identifiers」 for methods to help you do this.

The return value is the number of rows affected by the delete operation.

例 9.21. Deleting rows

<?php

$n = $db->delete('bugs', 'bug_id = 3');

If you omit the second argument, the result is that all rows in the database table are deleted.

If you provide an array of strings as the second argument, these strings are joined together as terms in an expression separated by AND operators.

9.1.5. Quoting Values and Identifiers

When you form SQL queries, often it is the case that you need to include the values of PHP variables in SQL expressions. This is risky, because if the value in a PHP string contains certain symbols, such as the quote symbol, it could result in invalid SQL. For example, notice the imbalanced quote characters in the following query:

$name = "O'Reilly";
$sql = "SELECT * FROM bugs WHERE reported_by = '$name'";

echo $sql;
// SELECT * FROM bugs WHERE reported_by = 'O'Reilly'

Even worse is the risk that such code mistakes might be exploited deliberately by a person who is trying to manipulate the function of your web application. If they can specify the value of a PHP variable through the use of an HTTP parameter or other mechanism, they might be able to make your SQL queries do things that you didn't intend them to do, such as return data to which the person should not have privilege to read. This is a serious and widespread technique for violating application security, known as "SQL Injection" (see http://en.wikipedia.org/wiki/SQL_Injection).

The Zend_Db Adapter class provides convenient functions to help you reduce vulnerabilities to SQL Injection attacks in your PHP code. The solution is to escape special characters such as quotes in PHP values before they are interpolated into your SQL strings. This protects against both accidental and deliberate manipulation of SQL strings by PHP variables that contain special characters.

9.1.5.1. Using quote()

The quote() method accepts a single argument, a scalar string value. It returns the value with special characters escaped in a manner appropriate for the RDBMS you are using, and surrounded by string value delimiters. The standard SQL string value delimiter is the single-quote (').

例 9.22. Using quote()

<?php

$name = $db->quote("O'Reilly");
echo $name;
// 'O\'Reilly'

$sql = "SELECT * FROM bugs WHERE reported_by = $name";

echo $sql;
// SELECT * FROM bugs WHERE reported_by = 'O\'Reilly'

Note that the return value of quote() includes the quote delimiters around the string. This is different from some functions that escape special characters but do not add the quote delimiters, for example mysql_real_escape_string().

9.1.5.2. Using quoteInto()

The most typical usage of quoting is to interpolate a PHP variable into a SQL expression or statement. You can use the quoteInto() method to do this in one step. This method takes two arguments: the first argument is a string containing a placeholder symbol (?), and the second argument is a value or PHP variable that should be substituted for that placeholder.

The placeholder symbol is the same symbol used by many RDBMS brands for positional parameters, but the quoteInto() method only emulates query parameters. The method simply interpolates the value into the string, escapes special characters, and applies quotes around it. True query parameters maintain the separation between the SQL string and the parameters as the statement is parsed in the RDBMS server.

例 9.23. Using quoteInto()

<?php

$sql = $db->quoteInto("SELECT * FROM bugs WHERE reported_by = ?", "O'Reilly");

echo $sql;
// SELECT * FROM bugs WHERE reported_by = 'O\'Reilly'

9.1.5.3. Using quoteIdentifier()

Values are not the only part of SQL syntax that might need to be variable. If you use PHP variables to name tables, columns, or other identifiers in your SQL statements, you might need to quote these strings too. By default, SQL identifiers have syntax rules like PHP and most other programming languages. For example, identifiers should not contain spaces, certain punctuation or special characters, or international characters. Also certain words are reserved for SQL syntax, and should not be used as identifiers.

However, SQL has a feature called delimited identifiers, which allows broader choices for the spelling of identifiers. If you enclose a SQL identifier in the proper types of quotes, you can use identifiers with spellings that would be invalid without the quotes. Delimited identifiers can contain spaces, punctuation, or international characters. You can also use SQL reserved words if you enclose them in identifier delimiters.

The quoteIdentifier() method works like quote(), but it applies the identifier delimiter characters to the string according to the type of Adapter you use. For example, standard SQL uses double-quotes (") for identifier delimiters, and most RDBMS brands use that symbol. MySQL uses back-quotes (`) by default. The quoteIdentifier() method also escapes special characters within the string argument.

例 9.24. Using quoteIdentifier()

<?php

// we might have a table name that is an SQL reserved word
$tableName = $db->quoteIdentifier("order");

$sql = "SELECT * FROM $tableName";

echo $sql
// SELECT * FROM "order"

SQL delimited identifiers are case-sensitive, unlike unquoted identifiers. Therefore, if you use delimited identifiers, you must use the spelling of the identifier exactly as it is stored in your schema, including the case of the letters.

In most cases where SQL is generated within Zend_Db classes, the default is that all identifiers are delimited automatically. You can change this behavior with the option Zend_Db::AUTO_QUOTE_IDENTIFIERS. Specify this when instantiating the Adapter. See 例 9.4. 「ファクトリでの自動クォートオプションの指定」.

9.1.6. Controlling Database Transactions

Databases define transactions as logical units of work that can be committed or rolled back as a single change, even if they operate on multiple tables. All queries to a database are executed within the context of a transaction, even if the database driver manages them implicitly. This is called auto-commit mode, in which the database driver creates a transaction for every statement you execute, and commits that transaction after your SQL statement has been executed. By default, all Zend_Db Adapter classes operate in auto-commit mode.

Alternatively, you can specify the beginning and resolution of a transaction, and thus control how many SQL queries are included in a single group that is committed (or rolled back) as a single operation. Use the beginTransaction() method to initiate a transaction. Subsequent SQL statements are executed in the context of the same transaction until you resolve it explicitly.

To resolve the transaction, use either the commit() or rollBack() methods. The commit() method marks changes made during your transaction as committed, which means the effects of these changes are shown in queries run in other transactions.

The rollBack() method does the opposite: it discards the changes made during your transaction. The changes are effectively undone, and the state of the data returns to how it was before you began your transaction. However, rolling back your transaction has no effect on changes made by other transactions running concurrently.

After you resolve this transaction, Zend_Db_Adapter returns to auto-commit mode until you call beginTransaction() again.

例 9.25. Managing a transaction to ensure consistency

<?php

// Start a transaction explicitly.
$db->beginTransaction();

try {
    // Attempt to execute one or more queries:
    $db->query(...);
    $db->query(...);
    $db->query(...);

    // If all succeed, commit the transaction and all changes
    // are committed at once.
    $db->commit();

} catch (Exception $e) {
    // If any of the queries failed and threw an exception,
    // we want to roll back the whole transaction, reversing
    // changes made in the transaction, even those that succeeded.
    // Thus all changes are committed together, or none are.
    $db->rollBack();
    echo $e->getMessage();
}

9.1.7. Listing and Describing Tables

The listTables() method returns an array of strings, naming all tables in the current database.

The describeTable() method returns an associative array of metadata about a table. Specify the name of the table as a string in the first argument to this method. The second argument is optional, and names the schema in which the table exists.

The keys of the associative array returned are the column names of the table. The value corresponding to each column is also an associative array, with the following keys and values:

表 9.1. Metadata fields returned by describeTable()

Key Type Description
SCHEMA_NAME (string) Name of the database schema in which this table exists.
TABLE_NAME (string) Name of the table to which this column belongs.
COLUMN_NAME (string) Name of the column.
COLUMN_POSITION (integer) Ordinal position of the column in the table.
DATA_TYPE (string) RDBMS name of the datatype of the column.
DEFAULT (string) Default value for the column, if any.
NULLABLE (boolean) True if the column accepts SQL NULLs, false if the column has a NOT NULL constraint.
LENGTH (integer) Length or size of the column as reported by the RDBMS.
SCALE (integer) Scale of SQL NUMERIC or DECIMAL type.
PRECISION (integer) Precision of SQL NUMERIC or DECIMAL type.
UNSIGNED (boolean) True if an integer-based type is reported as UNSIGNED.
PRIMARY (boolean) True if the column is part of the primary key of this table.
PRIMARY_POSITION (integer) Ordinal position (1-based) of the column in the primary key.
IDENTITY (boolean) True if the column uses an auto-generated value.

If no table exists matching the table name and optional schema name specified, then describeTable() returns an empty array.

9.1.8. Closing a Connection

Normally it is not necessary to close a database connection. PHP automatically cleans up all resources and the end of a request. Database extensions are designed to close the connection as the reference to the resource object is cleaned up.

However, if you have a long-duration PHP script that initiates many database connections, you might need to close the connection, to avoid exhausting the capacity of your RDBMS server. You can use the Adapter's closeConnection() method to explicitly close the underlying database connection.

例 9.26. Closing a database connection

<?php

$db->closeConnection();
[注意] Does Zend_Db support persistent connections?

The usage of persistent connections is not supported or encouraged in Zend_Db.

Using persistent connections can cause an excess of idle connections on the RDBMS server, which causes more problems than any performance gain you might achieve by reducing the overhead of making connections.

Database connections have state. That is, some objects in the RDBMS server exist in session scope. Examples are locks, user variables, temporary tables, and information about the most recently executed query, such as rows affected, and last generated id value. If you use persistent connections, your application could access invalid or privileged data that were created in a previous PHP request.

9.1.9. Running Other Database Statements

There might be cases in which you need to access the connection object directly, as provided by the PHP database extension. Some of these extensions may offer features that are not surfaced by methods of Zend_Db_Adapter_Abstract.

For example, all SQL statements run by Zend_Db are prepared, then executed. However, some database features are incompatible with prepared statements. DDL statements like CREATE and ALTER cannot be prepared in MySQL. Also, SQL statements don't benefit from the MySQL Query Cache, prior to MySQL 5.1.17.

Most PHP database extensions provide a method to execute SQL statements without preparing them. For example, in PDO, this method is exec(). You can access the connection object in the PHP extension directly using getConnection().

例 9.27. Running a non-prepared statement in a PDO adapter

<?php

$result = $db->getConnection()->exec('DROP TABLE bugs');

Similarly, you can access other methods or properties that are specific to PHP database extensions. Be aware, though, that by doing this you might constrain your application to the interface provided by the extension for a specific brand of RDBMS.

In future versions of Zend_Db, there will be opportunities to add method entry points for functionality that is common to the supported PHP database extensions. This will not affect backward compatibility.

9.1.10. Notes on Specific Adapters

This section lists differences between the Adapter classes of which you should be aware.

9.1.10.1. IBM DB2

  • Specify this Adapter to the factory() method with the name 'Db2'.

  • This Adapter uses the PHP extension ibm_db2.

  • IBM DB2 supports both sequences and auto-incrementing keys. Therefore the arguments to lastInsertId() are optional. If you give no arguments, the Adapter returns the last value generated for an auto-increment key. If you give arguments, the Adapter returns the last value generated by the sequence named according to the convention 'table_column_seq'.

9.1.10.2. MySQLi

  • Specify this Adapter to the factory() method with the name 'Mysqli'.

  • This Adapter utilizes the PHP extension mysqli.

  • MySQL does not support sequences, so lastInsertId() ignores its arguments and always returns the last value generated for an auto-increment key. The lastSequenceId() method returns null.

9.1.10.3. Oracle

  • Specify this Adapter to the factory() method with the name 'Oracle'.

  • This Adapter uses the PHP extension oci8.

  • Oracle does not support auto-incrementing keys, so you should specify the name of a sequence to lastInsertId() or lastSequenceId().

  • The Oracle extension does not support positional parameters. You must use named parameters.

  • Currently the Zend_Db::CASE_FOLDING option is not supported by the Oracle adapter. To use this option with Oracle, you must use the PDO OCI adapter.

9.1.10.4. PDO Microsoft SQL Server

  • Specify this Adapter to the factory() method with the name 'Pdo_Mssql'.

  • This Adapter uses the PHP extensions pdo and pdo_mssql.

  • Microsoft SQL Server does not support sequences, so lastInsertId() ignores its arguments and always returns the last value generated for an auto-increment key. The lastSequenceId() method returns null.

  • Zend_Db_Adapter_Pdo_Mssql sets QUOTED_IDENTIFIER ON immediately after connecting to a SQL Server database. This makes the driver use the standard SQL identifier delimiter symbol (") instead of the proprietary square-brackets syntax SQL Server uses for delimiting identifiers.

  • You can specify pdoType as a key in the options array. The value can be "mssql" (the default), "dblib", "freetds", or "sybase". This option affects the DSN prefix the adapter uses when constructing the DSN string. Both "freetds" and "sybase" imply a prefix of "sybase:", which is used for the FreeTDS set of libraries. See also http://www.php.net/manual/en/ref.pdo-dblib.connection.php for more information on the DSN prefixes used in this driver.

9.1.10.5. PDO MySQL

  • Specify this Adapter to the factory() method with the name 'Pdo_Mysql'.

  • This Adapter uses the PHP extensions pdo and pdo_mysql.

  • MySQL does not support sequences, so lastInsertId() ignores its arguments and always returns the last value generated for an auto-increment key. The lastSequenceId() method returns null.

9.1.10.6. PDO Oracle

  • Specify this Adapter to the factory() method with the name 'Pdo_Oci'.

  • This Adapter uses the PHP extensions pdo and pdo_oci.

  • Oracle does not support auto-incrementing keys, so you should specify the name of a sequence to lastInsertId() or lastSequenceId().

9.1.10.7. PDO PostgreSQL

  • Specify this Adapter to the factory() method with the name 'Pdo_Pgsql'.

  • This Adapter uses the PHP extensions pdo and pdo_pgsql.

  • PostgreSQL supports both sequences and auto-incrementing keys. Therefore the arguments to lastInsertId() are optional. If you give no arguments, the Adapter returns the last value generated for an auto-increment key. If you give arguments, the Adapter returns the last value generated by the sequence named according to the convention 'table_column_seq'.

9.1.10.8. PDO SQLite

  • Specify this Adapter to the factory() method with the name 'Pdo_Sqlite'.

  • This Adapter uses the PHP extensions pdo and pdo_sqlite.

  • SQLite does not support sequences, so lastInsertId() ignores its arguments and always returns the last value generated for an auto-increment key. The lastSequenceId() method returns null.

  • To connect to an SQLite2 database, specify 'dsnprefix'=>'sqlite2' in the array of parameters when creating an instance of the Pdo_Sqlite Adapter.

  • To connect to an in-memory SQLite database, specify 'dbname'=>':memory:' in the array of parameters when creating an instance of the Pdo_Sqlite Adapter.

  • Older versions of the SQLite driver for PHP do not seem to support the PRAGMA commands necessary to ensure that short column names are used in result sets. If you have problems that your result sets are returned with keys of the form "tablename.columnname" when you do a join query, then you should upgrade to the current version of PHP.