DB_Table_Database クラスのチュートリアル

DB_Table_Database クラスのチュートリアル --  リレーショナルデータベースへのインターフェイス

説明

DB_Table_Database は、 リレーショナルデータベース用のデータベース抽象化クラスです。 DB_Table クラスの最上位に位置する層となります。 DB_Table_Database オブジェクトの各テーブルは DB_Table オブジェクトで表します。 DB_Table_Database オブジェクトと DB_Table オブジェクト群との最大の違いは、 DB_Table_Database オブジェクトのプロパティにはデータベース全体を表すモデルが含まれているという点です。 ここにはテーブル間のリレーションといった情報も含まれます。

DB_Table_Database の機能は次のとおりです。

DB_Table と同様、DB_Table_Database は DB あるいは MDB2 のデータベース接続オブジェクトを使用します。 このクラスは PHP 4 と PHP 5 の両方で使用できますが、一部例外があります。 fromXML() メソッドは XML データベーススキーマから DB_Table_Database オブジェクトを作成するものですが、 このメソッドは PHP 5 でしか動きません。

DB_Table_Database クラスは、抽象基底クラス DB_Table_Base を継承したものです。 DB_Table_Base から継承したメソッドやプロパティについては、 このページの目次で "(DB_Table_Base から継承)" のように示します。

このチュートリアルでは、サンプルを使用して DB_Table_Database クラスでリレーショナルデータベースとのインターフェイスを作成する方法を説明します。

目次

サンプルデータベース

このチュートリアルでは、TestDB という名前のサンプルデータベースを用いて DB_Table_Database オブジェクトを利用します。 このサンプルデータベースについては後述します。RDBMS のテーブルに対応する DB_Table オブジェクトのインスタンスをまず最初に作成し、 それを親となる DB_Table_Database オブジェクトに追加 (リンク) します。

サンプルデータベース TestDB には、 さまざまな人たちの名前と電話番号、そして住所を格納することにします。 このデータベースは 4 つのテーブルで構成されています。 人の名前、電話番号、そして住所は、それぞれ Person、Phone、Addres という名前のテーブルに格納します。 複数の人がひとつの電話番号を共有することもありえますし、 逆に一人で複数の電話番号を保持することもありえます。 そのため、このデータベースでは Person と Phone の間に多対多のリレーションシップを作成できるようにします。 このリレーションシップを確率するために、PersonPhone という名前のリンクテーブルを追加します。 このテーブルでは、Person と Phone の外部キー参照を保持します。

DB_Table オブジェクトのインスタンスを作成してからでないと、 親の DB_Table_Database インスタンスにそれを追加することはできません。 DB_Table オブジェクトを作成する一般的な方法 (このクラスのチュートリアルで説明しています) は、各テーブルに対して DB_Table のサブクラスを個別に作成し、 そのサブクラスのインスタンスを作成するというものです。 このチュートリアルでは、たとえば "Entity" という名前のテーブルに対して関連づける DB_Table のサブクラスのクラス名は Entity_Table とし、 そのインスタンス名を $Entity とすることにします。 この方式は、DB_Table_Generator が生成するコードが用いている規約と同じです。

以下のコードは、Person テーブルに対応する Person_Table のサブクラスを定義するものです。
<?php
require_once 'DB/Table.php'

class Person_Table extends DB_Table
{
    // カラムを定義します
    $col = array (
        'PersonID' => array('type' => 'integer', 'require' => true),
        'FirstName' => array('type' => 'char', 'size' => 32, 'require' => true),
        'MiddleName' => array('type' => 'char', 'size' => 32),
        'LastName' => array('type' => 'char', 'size' => 64, 'require' => true),
        'NameSuffix' => array('type' => 'char', 'size' => 16),
        'AddressID' => array('type' => 'integer')
    );

    // インデックスを定義します。PersonID は主キーとして宣言します
    $idx = array(
        'PersonID' => array('cols' => 'PersonID',  'type' => 'primary'),
        'AddressID' => array('cols' => 'AddressID', 'type' => 'normal')
    );

    // 'PersonID' を自動インクリメント型のカラムとして宣言します
    $auto_inc_col = 'PersonID';

}
?>
ここで、'PersonID' は Person テーブルの主キー列となります。また AddressID は外部キーで、Address テーブル (後ほど定義します) の主キーを参照しています。

$auto_inc_col プロパティに代入している部分に注目しましょう。これは、 最近 DB_Table に追加されたプロパティです。 $auto_inc_col には、'自動インクリメント' として宣言するカラムの名前を設定します。 このカラムについての自動インクリメント機能は、 DB_Table の insert メソッドで DB あるいは MDB2 のシーケンスを用いて実装されます。

次のコードは、TestDB データベースの残りのテーブル Phone、Address および PersonPhone に対応する DB_Table のサブクラスを先ほどと同じ方法で定義するものです。
<?php
class Address_Table extends DB_Table
{
    $col = array(
        'AddressID' => array('type' => 'integer', 'require' => true),
        'Building' => array('type' => 'char', 'size' =>16),
        'Street' => array('type' => 'char', 'size' => 64),
        'UnitType' => array('type' => 'char', 'size' => 16),
        'Unit' => array('type' => 'char', 'size' => 16),
        'City' => array('type' => 'char', 'size' => 64),
        'StateAbb' => array('type' => 'char', 'size' => 2),
        'ZipCode' => array('type' => 'char', 'size' => 16)
    );
    $idx = array(
        'AddressID' => array('cols' => 'AddressID', 'type' => 'primary'),
    );
    $auto_inc_col = 'AddressID';
}
?>
<?php
class Phone_Table extends DB_Table
{
    $col = array(
        'PhoneID' => array('type' => 'integer', 'require' => true),
        'PhoneNumber' => array('type' => 'char', 'size' => 16, 'require' => true),
        'PhoneType'   => array('type' => 'char', 'size' => 4)
    );
    $idx = array(
        'PhoneID' => array('cols' => 'PhoneID', 'type' => 'primary')
    );
    $auto_inc_col = 'PhoneID';
}
?>
<?php
class PersonPhone_Table extends DB_Table
{
    $col = array(
        'PersonID' => array('type' => 'integer', 'require' => true),
        'PhoneID'  => array('type' => 'integer', 'require' => true)
    );

