CXLV. SDO リレーショナルデータアクセスサービス関数

導入

警告

この拡張モジュールは、 実験的 なものです。この拡張モジュールの動作・ 関数名・その他ドキュメントに書かれている事項は、予告なく、将来的な PHP のリリースにおいて変更される可能性があります。 このモジュールは自己責任で使用してください。

SDO でリレーショナルデータアクセスサービスを使用するには、 SDO の元となっている概念を理解しておく必要があります。例えば データグラフ、データオブジェクト、切断された動作、変更履歴、 XPath およびプロパティの表現方法などです。これらの考え方に なじみがない場合は、まず最初に SDO の節 を参照したほうがよいでしょう。 さらに、リレーショナル DAS では、バックエンドのデータベース固有の 実装を意識せずに使用させるために PDO 拡張モジュールを使用しています。 リレーショナル DAS を使用するには、PDO データベース接続を作成して 渡す必要があります。そのため、 PDO の節 も参照したほうがよいでしょう。

リレーショナル DAS の役割は、リレーショナルデータベースと アプリケーションの間でのデータのやり取りを行うことです。 これを行うためには、データベースのエンティティ - テーブル、カラム、主キー、外部キー - および SDO モデルの要素 - 型、プロパティ、包含関係など の対応関係をリレーショナル DAS が知っておく必要があります。 リレーショナル DAS を作成する際に、これらの情報をメタデータとして 指定します。

操作の概要

  1. まずはじめに リレーショナル DAS のコンストラクタをコールし、 データベースと SDO モデルの関連付けを定義したメタデータを渡します。 以下でいくつかの例を示します。

  2. 次に行うことは、リレーショナル DAS の executeQuery() メソッドをコールして 実行する SQL 文を渡すか、あるいは executePreparedQuery() メソッドをコールして プレースホルダつきのプリペアドステートメントと挿入する値のリストを 渡すことでしょう。また、実行するクエリ自身についてのメタデータも 指定する必要があります。DAS は、このメタデータによって データベースからどんなカラムがどんな順番で返ってくるのかを 知ります。そのほか、PDO データベース接続も渡さなければなりません。

    executeQuery() あるいは executePreparedQuery() から返される値は、 結果セットのすべてのデータを含む正規化されたデータグラフです。 複数のテーブルから取得したデータを返すクエリの場合、このグラフには 複数のデータオブジェクトが含まれ、それらは SDO の包含関係で 連結されています。データの中には、SDO の (包含関係ではない) 参照も含まれるかもしれません。

    クエリが実行されてデータグラフが作成された後は、リレーショナル DAS やデータベース接続のインスタンスは必要ありません。 データベース上でロックを保持することはありません。 リレーショナル DAS および PDO データベース接続は、それぞれ メモリから削除されます。

  3. データグラフの中のデータに変更が加えられることも大いにありえるでしょう。 データグラフは PHP のセッション内にシリアライズすることが可能で、 いちどきりのクライアント - サーバのやり取りだけではなく複数に またがって使用できます。データオブジェクトを作成してグラフに 追加する・グラフ内の既存のデータオブジェクトを削除する・ グラフ内のデータオブジェクトを変更するなどが可能です。

  4. 最後に、リレーショナル DAS の applyChanges() メソッドを使用して、データグラフに対する変更内容をデータベースに 書き戻します。そのためには、先ほどと同じメタデータを使用して リレーショナル DAS の新しいインスタンスを作成する必要があります。 また、データベースへの接続も確立しなければなりません。 接続およびデータグラフが、applyChanges() に渡されます。ここで、リレーショナル DAS は変更の内容を吟味して INSERT、UPDATE および DELETE のうち適切な SQL 文を作成します。 UPDATE および DELETE 文を実行するには、データベース内のデータが もとのままでなけれあbなりません。そのため、もしデータベース内の データが変更されていた場合は、それが検出されます。ここでは そのような衝突は発生しなかったことにしましょう。これにより、 変更内容がデータベースに適用されました。この後は、 データグラフにさらに変更を加えて再度適用することもできますし、 あるいはデータグラフを削除することもできます。

別の方法でデータベース内のデータを扱うこともできます。 例えば、事前に executeQuery() をコールせずに、 単にデータオブジェクトを作成してそれをデータベースに書き戻すことができるのです。 この方法については、下の で説明します。

インストール手順

すべての SDO コンポーネントのインストール手順は、 SDO のドキュメントにある インストール手順 に書かれています。

リレーショナル DAS が PHP で書かれているので、 include_path の場所に配置する必要があるということに注意しましょう。

もちろん、アプリケーションではこのようにしてリレーショナル DAS をインクルードする必要があります。

<?php
require_once 'SDO/DAS/Relational.php';
?>

要件

リレーショナル DAS を使用するには、SDO 拡張モジュールがインストール されている必要があります。SDO 拡張モジュールを使用するには、 PHP 5.1 以降のバージョンが必要で、リレーショナル DAS を使用するには より新しいバージョンが必要となります。これには PDO の重要な修正が 含まれています。PHP の要件についての最新情報は、PECL パッケージの CHANGELOG を参照ください。これを書いている時点では、リレーショナル DAS に必要なのは PHP 5.1 の最新のベータ版、すなわち PHP 5.1.0 です。

