Previous Next

Zend_Db_Table

導入

Zend_Db_Table クラスは、データベースのテーブルへの オブジェクト指向のインターフェイスです。 テーブルに対するさまざまな共通操作のためのメソッドを提供します。 基底クラスは拡張可能なので、独自のロジックを組み込むこともできます。

Zend_Db_Table は、 » テーブルデータゲートウェイ パターンを実装したものです。また、そのほかにも » 行データゲートウェイ パターンを実装したクラスも含んでいます。

Zend_Db_Table を具象クラスとして使用する方法

Zend Framework 1.9 以降では、Zend_Db_Table のインスタンスを作成できます。 つまり、一つのテーブルに対して select、insert、update、delete などといった単純な操作を行うためだけにわざわざ基底クラスを継承して設定する必要がなくなるということです。 以下に、もっとも単純な使用例を示します。

Example #1 文字列で名前を指定するだけのテーブルクラスの宣言

Zend_Db_Table::setDefaultAdapter($dbAdapter);
$bugTable = new Zend_Db_Table('bug');

これが、もっとも単純な使用例です。 以下で説明する Zend_Db_Table のオプションはまったく設定していません。 具象クラスの使用例に加えてより複雑なリレーション機能を使いたくなったときは Zend_Db_Table_Definition のドキュメントを参照ください。

テーブルクラスの定義

データベース内でアクセスしたいテーブルそれぞれについて、 Zend_Db_Table_Abstract を継承したクラスを定義します。

テーブル名およびスキーマの定義

そのクラスが定義しているデータベースのテーブルを定義するには、 protected な変数 $_name を使用します。 これは文字列で、データベースでのテーブル名を指定する必要があります。

Example #2 テーブル名を明示的に指定することによるテーブルクラスの宣言

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name = 'bugs';
}

テーブル名を指定しなかった場合のデフォルトは、クラス名となります。 このデフォルトを使用する場合は、クラス名をデータベースでのテーブル名と一致させる必要があります。

Example #3 テーブル名を暗黙的に指定することによるテーブルクラスの宣言

class bugs extends Zend_Db_Table_Abstract
{
    // テーブル名とクラス名が一致します
}

テーブルのスキーマについても、protected 変数 $_schema で宣言できます。 あるいは $_name プロパティでテーブル名の前にスキーマ名をつなげて指定することもできます。 $_name で指定したスキーマのほうが、 $_schema プロパティで指定したスキーマよりも優先されます。 RDBMS によってはスキーマのことを「データベース」や「表領域」 などということもありますが、同じように使用できます。 スキーマを、テーブル名の一部として宣言することもできます。

Example #4 テーブルクラスでのスキーマの宣言

// 一つ目の方法
class Bugs extends Zend_Db_Table_Abstract
{
    protected $_schema = 'bug_db';
    protected $_name   = 'bugs';
}

// 二つ目の方法
class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name = 'bug_db.bugs';
}

// スキーマを $_name と $_schema の両方で指定した場合は、
// $_name で指定したものが優先されます

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name   = 'bug_db.bugs';
    protected $_schema = 'ignored';
}

スキーマ名とテーブル名は、コンストラクタの設定ディレクティブでも指定できます。 これは、$_name$_schema といったプロパティで設定したデフォルト値を上書きします。 name ディレクティブで指定したスキーマ名は、 schema オプションで指定したスキーマ名より優先されます。

Example #5 インスタンス作成時のテーブル名とスキーマ名の指定

class Bugs extends Zend_Db_Table_Abstract
{
}

// 最初の方法

$tableBugs = new Bugs(array('name' => 'bugs', 'schema' => 'bug_db'));

// もうひとつの方法

$tableBugs = new Bugs(array('name' => 'bug_db.bugs'));

// スキーマを 'name' と 'schema' の両方で指定した場合は、
// 'name' で指定したものが優先されます

$tableBugs = new Bugs(array('name' => 'bug_db.bugs',
                            'schema' => 'ignored'));

スキーマ名を指定しなかった場合のデフォルトは、 そのデータベースアダプタが接続しているスキーマとなります。

テーブルの主キーの定義

すべてのテーブルは主キーを持たなければなりません。 主キーカラムを宣言するには、protected 変数 $_primary を使用します。 これは、単一のカラムの名前を表す文字列か、 もし主キーが複合キーの場合はカラム名の配列となります。

Example #6 主キーを指定する例

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name = 'bugs';
    protected $_primary = 'bug_id';
}

主キーを指定しなかった場合は、Zend_Db_Table_AbstractdescribeTable() メソッドの情報に基づいて主キーを見つけます。

Note:

すべてのテーブルクラスは、行を一意に決定するために どのカラムを使用するのかを知っている必要があります。 テーブルクラスの定義やコンストラクタの引数、 あるいは describeTable() によるメタデータで主キーカラムが定義されていない場合は、 そのテーブルを Zend_Db_Table で使用することはできません。

テーブルの設定メソッドのオーバーライド

テーブルクラスのインスタンスを作成する際に、 コンストラクタ内でいくつかの protected メソッドをコールします。 これにより、テーブルのメタデータを初期化します。 これらのメソッドを拡張して、メタデータを明示的に定義することも可能です。 その場合は、メソッドの最後で親クラスの同名のメソッドをコールすることを忘れないようにしましょう。

Example #7 _setupTableName() メソッドのオーバーライドの例

class Bugs extends Zend_Db_Table_Abstract
{
    protected function _setupTableName()
    {
        $this->_name = 'bugs';
        parent::_setupTableName();
    }
}

オーバーライドできるメソッドは、次のとおりです。

  • _setupDatabaseAdapter() は、アダプタが設定されているかどうかを調べ、 必要に応じてレジストリからデフォルトのアダプタを取得します。 このメソッドをオーバーライドすると、 データベースアダプタを別の場所から取得できます。

  • _setupTableName() は、デフォルトのテーブル名をクラス名に設定します。 このメソッドをオーバーライドすると、 この処理の前にテーブル名を指定できます。

  • _setupMetadata() はテーブル名が "schema.table" 形式の場合にスキーマを設定し、 describeTable() をコールしてメタデータ情報を取得します。 このメソッドが返す配列のカラム $_cols の情報をデフォルトで使用します。 このメソッドをオーバーライドすると、カラムを指定できます。

  • _setupPrimaryKey() はデフォルトの主キーを describeTable() から取得した内容に設定し、配列 $_cols に主キーカラムが含まれているかどうかを調べます。 このメソッドをオーバーライドすると、主キーカラムを指定できます。

