Cakephp3 migrations plugin (Phinx) を既存の環境に導入する

* cakephp 3.3~3.5, phinx 0.6.5で確認。 mysql前提
* 頑張ってphp(phinx)のみでやろうとせず、適時SQL文使う

## 現在のdbの状態をInitialとして定義

1. 現在のdb構造・マスタデータのdumpを取って、 `config/Migrations/initial.sql` に保存

    ```bash
    # dump例
    mysqldump --no-data [db] -h [host] -u [user] > config/Migrations/initial.sql
    ```
    * `migration_diff`, `migration_snapshot` はmysqlのみサポートされている型（enumなど）などは再現されないのでdump sqlを使う
    * 'phinxlog'テーブル存在する場合該当CREATE文は削除する
    * 必要に応じてマスタデータのinsert文等を含める

1. migration初期化

    ```
    bin/cake bake migration_snapshot Initial
    ```
    * この時点で、phinxlogテーブルが作られInitialのレコードが挿入されるはず（＝実行済み状態）

1. Initialクラス編集

    ```php
    <?php
    /**
     * up()すべて差し替え
     * down()は省略
     */
    use Migrations\AbstractMigration;

    class Initial extends AbstractMigration
    {
        public function up()
        {
            $file = CONFIG . 'Migrations/initial.sql';
            $this->execute(file_get_contents($file));
        }
    }
    ```

1. もしproduction環境が既に稼働している場合は、phinxlogテーブルとデータをproductionにコピーする
    * またはコードdeploy後 `bin/cake migrations mark_migrated`


## 運用

* migrationクラスには `change()` で自動reverse付き一括記述か、 `up(), down()` の両方記述か、どちらかを記述する。 drop table等はchange()で対応できないので、 up/downに記述することになる
* `change()` に記述する場合は、`$table->save()` は使わず `->create()`, `->update()`　を使う
* 逆に `up()`, `down()` では `save()` を使う（createとかでも問題ないかも）

### 新しいmigrationクラスを生成する

```bash
# 空のクラスを生成
bin/cake migrations create CreateTblTests

# bakeを使っても生成できるが結局調整したりするので上記のコマンドの方が個人的には良いと思う
bin/cake bake migration CreateTblTests name:string description:text
```


### テーブル作成するサンプル

* tinyint, bigintや mediumtext, mediumblobなどはlimitで指定する

```php
<?php
/** 
 * change()にphinxの構文で書くと、up,downを自動でやってくれる（対応メソッドに限りあり。up(),down()は定義しても実行されなくなる）
 * もし直SQL等で対応したい場合はup, downに $this->execute('SQL')を書く
 */
use Migrations\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;

class CreateTblTests extends AbstractMigration
{
    /**
     * Change Method.
     *
     * Write your reversible migrations using this method.
     *
     * More information on writing migrations is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
     *
     * The following commands can be used in this method and Phinx will
     * automatically reverse them when rolling back:
     *
     *    createTable
     *    renameTable
     *    addColumn
     *    renameColumn
     *    addIndex
     *    addForeignKey
     *
     * Remember to call "create()" or "update()" and NOT "save()" when working
     * with the Table class.
     */
    public function change()
    {
        $table = $this->table('tbl_tests', [
            'id' => false, // idの自動付与をしない
            'primary_key' => ['id'], // pkを idカラムに指定
            'collation' => 'utf8mb4_unicode_ci', // charsetは指定出来ない
            'comment' => 'TEST table, migrationサンプル',
        ]);

        $table->addColumn('id', 'integer', [
            'default' => null,
            'limit' => MysqlAdapter::INT_BIG, // INT_TINY, INT_SMALL, INT_MEDIUM, INT_REGULAR, INT_BIG
            'null' => false,
            'signed' => false, // unsigned 
            'identity' => true, // auto increment
            'comment' => 'idはaddColumnしなくても自動追加されるが、明示的に書きたい',
        ]);
        $table->addColumn('some_id', 'integer', ['null' => false, 'signed' => true]);
        $table->addColumn('combo_id', 'integer', ['null' => false, 'signed' => true]);

        $table->addColumn('name', 'string', [
            'default' => null,
            'limit' => 255,
            'null' => false,
            'comment' => 'varcharになる',
        ]);

        $table->addColumn('description', 'text', [
            'default' => '',
            'limit' => MysqlAdapter::TEXT_MEDIUM, // TEXT_TINY, TEXT_REGULAR, TEXT_MEDIUM, TEXT_LONG
            'null' => false,
        ]);

        $table->addColumn('blobdata', 'blob', [
            'default' => '',
            'limit' => MysqlAdapter::BLOB_REGULAR, // BLOB_TINY, BLOB_REGULAR, BLOB_MEDIUM, BLOB_LONG
            'null' => false,
        ]);

        $table->addColumn('status', 'enum', [
            'default' => 'enabled',
            'values' => ['enabled', 'disabled'],
            'null' => false,
        ]);

        $table->addColumn('created', 'datetime', [
            'default' => null,
            'null' => false,
        ]);

        $table->addColumn('modified', 'timestamp', [
            'default' => 'CURRENT_TIMESTAMP',
            'update' => 'CURRENT_TIMESTAMP',
            'null' => false,
        ]);

        // Index

        $table->addIndex(['some_id', 'combo_id'], [
            'unique' => true,
            'name' => 'idx_some_combo_id'
        ]);
        $table->addIndex(['name'], [
            'unique' => false,
            'name' => 'idx_name'
        ]);

        $table->create();
    }
}
```