リレーショナル DAS は、PDO を使用してリレーショナルデータベースに アクセスします。そのため、さまざまなリレーショナルデータベースで 実行できなければなりません。これを書いている時点では、以下の環境で テスト済みです。

  • MySQL 4.1.14 の Windows 版。 リレーショナル DAS は、PHP 5.1.0 のビルド済みバイナリに 含まれる php_pdo_mysql ドライバで正常に動作します。

  • MySQL 4.1.13 の Linux 版。 MySQL 用 PDO ドライバの最新版が必要です。これは PHP 5.1.0 に 組み込まれています。もし既に PECL から通常のドライバを インストールしている場合は、 pear uninstall pdo_mysql でそれをアンインストールします。その後、 --with-pdo-mysql オプションを指定して PHP の configure を行います。

  • DB2 8.2 Personal Edition の Windows 版。 リレーショナル DAS は、PHP 5.1.0 のビルド済みバイナリに 含まれる php_pdo_odbc ドライバで正常に動作します。

  • DB2 8.2 Personal Developer's Edition の Linux 版。 PHP の configure およびビルドの際に必要なインクルードファイルが 含まれているため、Developer's Edition を使用しなければなりません。 --with-pdo-odbc=ibm-db2 オプションを指定して PHP の configure を行います。

リレーショナル DAS は、変更をデータベースに適用する際に トランザクションを使用します。つまり、変更を適用する前には PDO::beginTransaction() をコールし、変更が完了すると PDO::commit() あるいは PDO::rollback() をコールします。どのデータベースを選択するにしても、そのデータベース および PDO ドライバはこれらのコールをサポートしていなければなりません。

制限事項

現在のリレーショナル DAS のリリースには、以下の制限があります。

  • null をサポートしていません。SQL の NULL 型をサポートしていません。 データオブジェクトのプロパティに PHP の NULL を代入することは できず、リレーショナル DAS はそれをデータベースに NULL としては 書き込みません。クエリの結果に null が見つかった場合、対応する プロパティには何も設定されません。

  • SDO のリレーションシップのうち、2 つの形式にしか対応していません。 以下で説明するリレーショナル DAS のメタデータは、 「複数の値をとる包含関係」「単一の値をとる (包含関係ではない) 参照」 の 2 つの SDO リレーションシップにしか対応していません。 SDO では、値が単一であるか複数であるかと包含関係の有無は それぞれ独立して指定可能です。つまり、SDO で定義されている すべてのリレーションには対応していないということです。 これらのリレーションを使用できれば有用でしょうが、現在の実装では 管理することができないということです。 例えば、「単一の値をとる包含関係」は使用できません。

  • SDO のデータ型のすべてに対応しているわけではありません。 リレーショナル DAS では、SDO モデルのすべてのプリミティブ型 プロパティを文字列型として扱います。 SDO では integer、float、boolean そして日付や時刻などの さまざまな型が定義されています。リレーショナル DAS では、文字列だけでこれらのデータを十分に扱うことができます。 なぜなら PHP、PDO とデータベースの間で、 データベースに保存する際に適切な型変換を行ってくれるからです。 他の DAS との間でデータグラフのやりとりをする際に、 これが何がしかの問題を起こすことがあります。

  • ひとつのテーブルに対して複数の外部キーを定義することはできません。 メタデータでは、テーブルごとにひとつの外部キーしか指定できません。 この外部キーは、サポートしている 2 つの SDO リレーションシップのうちの どちらかひとつに関連付けられます。この制限のもとでは 表すことのできない状況があるのは明らかです。例えば、 ひとつのテーブルから他のテーブルに対して、(包含関係でない) 参照をふたつ以上を設定することができません。

この節では、リレーショナル DAS を使用してリレーショナルデータベースの データを作成し、取得し、更新して削除する方法を説明します。 以下の例のほとんどで使用しているのは、3 つのテーブル (company、department、employee) からなるデータベースで、 会社の中には部署が存在し、部署には従業員が所属しているという構造です。 これは、SDO の資料中の例でも使用されています。 Service Data Objects specification の例か、SDO 拡張モジュールのマニュアルの を参照ください。

リレーショナル DAS は、リレーショナルデータベースの定義 およびそれをどのように SDO に関連付けるかを定義したメタデータを 使用して作成されます。これ以降で、メタデータの構造 およびリレーショナル DAS の作成方法について説明します。 以下の例では、インクルードされた PHP ファイル内に メタデータが存在するものとします。

以下で説明する例およびその他の例は、リレーショナル DAS パッケージの Scenarios ディレクトリ内に存在します。

メタデータに間違いがあった場合、あるいはデータベースに対する SQL 文の実行時にエラーが発生した場合には、リレーショナル DAS は例外をスローします。簡潔に説明するため、以下の例では リレーショナル DAS のコールの際の try/catch ブロックの使用を 省略しています。

これらの例は、SDO が想定している使用法とは 以下の 2 つの点で大きく異なります。

まず、ここではデータベースに対するすべての操作を ひとつのスクリプト内で完結させています。これは現実的ではありませんが、 リレーショナル DAS の使用法を説明するという点でこの方法を選択しました。 通常は、データベースに対する処理はいくつかに分割され、そのたびに PHP セッションへのデータグラフの保存やそこからの引き出しが発生します。 これにより、アプリケーションがユーザと対話的なやりとりをすることになるでしょう。