    $idx = array(
        'PersonID' => array('cols' => 'PersonID', 'type' => 'normal'),
        'PhoneID' => array('cols' => 'PhoneID', 'type' => 'normal')
    );
}
?>
このサンプルにおける Phone テーブルのカラム PhoneType は、 その電話番号が自宅のものか職場のものかあるいは携帯電話かを識別するものとします。 ここに格納する値は、4 文字の文字列 'HOME'、'WORK' あるいは 'CELL' のいずれかとなります。

次のコードは、各テーブルに関連づけた DB_Table のサブクラスのインスタンスを作成するものです。
<?php
$Person = new Person_Table($conn, 'Person', 'safe');
$Address = new Address_Table($conn, 'Address', 'safe');
$Phone = new Phone_Table($conn, 'Phone', 'safe');
$PersonPhone = new PersonPhone_Table($conn, 'PersonPhone', 'safe');
?>
ここで DB_Table のコンストラクタの 3 番目の引数に指定している値 'safe' の意味は、 「データベース内にその名前のテーブルが存在しない場合に限り、 そのテーブルをデータベース内に作成する」というものです。

コンストラクタ文は、DB_Table サブクラスの定義とは別のファイルに記述することをお勧めします。 そうすれば、 DB_TableDB_Table_Database のオブジェクトを シリアライズや復元 するのが簡単になります。 DB_Table サブクラスのインスタンスを復元する php ファイルはそのサブクラスの定義にアクセスできる必要があるが、 コンストラクタ文はそこに含まれてはならないからです。 各 DB_Table サブクラスの定義を別々のファイルに記述し、 そのファイル名はサブクラス名に拡張子 .php をつけたものにしておきます。 こうすることで、オブジェクトをシリアライズする際にサブクラスの定義が自動的に読み込まれます。 これについては 後ほど 説明します。

DB_Table オブジェクトを作成するもうひとつの方法としては、 DB_Table のサブクラスではなく DB_Table 自身のインスタンスを作成するという方法があります。 この場合、最初に DB_Table のオブジェクトを作成した時点ではテーブルのスキーマについての情報が含まれていません。 パブリックプロパティ $col および $idx を設定してテーブルのスキーマを定義する必要があります。 以下のコード例は、Person テーブルに対応する DB_Table のインスタンスを作成するものです。
<?php
$Person = new DB_Table($conn, 'Person');

$Person->col['PersonID'] = array('type' => 'integer', 'require' => true);
$Person->col['FirstName'] = array('type' => 'char', 'size' => 32, 'require' => true);
$Person->col['MiddleName'] = array('type' => 'char', 'size' => 32);
$Person->col['LastName'] = array('type' => 'char', 'size' => 64, 'require' => true);
$Person->col['NameSuffix'] = array('type' => 'char', 'size' => 16);
$Person->col['AddressID'] = array('type' => 'integer');

$Person->idx['PersonID'] = array('cols' => 'PersonID', 'type' => 'primary');
$Person->idx['PersonID'] = array('cols' => 'PersonID', 'type' => 'normal');

$Person->auto_inc_col = 'PersonID';
?>
この方法が使えるのは、DB_Table_Database クラスを持っている最新の DB_Table パッケージ (1.5.0RC1 以降) のみです。それ以前のバージョンの DB_Table では、DB_Table のサブクラスを作成することが必須となります。この方式で DB_Table オブジェクトを使用する上での唯一の問題は、 たとえば DB_Table のメソッドをオーバーライドして テーブルごとのビジネスロジックを実装することができないということです。 DB_Table オブジェクトは fromXML() メソッドで用いられます。 このメソッドのパラメータにはデータベーススキーマを表す XML を渡し、 返り値は DB_Table_Database オブジェクトとなります。 内部のテーブルは DB_Table のインスタンスで表されます。

コンストラクタ

DB_Table_Database オブジェクトのインスタンスは、 まず空の状態で作成します。そこに、テーブルや外部キー参照、リンク等を追加していきます。 コンストラクタのインターフェイスは、このようになります。
void DB_Table_Database(DB/MDB2 object $conn, string $name)
パラメータ $conn は DB あるいは MDB2 オブジェクトでなければなりません。 これを用いて RDBMS との接続を確立します。 パラメータ $name はデータベースの名前です。 TestDB という名前の MySQL データベースに対する DB 接続を表すオブジェクトを作成するには次のようにします (エラーチェックは省略しています)。
<?php
require_once 'DB/Table/Database.php'

$conn = DB::connect("mysqli://$user:$password@$host");
$db = new DB_Table_Database($conn, 'TestDB');
?>
$user、$password そして $host は、それぞれユーザ名、 データベースのパスワード、そしてホストマシンを表します。

モデルの作成

DB_Table オブジェクトと DB_Table_Database オブジェクトのインスタンスを作成した後でリレーショナルデータベースのモデルを作成するには、 まずデータベースオブジェクトにテーブルオブジェクトを追加し、 次に外部キー参照の宣言を行い、 それから多対多の関係を表すリンクテーブルの宣言を行う必要があります。

テーブルの追加

DB_Table オブジェクトのインスタンスを作成したら、 DB_Table_Database::addTable() メソッドで それを親データベースに追加することができます。 このメソッドのインターフェイスは次のようになります。
true|PEAR_Error addTable(object &$Table)
$Table は DB_Table オブジェクトで、 参照渡しとします。このメソッドは正常終了したときに true を返し、失敗したときには PEAR_Error オブジェクトを返します。

次のコードは、サンプルデータベースの 4 つのテーブルを DB_Table_Database オブジェクト $db に追加します。
<?php
$db->addTable($Person);
$db->addTable($Address);
$db->addTable($Phone);
$db->addTable($PersonPhone);
?>
この例を含むこれ以降の例では、エラー処理のコードは省略します。 実際に指導する場合はエラー処理を追加する必要があります。

外部キー参照の追加

テーブルをデータベースに追加したあとで、 複数テーブル間の参照を追加するには addRef() メソッドを使用します。

構文 (単純版):
true|PEAR_Error addRef(string $ftable, string|array $fkey, 
                       string $rtable, [string|array $rkey] )
