Laravel × Dacapo データベースマイグレーションサポートツールの使い方

![laravel-dacapo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/c834b027-40f4-7a65-62c6-3e0d9b59069e.png)

## ダカーポとは

https://github.com/ucan-lab/laravel-dacapo

Laravelマイグレーションのサポートライブラリです。
データベースのテーブル定義をYAMLファイル(スキーマファイル)で管理し、マイグレーションファイルの生成を行います。

※初期の開発時期に利用されることを想定しています。

## 2021.01.11 v4.0.0 リリース！

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">2020.01.11 Laravel Dacapo v4.0 Released !<a href="https://t.co/qCFAAkaDtm">https://t.co/qCFAAkaDtm</a> <a href="https://twitter.com/hashtag/Laravel?src=hash&amp;ref_src=twsrc%5Etfw">#Laravel</a> <a href="https://twitter.com/hashtag/PHP?src=hash&amp;ref_src=twsrc%5Etfw">#PHP</a> <a href="https://t.co/VbbuFSNdAu">pic.twitter.com/VbbuFSNdAu</a></p>&mdash; ucan@Qiita新人賞 (@ucan_lab) <a href="https://twitter.com/ucan_lab/status/1348461235939860482?ref_src=twsrc%5Etfw">January 11, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

v3.0から1年の時を経て、ついにバージョンv4.0をリリース致しました！🙇‍♂️

以前の古いコードをすべて破棄して新しく書き直してます。
少し挙動が異なる部分もあるのでこの機会に改めて使い方をご紹介します。

## ダカーポ関連記事