2 番目に、データベースに対するすべてのクエリがハードコーディングされており、 変数の置換を使用していません。例の中で使用するのは安全な executeQuery() コールであり、 例について説明するためにこのようにしています。 しかし、実際に使用する場合は、すべての SQL 文が安全であるとは限りません。 SQL クエリ内での変数の置換を安全に行い、SQL インジェクションを防ぐには、 executePreparedQuery() でプレースホルダを含むプリペアドステートメントを使用し、 置換する値のリストを渡します。

メタデータを指定する

最初の少し長めの節では、データベースや SDO モデルについて表したメタデータが、リレーショナル DAS からどのように返されるのかを説明します。

リレーショナル DAS のコンストラクタが実行される際に、 情報を受け取る必要があります。この情報は、 コンストラクタへの最初の引数として連想配列で渡します。 これにより、リレーショナル DAS に対して、 必要なデータベース情報を伝えます。 テーブル名、カラム、主キーや外部キーの情報がここに含まれます。 必要な内容を理解することはきわめて簡単でしょう。 一度書いてしまえば、それを PHP ファイルに保存して 必要に応じてインクルードすることができます。 コンストラクタの二番目、三番目の引数として、リレーショナル DAS がオブジェクト間の関連やデータグラフの形式を知るために必要な情報を渡します。 これによって、最終的にデータベースのデータをグラフに正規化する方法が決まります。

データベースのメタデータ

コンストラクタに対する最初の引数で、対象となる リレーショナルデータベースを定義します。

各テーブルは、最大で 4 つのキーからなる連想配列で表されます。

キー
nameテーブルの名前。
columns カラムの名前を含んだ配列。順不同。
PK主キーとなるカラムの名前。
FK ふたつのエントリ 'from' および 'to' からなる配列で、 外部キーを含むカラムおよび外部キーが参照するテーブルを指定します。 テーブルに外部キーが存在しない場合には 'FK' を指定する必要はありません。指定できる外部キーの数は ひとつだけです。また、外部キーが参照する先は、 そのテーブルの主キーでなければなりません。

<?php
/*****************************************************************
* データベースを定義するメタデータ
******************************************************************/
$company_table = array (
    
'name' => 'company',
    
'columns' => array('id', 'name',  'employee_of_the_month'),
    
'PK' => 'id',
    
'FK' => array (
        
'from' => 'employee_of_the_month',
        
'to' => 'employee',
        ),
    );
$department_table = array (
    
'name' => 'department',
    
'columns' => array('id', 'name', 'location', 'number', 'co_id'),
    
'PK' => 'id',
    
'FK' => array (
        
'from' => 'co_id',
        
'to' => 'company',
        )
    );
$employee_table = array (
    
'name' => 'employee',
    
'columns' => array('id', 'name', 'SN', 'manager', 'dept_id'),
    
'PK' => 'id',
    
'FK' => array (
        
'from' => 'dept_id',
        
'to' => 'department',
        )
    );
$database_metadata = array($company_table, $department_table, $employee_table);
?>

このメタデータに対応するリレーショナルデータベースは、 例えば MySQL の場合は以下のように定義されます。

create table company (
   id integer auto_increment,
   name char(20),
   employee_of_the_month integer,
   primary key(id)
 );
 create table department (
   id integer auto_increment,
   name char(20),
   location char(10),
   number integer(3),
   co_id integer,
   primary key(id)
 );
 create table employee (
   id integer auto_increment,
   name char(20),
   SN char(4),
   manager tinyint(1),
   dept_id integer,
   primary key(id)
 );

あるいは DB2 なら以下のようになります。

create table company ( \
    id integer not null generated by default as identity,  \
    name varchar(20), \
    employee_of_the_month integer, \
    primary key(id) )
create table department ( \
    id integer not null generated by default as identity, \
    name varchar(20), \
    location varchar(10), \
    number integer, \
    co_id integer, \
    primary key(id) )
create table employee ( \
    id integer not null generated by default as identity, \
    name varchar(20), \
    SN char(4), \
    manager smallint, \
    dept_id integer, \
    primary key(id) )

この例ではデータベースに外部キーが定義されておらず、 データベースには参照整合性が強制されていません。しかし、 department テーブルの co_id カラム および employee テーブルの dept_id カラムは、 それぞれ company あるいは department レコードの主キーを含むように 意図されています。そのため、これらの 2 つのカラムは外部キーのように動作します。

この例には 3 つめの外部キーがあります。それは company レコードの employee_of_the_month カラムが employee テーブルのひとつの行をさすというものです。この外部キーと、 その他の 2 つのキーとの違いに注意しましょう。 employee_of_the_month カラムは単一の値をとる関係を表します。ある会社には、 「今月の従業員」はひとりしか存在しえません。 co_id および dept_id は複数の値をとる関係を表します。会社には多くの部署がありますし、 ひとつの部署には多くの従業員が属しています。 この差は、 メタデータの残りの部分で company-department および department-employee の関係を包含関係として扱う時に明らかになります。