ここで、$ftable は参照テーブル (外部キーテーブル) の名前で、 $fkey は外部キー、$rtable は参照先のテーブルで、 $rkey は (オプションの) 参照されるキーとなります。 オプションの $rkey パラメータを省略したり null を指定したりした場合は、 参照されるキーは参照されるテーブルの主キーとなります。 外部キーと参照キーは、DB_Table でインデックスを定義する際の構文と同じ構文で指定します。 つまり、単一カラムのキーの場合はカラム名を表す文字列、 複数カラムのキーの場合はカラム名の配列となります。 このメソッドの返り値は、正常に終了した場合は true、 失敗した場合は PEAR_Error となります。 ここで示した単純版の構文では、オプションの (5 番目と 6 番目の) パラメータを省略しています。これは 'on delete' および 'on update' 時の動作を定義するためのものです。 完全なインターフェイスは 後で示します。 たとえば、
<?php
$db->addRef('Person', 'AddressID', 'Address', 'AddressID');
?>
このコマンドは、Person テーブルの外部キー Person.AddressID から Address テーブルの主キー Address.AddressID への参照を追加します。 参照キー 'AddressID' は 'Address' テーブルの主キーでもあるので、 これは次のように書くこともできます。
<?php
$db->addRef('Person', 'AddressID', 'Address');
?>
最初の例のように参照キーを明示すると、 主キーだけでなくそれ以外のユニークインデックスを持つキーも使用することができます。 これは、標準 SQL で定義されているものです。

2 つのテーブル間の参照を追加できるのは、参照元と参照先の DB_Table オブジェクトを両方作成して DB_Table_Datbase のインスタンスに追加した後のことです。

リンクの追加

あるテーブルを、他のふたつのテーブルの多対多の関係を確立するための "リンク" テーブルとして宣言することができます。 この宣言をすると、autoJoin メソッドの挙動が変化します。 $link という名前のテーブルをリンクテーブルとして宣言し、 $table1 と $table2 の間の多対多のリレーションを表すものとした場合、 autoJoin メソッドが $table1 と $table2 を連結する際に必要に応じてこのテーブルを使用します。

addLink() メソッドには、 リンクテーブル (関連テーブル) として使用するテーブルと 関連づける 2 つのテーブルを指定します。

構文:
true|PEAR_Error addLink(string $table1, string $table2, string $link)
ここで $link がリンクテーブルの名前となり、これが $table1 および $table2 のふたつのテーブルを関連づけます。 3 つのパラメータはすべて必須で、テーブル名を表す文字列でなければなりません。 リンクするテーブル $table1 および $table2 については、どちらを先に指定するかは問いません。 $table1 と $table2 をリンクするテーブルには、 両方のテーブルを参照する外部キーがそれぞれ必須となります。 リンクをモデルに追加できるのは、 リンクテーブルからリンク先テーブル両方への参照が追加されてからとなります。 このメソッドの返り値は、正常に終了した場合に true、 エラーが見つかった場合に PEAR_Error() となります。

