PHP
laravel

Laravel 5.5 make:migration コマンドの生成ファイルを改造する方法

TL;DR

  • Laravelの一部コアクラスはsingletonとしてバインドされている
  • サービスプロバイダからextendメソッドで拡張することで処理を挟み込める
  • stubsのパスを変更することで独自の生成ファイルを定義する

目的

「生成ファイル」とは、 artisan make:migration コマンドを叩いた際に、
自動で作成されるマイグレーションファイルを指します。

生成された時点で、独自のBlueprintを差し込んでおいたり、
IDをUUID型にしておきたい、などの要望が発生することもあると思います。

本記事では生成ファイルのテンプレート(Stub)を拡張し、
make:migration を叩くだけで思い通りのマイグレーションファイルを生成する方法を紹介します。

手順

1. サービスプロバイダを生成する

マイグレーションコマンドを拡張するためのサービスプロバイダを作成します。

$ php artisan make:provider MigrationServiceProvider

2. MigrationServiceProviderでコアクラスを拡張

make:migration は、
Illuminate\Database\Console\Migrations\MigrateMakeCommand を起点として、
Illuminate\Database\Migrations\MigrationCreator を呼び出しています。

https://github.com/laravel/framework/blob/a5903123344e8d6fc9ebab2447800348003d8536/src/Illuminate/Database/Migrations/MigrationCreator.php#L188-L191

MigrationCreatorは stubPath メソッドを持っており、
こちらで Stub のディレクトリパスを返しています。
今回はstubPathメソッドを拡張し、別のディレクトリのStubを読み込ませるよう変更します。

MigrationCreatorはインスタンス化の際、サービスプロバイダに登録済みのSingleton として呼び出されます。
そのため、サービスプロバイダの extend メソッドによって容易に拡張が可能です。
対象となるSingletonは migration.creator です。

extendメソッドの第二引数にあたるクロージャの受け取る引数は、
ひとつ目 ($creator) が、元のSingletonのインスタンス、
ふたつ目 ($app) が、コンテナインスタンスとなります。

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Migrations\MigrationCreator;

class MigrateServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->extend('migration.creator', function ($creator, $app) {
            return new class ($app['files']) extends MigrationCreator
            {
                public function stubPath()
                {
                    return resource_path() . '/stubs/Migration';
                }
            };
        });
    }
}

今回はアプリケーションの、resources/stubs/Migration にパスを変更しました。

3. Stubをコピー

アプリケーションルートで以下コマンドを実行し、フレームワークコアからStubファイルをコピーします。
blank.stub / create.stub / update.stub の3つのファイルが含まれます。
composerパッケージのインストール先がvendorと異なる場合は適宜パスを変更してください。

$ cp vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/ resources/stubs/Migration -R

4. Stubを編集

あとはStubを編集するだけです。
DummyClassDummyTable は後から適宜置換される部分なので編集しないよう気を付けてください。

下記例では、Schema Facadeを独自拡張に変更し、
IDの初期型をbigIncrementに変更、
論理削除カラムを追加しています。

create.stub
use App\Facades\CustomSchema as Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class DummyClass extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('DummyTable', function (Blueprint $table) {
            $table->bigIncrement('id');
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('DummyTable');
    }
}

5. make:migrationを実行

$ php artisan make:migration CreateUser --create users

Created Migration: yyyy_mm_dd_xxxxxxxx_create_user
yyyy_mm_dd_xxxxxxxx_create_user.php
<?php

use App\Facades\CustomSchema as Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUser extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrement('id');
            $table->timestamps();
            $table->softDeletes();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

備考

extendを利用すると、フレームワークコアでコンテナに登録されたインスタンスを何でも拡張できます。
今回の例では最初のインスタンスを捨てて拡張クラスを再インスタンス化していますが、
無駄なコストを消費するので、何度も呼び出されるインスタンスに対してこのような拡張の仕方をするのは避けましょう。

参考資料

https://stackoverflow.com/questions/34423170/override-singleton-in-laravel-container
https://code.tutsplus.com/tutorials/digging-in-to-laravels-ioc-container--cms-22167