テーブルの初期化

テーブルクラスの作成時にアプリケーション固有のロジックを初期化したい場合は、 その作業を init() メソッドで行います。 これは、テーブルのメタデータがすべて処理された後にコールされます。 メタデータを変更するつもりがないのなら、__construct メソッドよりもこちらを使用することを推奨します。

Example #8 init() メソッドの使用例

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_observer;

    public function init()
    {
        $this->_observer = new MyObserverClass();
    }
}

テーブルのインスタンスの作成

テーブルクラスを使用する前に、コンストラクタでそのインスタンスを作成します。 コンストラクタの引数はオプションの配列となります。 テーブルのコンストラクタのオプションのうち、最も重要なのは データベースアダプタのインスタンスとなります。 これは RDBMS への有効な接続を表します。 データベースアダプタをテーブルクラスに指定する方法は三通りあります。 それぞれについて、以下で説明します。

データベースアダプタの指定

データベースアダプタをテーブルクラスに指定する最初の方法は、 Zend_Db_Adapter_Abstract 型のオブジェクトをオプションの配列で渡すことです。 配列のキーは 'db' となります。

Example #9 アダプタオブジェクトを使用した、テーブルの作成の例

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

$table = new Bugs(array('db' => $db));

デフォルトのデータベースアダプタの設定

データベースアダプタをテーブルクラスに指定する二番目の方法は、 デフォルトのデータベースアダプタとして Zend_Db_Adapter_Abstract 型のオブジェクトを宣言することです。そのアプリケーション内で、 これ以降に作成したテーブルインスタンスについてこれが用いられます。 これを行うには、静的メソッド Zend_Db_Table_Abstract::setDefaultAdapter() を使用します。引数は、Zend_Db_Adapter_Abstract 型のオブジェクトとなります。

Example #10 デフォルトアダプタを使用した、テーブルの作成の例

$db = Zend_Db::factory('PDO_MYSQL', $options);
Zend_Db_Table_Abstract::setDefaultAdapter($db);

// その後...

$table = new Bugs();

これは、たとえば起動ファイルなどでデータベースアダプタオブジェクトを作成し、 それをデフォルトのアダプタとして保存しておく場合などに便利です。 これにより、アプリケーション全体で共通のアダプタを使用することが保証されます。 しかし、デフォルトのアダプタのインスタンスは、ひとつだけしか設定できません。

データベースアダプタのレジストリへの保存

データベースアダプタをテーブルクラスに指定する三番目の方法は、 文字列ををオプションの配列で渡すことです。 配列のキーは、この場合も 'db' となります。 この文字列は、静的な Zend_Registry インスタンスのキーとして使用します。 このキーのエントリが Zend_Db_Adapter_Abstract 型のオブジェクトとなります。

Example #11 レジストリのキーを使用した、テーブルの作成の例

$db = Zend_Db::factory('PDO_MYSQL', $options);
Zend_Registry::set('my_db', $db);

// その後...

$table = new Bugs(array('db' => 'my_db'));

デフォルトアダプタの指定と同様、これにより、 アプリケーション全体で共通のアダプタを使用することが保証されます。 レジストリには複数のアダプタインスタンスを保存できるため、 より柔軟に使用できます。指定したアダプタインスタンスは 特定の RDBMS やデータベースインスタンスに固有のものとなります。 複数のデータベースにアクセスする必要がある場合は、 複数のアダプタが必要です。

テーブルへの行の挿入

テーブルオブジェクトを使用して、そのオブジェクトの元になっているテーブルに 行を挿入できます。そのためには、テーブルオブジェクトの insert() メソッドを使用します。引数は連想配列で、 カラム名と値の対応を指定します。

Example #12 テーブルへの挿入の例

$table = new Bugs();

$data = array(
    'created_on'      => '2007-03-22',
    'bug_description' => '何かおかしい',
    'bug_status'      => 'NEW'
);

$table->insert($data);

デフォルトでは、配列内の値はリテラル値として扱われ、 パラメータを使用して挿入されます。これを SQL の式として扱いたい場合は、 文字列ではない形式で指定する必要があります。その際には Zend_Db_Expr 型のオブジェクトを使用します。

Example #13 式をテーブルに挿入する例

$table = new Bugs();

$data = array(
    'created_on'      => new Zend_Db_Expr('CURDATE()'),
    'bug_description' => '何かおかしい',
    'bug_status'      => 'NEW'
);

上の例では、テーブルには自動インクリメントの主キーがあるものとします。 これは Zend_Db_Table_Abstract のデフォルトの挙動ですが、 それ以外の形式の主キーも扱えます。以下の節では、 さまざまな形式の主キーを扱う方法を説明します。

自動インクリメントのキーを持つテーブルの使用

自動インクリメントの主キーは、SQLINSERT 文で主キー列を省略した場合に一意な整数値を生成します。

Zend_Db_Table_Abstract で protected 変数 $_sequence の値を boolean の TRUE にすると、そのテーブルは自動インクリメントの主キーを持つものとみなされます。

Example #14 自動インクリメントの主キーを持つテーブルを宣言する例

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name = 'bugs';

    // これは Zend_Db_Table_Abstract クラスのデフォルト設定です。
    // 特に定義する必要はありません。
    protected $_sequence = true;
}

MySQL、Microsoft SQL Server そして SQLite などの RDBMS が、主キーの自動インクリメントをサポートしています。

PostgreSQL の SERIAL 記法を使用すると、 テーブル名とカラム名をもとにして暗黙的にシーケンスを定義します。 新しい行を作成した際にはこのシーケンスを用いてキーの値を生成します。 IBM DB2 には、これと同等の動作をする IDENTIFY という記法があります。 これらの記法を使用する場合は、Zend_Db_Table クラスで $_sequenceTRUE と設定し、 自動インクリメントを有効にしてください。