### カラムやインデックスの追加はchangeに書けばOK

```php
<?php
/**
 */
use Phinx\Migration\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;

class ModifyTblTests extends AbstractMigration
{
    public function change()
    {
        $table = $this->table('tbl_tests');
        $table->addColumn('additional_id', 'integer', [
            'default' => null,
            'limit' => MysqlAdapter::INT_TINY,
            'null' => false,
            'signed' => true,
            'comment' => '追加のid',
            'after' => 'blobdata',
        ]);
        $table->addIndex(['some_id', 'additional_id'], [
            'unique' => false,
            'name' => 'idx_some_additional_id',
        ]);
        $table->update();
    }
}
```

### カラムやインデックスの削除はup/downを使う

```php

<?php
/**
 * change()が定義されてる時はup,downはskipされる
 */
use Phinx\Migration\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;

class ModifyIdxTblTests extends AbstractMigration
{
    public function up()
    {
        $table = $this->table('tbl_tests');
        $table->removeColumn('blobdata');
        $table->removeIndexByName('idx_name');
        $table->save(); // 上記は即実行されてsave()は不要かも？
    }


    public function down()
    {
        $table = $this->table('tbl_tests');
        $table->addColumn('blobdata', 'blob', [
            'default' => '',
            'limit' => MysqlAdapter::BLOB_REGULAR, // BLOB_TINY, BLOB_REGULAR, BLOB_MEDIUM, BLOB_LONG
            'null' => false,
            'after' => 'description',
        ]);
        $table->addIndex(['name'], [
            'unique' => false,
            'name' => 'idx_name'
        ]);
        $table->save();
    }
}
```

### テーブルの削除は、down()にCREATE文を載せるのが楽

```php
<?php
/**
 */
use Migrations\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;

class DropReadme extends AbstractMigration
{
    public function up()
    {
        $table = $this->table('tbl_tests');
        $table->drop();
    }


    public function down()
    {
        // SHOW CREATE TABLE `tbl_tests` を実行
        $sql = <<<SQL
CREATE TABLE `tbl_tests` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'idはaddColumnしなくても自動追加されるが、明示的に書くこと',
  `some_id` int(11) NOT NULL,
  `combo_id` int(11) NOT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'varcharになる',
  `description` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL,
  `additional_id` tinyint(4) NOT NULL COMMENT '追加のid',
  `status` enum('enabled','disabled') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'enabled',
  `created` datetime NOT NULL,
  `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_some_combo_id` (`some_id`,`combo_id`),
  KEY `idx_some_additional_id` (`some_id`,`additional_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='TEST table, migrationサンプル';
SQL;
        $this->execute($sql);
    }
}
```

### migrationにつかうコマンドを限定する

```bash
# 新規ファイル生成
bin/cake migrations create HogeFuga

# migration実行
bin/cake migrations migrate
# -cオプションでapp.phpの接続先、 -tオプションでphinxlog.versionの指定が可能
bin/cake migrations migrate --connection hoge -t YmdHis

# rollback（一つ戻す）
bin/cake migrations rollback
# 特定のバージョンまで戻す
bin/cake migrations rollback -t YmdHis
```