データベースのメタデータを作成する際に従わなければならない規則は 以下のとおりです。

  • すべてのテーブルには主キーが存在し、その主キーがメタデータで 指定されている必要があります。主キーがなければ、オブジェクトの 一意性を確保することができません。テーブルを作成する SQL 文を見てわかるように、主キーは自動生成することができます。 つまり、レコードが挿入される際に自動で値が設定されるということです。 この場合、自動生成された主キーはデータベースから取得可能で、 データベースに行が追加されると、すぐにデータオブジェクトに反映されます。

  • データベースに存在するすべてのカラムについてメタデータで記述する必要はありません。 実際に使用するものだけを指定します。例えば、company テーブルの中に SDO 経由でのアクセスを必要としないカラムがある場合、そのカラムを メタデータに記述する必要はありません。一方、それを記述したとしても 何の問題もありません。アプリケーションから値を取得したり代入したり することがなければ、未使用のカラムを記述していても何の影響も及ぼしません。

  • データベースのメタデータで、外部キーの指し示す先として カラム名ではなくテーブル名が指定されていることにお気づきでしょう。 厳密に言えば、リレーショナルモデルでは 主キー以外を対象とした外部キーを指定することも可能です。 しかし、SDO モデルを作成する際に使用できるのは 主キーを対象とした外部キーだけなので、 メタデータではテーブル名だけを指定します。 これにより、外部キーの指し示す先がそのテーブルの主キーであると解釈されます。

これらの規則に従い、データベースを定義する SQL 文があれば、データベースのメタデータを作成するのは簡単です。

リレーショナル DAS でのメタデータの扱い

リレーショナル DAS は、データベースのメタデータを使用して SDO モデルの大半を作成します。データベースのメタデータ内にある 各テーブルに対して、SDO 型が定義されます。 プリミティブ型を表すカラム (外部キーとして定義されていないカラム) は、SDO 型のプロパティとして追加されます。

すべてのプリミティブなプロパティは、SDO モデルにおいては 文字列型で表されます。これは、SQL での型が何であっても同じです。 データをデータベースに書き戻す際も、リレーショナル DAS が作成する SQL 文では文字列として扱います。 そして、データベースがそれを適切な型に変換します。

外部キーの扱いかたには二通りの方法があります。 どちらが使用されるかは、コンストラクタの第三引数 (SDO の包含関係について定義するもの) によって決まります。 そのため、これについては以下の SDO の包含関係 の節で改めて説明します。

アプリケーションのルート型の指定

コンストラクタの二番目の引数に指定するのが、アプリケーションのルート型です。 各データグラフの真のルートとなるのは特別なルート型のオブジェクトで、 すべてのアプリケーションデータオブジェクトはその下のどこかに位置します。 SDO モデルのさまざまなアプリケーション型のなかで、 どれかひとつの型がデータグラフのルート直下にある必要があります。 データベースのメタデータ内にテーブルがひとつしかない場合は、 アプリケーションのルート型は推定できます。そのため、この引数は省略可能です。

SDO 包含関係の指定

コンストラクタの三番目の引数で、モデルの型をどのように連結して グラフを構成するかを定義します。複数の型によってグラフが構成される場合に、 その親子関係を指定します。この関係は、 データ内の外部キーでサポートされている必要があります。 これについては後ほど説明します。

メタデータは、ひとつあるいは複数の連想配列を含む配列となります。 個々の連想配列が親と子を表します。以下に company (会社) と department (部署)、そして department (部署) と employee (従業員) の親子関係の例を示します。 これらのそれぞれが SDO のプロパティとなり、 SDO モデルにおける「複数の値をとる包含関係」を定義するものになります。

<?php
$department_containment
= array( 'parent' => 'company', 'child' => 'department');
$employee_containment = array( 'parent' => 'department', 'child' => 'employee');

$SDO_containment_metadata = array($department_containment, $employee_containment);
?>

データベースのメタデータ内にある外部キーは、 複数の値を持つ包含関係のプロパティか 単一の値を持つ包含関係でないプロパティのいずれかと解釈されます。どちらになるかは、 対応する SDO の包含関係がメタデータ内で定義されているかどうかによって決まります。 この例では、department から company への外部キー (department テーブルのカラム co_id) および employee から department への外部キー (employee テーブルのカラム dept_id) が SDO の包含関係と解釈されます。 SDO の包含関係メタデータで示されているそれぞれの関連については、 対応するデータベースメタデータで外部キーが存在しなければなりません。 外部キー列の値は、データオブジェクト内には現れません。その代わりに、 親から子に対する包含関係として表されます。つまり、例えばデータベースの department テーブルにあるカラム co_id は、department 型のプロパティにはなりません。その代わりに、 company 型のほうに department という名前の関係ができます。 ここで、外部キーと親子関係は向きが逆であることに注意しましょう。 外部キーは、department から company に対して指定しますが、 親子関係は company から department に向けて指定します。

この例の三番目の外部キーである employee_of_the_month は、異なる方法で処理されます。これは、SDO の包含関係メタデータとはならず、 もうひとつの方法で解釈されます。company オブジェクト上での 単一の値をとる包含関係でない参照となり、employee 型の SDO データオブジェクトへの参照をそこに代入します。 これは、company 型が持つプロパティとなります。 SDO データグラフ内でこのプロパティに値を代入するには、 employee オブジェクトを含むグラフを取得し、そこに値を代入します。 この方法については、後の例で説明します。

ひとつのテーブルによる例

これ以降の例でのリレーショナル DAS のデータグラフの構成は次のようになります。 アプリケーションデータオブジェクトがひとつだけ、ひとつの会社に関する情報が company テーブルに格納されています。これらの例は SDO の機能を活用するものではありませんし、 直接 SQL 文を発行したほうがずっと効率的でしょう。 これらの例は、リレーショナル DAS をどのように使用するのかを説明するためのものです。

そのため、データベースのメタデータは極力シンプルになるようにしました。 company テーブルだけしか含まないようにしています。 コンストラクタで使用している二番目、三番目の引数や、 クエリの例で使用しているカラム指定子は、オプションとなります。

