オープンソースこねこね

Webプログラミングなどについてあれこれ。

PHPMigrate - PHPでマイグレーションツールを作った

デプロイツールに引き続き、今度はPHPでMigrationツールを作りましたので、記事を書きます。

PHPMigrate - https://github.com/kohkimakimoto/phpmigrate

フレームワークとMigration

最近のWebアプリケーションフレームワークを使えば、まあだいたいMigrationの機能ついてくるんですよね。でもフレームワークに組み込まれているものだと、Migrationを実行するためにアプリを実行環境にデプロイしないとならなかったりします。Migrationとメインのアプリのコードがひとつのプロジェクトとしてフレームワークで規定された構造にパッケージされているので、Migrationのコードだけデプロイするというのはなかなか難しいものがあります。

Migrationで変更するDBのスキーマは実運用上だいたいカラムの追加やテーブルの追加がほとんどで、カラムにデフォルト値などを適切に設定しておけば、アプリのコードのデプロイより先行してDBのスキーマを変更しても問題ない。

けれどスキーマ変更より先にコードがデプロイされると、存在しないテーブルやカラムを参照してしまいエラーを起こしてしまいます。

デプロイ作業的には

DBスキーマ変更(Migration)→アプリのコードをデプロイ→キャッシュ削除などの後処理。

という順序でいきたいのだけど

アプリのコードをデプロイ→DBスキーマ変更(Migration)→キャッシュ削除などの後処理。

というようになってしまいます。これを避けるためには、Migrationだけを行う専用の実行環境を用意したりと、ちょっと工夫したりしなければならない。

フレームワークにMigrationが組み込まれているとDBの接続情報をアプリとMigrationで一元管理ができて、いいこともあるのだけど、個人的にはMigrationはフレームワークに対して、もうちょっと疎結合に構成されていたほうがいいんじゃないかという考えに至りました。

PHPMigrate

そういうわけで、すごくシンプルなMigrationツールをつくりました。

https://github.com/kohkimakimoto/phpmigrate

使い方

2013/04/22 追記

複数のDBに対してMigrationできるように修正したので、この記事の記述とは設定の書き方が変わりました。READMEを参考にしてください。

プログラムはmigrate.phpというPHPファイル一つだけなので、これをMigrationのタスクを管理するディレクトリにダウンロードします。ダウンロードしたら、エディタで開きます。ファイルの上の方にDBの接続情報を記述する箇所があるので、適宜環境にあわせて修正してください。

MigrationConfig::set('database_dsn',         'mysql:dbname=yourdatabase;host=localhost');
MigrationConfig::set('database_user',        'user');
MigrationConfig::set('database_password',    'password');
MigrationConfig::set('schema_version_table', 'schema_version');  # migarationの版を管理するテーブル名

修正したら

php migrate.php create [任意のマイグレーションタスク名]

というコマンドを打つと、

[タイムスタンプ]_[任意のマイグレーションタスク名].php

PHPファイルが生成されます。

<?php
/**
 * Migration class.
 */
class Dummy
{
  public function preUp()
  {
      // add the pre-migration code here
  }

  public function postUp()
  {
      // add the post-migration code here
  }

  public function preDown()
  {
      // add the pre-migration code here
  }

  public function postDown()
  {
      // add the post-migration code here
  }

  /**
   * Return the SQL statements for the Up migration
   *
   * @return string The SQL string to execute for the Up migration.
   */
  public function getUpSQL()
  {
    return "";
  }

  /**
   * Return the SQL statements for the Down migration
   *
   * @return string The SQL string to execute for the Down migration.
   */
  public function getDownSQL()
  {
     return "";
  }

}

そして、getUpSQLメソッドの戻り値にテーブル追加のSQL、getDownSQLにそれをもとに戻すSQLが戻されるようにコードを修正します。以下のような感じです。

  public function getUpSQL()
  {
     return <<<END

CREATE TABLE `sample` (
  `id` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_bin;

END;
  }


  public function getDownSQL()
  {
     return <<<END

DROP TABLE `sample`;

END;
  }

テーブルの変更内容はSQLを直接書きます。SelectやInsertを抽象化してくれるORMは好きなのですが、テーブル定義を行うDDLのようなSQLRDBMSの実装の影響も強く受けやすいと思っているので、無理に抽象化しない方針で設計しました。このほうが細かい調整がしやすいし、DBが最終的にどういう定義になるのかがわかりやすいと思っています。

あとは、以下のコマンドでmigrationを実行します

php migrate.php migrate

これで、変更が実行されます(上記の例だとsampleというテーブルが作成される)。そしてMigrationの版管理をするschema_versionというテーブルが自動で作成されてそこにファイルのプリフィクスだったタイムスタンプが登録されます。

戻す場合は

php migrate.php down

で戻せます。また他のコマンドとして

php migrate.php up      # 複数あるマイグレーションタスクで1件だけ進ませる。
php migrate.php status  # 現在の状態の表示。現在の版と、未実施のマイグレーションの一覧を表示。

があります。

またDB定義を書くのに、プログラムであるmigrate.phpを直接修正したくない場合は適当なPHPファイルを作って以下のようにmigrate.phpを読み込めば、定義情報を外出しすることもできます。

<?php
require 'migrate.php';

MigrationConfig::set('database_dsn',      'mysql:dbname=yourdatabase;host=localhost_modify');
MigrationConfig::set('database_user',     'user_modify');
MigrationConfig::set('database_password', 'password_modify');
MigrationConfig::set('schema_version_table',  'schema_version_modify');

Migration::main();