シーケンスを持つテーブルの使用

シーケンスとはデータベースのオブジェクトの一種で、 一意な値を生成するものです。これを、 ひとつあるいは複数のテーブルの主キーの値として使用できます。

$_sequence に文字列を設定すると、 Zend_Db_Table_Abstract は、それがデータベースの シーケンスオブジェクトの名前であるとみなします。 シーケンスを実行して新しい値を生成し、その値を INSERT 操作で使用します。

Example #15 シーケンスを用いたテーブルを宣言する例

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name = 'bugs';

    protected $_sequence = 'bug_sequence';
}

Oracle、PostgreSQL そして IBM DB2 などの RDBMS が、 データベースでのシーケンスオブジェクトをサポートしています。

PostgreSQL および IBM DB2 は、 暗黙的にシーケンスを定義してカラムに関連付ける構文もサポートしています。 この記法を使う場合は、 そのテーブルで自動インクリメントキーのカラムを使用するようにします。 シーケンスのキーの次の値を取得することがある場合にのみ シーケンス名を文字列で定義します。

自然キーを持つテーブルの使用

自然キーを持つテーブルもあります。自然キーとは、 テーブルやシーケンスによって自動生成されるもの以外のキーということです。 この場合は、主キーの値を指定する必要があります。

$_sequence の値を boolean の FALSE にすると、Zend_Db_Table_Abstract はそのテーブルが自然キーを持つものとみなします。 insert() メソッドを使用する際には、 主キーカラムの値をデータの配列で指定する必要があります。 指定しなかった場合、このメソッドは Zend_Db_Table_Exception をスローします。

Example #16 自然キーを用いたテーブルを宣言する例

class BugStatus extends Zend_Db_Table_Abstract
{
    protected $_name = 'bug_status';

    protected $_sequence = false;
}

Note:

自然キーのテーブルは、すべての RDBMS がサポートしています。 自然キーを使用するテーブルの例としては、 ルックアップテーブルや多対多リレーションの中間テーブル、 そして複合主キーを持つ大半のテーブルなどがあります。

テーブルの行の更新

データベースのテーブルの行を更新するには、テーブルクラスの update メソッドを使用します。 このメソッドには二つの引数を指定します。変更するカラムと それらのカラムに代入する新しい値を表す連想配列、 そして UPDATE 操作の対象となる行を指定する WHERE 句で使用する SQL 式です。

Example #17 テーブルの行の更新の例

$table = new Bugs();

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

$where = $table->getAdapter()->quoteInto('bug_id = ?', 1234);

$table->update($data, $where);

テーブルの update() メソッドはデータベースアダプタの update() メソッドへのプロキシなので、 二番目の引数は、SQL 式の配列にできます。 その場合、それぞれの式が論理演算子 AND で連結されます。

Note:

SQL 式の中の値や識別子は、自動的にはクォートされません。 クォートが必要な値や識別子を使用する場合は、自分でクォートする必要があります。 データベースアダプタの quote()quoteInto() および quoteIdentifier() を使用してください。

テーブルからの行の削除

データベースのテーブルから行を削除するには、テーブルクラスの delete() メソッドを使用します。 このメソッドにはひとつの引数を指定します。この引数は WHERE 句で使用する SQL 式で、 これにより、削除対象となる行を指定します。

Example #18 テーブルからの行の削除の例

$table = new Bugs();

$where = $table->getAdapter()->quoteInto('bug_id = ?', 1235);

$table->delete($where);

テーブルの delete() メソッドはデータベースアダプタの delete() メソッドへのプロキシなので、 引数は SQL 式の配列とすることもできます。 その場合、それぞれの式が論理演算子 AND で連結されます。

Note:

SQL 式の中の値や識別子は、自動的にはクォートされません。 クォートが必要な値や識別子を使用する場合は、自分でクォートする必要があります。 データベースアダプタの quote()quoteInto() および quoteIdentifier() を使用してください。

主キーによる行の検索

データベースのテーブルに対して、指定した主キーの値に対応する行を問い合わせるには find() メソッドを使用します。 このメソッドの最初の引数は、テーブルの主キーに対応する 単一の値か、あるいは複数の値の配列となります。

Example #19 主キーの値によって行を捜す例

$table = new Bugs();

// 単一の行を探し、
// Rowset を返します
$rows = $table->find(1234);

// 複数の行を探し、
// こちらも Rowset を返します
$rows = $table->find(array(1234, 5678));

単一の値を指定した場合は、このメソッドが返す行数は最大でも一行になります。 主キーの値が重複することはないので、指定した値に対応する行は テーブル内で最大でも一行だけだからです。 複数の値を配列で指定した場合は、このメソッドが返す結果の最大数は 配列で指定した値の数となります。

find() メソッドの返す行数は、主キーで指定した値より少なくなるかもしれません。 たとえば指定した値に対応する行がデータベースのテーブルに存在しなかった場合などです。 このメソッドが返す行数がゼロになる可能性もあります。 このように結果の行数が可変なので、 find() メソッドが返すオブジェクトの型は Zend_Db_Table_Rowset_Abstract となります。

主キーが複合キーの場合、つまり複数のカラムから構成されるキーの場合は、 追加のカラムを find() メソッドの引数で指定します。 テーブルの主キーのカラム数と同じ数の引数を指定しなければなりません。

複合主キーのテーブルから複数行を取得するには、 各引数を配列で指定します。これらすべての配列の要素数は同じでなければなりません。 各配列の値が、その順にキー列の値として用いられます。 たとえば、すべての配列の最初の要素で複合主キーの最初の値を指定し、 すべての配列の二番目の要素で複合主キーの二番目の値を設定し、…… というようになります。

Example #20 複合主キーの値の指定による行の取得の例

以下の find() メソッドは、データベース内のふたつの行にマッチします。 最初の行の主キーの値は (1234, 'ABC') で、次の行の主キーの値は (5678, 'DEF') となります。

class BugsProducts extends Zend_Db_Table_Abstract
{
    protected $_name = 'bugs_products';
    protected $_primary = array('bug_id', 'product_id');
}

$table = new BugsProducts();

// 単一の行を複合主キーで探し、
// Rowset を返します
$rows = $table->find(1234, 'ABC');