例 1. データオブジェクトの作成

もっとも単純な例として、データオブジェクトをひとつ作成して それをデータベースに書き込むことを考えます。この例では、 company オブジェクトがひとつ作成されます。その名前を 'Acme' と指定したうえで、リレーショナル DAS をコールして 変更内容をデータベースに書き込みます。会社名を設定する際には、 プロパティ名を使用します。オブジェクトのプロパティにアクセスする その他の方法については、SDO の を参照ください。

データオブジェクトを作成できるのは、 あらかじめ先立つデータオブジェクトがある場合のみです。そのため、 リレーショナル DAS への最初のコールは、 まずルートオブジェクトを取得するためのものとなります。 これは、空のデータグラフの作成をすることで行います。 特別なルートオブジェクトは、木構造の真のルートとなるものです。 company データオブジェクトは、その後ルートオブジェクトに対して createDataObject() をコールすることで作成します。 これは、company データオブジェクトを作成して、 データグラフのルートオブジェクトの 'company' という名前のプロパティにそれを挿入します。

リレーショナル DAS が変更を適用する際には、シンプルな insert 文 'INSERT INTO company (name) VALUES ("Acme");' が実行されます。 自動生成された主キーがデータオブジェクトに設定され、 変更概要がリセットされます。そのため、同じデータオブジェクトを そのまま使い続け、さらに変更した内容をもう一度適用することもできます。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/**************************************************************
* メタデータから、DAS を作成します。
***************************************************************/
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);

/**************************************************************
* ルートオブジェクトを取得し、その下に company オブジェクトを
* 作成します。そしてデータオブジェクトに対して変更を加えます。
***************************************************************/
$root = $das  -> createRootDataObject();
$acme = $root -> createDataObject('company');

$acme->name = "Acme";

/**************************************************************
* データベースに接続し、オブジェクトをデータベースに書き込みます。
***************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);
$das -> applyChanges($dbh, $root);
?>

例 2. データオブジェクトの取得

この例では、データベースから取得されるデータオブジェクトはひとつです。 あるいは、もし 'Acme' という名前の会社を複数登録していたのなら、 複数返される可能性もあります。返された会社データのそれぞれについて、 プロパティ name および id の値を出力します。

この例における executeQuery() の 3 番目の引数では、カラム名に修飾子が必要です。 というのも、メタデータ内の他のテーブルで name および id という名前のカラムが用いられているからです。 もし名前が重複するような心配がないのなら、これは省略できます。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/**************************************************************
* メタデータから、DAS を作成します。
***************************************************************/
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);

/**************************************************************
* データベース接続を取得します。
***************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);

/**************************************************************
* クエリを発行し、company オブジェクトを取得します。
* 複数存在するかもしれません。
***************************************************************/
$root = $das->executeQuery($dbh,
           
'select name, id from company where name="Acme"',
      array(
'company.name', 'company.id') );

/**************************************************************
* name および id を出力します。
***************************************************************/
foreach ($root['company'] as $company) {
  echo
"データベースから取得した会社の名前は " .
    
$company['name'] . " で、その id は " . $company['id'] . " です\n";
}
?>

例 3. データオブジェクトの更新

この例は、これまでのふたつを組み合わせたものです。というのも、 オブジェクトを更新するには、まずそれを取得しなければならないからです。 この例のコードは、会社名を逆さにし (つまり 'Acme' が 'emcA' となります)、 オブジェクトを作成したときと同じ方法でそれをデータベースに書き戻します。 この例のクエリではその両方の会社名で検索しているので、 プログラムを繰り返し実行すると、そのたびに会社名が反転します。

この例では、同じリレーショナル DAS のインスタンスが applyChanges() で再利用されます。 ちょうど PDO データベースハンドルと同じような扱いです。 このように使用しても問題ありません。また、既存のインスタンスを破棄して 新しいインスタンスを作り直すこともできます。 データグラフをアプリケーションに返した後は、 その状態に関する情報はリレーショナル DAS から削除されます。 必要なデータはすべてデータグラフ自身が保持しており、 もしそれが存在しない場合はメタデータから再作成されます。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/**************************************************************
* メタデータから、DAS を作成します。
***************************************************************/
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);

/**************************************************************
* データベース接続を取得します。
***************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);

/**************************************************************
* クエリを発行し、company オブジェクトを取得します。
* 複数存在するかもしれません。
***************************************************************/
$root = $das->executeQuery($dbh,
    
'select name, id from company where name="Acme" or name="emcA"',
    array(
'company.name', 'company.id') );

/**************************************************************
* 最初の会社の名前を変更します。
***************************************************************/
$company = $root['company'][0];
echo
"取得した会社の名前は " . $company->name . " です\n";
$company->name = strrev($company->name);

/**************************************************************
* 変更内容を書き戻します。
***************************************************************/
$das->applyChanges($dbh,$root);
?>

例 4. データオブジェクトの削除

'Acme'、あるいはその逆の 'emcA' という名前の会社をすべて取得することができました。 次に、これらをすべてグラフから削除するために unset を使用します。

この例では、ひとつの動作ですべてが削除されます。つまり、該当する (包含関係を定義する) プロパティを解放します。 これらを個別に削除していくことも可能です。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/**************************************************************
* メタデータから、DAS を作成します。
***************************************************************/
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);

