Doctrine Database Abstraction Layerは内臓されるフレームワークで、使用しているデータベースとコミュニケーションを行いデータベースタイプに応じて適切なSQLを送信するためにORMが使用しています。このフレームワークはデータベースが持つテーブルもしくはテーブルが持つフィールドのような情報をデータベースに問い合わせる機能も持ち、ORMが既存のデータベースからモデルを簡単に生成できる手段です。
このレイヤーはORMから独立して使うことができます。例えば既存のアプリケーションがPDOを直接使用しこれをDoctrine ConnectionsとDBALを使うためにこれを移植したい場合に役立ちます。後のフェーズで新しいことのためにORMを使い始めたりORMで使えるように古いピースを書き直したりします。
DBALは少数の異なるモジュールで構成されます。この章では異なるモジュールとそれらが担っている仕事を検討します
Exportモジュールはデータベース構造を管理するためのメソッドを提供します。メソッドはそれぞれの責務に基づいて分類できます。例えばデータベースの要素をcreate、edit(alterもしくはupdate)、listもしくはdelete (drop)するなどです。以下のドキュメントでは利用可能なメソッドの一覧と使い方の例を示します。
Exportモジュールでメソッドを変更するすべてのスキーマはalterオペレーションのために使われるSQLを返す同等物を持ちます。例えばcreateTable()はcreateTableSql()によって返されるクエリを実行します。
この章ではevents_dbという名前のデータベースで、次のテーブルが作成され、変更され最後には削除されます:
events
| 名前 | 型 | Primary | Auto Increment |
|---|---|---|---|
| id | integer | true | true |
| name | string(255) | false | false |
| datetime | timestamp | false | false |
people
| 名前 | 型 | Primary | Auto Increment |
|---|---|---|---|
| id | integer | true | true |
| name | string(255) | false | false |
event_participants
| 名前 | 型 | Primary | Auto Increment |
|---|---|---|---|
| event_id | integer | true | false |
| person_id | string(255) | true | false |
Doctrineで新しいデータベースを作成するのはシンプルです。作成するデータベースの名前を引数にしてcreateDatabase()メソッドを呼び出すだけです。
// test.php
// ...
$conn->export->createDatabase('events_db');
新しいevents_dbに接続するためにbootstrap.phpファイルの接続を変更してみましょう:
// bootstrap.php
/**
* Bootstrap Doctrine.php, register autoloader and specify
* configuration attributes
*/
// ...
$conn = Doctrine_Manager::connection('mysql://root:@localhost/events_db', 'doctrine');
// ...
データベースは作成され接続を再設定しました。テーブルに追加に移ります。createTable()メソッドは3つのパラメータ: テーブルの名前、フィールド定義の配列、と追加オプション(オプションでRDBMS固有)を受けとります。
eventsテーブルを作成しましょう:
// test.php
//
$definition = array(
'id' => array(
'type' => 'integer',
'primary' => true,
'autoincrement' => true
),
'name' => array(
'type' => 'string',
'length' => 255
),
'datetime' => array(
'type' => 'timestamp'
)
);
$conn->export->createTable('events', $definition);
定義配列のキーはテーブルのフィールド名です。値は他のキーと同じように必須キーのtypeを格納する配列で、typeの値によって、typeキーの値はDoctrineのデータ型と同じものが可能です。データ型によって、他のオプションが変わることがあります。
| データ型 | 長さ | デフォルト | not null | unsigned | autoincrement |
|---|---|---|---|---|---|
| string | x | x | x | ||
| boolean | x | x | |||
| integer | x | x | x | x | x |
| decimal | x | x | |||
| float | x | x | |||
| timestamp | x | x | |||
| time | x | x | |||
| date | x | x | |||
| clob | x | x | |||
| blob | x | x |
peopleテーブルを作ることができます:
// test.php
// ...
$definition = array(
'id' => array(
'type' => 'integer',
'primary' => true,
'autoincrement' => true
),
'name' => array(
'type' => 'string',
'length' => 255
)
);
$conn->export->createTable('people', $definition);
createTable()メソッドの3番目の引数としてオプションの配列を指定することもできます:
// test.php
// ...
$options = array(
'comment' => 'Repository of people',
'character_set' => 'utf8',
'collate' => 'utf8_unicode_ci',
'type' => 'innodb',
);
// ...
$conn->export->createTable('people', $definition, $options);
外部キーでevent_participantsテーブルを作成します:
// test.php
// ...
$options = array(
'foreignKeys' => array(
'events_id_fk' => array(
'local' => 'event_id',
'foreign' => 'id',
'foreignTable' => 'events',
'onDelete' => 'CASCADE',
)
),
'primary' => array('event_id', 'person_id'),
);
$definition = array(
'event_id' => array(
'type' => 'integer',
'primary' => true
),
'person_id' => array(
'type' => 'integer',
'primary' => true
),
);
$conn->export->createTable('event_participants', $definition, $options);
上記の例でperson_idに対して外部キーを省略していることに注目してください。この例では次の例で個別の外部キーをテーブルに追加する方法を示すために省略しました。通常はforeignKeysで定義された両方の外部キーがあることがベストです。
person_idカラムのevent_participantsテーブルに見つからない外部キーを追加してみましょう:
// test.php
// ...
$definition = array('local' => 'person_id',
'foreign' => 'id',
'foreignTable' => 'people',
'onDelete' => 'CASCADE');
$conn->export->createForeignKey('event_participants', $definition);
Doctrine_Exportドライバはデータベースがポータブルでありながら既存のデータベーステーブルを簡単に変更する方法を提供します。
// test.php
// ...
$alter = array(
'add' => array(
'new_column' => array(
'type' => 'string',
'length' => 255
),
'new_column2' => array(
'type' => 'string',
'length' => 255
)
)
);
echo $conn->export->alterTableSql('events', $alter);
alterTableSql()への呼び出しは次のSQLクエリを出力します:
ALTER TABLE events ADD new_column VARCHAR(255),
ADD new_column2 VARCHAR(255)
生成SQLのみを実行しこれを返したくない場合、alterTable()メソッドを使います。
// test.php
// ...
$conn->export->alterTable('events', $alter);
alterTable()メソッドは2つのパラメータを必須とし3番目のパラメータはオプションです:
| 名前 | 型 | 説明 |
|---|---|---|
| $name | string | 変更が想定されるテーブルの名前。 |
| $changes | array | 実行を前提とされる変更のそれぞれのタイプの詳細を含む連想配列。 |
オプションの3番目のパラメータ(デフォルト: false):
| 名前 | 型 | 説明 |
|---|---|---|
| $check | boolean | 実行前にDBMSが実際にオペレーションを実行できるかチェックする |
現在サポートされる変更のタイプは次のように定義されます:
| 変更 | 説明 |
|---|---|
| name | テーブル用の新しい名前 |
| add | 配列のインデックスとして追加されるフィールドの名前を格納する連想配列。配列のそれぞれのエントリの値は追加されるフィールドのプロパティを格納する別の連想配列に設定されます。フィールドのプロパティはDoctrineパーサーによって定義されたものと同じです。 |
| remove | 配列のインデックスとして削除されるフィールドの名前を格納する連想配列。現在それぞれのエントリに割り当てられた値は無視されます。空の配列は将来の互換性のために使われます。 |
| rename | 配列のインデックスとしてリネームされるフィールドの名前を格納する連想配列。配列のそれぞれのエントリの値は別の連想配列に設定されます。この別の連想配列は新しいフィールド名とCREATE TABLE文として使われるDBM固有のSQLコードで既にあるフィールドの宣言の一部を格納するものとして設定されるDeclarationという名前のエントリを持ちます。 |
| change | 配列のインデックスとして変更されるフィールドの名前を格納する連想配列。フィールドと他のプロパティを変更するか、change配列エントリは配列インデックスとしてフィールドの新しい名前を格納するかを念頭においてください。 |
配列のそれぞれのエントリの値はフィールドのプロパティを格納する別の連想配列に設定されます。これは配列エントリとして変更されることを意味します。これらのエントリはそれぞれのプロパティの新しい値に割り当てられます。フィールドのプロパティはDoctrineパーサーが定義するものと同じです。
// test.php
// ...
$alter = array('name' => 'event',
'add' => array(
'quota' => array(
'type' => 'integer',
'unsigned' => 1
)
),
'remove' => array(
'new_column2' => array()
),
'change' => array(
'name' => array(
'length' => '20',
'definition' => array(
'type' => 'string',
'length' => 20
)
)
),
'rename' => array(
'new_column' => array(
'name' => 'gender',
'definition' => array(
'type' => 'string',
'length' => 1,
'default' => 'M'
)
)
)
);
$conn->export->alterTable('events', $alter);
テーブルをeventにリネームしたことに注目してください。テーブルをeventsにリネームし直しましょう。機能を示すためだけにテーブルをリネームしたので次の例のためにテーブルをeventsと名づける必要があります。
// test.php
// ...
$alter = array(
'name' => 'events'
);
$conn->export->alterTable('event', $alter);
インデックスを作成するために、createIndex()メソッドが使われます。このメソッドはcreateConstraint()と似たシグニチャを持ち、テーブルの名前、インデックスの名前と定義配列を受け取ります。定義配列はfieldsという名前の1つのキーを持ち、その値はインデックスの一部であるフィールドを格納する別の連想配列です。フィールドは次のキーを持つ配列として定義されます: ソート、昇順と降順の長さを持つ値、整数値
すべてのRDBMSはインデックスソートもしくは長さをサポートしないので、これらの場合ドライバはこれらを無視します。テストのeventデータベースでは、アプリケーションが固有のtimeframeで起きるイベントを表示することを前提とすることができます。selectはWHERE条件でdatatimeフィールドを使います。このフィールドにインデックスが存在する場合に手助けになります。
// test.php
// ...
$definition = array(
'fields' => array(
'datetime' => array()
)
);
$conn->export->createIndex('events', 'datetime', $definition);
上記で示されたそれぞれのcreate*()メソッドに対して、データベース、テーブル、フィールド、インデックスもしくは制約を削除するために対応するdrop*()メソッドが存在します。drop*()メソッドは削除されるアイテムの存在をチェックしません。try-catchブロックを使用して例外をチェックするのは開発者の責務です:
// test.php
// ...
try {
$conn->export->dropSequence('nonexisting');
} catch(Doctrine_Exception $e) {
}
次のコードで制約を簡単に削除できます:
// test.php
// ...
$conn->export->dropConstraint('events', 'PRIMARY', true);
3番目の引数はこれが主キーであることのヒントを与えます。
// test.php
// ...
$conn->export->dropConstraint('event_participants', 'event_id');
次のコードでインデックスを簡単に削除できます:
$conn->export->dropIndex('events', 'event_timestamp');
次の2つの例を実際に実行するのは推奨されません。次のセクションで我々の例が無傷で動作できるようにevents_dbが必要です。
次のコードでデータベースからテーブルを削除します:
// test.php
// ...
$conn->export->dropTable('events');
次のコードでデータベースを削除できます:
// test.php
// ...
$conn->export->dropDatabase('events_db');
importモジュールによってデータベース接続の内容を検証できます。それぞれのデータベースとそれぞれのデータベースのスキーマを学びます。
データベースに何があるのか見るために、Importモジュールのlist*()ファミリーのメソッドを使うことができます。
| 名前 | 説明 |
|---|---|
| listDatabases() | データベースの一覧を表示する。 |
| listFunctions() | 利用可能なメソッドの一覧を表示する。 |
| listSequences($dbName) | 利用可能なシーケンスの一覧を表示する。オプションパラメータとしてデータベースの名前を受け取る。帝京されない場合、選択されたデータベースが想定されます。 |
| listTableConstraints($tableName) | 利用可能なテーブルの一覧を表示する。テーブルの名前を受け取る。 |
| listTableColumns($tableName) | テーブルで利用可能なカラムの一覧を表示する。 |
| listTableIndexes($tableName) | テーブルで定義されているインデックスの一覧を表示する。 |
| listTables($dbName) | データベースのテーブルの一覧を表示する。 |
| listTableTriggers($tableName) | テーブルのトリッガーの一覧を表示する。 |
| listTableViews($tableName) | テーブルで利用可能なビューの一覧を表示する。 |
| listUsers() | データベース用のユーザーの一覧を表示する。 |
| listViews($dbName) | データベース用のビューの一覧を表示する。 |
下記において上記のメソッドの使い方の例が見つかります:
// test.php
// ...
$databases = $conn->import->listDatabases();
print_r($databases);
// test.php
// ...
$sequences = $conn->import->listSequences('events_db');
print_r($sequences);
// test.php
// ...
$constraints = $conn->import->listTableConstraints('event_participants');
print_r($constraints);
// test.php
// ...
$columns = $conn->import->listTableColumns('events');
print_r($columns);
// test.php
// ...
$indexes = $conn->import->listTableIndexes('events');
print_r($indexes);
$tables = $conn->import->listTables();
print_r($tables);
現在、ビューを作成するメソッドは存在しないので、手動で作成してください。
$sql = "CREATE VIEW names_only AS SELECT name FROM people";
$conn->exec($sql);
$sql = "CREATE VIEW last_ten_events AS SELECT * FROM events ORDER BY id DESC LIMIT 0,10";
$conn->exec($sql);
先ほど作成したビューの一覧を表示できます:
$views = $conn->import->listViews();
print_r($views);
ネイティブのRDBMの型をDoctrineの型に変換するもしくはその逆を行うためにDoctrineは内部でDataDictモジュールを使用します。DataDictモジュールは変換のために2つのメソッドを使用します:
// test.php
// ...
$declaration = $conn->dataDict->getPortableDeclaration('VARCHAR(255)');
print_r($declaration);
上記の例は次の内容を出力します:
$ php test.php
Array
(
[type] => Array
(
[0] => string
)
[length] => 255
[unsigned] =>
[fixed] =>
)
// test.php
// ...
$portableDeclaration = array(
'type' => 'string',
'length' => 20,
'fixed' => true
);
$nativeDeclaration = $conn->dataDict->getNativeDeclaration($portableDeclaration);
echo $nativeDeclaration;
上記の例は次の内容を出力します:
$ php test.php
CHAR(20)
// test.php
// ...
$fields = array(
'id' => array(
'type' => 'integer',
'autoincrement' => true
),
'name' => array(
'type' => 'string',
'fixed' => true,
'length' => 8
)
);
次のオプションはMySQL固有で他のドライバはスキップします。
$options = array('type' => 'INNODB');
$sql = $conn->export->createTableSql('test_table', $fields);
echo $sql[0];
上記の例は次のSQLクエリを出力します:
CREATE TABLE test_table (id INT AUTO_INCREMENT,
name CHAR(8)) ENGINE = INNODB
この章は本当に素晴らしいものです。Doctrine DBALはそれ自身が偉大なツールです。おそらく最も機能を持つものの1つでPHPデータベース抽象化レイヤーを簡単に利用できます。
Transactionsの使い方を学ぶ準備が整いました。