// 複数の行を複合主キーで探し、
// こちらも Rowset を返します
$rows = $table->find(array(1234, 5678), array('ABC', 'DEF'));

行セットの問い合わせ

Select API

Warning

取得操作用の API は変更され、 Zend_Db_Table_Select オブジェクトでクエリを変更できるようになりました。 しかし、昔ながらの方法である fetchRow()fetchAll() は今でも同じように使用できます。

次の文は、どれも正しくて同じ動作をします。 しかし、新しい使用法に対応するためにもできるだけ新しい書き方に変更することをお勧めします。

/**
 * 行セットを取得します
 */
$rows = $table->fetchAll(
    'bug_status = "NEW"',
    'bug_id ASC',
    10,
    0
    );
$rows = $table->fetchAll(
    $table->select()
        ->where('bug_status = ?', 'NEW')
        ->order('bug_id ASC')
        ->limit(10, 0)
    );
// またはバインディングを用いて
$rows = $table->fetchAll(
    $table->select()
        ->where('bug_status = :status')
        ->bind(array(':status'=>'NEW')
        ->order('bug_id ASC')
        ->limit(10, 0)
    );

/**
 * 単一の行を取得します
 */
$row = $table->fetchRow(
    'bug_status = "NEW"',
    'bug_id ASC'
    );
$row = $table->fetchRow(
    $table->select()
        ->where('bug_status = ?', 'NEW')
        ->order('bug_id ASC')
    );
// またはバインディングを用いて
$row = $table->fetchRow(
    $table->select()
        ->where('bug_status = :status')
        ->bind(array(':status'=>'NEW')
        ->order('bug_id ASC')
    );

Zend_Db_Table_Select オブジェクトは Zend_Db_Select を継承したものであり、 クエリにはいくつか制限があります。追加された機能や制限事項を以下にまとめます。

  • fetchRow あるいは fetchAll のクエリで、カラムのサブセットを返すことが できます。 結果が巨大なものになるけれどもその中には使用しないカラムもある といった場合に有用です。

  • select する際に、式の結果をカラムとして指定することが できます。 しかし、この場合は行 (あるいは行セット) は readOnly となり、save() することはできません。readOnlyZend_Db_Table_Row に対して save() を実行しようとすると、例外がスローされます。

  • select で JOIN 句を使用して、複数テーブルからの検索を行うことが できます

  • JOIN したテーブルのカラムを結果の行や行セットに指定することは できません。 そうすると、PHP のエラーが発生します。 これにより、Zend_Db_Table の整合性が保証されます。つまり、 Zend_Db_Table_Row はその親のテーブルのカラムしか参照しないということです。

Example #21 単純な使用法

$table = new Bugs();

$select = $table->select();
$select->where('bug_status = ?', 'NEW');

$rows = $table->fetchAll($select);

このコンポーネントでは「流れるようなインターフェイス」 を実装しているので、この例はもっと省略して書くこともできます。

Example #22 流れるようなインターフェイスの例

$table = new Bugs();

$rows =
    $table->fetchAll($table->select()->where('bug_status = ?', 'NEW'));

行セットの取得

主キーの値以外を条件として行のセットを問い合わせるには、 テーブルクラスの fetchAll() メソッドを使用します。 このメソッドは、Zend_Db_Table_Rowset_Abstract 型のオブジェクトを返します。

Example #23 式から行を取得する例

$table = new Bugs();

$select = $table->select()->where('bug_status = ?', 'NEW');

$rows = $table->fetchAll($select);

ORDER BY での並べ替えの条件句やオフセットを表す整数値を指定して、 クエリの返す結果を絞りこむことができます。 これらの値は LIMIT 句で用いられます。 LIMIT 構文をサポートしていない RDBMS では、それと同等のロジックで用いられます。

Example #24 式を使用した行の検索の例

$table = new Bugs();

$order  = 'bug_id';

// 21 番目から 30 番目の行を返します
$count  = 10;
$offset = 20;

$select = $table->select()->where('bug_status = ?', 'NEW')
                          ->order($order)
                          ->limit($count, $offset);

$rows = $table->fetchAll($select);

これらのオプションはどれも、必須ではありません。 ORDER 句を省略した場合は、結果セットに複数の行が含まれる場合の並び順は予測不可能です。 LIMIT 句を省略した場合は、WHERE 句にマッチするすべての行を取得することになります。

高度な使用法

リクエストの内容をより明確に指定して最適化するために、 行/行セットが返すカラムの数を絞り込みたいこともあるでしょう。 これは、select オブジェクトの FROM 句で行います。 FROM 句の最初の引数は Zend_Db_Select オブジェクトと同じですが、 さらに Zend_Db_Table_Abstract のインスタンスを渡すこともでき、テーブル名を自動的に検出します。

Example #25 指定したカラムの取得

$table = new Bugs();

$select = $table->select();
$select->from($table, array('bug_id', 'bug_description'))
       ->where('bug_status = ?', 'NEW');

$rows = $table->fetchAll($select);

Important

この状態でも、行セット自体は '正しい' 形式です。 単にひとつのテーブルの中の一部のカラムを含んでいるというだけです。 この中の行に対して save() メソッドをコールすると、 そこに含まれているフィールドだけを更新します。

FROM 句で式を指定すると、その結果を readOnly の行/行セット として返します。この例では、bugs テーブルを検索して 個人別のバグの報告件数を取得しています。 GROUP 句に注目しましょう。これで、返される行に 'count' というカラムが含まれるようになり、 スキーマの他のカラムと同じようにアクセスできるようになります。

Example #26 式の結果をカラムとして取得する

$table = new Bugs();

$select = $table->select();
$select->from($table,
              array('COUNT(reported_by) as `count`', 'reported_by'))
       ->where('bug_status = ?', 'NEW')
       ->group('reported_by');

$rows = $table->fetchAll($select);
クエリの一部にルックアップテーブルを使用し、 より絞り込んで取得することもできます。 この例では、検索時に accounts テーブルを使用して 'Bob' が報告したすべてのバグを探しています。

Example #27 ルックアップテーブルによる fetchAll() の結果の絞込み

$table = new Bugs();

// join する際には、from 部を指定して取得するのが重要です
$select = $table->select(Zend_Db_Table::SELECT_WITH_FROM_PART);
$select->setIntegrityCheck(false)
       ->where('bug_status = ?', 'NEW')
       ->join('accounts', 'accounts.account_name = bugs.reported_by')
       ->where('accounts.account_name = ?', 'Bob');

$rows = $table->fetchAll($select);

Zend_Db_Table_Select の主な使用目的は、 制約を強要して正しい形式の SELECT クエリを作成することです。 しかし時には、Zend_Db_Table_Row の柔軟性が必要であって 行を更新したり削除したりすることはないということもあります。 そんな場合には、setIntegrityCheck に false を渡して行/行セットを取得できます。 この場合に返される行/行セットは 'ロックされた' 行 (save()、delete() やフィールドの設定用メソッドを実行すると例外が発生する) となります。

Example #28 Zend_Db_Table_Select の整合性チェックを削除し、JOIN した行を許可する

$table = new Bugs();

$select = $table->select(Zend_Db_Table::SELECT_WITH_FROM_PART)
                ->setIntegrityCheck(false);
$select->where('bug_status = ?', 'NEW')
       ->join('accounts',
              'accounts.account_name = bugs.reported_by',
              'account_name')
       ->where('accounts.account_name = ?', 'Bob');

$rows = $table->fetchAll($select);

単一の行の問い合わせ

fetchAll() と同じような条件を指定して、 単一の行を問い合わせることができます。

Example #29 式から単一の行を取得する例

$table = new Bugs();

$select  = $table->select()->where('bug_status = ?', 'NEW')
                           ->order('bug_id');

$row = $table->fetchRow($select);

このメソッドは、Zend_Db_Table_Row_Abstract 型のオブジェクトを返します。 指定した検索条件に一致する行がデータベースのテーブルにない場合は、 fetchRow()PHPNULL 値を返します。

テーブルのメタデータ情報の取得

Zend_Db_Table_Abstract クラスは、メタデータに関するいくつかの情報を提供します。 info() メソッドは配列を返し、その中には テーブルについての情報、カラムや主キー、その他のメタデータが含まれます。

Example #30 テーブル名を取得する例

$table = new Bugs();

$info = $table->info();

echo "テーブル名は " . $info['name'] . " です\n";

info() メソッドが返す配列のキーについて、 以下にまとめます。

  • name => テーブルの名前。

  • cols => テーブルのカラム名を表す配列。

  • primary => 主キーのカラム名を表す配列。

  • metadata => カラム名とカラムに関する情報を関連付けた連想配列。 これは describeTable() メソッドが返す情報です。

  • rowClass => このテーブルインスタンスのメソッドが返す行オブジェクトで使用する 具象クラス名。デフォルトは Zend_Db_Table_Row です。

  • rowsetClass => このテーブルインスタンスのメソッドが返す行セットオブジェクトで使用する 具象クラス名。デフォルトは Zend_Db_Table_Rowset です。

  • referenceMap => このテーブルから任意の親テーブルに対する参照の情報を含む連想配列。 リレーションの定義 を参照ください。

  • dependentTables => このテーブルを参照しているテーブルのクラス名の配列。 リレーションの定義 を参照ください。

  • schema => テーブルのスキーマ (あるいはデータベース、あるいは表領域) の名前。

テーブルのメタデータのキャッシュ

デフォルトでは Zend_Db_Table_Abstract の問合せ先は テーブルオブジェクトのインスタンスの テーブルメタデータ が指すデータベースとなります。 つまり、テーブルオブジェクトを作成する際にデフォルトで行われれることは、アダプタの describeTable() メソッドによってデータベースからテーブルのメタデータを取得するということになります。 これを必要とする操作には次のようなものがあります。

  • insert()

  • find()

  • info()

同一のテーブルに対して複数のテーブルオブジェクトを作成する場合などに、 毎回テーブルのめたデータをデータベースに問い合わせることは パフォーマンスの観点からも好ましくありません。 このような場合のために、データベースから取得したテーブルメタデータをキャッシュしておくことができます。

テーブルのメタデータをキャッシュする主な方法は、次のふたつです。

  • Zend_Db_Table_Abstract::setDefaultMetadataCache() をコールする - これは、すべてのテーブルクラスで使用するデフォルトのキャッシュオブジェクトを一度で設定できます。

  • Zend_Db_Table_Abstract::__construct() を設定する - これは、特定のテーブルクラスのインスタンスでh使用するキャッシュオブジェクトを設定できます。

どちらの場合においても、メソッドの引数はひとつで、NULL (キャッシュを使用しない) あるいは Zend_Cache_Core のインスタンスを指定します。これらを組み合わせることで、 デフォルトのメタデータキャッシュを指定した上で 特定のテーブルオブジェクトについてのみ別のキャッシュを使用させることができます。

Example #31 すべてのテーブルオブジェクトでのデフォルトのメタデータキャッシュの使用

次のコードは、デフォルトのメタデータキャッシュをすべてのテーブルオブジェクトで使用する方法を示すものです。

// まずキャッシュを作成します
$frontendOptions = array(
    'automatic_serialization' => true
    );

$backendOptions  = array(
    'cache_dir'                => 'cacheDir'
    );

$cache = Zend_Cache::factory('Core',
                             'File',
                             $frontendOptions,
                             $backendOptions);

// 次に、それをすべてのテーブルオブジェクトで使用するように設定します
Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);

// テーブルクラスも必要です
class Bugs extends Zend_Db_Table_Abstract
{
    // ...
}

// Bugs の各インスタンスは、これでデフォルトのメタデータキャッシュを用いるようになります
$bugs = new Bugs();

Example #32 特定のテーブルオブジェクトでのメタデータキャッシュの使用

次のコードは、メタデータキャッシュを特定のテーブルオブジェクトに設定する方法を示すものです。

// まずキャッシュを作成します
$frontendOptions = array(
    'automatic_serialization' => true
    );

$backendOptions  = array(
    'cache_dir'                => 'cacheDir'
    );

$cache = Zend_Cache::factory('Core',
                             'File',
                             $frontendOptions,
                             $backendOptions);

// テーブルクラスも必要です
class Bugs extends Zend_Db_Table_Abstract
{
    // ...
}

// インスタンスを設定します
$bugs = new Bugs(array('metadataCache' => $cache));

Note: キャッシュのフロントエンドにおける自動シリアライズ

アダプタの describeTable() メソッドの返す内容は配列なので、 Zend_Cache_Core フロントエンドのオプション automatic_serializationTRUE と設定しましょう。

上の例では Zend_Cache_Backend_File を使用していますが、 状況に応じて適切なバックエンドを使い分けることができます。詳細な情報は Zend_Cache を参照ください。

テーブルのメタデータのハードコーディング

メタデータのキャッシュをより高速にするために、 メタデータをハードコーディングすることもできます。 しかし、そうすると、 テーブルのスキーマが変わるたびにコードを変更しなければならなくなります。 この方法をおすすめできるのは、 実運用環境で最適化が必要となった場合のみです。

メタデータの構造は次のようになります。

protected $_metadata = array(
    '' => array(
        'SCHEMA_NAME'      => ,
        'TABLE_NAME'       => ,
        'COLUMN_NAME'      => ,
        'COLUMN_POSITION'  => ,
        'DATA_TYPE'        => ,
        'DEFAULT'          => NULL|,
        'NULLABLE'         => ,
        'LENGTH'           => ,
        'SCALE'            => NULL|,
        'PRECISION'        => NULL|,
        'UNSIGNED'         => NULL|,
        'PRIMARY'          => ,
        'PRIMARY_POSITION' => ,
        'IDENTITY'         => ,
    ),
    // さらなるカラム...
);

適切な値を知るには、メタデータキャッシュを使用するのが簡単でしょう。 キャッシュに格納された値をデシリアライズするのです。

この最適化を無効にするには、 metadataCacheInClass フラグをオフにします。

// インスタンス作成時
$bugs = new Bugs(array('metadataCacheInClass' => false));

// その後
$bugs->setMetadataCacheInClass(false);

このフラグはデフォルトで有効になっています。この場合は、 $_metadata 配列はインスタンスの作成時にのみ作成されます。

テーブルクラスのカスタマイズおよび拡張

独自の行クラスあるいは行セットクラスの使用

デフォルトでは、テーブルクラスが返す行セットは 具象クラス Zend_Db_Table_Rowset のインスタンスであり、 行セットには具象クラス Zend_Db_Table_Row のインスタンスの集合が含まれます。 これらのいずれについても、別のクラスを使用することが可能です。 しかし、使用するクラスはそれぞれ Zend_Db_Table_Rowset_Abstract および Zend_Db_Table_Row_Abstract を継承したものでなければなりません。

行クラスおよび行セットクラスを指定するには、 テーブルのコンストラクタのオプション配列を使用します。 対応するキーは、それぞれ 'rowClass' および 'rowsetClass' となります。 ここには、クラスの名前を文字列で指定します。

Example #33 行クラスおよび行セットクラスの指定の例

class My_Row extends Zend_Db_Table_Row_Abstract
{
    ...
}

class My_Rowset extends Zend_Db_Table_Rowset_Abstract
{
    ...
}

$table = new Bugs(
    array(
        'rowClass'    => 'My_Row',
        'rowsetClass' => 'My_Rowset'
    )
);

$where = $table->getAdapter()->quoteInto('bug_status = ?', 'NEW')

// My_Rowset 型のオブジェクトを返します。
// その中には My_Row 型のオブジェクトの配列が含まれます。
$rows = $table->fetchAll($where);

クラスを変更するには、 setRowClass() メソッドおよび setRowsetClass() メソッドを使用します。 これは、それ以降に作成される行および行セットに適用されます。 すでに出来上がっている行オブジェクトや行セットオブジェクトには 何の影響も及ぼしません。

Example #34 行クラスおよび行セットクラスの変更の例

$table = new Bugs();

$where = $table->getAdapter()->quoteInto('bug_status = ?', 'NEW')

// Zend_Db_Table_Rowset 型のオブジェクトを返します。
// その中には Zend_Db_Table_Row 型のオブジェクトの配列が含まれます。
$rowsStandard = $table->fetchAll($where);

$table->setRowClass('My_Row');
$table->setRowsetClass('My_Rowset');

// My_Rowset 型のオブジェクトを返します。
// その中には My_Row 型のオブジェクトの配列が含まれます。
$rowsCustom = $table->fetchAll($where);

// $rowsStandard オブジェクトはまだ存在しますが、なにも変更されていません

行クラスおよび行セットクラスについての詳細は Zend_Db_Table_Row および Zend_Db_Table_Rowset を参照ください。

Insert、Update および Delete 時の独自ロジックの定義

テーブルクラスの insert() メソッドや update() メソッドをオーバーライドできます。 これにより、データベース操作の前に実行される独自のコードを実装できます。 最後に親クラスのメソッドをコールすることを忘れないようにしましょう。

Example #35 タイムスタンプを処理する独自ロジック

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name = 'bugs';

    public function insert(array $data)
    {
        // タイムスタンプを追加します
        if (empty($data['created_on'])) {
            $data['created_on'] = time();
        }
        return parent::insert($data);
    }

    public function update(array $data, $where)
    {
        // タイムスタンプを追加します
        if (empty($data['updated_on'])) {
            $data['updated_on'] = time();
        }
        return parent::update($data, $where);
    }
}

