LaravelのMigrationが記述した順番に実行されると勘違いして嵌ったので、メモとして書いておきます。
LaravelのMigrationは書いた順に実行されるわけじゃない
Laravelで以下のようなMigrationファイルを作成するとします。すこし複雑ですが、aというカラムをprimary keyではなくし、代わりにbというカラムをprimary keyにする、そのためにnullableの条件を変える、というようなMigrationですね。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Sample extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('sample', function (Blueprint $table) {
$table->dropPrimary();
$table->unsignedInteger('a')->nullable()->change();
$table->dropColumn('a');
$table->unsignedInteger('b')->nullable(false)->change();
$table->primary(['b']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('sample', function (Blueprint $table) {
// TODO
});
}
}
この場合、生成されるSQLは以下になります。
array:4 [
0 => "ALTER TABLE sample CHANGE a a INT UNSIGNED DEFAULT NULL, CHANGE b b INT UNSIGNED NOT NULL"
1 => "alter table `sample` drop primary key"
2 => "alter table `sample` drop `a`"
3 => "alter table `sample` add primary key `sample_b_primary`(`b`)"
]
なんで1つめだけ大文字なんだろう…という疑問もありますが、実行順が全然違いますね。
この場合、aをdrop primary keyする前にchange default nullしていて、primary keyはnullableにできないので落ちます。
LaravelのMigrationはどういう順番で実行されるのか
LaravelのフレームワークのIlluminate/Database/Schema/Blueprint.php
のファイルの以下の箇所を見ると参考になります。
/**
* Add the commands that are implied by the blueprint's state.
*
* @param \Illuminate\Database\Schema\Grammars\Grammar $grammar
* @return void
*/
protected function addImpliedCommands(Grammar $grammar)
{
if (count($this->getAddedColumns()) > 0 && ! $this->creating()) {
array_unshift($this->commands, $this->createCommand('add'));
}
if (count($this->getChangedColumns()) > 0 && ! $this->creating()) {
array_unshift($this->commands, $this->createCommand('change'));
}
$this->addFluentIndexes();
$this->addFluentCommands($grammar);
}
ここはMigration内で実行されるSQLをCommandという形で詰め込んでいる箇所です。array_unshift
を使ってcommandを詰めていることを考えると、
- change
- add
- その他(drop、foreign、dropForeign、primary、dropPrimaryなど)
という順番で実行されるようです。
どうすればよいか
Migrationの単位を細かく分割すると良いと思います。
最初の例だと以下のように3つに分割すれば意図したとおりに実行できます。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class Sample extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('sample', function (Blueprint $table) {
$table->dropPrimary();
});
Schema::table('sample', function (Blueprint $table) {
$table->unsignedInteger('a')->nullable()->change();
$table->dropColumn('a');
});
Schema::table('sample', function (Blueprint $table) {
$table->unsignedInteger('b')->nullable(false)->change();
$table->primary(['b']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('sample', function (Blueprint $table) {
// TODO
});
}
}