/**************************************************************
* データベース接続を取得します。
***************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);

/**************************************************************
* クエリを発行し、company オブジェクトを取得します。
* 複数存在するかもしれません。
***************************************************************/
$root = $das->executeQuery($dbh,
    
'select name, id from company where name="Acme" or name="emcA"',
    array(
'company.name', 'company.id') );

/**************************************************************
* データグラフのすべての会社を削除します。
***************************************************************/
unset($root['company']);

/**************************************************************
* 変更内容を書き戻します。
***************************************************************/
$das->applyChanges($dbh,$root);
?>

二つのテーブルの例

これ以降の例では、company データベース内のふたつのテーブル company と department を使用します。これらの例は、 リレーショナル DAS の機能をより活用するためのものです。

この一連の例では、まず company および department を作成し、 それを取得し、更新して最後に削除します。複数のオブジェクトからなる データグラフの動きについて、ここで説明します。 この例では、まず最初に company および department のふたつのテーブルの中身を削除していることに注意しましょう。 これにより、クエリの結果がどのようになるのかをはっきりさせています。

これらの例については、リレーショナル DAS パッケージの Scenarios ディレクトリ内にあるスクリプト 1cd-CRUD にまとめてあります。

例 5. 会社がひとつ、部署がひとつの例 - 作成

先の、company データオブジェクトをひとつだけ作成する例で示したように、 リレーショナル DAS を作成した後でまず行うことは createRootDataObject() をコールして空のデータグラフを持つ特別なルートオブジェクトを取得することです。 その後、このルートオブジェクトの子要素として company オブジェクトを作成し、company オブジェクトの子要素として department オブジェクトを作成します。

変更内容を適用する際には、包含関係を定義する外部キー制約を満たすため、 リレーショナルが特別な処理を行います。特に、 自動生成される主キーが含まれれる場合の処理が大切です。 この例では、company テーブルで自動生成された主キー id と department テーブルのカラム co_id の関係を指定しなければなりません。 company および department に最初にデータを挿入する際、 リレーショナル DAS はまず最初に company に行を挿入してから PDO の getLastInsertId() メソッドで 自動生成された主キーを取得し、department に行を挿入する際の co_id カラムにその値を指定しなければなりません。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/*************************************************************************************
* ふたつのテーブルを空にします。
*************************************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);
$pdo_stmt = $dbh->prepare('DELETE FROM COMPANY;');
$rows_affected = $pdo_stmt->execute();
$pdo_stmt = $dbh->prepare('DELETE FROM DEPARTMENT;');
$rows_affected = $pdo_stmt->execute();

/**************************************************************
* Acme という名前の会社、Shoe という名前の部署を作成します。
***************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);

$root = $das -> createRootDataObject();

$acme = $root -> createDataObject('company');
$acme -> name = "Acme";

$shoe = $acme->createDataObject('department');
$shoe->name = 'Shoe';

$das -> applyChanges($dbh, $root);

?>

例 6. 会社がひとつ、部署がひとつの例 - 取得および更新

この場合、executeQuery() に渡す SQL クエリは inner join を使用して company テーブルと department テーブルを連結します。company テーブルと department テーブルの両方の主キーがクエリに含まれている必要があります。 結果セットは正規化されたデータグラフ形式になります。 executeQuery() をコールする際に、 三番目の引数としてカラム指定子を渡していることに注意しましょう。 これにより、どのカラムが結果セットのどこに対応するのかを リレーショナル DAS に教えています。

クエリ内でカラム co_id が使用されていますが、 これは結果セットでは必要ないことに注意しましょう。 データグラフを作成する際にリレーショナル DAS がどのような処理をするのかを理解するには、 結果セットがどのようになるのかを図示してみるといいでしょう。 データベース内のデータは正規化されており、 複数の部署データが外部キーを通じてひとつの会社データを参照するようになっていますが、 結果セット内のデータは正規化されていません。つまり、 もしひとつの会社に複数の部署があるのなら、 会社のデータはその行の数だけ繰り返されます。 リレーショナル DAS は、この手順を逆転させて 結果セットを正規化されたデータグラフに戻し、company オブジェクトがひとつだけになるようにしなければなりません。

この例では、リレーショナル DAS が結果セットとカラム指定子を調べ、 company テーブルと department テーブルからデータを取得し、 それぞれの主キーを検索し、各 department とその親となる company を関連付けます。該当する company をまだ取得していない場合は (これは主キーで判断します)、新しい company オブジェクトを作成して department オブジェクトをその下に関連付けます。 すでに company オブジェクトが存在する場合は、 その下に department オブジェクトを作成するだけです。

この方法によって、リレーショナル DAS は複数の会社と複数の部署が含まれるデータを 取得して再度正規化します。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/**************************************************************
* 会社、そして部署 Shoe を取得します。
* それから Shoe を削除して IT を追加します。
***************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);

$root = $das->executeQuery($dbh,
'select c.id, c.name, d.id, d.name from company c, department d where d.co_id = c.id',
array(
'company.id','company.name','department.id','department.name'));

$acme = $root['company'][0];            // 最初の会社を取得します - 'Acme' となるでしょう。
$shoe = $acme['department'][0];         // その下の最初の部署を取得します - 'Shoe' となるでしょう。

unset($acme['department'][0]);

$it = $acme->createDataObject('department');
$it->name = 'IT';

$das -> applyChanges($dbh, $root);
?>

例 7. 会社がひとつ、部署がふたつの例 - 取得および削除

この例では、会社と部署を取得し、それから削除します。 それぞれを個別に削除する必要はありません (そうすることも可能です)。 データグラフから company オブジェクトを削除すると、 その下に関連付けられている department も削除されます。

company オブジェクトを削除する際に、PHP の unset をコールしていることに注意しましょう。unset は、 それを保持しているプロパティに対して行わなければなりません。 この場合は、ルートオブジェクトの company プロパティに対して行います。 つまり、このようにしなければなりません。

<?php
unset($root['company'][0]);
?>
次のようにしてはいけません。
<?php
unset($acme); // 間違い
?>
単に $acme を unset しただけだと、 変数は削除されますが、データグラフ内のデータはそのまま残ってしまいます。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/**************************************************************
* 会社、そして部署 IT を取得します。
* 会社全体を削除します。
***************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);

$root = $das->executeQuery($dbh,
'select c.id, c.name, d.id, d.name from company c, department d where d.co_id = c.id',
array(
'company.id','company.name','department.id','department.name'));

$acme = $root['company'][0];
$it = $acme['department'][0];

unset(
$root['company'][0]);

$das -> applyChanges($dbh, $root);

?>

三つのテーブルの例

これ以降の例では、company データベース内の三つのテーブル company と department そして employee をすべて使用します。 これらの例では、これまでに説明してこなかった機能を説明します。 それは、包含関係ではない参照である employee_of_the_month です。

先に説明した company と department の例と同様、 これ以降の例でもデータグラフに対する一連の処理をすべて説明していきます。

例 8. 会社がひとつ、部署がひとつ、従業員が一人の例 - 作成

この例では、ひとつの会社にひとつの部署があり、 そこに従業員が一人だけ所属しているという構成になります。 この例では、まず最初に三つのテーブルの中身を削除していることに注意しましょう。 これにより、クエリの結果がどのようになるのかをはっきりさせています。

company、department および employee を作成した後で、company の employee_of_the_month プロパティを作成して新しい従業員を参照させているところに注目しましょう。 これは包含関係ではない参照なので、データグラフ内で employee オブジェクトを作成するまでは参照させることができません。 包含関係ではない参照の管理には注意が必要です。例えば、 ある employee が department から削除されたとすると、 employee_of_the_month プロパティを削除するか代入しなおさない限り、 データグラフを保存できなくなります。SDO データグラフの制約上、 包含関係でない参照の対象となっているオブジェクトは、 同時にいずれかの包含関係から到達可能である必要があります。

グラフの内容をデータベースに挿入する際の手順は、 company と department だけの時の例と似ています。しかし、 employee_of_the_month があるために少し複雑になります。 リレーショナル DAS は、包含関係で構成される木構造の順に、 オブジェクトを挿入していく必要があります。つまりまず company、 次に department、そして employee となります。なぜなら、 親データの自動生成された主キーの値を、子のデータに含めなければならないからです。 しかし、company を挿入した時点ではまだ「今月の従業員」 になる employee が挿入されておらず、その主キーの値がわかりません。 そこで、employee レコードが挿入されてその主キーが判明した時点で、 最後の処理が行われます。つまり company レコードを employee の主キーで更新します。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/*************************************************************************************
* 3 つのテーブルを空にします。
*************************************************************************************/
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);
$pdo_stmt = $dbh->prepare('DELETE FROM COMPANY;');
$rows_affected = $pdo_stmt->execute();
$pdo_stmt = $dbh->prepare('DELETE FROM DEPARTMENT;');
$rows_affected = $pdo_stmt->execute();
$pdo_stmt = $dbh->prepare('DELETE FROM EMPLOYEE;');
$rows_affected = $pdo_stmt->execute();

