Symfony Advent Calendar JP 2011 19日目です。
昨日は
@co3kさんでした。
doctrineのmigration機能、使ってますか?
そもそもsymfony+doctrineのmigrationとは何かというと、一旦決めたDBスキーマが後から変更になったとき、テーブル作成・カラムの追加/変更などの作業を一元管理できる仕組みです。(RubyOnRails由来)
例えばカラムが追加になった時、プロジェクト側で持っているスキーマ情報(schema.yml)を変更した時に、手動で(コマンドやphpmyadmin等から)実際のDBテーブルにもカラムの追加を反映するのはなかなか大変です。追加カラムが1個ならまだしも、何個もあったり、複数テーブルに渡っていたりするととても面倒です。
面倒な作業=ミスが発生しやすい のでできるだけ手動でやるのは避けたいですよね。
これを解決してくれるのがdoctrineのmigration機能です。
スキーマ情報を変更するだけで、あとはコマンドを手順通り発行すれば実際のDBテーブルに変更を反映してくれます。
既に使っている人にはもはや手放せないぐらい便利な(?)機能なのですが、あまり日本語ドキュメントがないので、この機会に書いてみることにしました。
■ケース1:DBスキーマを変更する(基本の使い方)
下のようなプロジェクトの初期schema.ymlがあったとします。
後からやっぱり商品説明もDB管理したいとなったとき、migrationの出番です。
まず、schema.ymlを変更してdescriptionを追加します。
スキーマの差分を自動生成するコマンドを実行します。
./symfony doctrine:generate-migrations-diff
lib/migration/doctrineにmigrationクラスが自動的に生成されています。
(※生成されたクラスの中身を見れば、別に自動生成に頼らずとも手動でも書こうと思えば書けるクラスだということはわかると思います→ケース3参照)
いよいよmigrationを実行します。
./symfony doctrine:migrate 1
→productテーブルの構造が変更され、descriptionカラムが追加されます。
※オプションの1はバージョン番号です。未指定の場合、lib/migration/doctrineにあるうち、最新のバージョンまで順番に全て実行されます。
model,form,filterはdescriptionを扱えるように生成し直す必要があります。
./symfony doctrine:build --model --forms --filters
(form,filterを使ってない時は--modelだけでOK)
これでDBスキーマ変更が完了してしまいます!
migrationのバージョン番号は、migration_versionというテーブルが勝手に作られ、その中のversionというカラムで管理されます。
一旦descriptionを足した後で不具合等が確認され「やっぱりちょっとリリース待って!」という事態になった時は、バージョンを下げることができます。
./symfony doctrine:migrate 0
→descriptionカラムが削除されます。modelの再生成をしてしまっていたら、再々生成の必要があります。
※オプションの0はバージョン番号です。
もし一度追加したカラムを永久的に削除する(一旦下げるのではなく)場合は、バージョンを下げるのではなくカラム削除のmigrationを新たに行うほうが無難でしょう。
なお、実行する環境と実行する人の手際によって、ほんのわずかの間ですがmodelと実際のDBの間に齟齬が発生します。そのため、あるはずのカラムがない状態になる等で運用中のシステム上でエラーが発生する可能性があります。
が、万一ダウンタイムになっても、手動でDBを変更しながらmodelを再生成する場合に比べて最小限で済むと思われます。
対策としては、
・カラムの削除だけを行うmigrationの場合は上記の手順を逆にして、先にmodelの再生成を行ってからdoctrine:migrateを実行する
・カラムの追加だけを行うmigrationの場合は先にdoctrine:migrateを実行してからmodelを再生成する
・カラムの追加と削除が同時に発生する要件でも、敢えてschema.ymlの編集を2回に分けて、カラム追加とカラム削除のmigrationの実行を分ける(カラム追加のmigrate→model再生成→カラム削除のmigrate→model再生成のような手順で行う)
が考えられます。
この回避策は一般的なものですので、個々のケースに合わせて考えて実行してみてください。
■ケース2:migrationが積み重なったプロジェクトを新規にセットアップし、さらにDBスキーマを変更する(実際のプロジェクトでの使い方)
ケース1を経てリリースされたシステムをサーバー移転等で一から再構築した場合を考えてください。
./symfony doctrine:build --all
で構築すると、最初から既に変更済みのschema.ymlでDBテーブルが作成されます。
そのまま運用する場合には問題ないのですが、更にDBテーブルの変更が発生すると再びmigrationの出番です。
descriptionだけでなく、社内用の備考memoもDB管理することになったとして考えてみましょう。
まず、ケース1と同じようにschema.ymlを変更し、差分自動作成のコマンドを実行します。
./symfony doctrine:generate-migrations-diff
lib/migration/doctrineにVersion2クラスが生成されます。(ここまでケース1と同じですね)
さて、ここで問題になるのが、既にversion1までの内容が適用済みのDBに対して、バージョン1→バージョン2だけのmigrationを行いたいのですが、このサーバーではmigrationを行ったことがないため、migration_versionテーブルがまだありません。つまり、doctrineはバージョン0と認識してしてしまう状態です。
が、心配は要りません。黙ってバージョン2へのmigrationを行いましょう。
./symfony doctrine:migrate 2
やはり、予想通りに(?)エラーが出ます。
- SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'descrip
tion'. Failing Query: "ALTER TABLE product ADD description TEXT"
が、エラーになっても止まらずに指定したバージョンまで順に進めてくれるため、問題なくDBテーブルの変更自体は完了します。
modelの再生成を行えば、DBテーブルの変更は完了となります。
ケース2ではもう1個しておいた方が良い作業があります。
この段階まで終わった状態でmigration_versionテーブルを見ると、versionカラムが空になっていると思います。
versionカラムに適用済みにしたバージョン番号(このケースの場合だと2)を手動で(コマンド又はphpmyadmin等で)入れます。
こうしておくと、更にもう1回以上DBスキーマ変更が発生した時でも、今度はバージョン2まで適用済みとして認識させることができます。
※これを怠ると次もバージョン0から実行されてしまい、doctrine:migrate実行時にSQLのエラーを見ることになります。
影響がないとはいえ一時でもエラーを見たくない、という人は、migrationの前に自分でmigration_versionテーブルを作って、現在のバージョンを正しく認識させることでエラー表示を回避できます。
migration_versionテーブルを作り、integerでversionカラムを作ります。index等は要りません。
既に適用済のバージョン番号(このケースの場合だと1)を値としてversionに入れてから
./symfony doctrine:migrate 2
を実行しましょう。今度はエラーなくmigrationが完了するはずです。
■ケース3:behaviorのmigration
便利なdoctrineのbehaviorですが、デフォルトのbehavior(Timestampable,NestedSet等)でなく自作のものを使っている時に、behaviorで追加しているカラム自体が変更になった時のmigrationは注意が必要です。
具体的には、doctrine:generate-migrations-diffコマンドではschema.ymlとmodelの間の差分のみを見るため、behaviorの変更分は自動でmigrationクラスを作ってくれないからです。
このような場合、doctrineのmigrationを使ってDBテーブルに変更を反映するには、migrationクラスを手動で書く必要が出てきます。
lib/migration/doctrineにversion(バージョン番号).phpファイルを作り、自分で内容を書きます。
Version(バージョン番号)クラスは、Doctrine_Migration_Baseをextendsし、upとdownメソッドをpublicで定義すれば手動で書いてもちゃんと認識・実行されます。
ケース1やケース2の自動生成されたVersion1,Version2クラスでカラムの追加・変更の書き方は出てきたと思いますが、他にもカラムの型を変更したりindexを足したりすることができます。
一覧が
http://www.doctrine-project.org/projects/orm/1.2/docs/manual/migrations/ja#%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E6%9B%B8%E3%81%8F:%E5%88%A9%E7%94%A8%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3にありますので、実際に書き始める前に一度チェックしてみてください。
一度わかってしまえばすごく便利なdoctrine:migrate、symfony1.4で開発する時には是非使ってみてださい^^
Symfony2ネタが続く中、空気読まずにsymfony1.4.xネタでお送りしました☆
明日は
@fivestrさんです!