delete() メソッドをオーバーライドすることもできます。

Zend_Db_Table における独自の検索メソッドの定義

もし特定の条件によるテーブルの検索を頻繁に行うのなら、 独自の検索メソッドをテーブルクラスで実装できます。 大半の問い合わせは fetchAll() を用いて書くことができますが、 アプリケーション内の複数の箇所でクエリを実行する場合には 問い合わせ条件を指定するコードが重複してしまいます。 そんな場合は、テーブルクラスでメソッドを実装し、 よく使う問い合わせを定義しておいたほうが便利です。

Example #36 状況を指定してバグを検索する独自メソッド

class Bugs extends Zend_Db_Table_Abstract
{
    protected $_name = 'bugs';

    public function findByStatus($status)
    {
        $where = $this->getAdapter()->quoteInto('bug_status = ?', $status);
        return $this->fetchAll($where, 'bug_id');
    }
}

Zend_Db_Table における語尾変化の定義

テーブルのクラス名を RDBMS のテーブル名とあわせるために、 inflection (語尾変化) と呼ばれる文字列変換を使用することを好む方もいます。

たとえば、テーブルのクラス名が "BugsProducts" だとすると、クラスのプロパティ $_name を明示的に宣言しなかった場合は データベース内の物理的なテーブル "bugs_products" にマッチします。この関連付けでは、 "CamelCase" 形式のクラス名が小文字に変換され、単語の区切りがアンダースコアに変わります。