- [Docker × Laravel × Dacapo × SchemaSpyでER図を自動生成する](https://qiita.com/ucan-lab/items/76316c2723bdcce6eee1)

## 以前のダカーポの記事

- [Laravel 整理整頓したマイグレーションファイルを生成するライブラリを作った話](https://qiita.com/ucan-lab/items/10bb60e10c5b26a59b92)
- [Laravel × Dacapo で始める快適マイグレーション生活！](https://qiita.com/ucan-lab/items/8d23dc08fc5f964a3e72)

## ダカーポの由来

[ダ・カーポ（da capo）](https://ja.wikipedia.org/wiki/%E3%83%80%E3%83%BB%E3%82%AB%E3%83%BC%E3%83%9D)は音楽用語で「始めに戻って繰り返す」という意味があります。

PHPのパッケージ管理ツールである[Composer](https://getcomposer.org)も作曲家という音楽用語であり、マイグレーションファイルをすべて削除して、最初から生成し直すことからピッタリな名前だなと思いました。

## ダカーポを作った理由

- @mpyw さんの[LT](https://qiita.com/mpyw/items/5c1943ed9edd381fe50d#1-%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%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%95%B4%E7%90%86%E6%95%B4%E9%A0%93%E3%81%97%E3%81%A6%E6%9B%B8%E3%81%8D%E3%81%9F%E3%81%84)に感化された
- SymfonyのようにYAMLでテーブル定義するツールが欲しかった
- ファイル名、クラス名、upメソッド、downメソッドを書くのが面倒だった

## デフォルトのマイグレーションのツラミ

- make:migration で自動生成された日付がバラバラで見にくい
- make:migration のコマンドの実行時間が遅い
- テーブル定義 → インデックス定義 → 外部キー制約の順に定義する手間
- ゴミマイグレーションが残る
  - 最終的なテーブル構成はDBを直接見ないと把握しづらい

## ダカーポの歴史

- 2018.11.06 v0.0.1   4files 初回リリース
- 2019.08.22 v1.0.0  22files 1ファイルで収まらなくなり、すべて書き直し
- 2019.08.22 v2.0.0  24files schema.ymlを分割可能、全カラムタイプに対応
- 2019.11.10 v3.0.0  35files 細々とバグFIXを続ける
- 2021.01.11 v4.0.0 144files メタプロでメンテしづらく、すべて書き直し

v1.0では4ファイルだけで書いてました...1日で主機能を書き上げたのでとてもFatなソースコードになってましたね。
それに比べて最新のv4.0ではファイル数が36倍に増えましたww
今後のバージョンアップに備えてLaravelでサポートされているカラムタイプやカラム修飾子をすべてクラスで管理するようにしました。

## ダカーポのインストール

```shell-session
$ composer require --dev ucan-lab/laravel-dacapo
```

## ダカーポの執筆時バージョン

- Dacapo: 4.0.1

## ダカーポのサポート

- PHP 7.4 以降
- Laravel 6.x 以降
- MySQL
- PostgreSQL
- SQL Server(サポート対象外)
- SQLite(サポート対象外)

## ダカーポの初期化

```shell-session
$ php artisan dacapo:init
```

`dacapo:init` Artisanコマンドを使用して、ダカーポの初期化を行います。

`database/schemas/default.yml` が生成されます。
また、`database/migrations` ディレクトリ内のマイグレーションファイルも削除されます。

### --no-clear オプション

```shell-session
$ php artisan dacapo:init --no-clear
```

`database/migrations` ディレクトリ内のマイグレーションファイルの削除は行いません。

### --laravel6, --laravel7 オプション

```shell-session
$ php artisan dacapo:init --laravel6
$ php artisan dacapo:init --laravel7
$ php artisan dacapo:init --laravel8 # default
```

それぞれ[Laravel6](https://github.com/laravel/laravel/tree/6.x/database/migrations), [Laravel7](https://github.com/laravel/laravel/tree/7.x/database)のマイグレーションファイルを生成します。

デフォルトでは、[Laravel8](https://github.com/laravel/laravel/tree/8.x/database)のマイグレーションファイルを生成します。

それぞれのバージョンで微妙にテーブル定義内容が異なるので、テンプレートを分けています。

### database/schemas/default.yml

`database/schemas/default.yml` には、Laravelフレームワークにて予め用意されている([database/migrations](https://github.com/laravel/laravel/tree/8.x/database/migrations))マイグレーション3ファイルのテーブルの定義しています。

```yaml:database/schemas/default.yml
users:
  columns:
    id:
    name: string
    email:
      type: string
      unique:
    email_verified_at:
      type: timestamp
      nullable:
    password: string
    rememberToken:
    timestamps:

password_resets:
  columns:
    email:
      type: string
      index:
    token: string
    created_at:
      type: timestamp
      nullable:

failed_jobs:
  columns:
    id:
    uuid:
      type: string
      unique:
    connection: text
    queue: text
    payload: longText
    exception: longText
    failed_at:
      type: timestamp
      useCurrent:
```

## ダカーポの実行

```shell-session
$ php artisan dacapo
```

`database/schemas/*.yml` からテーブル構成を読み込んで `database/migrations/1970_01_01_*.php` のLaravelマイグレーションファイルを生成します。
また、ダカーポで生成された古いマイグレーションファイルはコマンド実行の都度削除されます。

続けて、 `php artisan migrate:fresh` コマンドが実行されます。
すべてのテーブルの削除、及びマイグレーションの実行が行われます。

### --seed オプション

```shell-session
$ php artisan dacapo --seed
```

`php artisan migrate:fresh` に続けて
`php artisan db:seed` コマンドが実行されます。

### --no-migrate オプション

```shell-session
$ php artisan dacapo --no-migrate
```

スキーマファイルからマイグレーションファイルを実行したあと、 `php artisan migrate:fresh` コマンドを実行しまいオプションです。

### --refresh オプション

```shell-session
$ php artisan dacapo --refresh
```

ロールバック処理が動作するか、動作確認のためのオプションです。

## スキーマの定義フォーマット

```yaml
テーブル名:
  columns:
    カラム名: カラムタイプ
    カラム名:
      type: カラムタイプ
      args: カラムタイプの引数(任意)

  indexes:
    - columns: [カラム名]
      type: インデックス修飾子
      name: インデックス名(任意)

  foreign_keys:
    - columns: [カラム名]
      references: [カラム名]
      table: テーブル名
```

- YAMLファイルは `database/schemas` ディレクトリに複数任意の名前で配置できます
- YAMLファイル内に複数のテーブルを定義できます

## スキーマの定義

### カラムの定義

```yaml:database/schemas/default.yml
users:
  columns:
    # カラムタイプのみ指定する場合
    votes: integer
    # カラムタイプに引数を指定する場合
    amount:
      type: double
      args: 8, 2
```

上記のYAMLを定義して `php artisan dacapo` を実行すると、下記のマイグレーションファイルが出力されます。

```php:database/migrations/1970_01_01_000001_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->integer('votes');
            $table->double('amount', 8, 2);
            $table->string('email')->nullable();
        });
    }

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

## 利用可能なカラムタイプ

`*.columns.*.type` に指定できるカラムタイプです。
[Laravelマイグレーションで利用可能なカラムタイプ](https://readouble.com/laravel/8.x/ja/migrations.html#available-column-types)はすべて対応してます。

bigIncrements, bigInteger, binary, boolean, char, dateTime, dateTimeTz, date, decimal, double, enum, float, foreignId, geometryCollection, geometry, id, increments, integer, ipAddress, jsonb, json, lineString, longText, macAddress, mediumIncrements, mediumInteger, mediumText, morphs, multiLineString, multiPoint, multiPolygon, nullableMorphs, nullableTimestamps, nullableUuidMorphs, point, polygon, rememberToken, set, smallIncrements, smallInteger, softDeletes, softDeletesTz, string, text, timestamps, timestampsTz, timestamp, timestampTz, time, timeTz, tinyIncrements, tinyInteger, unsignedBigInteger, unsignedDecimal, unsignedInteger, unsignedMediumInteger, unsignedSmallInteger, unsignedTinyInteger, uuidMorphs, uuid, year

## カラム修飾子

上記リストのカラムタイプに加え、データベーステーブルにカラムを追加するときに使用できるカラム「修飾子」もあります。

```yaml
users:
  columns:
    email:
      type: string
      nullable: true
```

上記のYAMLを定義して `php artisan dacapo` を実行すると、下記のマイグレーションファイルが出力されます。

```php:database/migrations/1970_01_01_000001_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->string('email')->nullable();
        });
    }

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

## 利用可能なカラム修飾子

alwaysModifier, autoIncrementModifier, charsetModifier, collationModifier, commentModifier, defaultModifier, defaultRawModifier, fromModifier, generatedAsModifier, indexModifier, nullableModifier, storedAsModifier, uniqueModifier, unsignedModifier, useCurrentModifier, useCurrentOnUpdateModifier, virtualAsModifier

## 利用不可なカラム修飾子

下記のカラム修飾子はダカーポではサポート対象外となっております。

after, change, first, renameColumn, dropColumn, dropMorphs, dropRememberToken, dropSoftDeletes, dropSoftDeletesTz, dropTimestamps, dropTimestampsTz, constrained, onUpdate, onDelete

## インデックス修飾子

```yaml:database/schemas/default.yml
users:
  columns:
    id: bigIncrements
    name: string
    email: string

  indexes:
    - columns: [name, email]
      type: index
      name: users_name_index
```

- 1970_01_01_000001_create_users_table.php
- 1970_01_01_000002_create_users_index.php
  - インデックスの定義ファイルは `000002` のプレフィックスが付きます。

```php:database/migrations/1970_01_01_000001_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email');
        });
    }

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

```php:database/migrations/1970_01_01_000002_create_users_index.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersIndex extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->index(['name', 'email'], 'users_name_index');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropIndex('users_name_index');
        });
    }
}
```

## 利用可能なインデックス修飾子

primary, unique, index, spatialIndex

## 外部キー制約

```yaml:database/schemas/default.yml
users:
  columns:
    id: bigIncrements
    name: string
    email: string

posts:
  columns:
    id: bigIncrements
    user_id: unsignedBigInteger
    content: string

  foreign_keys:
    - columns: [user_id]
      references: [id]
      table: users
```

- 1970_01_01_000001_create_users_table.php
- 1970_01_01_000001_create_posts_table.php
- 1970_01_01_000003_constraint_posts_foreign_key.php
  - 外部キーの定義ファイルは `000003` のプレフィックスが付きます。

```php:database/migrations/1970_01_01_000001_create_users_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email');
        });
    }

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