/*************************************************************************************
* 小規模ながらも完全なる会社を作成します。
*  会社の名前は Acme。
*  部署はひとつだけで、Shoe。
*  従業員は一人だけで、Sue。
*  「今月の従業員」は Sue。
*************************************************************************************/
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);

$root       = $das  -> createRootDataObject();
$acme       = $root -> createDataObject('company');
$acme -> name   = "Acme";
$shoe       = $acme -> createDataObject('department');
$shoe -> name   = 'Shoe';
$shoe -> location = 'A-block';
$sue      = $shoe -> createDataObject('employee');
$sue -> name  = 'Sue';
$acme -> employee_of_the_month = $sue;

$das -> applyChanges($dbh, $root);

echo
"Acme のデータを、ひとつの部署と一人の従業員とともに書き戻します\n";
?>

例 9. 会社がひとつ、部署がひとつ、従業員が一人の例 - 取得および更新

この場合、リレーショナル DAS に渡す SQL 文は、inner join を使用して三つのテーブルからのデータを取得します。ここでは、 これまでの例に出てこなかった新しい内容は特にありません。

新しい部署と従業員の追加によってグラフが更新され、 グラフ内の既存のオブジェクトの name プロパティに少し変更を加えます。 その後、それらの変更内容を書き戻します。リレーショナル DAS は、 データグラフへの追加・変更および削除の内容を適用します。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/*************************************************************************************
* 会社のデータを取得し、いろいろ変更します。
*  会社、部署そして従業員の名前を変更します。
*  新しい部署と従業員を追加します。
*  「今月の従業員」を変更します。
*************************************************************************************/
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);

$root = $das->executeQuery($dbh,
    
"select c.id, c.name, c.employee_of_the_month, d.id, d.name, e.id, e.name " .
    
"from company c, department d, employee e " .
    
"where e.dept_id = d.id and d.co_id = c.id and c.name='Acme'",
     array(
'company.id','company.name','company.employee_of_the_month',
     
'department.id','department.name','employee.id','employee.name'));
$acme   = $root['company'][0];