データベースのテーブル名を、クラス名とは独立したものにすることもできます。 その場合は、テーブルクラスのプロパティ $_name に、そのクラス名を指定します。

Zend_Db_Table_Abstract は、クラス名とテーブル名を関連付けるための語尾変化は行いません。 テーブルクラスで $_name の宣言を省略すると、 そのクラス名に正確に一致する名前のテーブルと関連付けられます。

データベースの識別子を変換することは、適切ではありません。 なぜなら、それは不明確な状態を引き起こし、 時には識別子にアクセスできなくなってしまうからです。 SQL の識別子をデータベース内にあるそのままの形式で扱うことで、 Zend_Db_Table_Abstract はシンプルで柔軟なものになっています。

語尾変化を行いたい場合は、その変換を独自に実装しなければなりません。そのためには テーブルクラスで _setupTableName() メソッドをオーバーライドします。 これを行うひとつの方法としては、Zend_Db_Table_Abstract を継承した抽象クラスを作成し、さらにそれを継承したテーブルクラスを作成するという方法があります。

Example #37 語尾変化を実装した抽象テーブルクラスの例

abstract class MyAbstractTable extends Zend_Db_Table_Abstract
{
    protected function _setupTableName()
    {
        if (!$this->_name) {
            $this->_name = myCustomInflector(get_class($this));
        }
        parent::_setupTableName();
    }
}