たとえば、
<?php
$db->addLink('Person', 'Phone, 'PersonPhone')
?>
これは、PersonPhone をリンクテーブルとして Person と Phone のふたつのテーブルをリンクします。

addLink() メソッドで、 ふたつのテーブルの間に複数のリンクテーブルを宣言することもできます。 しかし、そんなことをするとリンクの宣言が無意味になってしまいます。 autoJoin() メソッドが複数のテーブルを連結する際にリンクテーブルを使用することになったとき、 複数のリンクテーブルが指定されているとこのメソッドが失敗してしまうからです。

以下のコマンド
<?php
$db->addAllLinks()
?>
は、リンクテーブルと思われるテーブルをすべてデータベースに追加します。 このメソッドは、$table1 および $table2 を指す外部キーを持つテーブルが それらのテーブル間のリンクテーブルであるとみなします。 データベースによっては、リンクを宣言するのにこれを使えば簡単になることがあるでしょう。 まず addAllLinks メソッドでリンクテーブルと思われるものを作成し、 それから deleteLink() メソッドを使用して実際にはリンクテーブルでないものを削除していきます。

すべてのテーブルが追加され、すべての参照が追加され、 そしてすべてのリンクが追加された時点でデータベースモデルが完成したことになります。

サンプル - すべてをひとまとめにしたもの

サンプル用にディレクトリを作成し、 データベースへのインターフェイス用のコードをすべてそこにまとめていくようにします。 DB_Table の各サブクラスの定義を個別のファイルに分割し、 クラス名の後ろに拡張子 '.php' をつけた名前のファイルでそのディレクトリに保存します。 さらに、'Database.php' という名前のファイルをひとつ作成します。 このファイルで DB あるいは MDB2 の接続を作成し、 各テーブル用のオブジェクトを作成し、そして親の DB_Table_Database オブジェクトを作成します。 この構造は、既存のデータベースをもとに DB_Table_Generator が自動生成するコードと同じものです。 サンプルデータベース用の最小限の 'Database.php' ファイルを以下に示します。

例 39-1Database.php ファイル

<?php
require_once 'MDB2.php';
require_once 'DB/Table/Database.php';
require_once 'Person_Table.php';
require_once 'Address_Table.php';
require_once 'Phone_Table.php';
require_once 'PersonPhoneAssoc_Table.php';

// 注意: $dsn を作成するコードを編集します
$phptype  = 'mysqli';
$username = 'root';
$password = 'password';
$hostname = 'localhost';
$dsn = "$phptype://$username:$password@$hostname";

// DB/MDB2 接続オブジェクト $conn のインスタンスを作成します
$conn =& MDB2::connect($dsn);
if (PEAR::isError($conn)) {
    print "Error connecting to database server\n";
    print $conn->getMessage();
    die;
}

// 各 DB_Table サブクラスのインスタンスを作成します
$Person = new Person_Table($conn, 'Person');
$Address = new Address_Table($conn, 'Address');
$Phone = new Phone_Table($conn, 'Phone');
$PersonPhoneAssoc = new PersonPhoneAssoc_Table($conn, 'PersonPhoneAssoc');

// 親 DB_Table_Database オブジェクト $db のインスタンスを作成します
$db = new DB_Table_Database($conn, '42A');

// DB_Table オブジェクトを親 DB_Table_Database オブジェクトに追加します
$db->addTable($Person);
$db->addTable($Address);
$db->addTable($Phone);
$db->addTable($PersonPhoneAssoc);

// 外部参照を追加します
$db->addRef('PersonPhoneAssoc', 'PersonID', 'Person');
$db->addRef('PersonPhoneAssoc', 'PhoneID', 'Phone');
$db->addRef('Person', 'AddressID', 'Address');

// すべてのリンクテーブルを追加します
$db->addAllLinks();

?>

このサンプルファイルは、既存のデータベースをもとに DB_Table_Generator が作成する雛形ファイルとよく似ています。 主な違いは、自動生成されたファイルにはいくつかコメントされていたり編集を要したりする場所 (たとえばデータベースの DSN 定義部分など) があるということです。この例では、 addAllLinks() をコールすると 'PersonPhoneAssoc' が 'Person' と 'Phone' を連結することを正しく認識します。 このサンプルには、'ON DELETE' や 'ON UPDATE' といったトリガーの定義はありません。これは後で説明しますが、 同じファイルの最後に追加することができます。

テーブル、参照およびリンクの削除

deleteTable()deleteRef() および deleteLinks() メソッドを使用すると、それぞれテーブルや外部キー参照、 リンクテーブルの宣言を DB_Table_Database モデルから削除することができます。

deleteTable() - データベースモデルからテーブルを削除する

構文:
void deleteTable(string $table)
パラメータ $table は削除したいテーブルの名前です。 テーブルを削除すると、 そのテーブルの存在に依存しているすべてのモデルのエンティティも削除されます。 たとえばそのテーブルに関連する外部キー参照や 外部キー参照に依存しているリンク情報などです。

deleteRef() - データベースモデルから参照を削除する

構文:
void deleteRef(string $ftable, string $rtable)
$ftable および $rtable は参照するテーブルと参照されるテーブルの名前を表します。 外部キー参照を削除すると、その参照を使用しているすべてのリンクも削除されます。 つまり、$ftable がリンクテーブルで $rtable がリンクされるテーブルのひとつであるようなものです。

deleteLink() - リンクテーブルの宣言を削除する

構文:
void deleteLink(string $table1, string $table2, [string $link])
ここで、$table1 と $table2 はリンク対象のテーブルの名前、 そしてオプションのパラメータ $link はリンクテーブルの名前を指します。 $link が null だったり省略したりした場合は、$table1 と $table2 の間のすべてのリンクテーブルの宣言が削除されます。 $link が存在する場合は、$table1 と $table2 の間のリンクテーブル宣言のうち $link だけが (もし存在すれば) 削除されます。 deleteLinks() メソッドは、addAllLinks() でリンクテーブルの宣言を設定したあとで不要な物を削除するために使用することもあります。

On Delete アクションおよび On Update アクション

DB_Table_Database は、参照整合性を保つためのアクションをオプションで提供しています。 これは ANSI SQL で定義されているものですが、これをサポートしていないデータベースもあります (たとえば SQLite やデフォルトの MySQL エンジンなど)。 DB_Table_Database は、オプションで ON DELETE や ON UPDATE アクション (たとえば連鎖削除など) の PHP によるエミュレート機能を提供しています (ここで説明します)。 また、同じくオプションで、追加や更新の前に外部キーの値の検証を行います (あとで 説明します)。

参照に関連づけられた ON DELETE および ON UPDATE アクションがもしあった場合にそれを宣言するには、 addRef() の追加のパラメータを使用するか、あるいは setOnDelete() および setOnUpdate() を使用します。

addRef() によるアクションの宣言

参照される行が削除されたり更新されたりしたときのアクションを宣言するには、 モデルに参照を追加する際の addRef() メソッドに 2 つのオプションパラメータを指定します。 次の例は addRef() の拡張形式で、 上で示した PersonPhone から Person への参照を追加するものです。 また、Person の行が削除されたときの cascade アクションと更新されたときの restrict アクションを宣言しています。
<?php
$db->addRef('PersonPhone', 'PersonID', 'Person', null, 'cascade', 'restrict');
?>
4 番目のパラメータに null を渡すと、参照されるキーは デフォルトで参照テーブル Person の主キーとなることを意味します。 5 番目と 6 番目のパラメータは、それぞれ削除時のアクション ('cascade') と更新時のアクション ('restrict') を表します。 これらのパラメータに null を渡したり省略したりすると、 削除あるいは更新の際に起動するアクションが存在しないことになります。

上の例で 5 番目のパラメータに指定した値 'cascade' の意味は、 Person の行が削除されたときに、対応する PersonPhone の行をすべて削除する (連鎖削除) ということです。 6 番目のパラメータに指定した 'restrict' は、PersonPhone の行から参照されている Person テーブルの主キー PersonID を更新しようとしたときにそれを阻止します (update アクションを '制限' します)。そして update メソッドはエラーを発生させます。

addRef() メソッドの完全なインターフェイスは次のようになります。
true|PEAR_Error addRef(string $ftable, mixed $fkey, string $rtable, [mixed $rkey], 
                       [string|null $on_delete], [string|null $on_update])
$ftable は参照するテーブル、$fkey は外部キー、$rtable は参照されるテーブル、そして $rkey が参照されるキーとなります。 $fkey と $rkey にはカラム名を表す文字列かカラム名の配列を指定します。 また $rkey は null とすることもできます。 $rkey を省略したり null を指定したりした場合は、 参照されるテーブルの主キーを指定したものとみなします。 $on_delete および $on_update パラメータには、 参照される行が削除あるいは更新されたときのアクションを指定します。 $on_delete および $on_update に指定できる値は、以下の文字列リテラルのいずれか
'cascade' | 'restrict' | 'set null' | 'set default'
あるいは null のみです。デフォルト値は、どちらのパラメータも null です。アクション文字列として指定する内容は、標準 SQL で定義されているアクションを小文字に変換したものです。 このアクションを、PHP でエミュレートします。 それぞれのアクションを省略したり null 値を指定したりした場合は、 参照される行の削除時あるいは更新時に PHP 側では何の処理もしないことを意味します (PHP の null 値は、アクション文字列 'set null' ではないことに注意しましょう)。

次の例は、サンプルデータベースに必要なすべての外部キー参照と同時に、 適切なトリガーアクションを宣言するものです。
<?php
$db->addRef('Person', 'AddressID', 'Address', null, 'set null', 'cascade');
$db->addRef('PersonPhone', 'PersonID', 'Person', null, 'cascade', 'cascade');
$db->addRef('PersonPhone', 'PhoneID', 'Phone', null, 'cascade', 'cascade');
?>
これらの宣言の結果、リンクテーブル PersonPhone の行は 対応する Person あるいは Phone が削除されると同時に削除されるようになり、 Person あるいは Phone の主キーが更新されると対応する行も更新されるようになります。 Person テーブルの外部キー AddressID は、対応する Address が削除されたときには (アドレスが不明であることを示すよう) null に設定され、 Address の主キーが変更されたときには同じように更新されます。

setOnDelete() および setOnUpdate()

外部キー参照でのトリガーアクションを変更したり無効にしたりするために、 setOnDelete() メソッドや setOnUpdate() メソッドを使用することもできます。

構文:
void setOnDelete(string $ftable, string $rtable, string|null $action)
void setOnUpdate(string $ftable, string $rtable, string|null $action)
$ftable と $rtable は、それぞれ参照するテーブル (外部キーを持つテーブル) と参照されるテーブルです。$action パラメータは、その参照の on_delete あるいは on_update アクションの値です。アクション文字列あるいは null を指定します。null は、テーブル $rtable の行を削除あるいは更新する際に 何もアクションを起こさないことを意味します。

たとえば次のコードは、'PersonPhone' から 'Person' への参照の 'on_update' アクションを 'restrict' に変更します。
<?php
$db->setOnUpdate('PersonPhone', 'Person', 'restrict');
?>
その結果、PersonPhone の行から参照されている Person の行の主キーを更新することができなくなります。

setActOnDelete() および setActOnUpdate()

参照トリガーアクションの PHP でのエミュレートをデータベース全体で有効あるいは無効にするには setActOnDelete() および setActOnUpdate() メソッドを使用します。

構文:
void setActOnDelete(bool $flag)
void setActOnUpdate(bool $flag)
それぞれのメソッドに true を渡すと、すべての ON DELETE アクションあるいは ON UPDATE アクションの PHP によるエミュレートを有効にします。 一方、false を指定するとそれを無効にします。 デフォルトでは、ON DELETE アクションおよび ON UPDATE アクションの PHP によるエミュレートは両方とも有効になっています。 これらのメソッドのいずれかに false を指定してコールしても、 各参照に関連づけられた on delete アクションあるいは on update アクションを表すインスタンスプロパティ ($_ref プロパティ) は変更されません。ただ単に、 DB_Table_Database::delete() および DB_Table_Database::update() メソッドによるこれらのアクションのエミュレートを無効にするだけです。

外部キーの検証

デフォルトでは、DB_Table_Database で外部キーを持つテーブルに行を追加したり変更したりする前には外部キーの値の妥当性を検証します。 つまり、行を追加したり外部キーカラムの値を変更したりする前に、 DB_Table_Databaseinsert() メソッドや update() メソッドは参照先テーブルの既存の行の値を確認するクエリを実行するのです。 デフォルトでは、チェックに失敗した場合にこれらのメソッドはエラーを返し、 データの変更は行いません。

PHP レベルでの外部キーの妥当性チェックをデータベース内のすべてのテーブルで有効あるいは無効にするには setCheckFKey() メソッドを使用します。 このメソッドのインターフェイスは次のとおりです。
void setCheckFKey(bool $flag)
$flag に true を渡すと外部キーのチェックを有効 (デフォルト) にし、 false を渡すとチェックを無効にします。

データの取得

DB_Table_Database は、SQL の select 文用のオブジェクト指向のインターフェイスを提供します。 これは DB_Table が提供するものとほぼ同じです。

クエリ配列

DB_Table と同様、 DB_Table_Database ではクエリを配列で表します。 配列の各要素が SQL の select 文における句を表します。 たとえば、AnyTown の Oak Street に住むすべての人の名前をサンプルデータベースから取得するクエリは、 次のようになります。
<?php
$oak = array(
   'select'  => 'Person.FirstName, Person.LastName, Address.Building',
   'from'    => 'Person, Address',
   'where'   => "Person.AddressID = Address.AddressID\n" 
              . "  AND Address.Street = 'Oak Street'\n"
              . "  AND Address.City = 'AnyTown'",
   'order'   => 'Address.Building' );
?>
buildSQL() メソッドはこのようなクエリ配列をパラメータとして受け取り、 対応する SQL コマンドを文字列で返します。たとえば
<?php
echo $db->buildSQL($oak);
?>
の結果は次のようになります。
SELECT Person.FirstName, Person.LastName, Address.Building
FROM Person, Address
WHERE Person.AddressID = Address.AddressID
  AND Address.Street = 'Oak Street'
  AND Address.City = 'AnyTown'
ORDER BY Address.Building
この配列の文字列のほとんどは、 'SELECT' や 'FROM' などのキーワードを先頭につけて RDBMS にそのままの形で渡されます。 ひとつのテーブルだけにしか登場しないカラム名については、 上の例のようにテーブル名をつけて指定する必要はありません。

DB_Table と同様、 クエリ配列を public プロパティ配列 $sql に格納することもできます。
<?php
$db->sql['oak'] = $oak
?>
クエリを文字列ではなく配列形式で管理することで、 クエリを変更するのが簡単になります。 たとえば 'where' 句の最後に制限を追加するなどといったことが簡単にできるようになります。

Select* メソッド: select()selectResult() および selectCount()

select*() メソッドは、 DB_Table_Database クラスも DB_Table クラスも DB_Table_Base クラスから継承しており、 そのインターフェイスや振る舞いは同じです。 また、これら 3 つのメソッドのインターフェイスもすべて同じです。 最初の引数は必須で、先ほど保存したクエリ配列のキー
<?php
$result = $db->select('oak')
?>
あるいは対応する配列の値
<?php
$result = $db->select($oak)
?>
を指定します。 select メソッドは、結果セットを行の数値添字配列として返します。 各行の内容は、連想配列か数値添字の配列、あるいはオブジェクトのいずれかとなります。 これは DB_Table_Database オブジェクトの $fetchmode プロパティの値によって決まります。もしこのプロパティが null の場合は、その元となっている DB あるいは MDB2 オブジェクトの fetchmode で判断します。

これら 3 つの select* メソッド select()selectCount() そして selectResult() に共通のインターフェイスは、このようになります。
mixed select*( array|string $sql_key, [string $filter], [string $order], 
               [int $start], [int $count], [array $params])
さきほど説明したように、$sql_key にはクエリ配列を指定することもできますし、 $sql プロパティに設定されたベースクエリ配列のキーを指定することもできます。 $filter パラメータは SQL の論理式を表す文字列で、結果セットを絞り込みます。 ここで指定した条件は、$sql_key クエリ配列の 'where' 要素の最後に追加 (AND で連結) されます。$order パラメータは ORDER BY 句 (最初の ORDER BY を除く) で、$sql_key 配列の 'order' 要素を上書きします。 整数パラメータ $start は、結果セット全体の中の何行目からを結果に含めるかを指定します。 一方 $count は、返り値に含める最大の行数を指定します。 $params が存在した場合、それはプリペアドクエリのプレースホルダを置き換える値の配列となります。

autoJoin()

autoJoin() - カラム名の配列やテーブル名の配列をパラメータとして受け取り、 それらを自動的に join した WHERE 句を含むクエリ配列を返します。

構文:
array|PEAR_Error autoJoin([array $cols], [array $tables], [string $filter])
$cols は必要なカラム名の配列、 $tables は join したいテーブルの配列、そして $filter は結果を絞り込むために使用する SQL の論理式です。 $filter は、join 条件を自動生成した後で 'where' 要素の最後に追加 (AND で連結) されます。 $col および $tables はどちらもオプションですが、 少なくともどちらか一方は指定する必要があります。 autoJoin が返すクエリは、$tables に含まれるすべてのテーブル (あるいは $cols のカラムが含まれるすべてのテーブル) とそれらを連結するために必要なリンクテーブルを inner join したものとなります。

次の例は、ある人の名前と自宅の電話番号そして住所を取得するクエリを作成して実行するものです。 必要なカラムを指定して作成しています。 今回のサンプルデータベースの場合、これは 4 つのテーブルすべてを連結することになります。
<?php
$cols = array('FirstName', 'LastName', 'PhoneNumber', 'Building', 'Street', 'City')
$report = $db->autoJoin($cols, "Phone.PhoneType = 'HOME'");
$result = $db->select($report);
?>
$cols パラメータのカラム名は、 完全に特定できるのならテーブル名をつける必要はありません。 autoJoin メソッドの内部で validCol() メソッドを使用しており、ここでカラム名の検証と解決を行います。

このクエリ配列に対応する SQL コマンドを取得するには buildSQL() を使用します。 この例の場合、
<?php
echo $db->buildSQL($report);
?>
の結果は次のようになります。
SELECT Person.FirstName, Person.LastName, Phone.PhoneNumber, Address.Building, Address.Street, Address.City
FROM Person, Phone, Address, PersonPhone
WHERE PersonPhone.PhoneID = Phone.PhoneID
  AND PersonPhone.PersonID = Person.PersonID
  AND Person.AddressID = Address.AddressID
上の例のように autoJoin() にカラム名のみを渡した場合、 それらのカラムが含まれるテーブル群を取得て連結します。 多対多のリレーションを実現するためにリンクテーブルを使用している場合、 それも連結します。

次の例では $cols パラメータが null となっています。 しかし、そのかわりに連結するテーブル名を $tables パラメータで指定しています。
<?php
$tables = array('Person', 'Address', 'Phone');
$report = $db->autoJoin(null,$tables);
$result = $db->select($report);
?>
この結果できあがる SQL コマンドは、次のようになります。
SELECT *
FROM Person, Phone, Address, PersonPhone
WHERE PersonPhone.PhoneID = Phone.PhoneID
  AND PersonPhone.PersonID = Person.PersonID
  AND Person.AddressID = Address.AddressID
この例のように autoJoin の最初のパラメータが null の場合、 SELECT 句はデフォルトで 'SELECT *' を使用します。 FROM 句および WHERE 句で、明示的に指定していない PersonPhone テーブルが追加されていることに注目しましょう。 このテーブルは、必要なデータを含むテーブルを連結するために必要となるからです。

アルゴリズム: autoJoin() は適切な結合条件が存在するかどうかを探し、 曖昧さがない状態で見つかった場合にはそれを使用します。 参照構造やリンクテーブルによって複数の結合が可能な場合や 適切な結合条件が作成できない場合には PEAR Error を返します。 このメソッドはまず最初に $col プロパティおよび $table プロパティを調べ、連結しなければならないテーブルの一覧を取得します。 それから、連結されたテーブルのネットワーク (joined set) を作成します。まずひとつのテーブルから始め、そこにまだ連結されていないテーブル (the unjoined set) の内容を順に追加していくという方式です。 この処理は、まず最初に joined set の核として使用するテーブルをひとつ取り出し、 unjoined set の中からそのテーブルに連結できるテーブルを探していきます。 これ以降の処理も同様に、unjoined set の中のテーブルを順に調べて joined set のいずれかのテーブルに連結できるもの (joined set の中のテーブルを参照している、あるいは参照されているもの) を探すというようになります。 まだ連結されていないテーブルの中で joined set 内のひとつのテーブルだけに連結できるテーブルが見つかった場合はそのテーブルを joined set に追加し、次のテーブルを探し始めます。unjoined set の中に joined set の複数のテーブルに連結できるテーブルが見つかった場合は、 結合条件が曖昧であるというエラーを返します。このメソッドはツリーグラフ (テーブルがノードに対応する) 状の結合にのみ対応しており、 複数接続された連結には対応していません。 unjoined set のテーブルの中に、joined set 内のテーブルに直接連結できるものが見つからない場合は、 unjoined set 内をもう一度見直し、リンクテーブル経由で joined set 内のひとつのテーブルに連結できるテーブルを探します。 リンクテーブルを使用して joined set 内の複数のテーブルに連結できるテーブルが見つかった場合は、 エラーとなります。まだ連結されていないテーブルの中に、 直接であってもリンクテーブル経由であっても連結できないものが見つかった場合は、 テーブルのセットが連結できないというエラーとなります。

SQL ユーティリティ

quote()

quote メソッドは、パラメータ $value を SQL リテラル文字列で表したものを返します。

構文:
string quote(mixed $value)
DB_Table_Database::quote() メソッドは、その内部で DB::quoteSmart() メソッドあるいは MDB2::quote() メソッドをコールします。返り値は常に文字列で、 そのフォーマットは $value のデータ型によって異なります。 $value が文字列の場合は、このメソッドは実際の RDBMS にあわせて適切にクォートやエスケープをした文字列を返します。 $value が整数値あるいは浮動小数点数値である場合は、 その数値をクォートせずに文字列として返します。 $value が boolean の場合は、true なら '1'、false なら '0' を返します (これは、DB_Table の抽象データ型が使用している boolean と整数値の対応と同じです)。 $value が null の場合は、クォートしていない文字列 NULL を返します。

buildFilter()

buildFilter() は SQL の論理式を返します。 この式は、指定したデータベースのカラムが SQL のリテラル値に等しい場合に true となります。パラメータには配列を渡す必要があります。 配列のキーがカラム名、そして配列の値が要求する内容となります。

次の例は、buildFilter メソッドを使用して Peoria の Pine St. に住む人を抽出するフィルタを作成するものです。
<?php
$data = array('Street' => 'Pine St', 'City' => 'Peoria');
$filter = $db->buildFilter($data);
?>
$filter を表示すると、次のような SQL 部分文字列となります。
Street = 'Pine St' AND City = 'Peoria'
この例では、コードを書くより直接 SQL を書いた方が簡単かもしれません。 この関数が有用なのは、カラム名や値が単なる文字列リテラルではなく、 さまざまな型の変数であったりエスケープを要したりする場合です。 buildFilter メソッドは内部で quote メソッドを使用して SQL リテラル文字列を作成しています。

buildSQL()

buildSQL() - select* メソッドで使用する形式のクエリ配列を受け取り、 対応する SQL コマンド文字列を返します。これは select*() メソッドから内部的にコールされます。buildSQL()select*() メソッドは、どちらも DB_Table_Base から継承したものです。

構文: インターフェイスは select*() メソッドに似ています
string|PEAR_Error buildSQL(array|string $query, [string $filter], [string $order], 
                           [int $start], [int $count])
select*() メソッドと同様、$query はクエリ配列あるいは $sql プロパティに保存されたクエリ配列のキーとなります。 また $filter は SQL の論理条件で、クエリ配列の 'where' 要素の最後に追加されます。$order は ORDER BY 句で、クエリ配列に 'order' 要素がある場合はそれを上書きします。$start と $count は、返される結果セットの最初の行番号と返す最大行数を表します。 $query 引数のみが必須となります。

validCol()

validCol() - カラム名を検証し、 (必要に応じて) 曖昧さを解決します。

構文:
array|PEAR_Error validCol(string $col, [array $from])
必須パラメータ $col はカラム名です。オプションのパラメータ $from は、テーブル名の配列です。

$col パラメータは、カラム名の前にテーブル名をつけて SQL で使用する table.column 形式とすることもできますし、 もしカラム名が曖昧さなしに解決できるのであればテーブル名をつけなくてもかまいません。 validCol の返り値は、成功した場合は配列となります。 配列の 2 番目の要素は修飾していないカラム名で、最初の要素はテーブル名 ($col がテーブル名で修飾されているか、 あるいはテーブル名が一意に決まる場合) あるいはカラム名の配列 ($col がテーブル名で修飾されておらず、 そのカラム名が複数のテーブルに存在する場合) となります。指定した名前のカラムがデータベースに存在しない場合は PEAR error が返されます。

オプションの $from パラメータは、$col が明示的にテーブル名で修飾されていない場合にのみ使用します。 $from はテーブル名 (SQL の select 文の from 句で指定したもの) の配列で、ここから指定した名前のカラムを探します。 この場合、validCol は最初に $from のテーブルを探し、 一意な結果が得られた場合にテーブル名を返します。 指定した名前のカラムを含むテーブルが $from の中に複数見つかった場合は、返り値はそのセットとなります。 $from の中に指定した名前のカラムを含むテーブルがなかった場合は、 検索範囲をデータベース内のすべてのテーブルに広げます。 それでもまだ複数の選択肢が残った場合 (from の複数のテーブルがあてはまった場合、 あるいはデータベース内のそれ以外のテーブルの中に複数あてはまるものがあった場合)、 もしそのカラムがテーブルの外部キーではないテーブルがあるのなら、 カラムが外部キーとなっているテーブルを除外します。

データの変更: insert()update() および delete()

DB_Table_Databaseinsert()delete() および update() メソッドのインターフェイスや振る舞いは、 DB_Table の対応するメソッドと似ています。 唯一のインターフェイスの違いは、DB_Table_Database のメソッドでは最初のパラメータとして SQL を適用するテーブル名を指定する必要があるということです。

構文:
true|PEAR_Error insert(string $table, array $data)
true|PEAR_Error delete(string $table, [string $where])
true|PEAR_Error update(string $table, array $data, [string $where])
これらの 3 つの関数で、$table_name はその操作を適用するテーブルの名前を表します。 insert および update メソッドでは、$data は追加したり更新したりするデータの連想配列となります。 連想配列のキーはカラム名を表す文字列で、 連想配列の値はデータベースに追加したり更新したりする値です。 delete および update メソッドでは、オプションのパラメータ $where で SQL の論理条件を文字列で指定します。 これを使用して、削除したり更新したりする行を絞り込みます。 つまり、$where パラメータの内容は、それぞれ対応する SQL における WHERE 句から最初の 'WHERE ' を除いた内容となります。 これらのメソッドは、操作が正常に終了した場合に true を返します。 またエラーが発生した場合に PEAR Error を返します。

DB_Table_Database のこれらのメソッドは、 対応する DB_Table のメソッドを内部でコールする単なるラッパーです。 その結果、DB_Table のサブクラスでこれらのメソッドを上書きして特定のテーブルの振る舞いを変更すると、 自動的に DB_Table_Database のメソッドの振る舞いも変わります。

DB_Table_Databaseinsert() メソッドや update() メソッドは、データベースのデータを実際に変更する前に外部キーの検証を行います。 また、外部キーの検証とトリガーアクションが有効な場合は 連鎖削除のような参照トリガーアクションをエミュレートすることもできます。 DB_Table の対応するメソッドは、 DB_Table が親の DB_Table_Database オブジェクトに追加されて (親オブジェクトへの参照が含まれて) そのアクションが親の DB_Table_Database オブジェクトで有効になっている場合に同じ動作をします。 外部キーの検証は、デフォルトでは無効になっています。 参照トリガーアクションはデフォルトで有効になっており、 データベースモデルで宣言されているすべてのアクションが有効となります。

insert() メソッドおよび update() メソッドは、外部キーの検証に失敗した場合やデータベースコマンドでエラーが発生した場合に PEAR_Error オブジェクトを返します。 また、ON DELETE アクションあるいは ON UPDATE アクションとして 'restrict' が宣言されて有効になっている場合に、 他のテーブルの外部キーから参照されている行を削除あるいは更新しようとした際にも PEAR_Error を返します。

PHP のシリアライズ

DB_Table_Database の状態をページをまたがって管理するには、データベース全体を文字列にシリアライズし、 それをセッション変数やファイルあるいはデータベースに保存します。 シリアライズされた DB_Table_Database オブジェクトには、すべてのテーブルについてシリアライズされた情報が含まれます。 つまり、あとでデータベースを復元するときに必要な情報が含まれているというわけです。 シリアライズを行うには、PHP の serialize 関数を使用します。
<?php
$db_serial = serialize($db);
?>
DB_Table_Database オブジェクトの状態を アンシリアライズして復元するには、次のふたつのコマンドが必要です。
<?php
$db = unserialize($db_serial);
$db->setDBconnection($DB_object);
?>
ここで、$DB_object は DB あるいは MDB2 の接続オブジェクトとなります。 setDBconnection メソッドは、データベース接続を 親の DB_Table_Database オブジェクトとそのすべての子オブジェクト DB_Table に設定します。

DB_Table_Database オブジェクトがアンシリアライズされると、 子である DB_Table オブジェクトも DB_Table_Database::__wakeup() メソッドによってアンシリアライズされます。 DB_Table オブジェクトが DB_Table のサブクラスのインスタンスである場合、 テーブルをアンシリアライズする前にそのサブクラスの定義がメモリ上になければなりません。 これは、該当するクラスの定義を含むファイルを明示的にインクルードしておくか、 あるいは wake-up メソッドに組み込まれている auto-load の仕組みを使用します。

サブクラスの定義を自動で読み込ませるには、 各サブクラスをデフォルトのディレクトリ内の個別のファイルで定義する必要があります。 ファイル名は、クラス名に拡張子 '.php' を付加したものとなります。 アンシリアライズの際に "classname" という名前の DB_Table のサブクラスが必要となり、まだその定義がメモリ上になければ、 __wakeup() メソッドはこのディレクトリから "classname.php" というファイルをインクルードしようとします。

自動読み込みを機能させるには、get_class 関数で取得できるクラス名と同じ名前のファイルを用意する必要があります。 PHP 4 ではこれはクラス名を小文字にしたものとなりますが、 PHP 5 では大文字小文字がそのまま保持されます。

setTableSubclassPath()

setTableSubclassPath() - DB_Table のサブクラス定義を格納するデフォルトのディレクトリへのパスを設定します。

構文:
void setTableSubclassPath(string $path)
パラメータ $path はディレクトリへのパスです。 最後のディレクトリ区切り文字はつけません。 パスの指定は、現在の PHP の設定での 'require_once' 文と同じ形式でなければなりません。

XML へのシリアライズ

toXML() メソッドと fromXML() メソッドを使用すると、データベーススキーマを XML 文字列にシリアライズしたりその逆を行ったりすることができます。 fromXML() メソッドは simpleXML を使用して XML 文字列をパースします。したがって PHP 5 が必要となります (このクラスの中で唯一 PHP 4 では使用できないメソッドです)。

これらのメソッドで使用する XML スキーマは、現在の MDB2_Schema の DTD を拡張して外部キー参照を指定できるようにしたものです。 外部キー対応の拡張は、将来の MDB2_Schema のリリースに取り込まれる予定です。

toXML() メソッドは、データベース全体を XML 文字列で返します。つまりすべてのテーブルや外部キー参照を含んだもので、 次のように使用します。
<?php
$xml_string = $db->toXML();
?>
DB_Table_Database::fromXML() は静的メソッドで、 MDB2 XML データベーススキーマ文字列をパラメータを受け取って DB_Table_Database オブジェクトを返します。 このオブジェクトにはデータベース内のすべてのテーブルや参照の情報が含まれます。 次のコマンドは、DB_Table_Database オブジェクトを作成して RDBMS に接続するために必要なものです。
<?php
$db = DB_Table_Database::fromXML($xml_string);
$db->setDBconnection($DB_object);
?>
ここで、アンシリアライズの際には setDBconnection() メソッドを使用してオブジェクトからデータベースへの接続を確立しています。 $DB_object は DB あるいは MDB2 の接続オブジェクトです。 fromXML() が返す DB_Table_Database オブジェクトのテーブルはすべて DB_Table のインスタンスであり、DB_Table のサブクラスのインスタンスではありません。 fromXML() は、XML 文字列のパースに失敗したり DB_Table_Database オブジェクトや子 DB_Table オブジェクトのインスタンス化でエラーが発生したり PHP 4 のインタプリタからコールされたり (PHP 5 が必須です) した場合に PEAR_Error を返します。

DB_Table プロパティの設定

DB_Table_Database のメソッドの中には、 データベース内の全テーブル用に DB_Table のプロパティの値を設定するものもあります。 これらのメソッドは、どれも DB_Table の対応するメソッドと同じ名前とインターフェイスを持っています。
void autoValidInsert(bool $flag);
void autoValidUpdate(bool $flag);
void autoRecast(bool $flag);
void autoInc(bool $flag);
それぞれの boolean 引数を設定することで、DB_Table の特定の機能を有効 (true) にしたり無効 (false) にしたりすることができます。 これらの機能はすべてデータの追加や更新に関連するもので、 DB_Tableinsert() メソッドおよび update() メソッドの中で実装されています。 DB_Table_Databaseinsert() メソッドおよび update() メソッドは単純に DB_Table の対応するメソッドをコールするだけです。 したがって、これらのプロパティを変更すると DB_Table_Database のメソッドの振る舞いも変わります。

autoValidInsert メソッドおよび autoValidUpdate メソッドは、 データを追加したり更新したりする前のデータ型の検証機能を有効あるいは無効にします。 autoRecast メソッドは、データを追加したり更新したりする前の自動型変換機能を有効あるいは無効にします。 autoInc は、追加の際の $auto_inc_col カラムの値の PHP による自動インクリメント機能を有効あるいは無効にします。 この機能を有効にしても、追加する際にそのカラムを null のままにしておかない限りは自動インクリメントは行われないことに注意しましょう。

Get* メソッド

DB_Table_Database の大半のプロパティは private です。get* メソッドは、これらの private プロパティ用に定義されています。 プロパティやそれに対応する get* メソッドの一覧については API ドキュメントを参照ください。