```php:database/migrations/1970_01_01_000001_create_posts_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('content');
        });
    }

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

```php:database/migrations/1970_01_01_000003_constraint_posts_foreign_key.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class ConstraintPostsForeignKey extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->foreign(['user_id'])->references(['id'])->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropForeign(['user_id']);
        });
    }
}
```

## その他

### 通常のマイグレーションファイルと併用可能

```shell-session
$ php artisan make:migration create_flights_table
```

`php artisan dacapo` で `database/migrations` ディレクトリ内のマイグレーションファイルが削除されますが、
ダカーポで生成されたファイルのみ削除されます。(`1970_01_01` のプレフィックスが付いたファイルのみ)

YAMLで表現できないようなテーブル定義は通常のマイグレーションファイルを作成してください。

### 複数のスキーマファイルを利用可能

`database/schemas/*.yml` ファイルを読み込むので、YAMLを分けて定義できます。

- `database/schemas/user.yml`
- `database/schemas/staff.yml`

ユーザー関連のテーブル、スタッフ関連のテーブル等、グループ分けをしてテーブルを定義していくこともできます。

## 今後の予定

ざっくり今後のダカーポについて検討していることを書きます。
あくまで予定で、全機能実装できるかは未定です...。

### v4.0

年末年始に一気に書き殴ったので、しばらくはリファクタリングに勤しみます。

## 新機能 v4.1

### dacapo --mixin オプション

SQLiteは外部キー制約を書く場合は、テーブル定義と同じタイミングで実行する必要があるみたいです。
懸念点としては、制約を貼る順番によってはエラーになりそうなので生成順序を気にしないといけなさそう...🤔
難しそうなのでこのオプションの実装悩んでます。。

### dacapo --squash オプション

Laravel8に搭載されたマイグレーションスカッシングのイメージです。
ただ、`mysqldump` コマンド入れてる必要があったり、`.sql` ファイルで出力されちゃったりと不満があります。

`--squash` オプションは1ファイルのマイグレーションファイルにまとめるオプションです。
これは需要ありそうかなと思ってるんですけどどうでしょうか？🤔

## 新機能 v4.2

### テンプレート生成するコマンド

```shell-session
$ php artisan dacapo:generate:model
$ php artisan dacapo:generate:factory
$ php artisan dacapo:generate:seeder
$ php artisan dacapo:generate:validation
```

スキーマYAMLから各種テンプレートを生成する機能です。
v3系には合ったのですが、バージョンアップの兼ね合いで一旦コマンドを削除しました。

v4.0系でも実装しようと思ったのですが、
Laravel8でモデルファクトリ書き方が丸っと変わったのでちょっと断念しました。

## 新機能 v4.3

### データベースからスキーマYAMLを逆生成するコマンド

```shell-session
$ php artisan dacapo:db:import
```

この機能が欲しくてv4.0の書き直しをしたので、なんとしてでもこの機能は実装する！

## 新機能 v4.4

### データベースとスキーマYAMLの差分からマイグレーションを生成するコマンド

```shell-session
$ php artisan dacapo:db:diff
```

`import` コマンドが実装できたら現行のテーブル構成とスキーマYAMLの差分ができたらいいなと思ってます。
これは実装難しそうなので悩んでます。