class BugsProducts extends MyAbstractTable
{
}

語尾変化を行う関数を書くのはあなたの役割です。 Zend Framework にはそのような関数はありません。

Previous Next
Introduction to Zend Framework
概要
インストール
Zend_Acl
導入
アクセス制御の洗練
高度な使用法
Zend_Amf
導入
Zend_Amf_Server
Zend_Application
導入
Zend_Application クイックスタート
Theory of Operation
コア機能
利用できるリソースプラグイン
Zend_Auth
導入
データベースのテーブルでの認証
ダイジェスト認証
HTTP 認証アダプタ
LDAP 認証
Open ID 認証
Zend_Cache
導入
キャッシュの仕組み
Zend_Cache のフロントエンド
Zend_Cache のバックエンド
Zend_Captcha
導入
Captcha の方法
CAPTCHA アダプタ
Zend_CodeGenerator
導入
Zend_CodeGeneratorサンプル
Zend_CodeGeneratorリファレンス
Zend_Config
導入
動作原理
Zend_Config_Ini
Zend_Config_Xml
Zend_Config_Writer
Zend_Config_Writer
Zend_Console_Getopt
導入
Getopt の規則の宣言
オプションおよび引数の取得
Zend_Console_Getopt の設定
Zend_Controller
Zend_Controller クイックスタート
Zend_Controller の基本
フロントコントローラ
リクエストオブジェクト
標準のルータ
ディスパッチャ
アクションコントローラ
アクションヘルパー
レスポンスオブジェクト
プラグイン
モジュラーディレクトリ構造の規約の使用
MVC での例外
以前のバージョンからの移行
Zend_Currency
Zend_Currency について
通貨の操作方法
以前のバージョンからの移行
Zend_Date
導入
動作原理
基本メソッド
Zend_Date API の概要
日付の作成
日付関数全般用の定数
動作例
Zend_Db
Zend_Db_Adapter
Zend_Db_Statement
Zend_Db_Profiler
Zend_Db_Select
Zend_Db_Table
Zend_Db_Table_Row
Zend_Db_Table_Rowset
導入
Zend_Db_Table_Definition
Zend_Debug
変数の出力
Zend_Dojo
導入
Zend_Dojo_Data: dojo.data エンベロープ
Dojo ビューヘルパー
Dojoフォーム要素とデコレーター
Zend_Dojo build layer support
Zend_Dom
導入
Zend_Dom_Query
Zend_Exception
例外の使用法
Zend_Feed
導入
フィードの読み込み
ウェブページからのフィードの取得
RSS フィードの使用
Atom フィードの使用
単一の Atom エントリの処理
フィードおよびエントリの構造の変更
独自のフィードクラスおよびエントリクラス
Zend_Feed_Reader
Zend_File
Zend_File_Transfer
Zend_File_Transfer 用のバリデータ
Filters for Zend_File_Transfer
以前のバージョンからの移行
Zend_Filter
導入
標準のフィルタクラス群
フィルタチェイン
フィルタの書き方
Zend_Filter_Input
Zend_Filter_Inflector
前バージョンからの移行
Zend_Form
Zend_Form
Zend_Form クイックスタート
Zend_Form_Element を用いたフォーム要素の作成
Zend_Form によるフォームの作成
Zend_Form_Decorator による独自のフォームマークアップの作成
Zend Framework に同梱されている標準のフォーム要素
Zend Framework に同梱されている標準のデコレータ
Zend_Form の国際化
Zend_Form の高度な使用法
Zend_Gdata
導入
AuthSub による認証
Using the Book Search Data API
ClientLogin による認証
Google Calendar の使用法
Google Documents List Data API の使用法
Using Google Health
Google Spreadsheets の使用法
Google Apps Provisioning の使用法
Google Base の使用法
Picasa Web Albums の使用法
YouTube Data API の使用法
Gdata の例外処理
Zend_Http
導入
Zend_Http_Client - 高度な使用法
Zend_Http_Client - 接続アダプタ
前バージョンからの移行
Zend_Http_Cookie および Zend_Http_CookieJar
Zend_Http_Response
Zend_InfoCard
導入
Zend_Json
導入
基本的な使用法
Zend_Json の高度な使用法
XML から JSON への変換
Zend_Json_Server - JSON-RPCサーバー
Zend_Layout
導入
Zend_Layout クイックスタート
Zend_Layout の設定オプション
Zend_Layout の高度な使用法
Zend_Ldap
導入
API概要
利用シナリオ
ツール
Zend_Ldap_Nodeを使用してLDAPツリーへのオブジェクト指向アクセス
LDAPサーバから情報を取得
LDIFへ、または、からのLDAPデータシリアライズ
Zend_Loader
ファイルやクラスの動的な読み込み
The Autoloader
Resource Autoloaders
プラグインのロード
Zend_Locale
導入
Zend_Locale の使用法
正規化および地域化
日付および時刻の扱い
サポートするロケール
以前のバージョンからの移行
Zend_Log
概要
ライター
フォーマッタ
フィルタ
Zend_Mail
導入
SMTP 経由での送信
SMTP 接続による複数のメールの送信
異なる転送手段の使用
HTML メール
ファイルの添付
受信者の追加
MIME バウンダリの制御
追加のヘッダ
文字セット
エンコーディング
SMTP 認証
セキュアな SMTP トランスポート
メールメッセージの読み込み
Zend_Measure
導入
計測値の作成
計測値の出力
計測値の操作
計測値の型
Zend_Memory
概要
メモリマネージャ
メモリオブジェクト
Zend_Mime
Zend_Mime
Zend_Mime_Message
Zend_Mime_Part
Zend_Navigation
導入
画面
Containers
Migrating from Previous Versions
Zend_OpenId
導入
Zend_OpenId_Consumer の基本
Zend_OpenId_Provider
Zend_Paginator
導入
使用法
設定
高度な使用法
Zend_Pdf
導入
PDF ドキュメントの作成および読み込み
PDF ドキュメントへの変更内容の保存
ページの操作
描画
Interactive Features
ドキュメントの情報およびメタデータ
Zend_Pdf モジュールの使用例
Zend_ProgressBar
Zend_ProgressBar
Zend_Queue
導入
使用例
フレームワーク
アダプタ
Zend_Queueのカスタマイズ
Stomp
Zend_Reflection
導入
Zend_Reflectionサンプル
Zend_Reflectionリファレンス
Zend_Registry
レジストリの使用法
Zend_Rest
導入
Zend_Rest_Client
Zend_Rest_Server
Zend_Search_Lucene
概要
インデックスの構築
インデックスの検索
クエリ言語
クエリ作成用の API
文字セット
拡張性
Java Lucene との相互運用
応用
ベストプラクティス
Zend_Server
導入
Zend_Server_Reflection
Zend_Service
導入
Zend_Service_Akismet
Zend_Service_Amazon
Zend_Service_Amazon_Ec2
Zend_Service_Amazon_Ec2: Instances
Zend_Service_Amazon_Ec2: Windows Instances
Zend_Service_Amazon_Ec2: Reserved Instances
Zend_Service_Amazon_Ec2: CloudWatch Monitoring
Zend_Service_Amazon_Ec2: Amazon Machine Images (AMI)
Zend_Service_Amazon_Ec2: Elastic Block Stroage (EBS)
Zend_Service_Amazon_Ec2: Elastic IP Addresses
Zend_Service_Amazon_Ec2: Keypairs
Zend_Service_Amazon_Ec2:リージョンおよび利用可能ゾーン
Zend_Service_Amazon_Ec2: Security Groups
Zend_Service_Amazon_S3
Zend_Service_Amazon_Sqs
Zend_Service_Audioscrobbler
Zend_Service_Delicious
Zend_Service_Flickr
Zend_Service_Nirvanix
Zend_Service_ReCaptcha
Zend_Service_Simpy
導入
Zend_Service_StrikeIron
Zend_Service_StrikeIron: バンドルされているサービス
Zend_Service_StrikeIron: 応用編
Zend_Service_Technorati
Zend_Service_Twitter
Zend_Service_Yahoo
Zend_Session
導入
基本的な使用法
高度な使用法
グローバルセッションの管理
Zend_Session_SaveHandler_DbTable
Zend_Soap
Zend_Soap_Server
Zend_Soap_Client
WSDLアクセッサ
自動検出
Zend_Tag
導入
Zend_Tag_Cloud
Zend_Test
導入
Zend_Test_PHPUnit
Zend_Test_PHPUnit_Db
Zend_Text
Zend_Text_Figlet
Zend_Text_Table
Zend_TimeSync
導入
Zend_TimeSync の動作
Zend_Tool_Framework
導入
CLIツールの使用
アーキテクチャ
Zend_Tool_Frameworkを利用してプロバイダを作成する
出荷されたシステムプロバイダー
Extending and Configuring Zend_Tool_Framework
Zend_Tool_Project
導入
プロジェクトを作成
Zend Toolプロジェクトプロバイダー
Zend_Translate
導入
Zend_Translate のアダプタ
翻訳アダプタの使用法
ソースファイルの作成
翻訳用の追加機能
Plural notations for Translation
以前のバージョンからの移行
Zend_Uri
Zend_Uri
Zend_Validate
導入
標準のバリデーションクラス群
バリデータチェイン
バリデータの書き方
検証メッセージ
Zend_Version
Zend Framework のバージョンの取得
Zend_View
導入
コントローラスクリプト
ビュースクリプト
ビューヘルパー
Zend_View_Abstract
以前のバージョンからの移行
Zend_Wildfire
Zend_Wildfire
Zend_XmlRpc
導入
Zend_XmlRpc_Client
Zend_XmlRpc_Server
Zend Framework のシステム要件
導入
Zend Framework PHP 標準コーディング規約
概要
PHP ファイルの書式
命名規約
コーディングスタイル
Zend Framework Documentation Standard
Overview
Documentation File Formatting
Recommendations
Zend Framework MVC アプリケーションのために推奨されるプロジェクト構造
概要
推奨されるプロジェクト・ディレクトリ構造
モジュール構造
リライト設定ガイド
Zend Framework Performance Guide
導入
クラスの読み込み
Zend_Dbパフォーマンス
国際化(i18n)とローカライズ(l10n)
ビューのレンダリング
著作権に関する情報