81
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LaravelAdvent Calendar 2019

Day 1

Laravel × Dacapo で始める快適マイグレーション生活!

Last updated at Posted at 2019-11-30

新しくv4.0をリリースしたので、解説記事をリニューアルしました!
Laravel × Dacapo データベースマイグレーションサポートツールの使い方

Laravel Advent Calendar 2019 - Qiita の 1日目 の記事です。

去年(Laravel Advent Calendar 2018)の記事

実際に弊社の実務でDacapoを運用し、他の会社さんから使ってもらったフィードバックを活かして去年より大幅アップデートしてます。

リポジトリ

何をするツールか

Laravelのマイグレーションファイル生成をサポートするライブラリです。

困っていること

  • 開発中はテーブル構成を変更する頻度が高い
  • 不要なマイグレーションファイルが増える
  • 似た構成のテーブルをコピペして作るにも変更箇所が多い
    • ファイル名、クラス名、テーブル名を直すのがめんどう...
    • 手動で直すとオートローダーがバグる
  • 最新のテーブル構成は実際のDBを見ないと分からない
  • インデックスや外部キーが入るとさらにややこしく...

ダカーポ開発の流れ

  • database/schemas/*.yml テーブル定義をymlファイルに記述する
    • 「スキーマ」と呼びます。
  • php artisan dacapo マイグレーションファイルを生成&マイグレーション実行

スキーマを変更した場合は必ず、 php artisan dacapo して、マイグレーションファイルを生成し直す流れになります。

ダカーポの注意・留意事項

  • 特性上、プロジェクトの初期フェーズに使用するライブラリです。

  • 運用フェーズに入ったらダカーポはアンインストールし、通常のマイグレーション運用に戻します。

  • 本番のデータが全部消えてしまうので...。。

  • schema.yml の記述を元に常に最新のマイグレーションファイルを生成します。(上書き)

  • php artisan migrate:fresh で全テーブルの削除し、マイグレーションを実行する運用です。

生成されるマイグレーションファイルの種類

  • 1970_01_01_000000_*.php テーブル作成のマイグレーションファイル
  • 1970_01_01_000001_*.php 一意制約、インデックスのマイグレーションファイル
  • 1970_01_01_000002_*.php 外部キー制約のマイグレーションファイル

マイグレーションファイルが database/migrations に出力されます。
マイグレーションはファイル名順に実行されます。
テーブルを作成したあとに制約を貼るようにしています。

整理整頓されたマイグレーションファイルが出力されます☺️

インストール手順

$ composer require --dev ucan-lab/laravel-dacapo

Laravelの初期マイグレーションを生成(任意)

Laravelでは、標準で下記の3テーブルのマイグレーションが用意されています。

  • users
  • password_resets
  • failed_jobs

これと同じ構成のスキーマファイルを生成するダカーポコマンドを用意しました。

dacapo:init コマンド

$ php artisan dacapo:init

database/schemas/default.yml ファイルが生成されます。

users:
  columns:
    id: bigIncrements
    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:

personal_access_tokens:
  columns:
    id:
    tokenable: morphs
    name: string
    token:
      type: string
      args: 64
      unique:
    abilities:
      type: text
      nullable:
    last_used_at:
      type: timestamp
      nullable
    timestamps:

dacap コマンド

先ほどの dacapo:init で生成した default.yml がある状態で dacapo を実行します。

$ php artisan dacapo

1970_01_01_000000_create_users_table.php

<?php

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

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')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

1970_01_01_000000_create_password_resets_table.php

<?php

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

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

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

1970_01_01_000000_create_failed_jobs_table.php

<?php

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

class CreateFailedJobsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('failed_jobs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->text('connection');
            $table->text('queue');
            $table->longText('payload');
            $table->longText('exception');
            $table->timestamp('failed_at')->useCurrent();
        });
    }

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

このように最小限のテーブル構成をymlに記述すればマイグレーションファイルを生成してくれます。

dacapo オプション

# seedオプション
$ php artisan dacapo --seed
# no-migrateオプション
$ php artisan dacapo --no-migrate

マイグレーション生成&マイグレーション実行&シーディング実行をオプション付けて一緒に実行できます。

スキーマの書き方

  • database/schemas/*.yml と配置します。
  • 複数ファイルを配置できます。

スキーマのフォーマット

テーブル名:
  columns:
    カラム名: カラムタイプ
    カラム名:
      type: カラムタイプ
      args: カラムタイプの引数

カラムタイプの指定

テーブルを構築する時に使用する様々なカラムタイプを指定できます。

how_to_column_types:
  columns:
    id: bigIncrements
    name1: string
    name2:
      type: string
    name3:
      type: string
      args: 100
    name4:
      type: string
      args: [100]
    amount1: decimal
    amount2:
      type: decimal
    amount3:
      type: decimal
      args: 8
    amount4:
      type: decimal
      args: [8]
    amount5:
      type: decimal
      args: [8, 2]
    level:
      type: enum
      args: ['easy', 'hard']
    timestamps:
    # timestamps: 0
    # timestamps: [0]
    softDeletes:
    # softDeletes: 'deleted_at'
    # softDeletes: ['deleted_at']
    # softDeletes: ['deleted_at', 0]

database/migrations/1970_01_01_000000_create_how_to_column_types_table.php

    public function up()
    {
        Schema::create('how_to_column_types', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name1');
            $table->string('name2');
            $table->string('name3', 100);
            $table->string('name4', 100);
            $table->decimal('amount1');
            $table->decimal('amount2');
            $table->decimal('amount3', 8);
            $table->decimal('amount4', 8);
            $table->decimal('amount5', 8, 2);
            $table->enum('level', ['easy', 'hard']);
            $table->timestamps();
            // $table->timestamps(0); // timestamps: 0
            // $table->timestamps(0); // timestamps: [0]
            $table->softDeletes();
            // $table->softDeletes('deleted_at'); // softDeletes: 'deleted_at'
            // $table->softDeletes('deleted_at'); // softDeletes: ['deleted_at']
            // $table->softDeletes('deleted_at', 0); // softDeletes: ['deleted_at', 0]
        });
    }

カラム修飾子

カラム修飾子 |
Laravel 6.x データベース:マイグレーション

上記のカラムタイプに付け加え、カラムを追加するときに使用できる様々な修飾子もあります。たとえばカラムを「NULL値設定可能(nullable)」にしたい場合は、nullableメソッドを使います。

また、ダカーポの特性上 after, first のカラム修飾子は使用できません。

how_to_column_modifiers:
  columns:
    id:
      type: integer
      autoIncrement: true
    name:
      type: string
      charset: utf8
      collation: utf8_unicode_ci
      comment: my comment
      default: test value
      nullable: true
    price:
      type: integer
      unsigned: true
    count:
      type: integer
      unsigned: true
    total_stored:
      type: integer
      storedAs: price * count
    total_virtual:
      type: integer
      virtualAs: price * count
    total_generated:
      type: integer
      generatedAs: price * count
    created_at:
      type: timestamp
      useCurrent: true
    seq:
      type: integer
      always: true

database/migrations/1970_01_01_000000_create_how_to_column_modifiers_table.php

        Schema::create('how_to_column_modifiers', function (Blueprint $table) {
            $table->integer('id')->autoIncrement();
            $table->string('name')->charset('utf8')->collation('utf8_unicode_ci')->comment('my comment')->default('test value')->nullable();
            $table->integer('price')->unsigned();
            $table->integer('count')->unsigned();
            $table->integer('total_stored')->storedAs('price * count');
            $table->integer('total_virtual')->virtualAs('price * count');
            $table->integer('total_generated')->generatedAs('price * count');
            $table->timestamp('created_at')->useCurrent();
            $table->integer('seq')->always();
        });

インデックス

users1:
  indexes:
    email_index:
      columns: name
      type: index
  columns:
    id: bigIncrements
    name:
      type: string
    email:
      type: string

users2:
  indexes:
    email_index:
      columns:
        - name
        - email
      type: index
      alias: users_name_index
  columns:
    id: bigIncrements
    name:
      type: string
    email:
      type: string

users3:
  indexes:
    email_index:
      columns:
        - name
        - email
      type: index
      alias: custom_index
  columns:
    id: bigIncrements
    name:
      type: string
    email:
      type: string

実際に出力されるファイル:
https://github.com/ucan-lab/laravel-dacapo/tree/master/tests/Storage/index

一意制約

users1:
  indexes:
    email_index:
      columns: name
      type: unique
  columns:
    id: bigIncrements
    name:
      type: string
    email:
      type: string

users2:
  indexes:
    email_index:
      columns:
        - name
        - email
      type: unique
      alias: users_name_unique_index
  columns:
    id: bigIncrements
    name:
      type: string
    email:
      type: string

users3:
  indexes:
    email_index:
      columns:
        - name
        - email
      type: unique
      alias: custom_index
  columns:
    id: bigIncrements
    name:
      type: string
    email:
      type: string

実際に出力されるファイル:
https://github.com/ucan-lab/laravel-dacapo/tree/master/tests/Storage/unique_index

外部キー制約

users:
  columns:
    id: bigIncrements
    name: string

tags:
  columns:
    id: bigIncrements
    title: string

tasks:
  relations:
    - foreign: user_id
      references: id
      on: users
      onUpdate: cascade
      onDelete: cascade
  columns:
    id: bigIncrements
    user_id: unsignedBigInteger
    content: string

task_tag:
  relations:
    - name: task_tag_custom_task_id_foreign
      foreign: task_id
      references: id
      on: tasks
    - foreign: tag_id
      references: id
      on: tags
  columns:
    task_id: unsignedBigInteger
    tag_id: unsignedBigInteger

実際に出力されるファイル:
https://github.com/ucan-lab/laravel-dacapo/tree/master/tests/Storage/relation/migrations

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

開発時は全テーブルドロップしてマイグレーション、シーディング実行で問題ありませんが、
運用開始してしまうと、本番テーブルをドロップする訳にはいきません...

運用フェーズに移行したらダカーポを削除して通常のマイグレーション運用に戻しましょう。

$ php artisan dacapo:uninstall
$ composer remove --dev ucan-lab/laravel-dacapo

Dacapo (ダカーポ) の由来

ライブラリ名の由来ですが、演奏記号のダ・カーポから命名しました。

ダカーポは「最初に戻って繰り返す。」という意味があります。

PHPのパッケージ管理ツールComposerが作曲家という意味なので音楽用語でこれだと思った名前に決めました!

着想

ダカーポライブラリの着想は Laravel/Vue.js勉強会#3 - connpass に参加して @mpyw さんのLTに影響を受けて作成しました。
また、Symfony1.4系の頃にあったDoctrineのDBスキーマ定義も参考にしてます。

Laravel × Dacapo で快適なマイグレーション生活を!

81
47
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
81
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?