Doctrineのマイグレーションパッケージのプログラミングインターフェイスを通して本番のデータベースを簡単に更新できます。データベースがバージョン管理されバージョンを通して差し戻しできるように変更が行われます。
マイグレーションクラスの作り方を学んだので次のセクションでDoctrinenのテスト環境でマイグレーションを実装できるようにマイグレーションの実行の仕方を見てみましょう。
最初にDoctrine_Migrationの新しいインスタンスを作りこれにマイグレーションクラスへのパスを渡しましょう:
$migration = new Doctrine_Migration('/path/to/migration_classes', $conn);
migrate()メソッドを呼び出すことで最新バージョンに移行できます:
$migration->migrate();
特定のバージョンにマイグレートするにはmigrate()に引数を渡します。例えばバージョン0から3にマイグレートできます:
$migration->migrate(3);
バージョン3から0に戻すことができます:
$migration->migrate(0);
データベースの現在のバージョンを取得するにはgetCurrentVersion()メソッドを使います:
echo $migration->getCurrentVersion();
migrate()メソッドのバージョン番号の引数を省略するとDoctrineは内部でディレクトリで見つかるクラスの最新バージョン番号にマイグレートしようとします。
マイグレーションのトランザクション
内部ではDoctrineはトランザクションのマイグレーションバージョンをラップしません。マイグレーションクラスで例外とトランザクションを処理するのは開発者しだいです。トランザクションDDLをサポートするデータベースはごくわずかであることを覚えておいてください。大抵のデータベースでは、トランザクションでマイグレーションをラップする場合でも、create、alter、drop、renameなどのステートメントは効果があります。
マイグレーションの実施方法を理解したのでmigrate.phpという名前でテスト環境のスクリプトを実装してみましょう。
最初にマイグレーションクラスを保存する場所が必要なのでmigrationsという名前のディレクトリを作りましょう:
$ mkdir migrations
migrate.phpスクリプトを作り次のコードを記入します:
// migrate.php
require_once('bootstrap.php');
$migration = new Doctrine_Migration('migrations');
$migration->migrate();
マイグレーションクラスはDoctrine_Migrationを継承するシンプルなクラスで構成されます。up()とdown()メソッドを定義します。これらのメソッドはそれぞれ指定されたマイグレーションバージョンでのデータベースの変更とその取り消しを意味します。
クラスの名前がなんであれ、正しい順序でマイグレーションをロードするために使われる数字が含まれる接頭辞をクラスが含まれるファイルの名前につけなければなりません。
バージョン1から始まるデータベースをビルドするために使うマイグレーションクラスの例です。
最初のバージョンとしてmigration_testという名前の新しいテーブルを作りましょう:
// migrations/1_add_table.php
class AddTable extends Doctrine_Migration_Base
{
public function up()
{
$this->createTable('migration_test', array('field1' => array('type' => 'string')));
}
public function down()
{
$this->dropTable('migration_test');
}
}
前のバージョンで追加したテーブルに新しいカラムを追加した2番目のバージョンを作りましょう:
// migrations/2_add_column.php
class AddColumn extends Doctrine_Migration_Base
{
public function up()
{
$this->addColumn('migration_test', 'field2', 'string');
}
public function down()
{
$this->removeColumn('migration_test', 'field2');
}
}
最後に、field1カラムの型を変更してみましょう:
// migrations/3_change_column.php
class ChangeColumn extends Doctrine_Migration_Base
{
public function up()
{
$this->changeColumn('migration_test', 'field2', 'integer');
}
public function down()
{
$this->changeColumn('migration_test', 'field2', 'string');
}
}
3つのマイグレーションクラスを作成したので以前実装したmigrate.phpスクリプトを実行できます:
$ php migrate.php
データベースを見るとmigrate_testという名前のテーブルが存在しmigration_versionのバージョン番号が3に設定されることが確認できます。
最初の状態に差し戻したい場合migrate.phpスクリプトでmigrate()メソッドにバージョン番号を渡します:
// migrate.php
// ...
$migration = new Doctrine_Migration('migrations');
$migration->migrate(0);
そしてmigrate.phpスクリプトを実行します:
$ php migrate.php
データベースを見ると、up()メソッドで行ったすべての内容がdown()メソッドの内容によって差し戻されます。
マイグレーションクラスでデータベースを変えるために利用できるメソッドの一覧は次の通りです。
// ...
public function up()
{
$columns = array(
'id' => array(
'type' => 'integer',
'unsigned' => 1,
'notnull' => 1,
'default' => 0
),
'name' => array(
'type' => 'string',
'length' => 12
),
'password' => array(
'type' => 'string',
'length' => 12
)
);
$options = array(
'type' => 'INNODB',
'charset' => 'utf8'
);
$this->createTable('table_name', $columns, $options);
}
// ...
スキーマを操作するために使われるデータ構造とデータベース抽象化レイヤーで使われるデータ構造が同じであることにお気づきかもしれません。これはマイグレーションクラスで指定されているオペレーションを実行するために内部でマイグレーションパッケージがデータベース抽象化レイヤーを使用しているからです。
// ...
public function down()
{
$this->dropTable('table_name');
}
// ...
// ...
public function up()
{
$this->renameTable('old_table_name', 'new_table_name');
}
// ...
// ...
public function up()
{
$definition = array(
'fields' => array(
'username' => array()
),
'unique' => true
);
$this->createConstraint('table_name', 'constraint_name', $definition);
}
// ...
Now the opposite down() would look like the following:
// ...
public function down()
{
$this->dropConstraint('table_name', 'constraint_name');
}
// ...
// ...
public function up()
{
$definition = array(
'local' => 'email_id',
'foreign' => 'id',
'foreignTable' => 'email',
'onDelete' => 'CASCADE'
);
$this->createForeignKey('table_name', 'email_foreign_key', $definition);
}
// ...
$definition用の有効なオプションは次の通りです:
| 名前 | 説明 |
|---|---|
| name | オプションの制約名 |
| local | ローカルフィールド |
| foreign | 外部参照フィールド |
| foreignTable | 外部テーブルの名前 |
| onDelete | 参照の削除アクション |
| onUpdate | 参照の更新アクション |
| deferred | 延期された制約チェック |
// ...
public function down()
{
$this->dropForeignKey('table_name', 'email_foreign_key');
}
// ...
// ...
public function up()
{
$this->addColumn('table_name', 'column_name', 'string', $length, $options);
}
// ...
sqliteのような一部のDBMSはカラムのリネームオペレーションを実装していません。sqlite接続を使用している場合カラムをリネームしようとすると例外が投げられます。
// ...
public function up()
{
$this->renameColumn('table_name', 'old_column_name', 'new_column_name');
}
// ...
既存のカラムのアスペクトを変更する:
// ...
public function up()
{
$options = array('length' => 1);
$this->changeColumn('table_name', 'column_name', 'tinyint', $options);
}
// ...
// ...
public function up()
{
$this->removeColumn('table_name', 'column_name');
}
// ...
リバースできないup()メソッドでオペレーションを実行することがあります。例えばテーブルからカラムを削除する場合です。この場合新しいDoctrine_Migration_IrreversibleMigrationException例外を投げる必要があります。
// ...
public function down()
{
throw new Doctrine_Migration_IrreversibleMigrationException(
'The remove column operation cannot be undone!'
);
}
// ...
// ...
public function up()
{
$options = array('fields' => array(
'username' => array(
'sorting' => 'ascending'
),
'last_login' => array()));
$this->addIndex('table_name', 'index_name', $options)
}
// ...
// ...
public function down()
{
$this->removeIndex('table_name', 'index_name');
}
// ...
モデルでデータベースのデータを変えることが必要な場合があります。テーブルを作成もしくは変更するのでup()もしくはdown()メソッドが処理された後でデータを変更しなければなりません。preUp()、postUp()、preDown()、とpostDown()という名前でフックを用意します。定義すればこれらのメソッドは実行されます:
// migrations/1_add_table.php
class AddTable extends Doctrine_Migration_Base
{
public function up()
{
$this->createTable('migration_test', array('field1' => array('type' => 'string')));
}
public function postUp()
{
$migrationTest = new MigrationTest();
$migrationTest->field1 = 'Initial record created by migrations';
$migrationTest->save();
}
public function down()
{
$this->dropTable('migration_test');
}
}
上記の例はMigrationTestモデルを作成し利用可能にしたことを前提とします。up()メソッドが実行されるとmigration_testテーブルが作成されるのでMigrationTestモデルが使われます。このモデルの定義は下記の通りです。
// models/MigrationTest.php
class MigrationTest extends Doctrine_Record
{
public function setTableDefinition()
{
$this->hasColumn('field1', 'string');
}
}
YAMLフォーマットでの例は次の通りです。YAML Schema Filesの章でYAMLの詳細を学びます:
---
# schema.yml
# ...
MigrationTest:
columns:
field1: string
Doctrineのマイグレーション機能では大抵の場合マイグレーションメソッドの反対側を自動化することが可能です。例えばマイグレーションのupで新しいカラムを作成する場合、downを簡単に自動化するのは可能で必要なのは作成されたカラムを削除することです。これはupとdownの両方に対してmigrate()メソッドを使用して実現可能です。
migrate()メソッドは$directionの引数を受け取りupもしくはdownの値を持つようになります。この値はcolumn、table、のようなメソッドの最初の引数に渡されます。
カラムの追加と削除を自動化する例は次の通りです。
class MigrationTest extends Doctrine_Migration_Base
{
public function migrate($direction)
{
$this->column($direction, 'table_name', 'column_name', 'string', '255');
}
}
上記のマイグレーションでupを実行するときカラムが追加され、downが実行されるときカラムが削除されます。
自動化できるマイグレーションメソッドのリストは次の通りです:
| 自動メソッド名 | 自動化 |
|---|---|
| table() | createTable()/dropTable() |
| constraint() | createConstraint()/dropConstraint() |
| foreignKey() | createForeignKey()/dropForeignKey() |
| column() | addColumn()/removeColumn() |
| index() | addInex()/removeIndex() |
Doctrineはいくつかの異なる方法でマイグレーションクラスを生成する機能を提供します。既存のデータベースを再現するマイグレーションのセットを生成する、もしくは既存のモデルのセット用にデータベースを作成するマイグレーションクラスを生成します。2つのスキーマ情報の2つのセットの間の違いからマイグレーションを生成することもできます。
既存のデータベース接続からマイグレーションのセットを生成するには、Doctrine_Core::generateMigrationsFromDb()を使います。
Doctrine_Core::generateMigrationsFromDb('/path/to/migration/classes');
既存のモデルのセットからマイグレーションのセットを生成するには、Doctrine_Core::generateMigrationsFromModels()を使うだけです。
Doctrine_Core::generateMigrationsFromModels('/path/to/migration/classes', '/path/to/models');
ときにはモデルを変更して変更に対するマイグレーション処理を自動化できるようにしたいことがあります。以前は変更に対してマイグレーションクラスを書かなければなりませんでした。しかし差分ツールによって変更を行い変更用のマイグレーションクラスを生成できます。
差分ツールはシンプルで使いやすいです。これは"from"と"to"を受け取り、これらは次のうちのどれかになります:
2つのYAMLスキーマファイルを作るシンプルな例を考えます。1つはschema1.ymlでもう1つはschema2.ymlという名前です。
schema1.ymlはシンプルなUserモデルを含みます:
---
# schema1.yml
User:
columns:
username: string(255)
password: string(255)
スキーマを修正してemail_addressカラムを追加する場合を考えてみましょう:
---
# schema1.yml
User:
columns:
username: string(255)
password: string(255)
email_address: string(255)
これでデータベースに新しいカラムを追加できるマイグレーションクラスを簡単に作ることができます:
Doctrine_Core::generateMigrationsFromDiff('/path/to/migration/classes', '/path/to/schema1.yml', '/path/to/schema2.yml');
これによって/path/to/migration/classes/1236199329_version1.phpのパスでファイルが生み出されます。
class Version1 extends Doctrine_Migration_Base
{
public function up()
{
$this->addColumn('user', 'email_address', 'string', '255', array ());
}
public function down()
{
$this->removeColumn('user', 'email_address');
}
}
データベースを簡単にマイグレートして新しいカラムを追加できます!