$shoe = $acme->department[0];
$sue  = $shoe -> employee[0];

$it   = $acme->createDataObject('department');
$it->name = 'IT';
$it->location = 'G-block';
$billy  = $it->createDataObject('employee');
$billy->name = 'Billy';

$acme->name = 'MegaCorp';
$shoe->name = 'Footwear';
$sue->name = 'Susan';

$acme->employee_of_the_month = $billy;
$das -> applyChanges($dbh, $root);
echo
"会社のデータを、追加した部署や従業員そして変更後の名前 (Megacorp/Footwear/Susan) などどともに書き戻します\n";

?>

例 10. 会社がひとつ、部署がふたつ、従業員が二人の例 - 取得および削除

company は、5 つのデータオブジェクトを含むデータグラフとして取得されます。 5 つとは、すなわちその会社、ふたつの部署、そして二人の従業員です。 company オブジェクトをグラフから削除すると、 その配下にあるすべてのオブジェクトがグラフから削除されます。 つまり、5 つの SQL DELETE 文が作成されて実行されます。 取得したすべてのフィールドの内容が WHERE 句に指定されるので、 別のプロセスがデータベースの内容を変更していた場合にはそれを検出することができます。

<?php
require_once 'SDO/DAS/Relational.php';
require_once
'company_metadata.inc.php';

/*************************************************************************************
* もう一度読み込み、それを削除します。
* 一部を削除し、変更を適用し、同じグラフで作業を続けることもできますが、
* 破綻しないように注意が必要です。「今月の従業員」になっている従業員を削除するには、
* まずその割り当てを変更しておかなければなりません。安全のため、ここでは
* 会社ごとすべて削除しています。
*************************************************************************************/
$das = new SDO_DAS_Relational ($database_metadata,'company',$SDO_containment_metadata);
$dbh = new PDO(PDO_DSN,DATABASE_USER,DATABASE_PASSWORD);

$root = $das->executeQuery($dbh,
    
"select c.id, c.name, c.employee_of_the_month, d.id, d.name, e.id, e.name " .
    
"from company c, department d, employee e " .
    
"where e.dept_id = d.id and d.co_id = c.id and c.name='MegaCorp';",
     array(
'company.id','company.name','company.employee_of_the_month',
     
'department.id','department.name','employee.id','employee.name'));
$megacorp = $root['company'][0];

unset(
$root['company']);
$das -> applyChanges($dbh, $root);

echo
"Deleted the company, departments and employees all in one go.\n";

?>

トレース

変更をデータベースに書き戻す際に、実際にはどのような SQL 文が生成されるのかが気になることもあるでしょう。 SDO/DAS/Relational.php の先頭で、SQL 文の作成および実行の処理を追跡するための定数が定義されていることがわかります。 生成された SQL 文を見るには、DEBUG_EXECUTE_PLANTRUE にしてみましょう。

定義済みクラス

リレーショナル DAS では 2 つのクラスが提供されています。それは リレーショナル DAS 自身、および例外時にスローされる Exception のサブクラスです。リレーショナル DAS で使用できるパブリックコールは 4 つです。まずコンストラクタ、それから 空のデータグラフからルートオブジェクトを取得するための createRootDataObject()、 リレーショナルデータベースからのデータを含むデータグラフを取得するための executeQuery()、 データグラフへの変更をリレーショナルデータベースに書き戻すための applyChanges() です。

SDO_DAS_Relational

SDO_DAS_Relational_Exception 以外で唯一、 アプリケーションと直接やりとりすることが想定されているオブジェクトです。

メソッド

  • __construct - 渡されたメタデータに基づくモデルからリレーショナル DAS を構築します。

  • createRootDataObject - 特別なルートオブジェクトを含む空ではないデータグラフを取得します。

  • executeQuery - リテラル文字列で渡された SQL クエリを実行し、 正規化されたデータグラフ形式で結果を返します。

  • executePreparedQuery - プリペアドステートメントとして渡された SQL クエリに プレースホルダを置換する値のリストを指定して実行し、 正規化されたデータグラフ形式でデータを返します。

  • applyChanges - データグラフの変更の概要を調べ、それをデータベースに書き戻します。 楽観的な同時並行性 (concurrency) に従います。

SDO_DAS_Relational_Exception

PHP の Exception のサブクラスです。 Exception に対して何も機能を追加しません。 メタデータ内でエラーが発生したり SQL の実行時に予期せぬ失敗が 発生した際に、有用な説明を含めてスローされます。

目次
SDO_DAS_Relational::applyChanges --  データグラフに対する変更を、データベースに書き戻す
SDO_DAS_Relational::__construct --  リレーショナルデータアクセスサービスのインスタンスを作成する
SDO_DAS_Relational::createRootDataObject --  別の空のデータグラフ内の、特別なルートオブジェクトを返す。 データグラフをスクラッチから作成する際に使用する
SDO_DAS_Relational::executePreparedQuery --  プリペアドステートメントとして渡された SQL クエリにプレースホルダ置換用の値を指定して実行し、 結果を正規化されたデータグラフ形式で返す
SDO_DAS_Relational::executeQuery --  SQL クエリをリレーショナルデータベースに対して実行し、 結果を正規化されたデータグラフ形式で返す