LaravelのMigrationは書いた順に実行されるわけじゃない

LaravelのMigrationが記述した順番に実行されると勘違いして嵌ったので、メモとして書いておきます。

# LaravelのMigrationは書いた順に実行されるわけじゃない
Laravelで以下のようなMigrationファイルを作成するとします。すこし複雑ですが、aというカラムをprimary keyではなくし、代わりにbというカラムをprimary keyにする、そのためにnullableの条件を変える、というようなMigrationですね。

```php:2020_01_09_000000_sample.php
<?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は以下になります。

```php

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`のファイルの以下の箇所を見ると参考になります。

```php: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を詰めていることを考えると、

1. change
2. add
3. その他(drop、foreign、dropForeign、primary、dropPrimaryなど)

という順番で実行されるようです。


# どうすればよいか
Migrationの単位を細かく分割すると良いと思います。
最初の例だと以下のように3つに分割すれば意図したとおりに実行できます。

```php:2020_01_09_000000_sample.php
<?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
        });
    }
}

```