DoctrineMigrationsBundle 2 → 3 メジャーバージョンアップ対応

## 概要

SymfonyアプリでDBマイグレーションをやってくれる [DoctrineMigrationsBundle](https://github.com/doctrine/DoctrineMigrationsBundle) の3系が2020年06月にリリースされました。

2系から3系へのメジャーバージョンアップ対応を記します :pencil: 

## 環境

- PHP `7.2`
- Symfony `4.4`
- DoctrineMigrationsBundle `2.1.2` --> `3.0.1`
- MySQL `5.6`

## 本題

変更点等は [UPGRADE.md](https://github.com/doctrine/DoctrineMigrationsBundle/blob/3.0.1/UPGRADE.md) に記されています

`doctrine/doctrine-migrations-bundle` パッケージは 
`doctrine/migrations` パッケージを require しているので、こちらの [UPGRADE.md](https://github.com/doctrine/migrations/blob/3.0.1/UPGRADE.md) も読んでおくといいかもです

要点としては以下です

- **『マイグレーションテーブルの更新』**
  - テーブル名: これまで `migration_versions` がデフォルトでしたが、 `doctrine_migration_versions` がデフォルトになります（設定で変更可能）
  - カラム
      - `version` : これまで `20200601000000` のように `YmdHis` フォーマットでしたが、3系からは `FQCN` (Fully Qualified Class Name)になります
      - `execution_time` : 新規に追加されます。マイグレーションにかかった時間を格納するようです。既存の `executed_at` （マイグレーションを適用した日時）とは別カラムになります
- 『[Code BC Breaks](https://github.com/doctrine/migrations/blob/3.0.1/UPGRADE.md#code-bc-breaks)』
  - いくつかのクラスが final classになっていたり、削除されていたりします

『Code Bc Breaks』 については、各自のアプリケーションでの実装次第となりそうなので、
final classになったものについては継承する形式をやめて construct で内包する形に変更するなどの対応になるかと思います。

この記事では、
共通してつまづきそうな『マイグレーションテーブルの更新』について記します。


『マイグレーションテーブルの更新』に合わせて、  **`doctrine_migrtions` の設定方法が変わりました** :rotating_light: 

変更点は主に以下の2つです
- マイグレーションファイルのディレクトリ・namespaceの設定方法の変更
- マイグレーションテーブルの設定方法の変更



### マイグレーションファイルのディレクトリ・namespaceの設定方法の変更

マイグレーションファイルを保存してあるディレクトリに合わせて以下の設定を加えます

After は、デフォルトの値になるので、各自アプリケーションでのディレクトリ・namespaceに合わせて設定する必要があります

デフォルトと異なる場合、合わせて指定してあげないと「マイグレーションファイルが存在しない」と認識されエラーになります


Before ※ 元々、設定がない場合もあります

```yaml
doctrine_migrations:
    dir_name: '%kernel.project_dir%/src/Migrations'
    namespace: DoctrineMigrations
```

After

```yaml
doctrine_migrations:
    migrations_paths:
        DoctrineMigrations: '%kernel.project_dir%/src/Migrations'
        # 👆 ここが {namespace}: {dir_name} となるように設定します
```

例えば、以下のようなファイル構成と namespace であれば、デフォルトのままで問題ないですが、
どちらかでもカスタムしていれば合わせて設定してください 

```shell
% tree ./path_to_app/src
..
├── Migrations  # 👈 {dir_name} として設定
│   ├── Version20200601000000.php 
│   ├── Version20200602000000.php 
..
```

```php
namespace DoctrineMigrations; # 👈 {namespace} として設定

final class Version20200601000000 extends AbstractMigration
{
  .. 
```

### マイグレーションテーブルの設定方法の変更

次にマイグレーションテーブルの設定方法も変更になりました。

デフォルトのテーブル名が `migration_versions` から `doctrine_migration_versions` に変わったので、
既存のマイグレーションテーブルをそのまま使うのであれば、指定してあげる必要があります。

Before ※ 元々、設定がない場合もあります

```yaml
doctrine_migrations:
    table_name: 'migration_versions'
    column_name: 'version'
    column_length: 14
    executed_at_column_name: 'executed_at'
```

After

```yaml
doctrine_migrations:
    storage:
        table_storage:
            table_name: 'migration_versions'       # 注意①
            version_column_name: 'version'         # 元々、versionsカラムであれば指定しなくても大丈夫です
            version_column_length: 191             # 注意②
            executed_at_column_name: 'executed_at' # 元々、executed_atカラムであれば指定しなくても大丈夫です
```

#### 注意① `table_name`

3系からデフォルトのマイグレーションテーブルのテーブル名は `doctrine_migration_versions` に変更されます。

おそらく（？）みなさんのマイグレーションテーブルのテーブル名は指定して作成していなければ `migration_versions` のはずなので、
3系からは設定で指定してあげる必要があります。

そうとは知らず、省略していると、 `doctrine_migration_versions` テーブルが新規作成され、1からマイグレーションが実行されてしまうので注意してください :rotating_light: 


#### 注意② `version_column_length`

3系から `version` カラムに入る値は `20200601000000` のように `YmdHis` フォーマットではなく FQCN になります。

2系のカラム長のデフォルト値 `14` のままでは、 FQCN が入り切らない可能性があるため、3系では増えました。

UPGRADE.md に記載のあるように `1024` を設定しまうと、 MySQL 5.6 ではエラーになってしまうので注意してください :rotating_light: 

理由は、MySQL 5.6の単一カラムのインデックスキーは `767byte` までしか指定できないからです。

なので `191` (191文字 * 4byte = 764byte < 767byte) ※ 4byte文字を考慮

---

`execution_time` カラムが追加されたこともあり、マイグレーションテーブルの2系と3系でどう変わるのか？は実際のデータを見てもらうのが早いので、以下に示します

2系

```sql
mysql> select * from migration_versions;
+----------------+---------------------+
| version        | executed_at         |
+----------------+---------------------+
| 20200601000000 | 2020-06-01 00:01:00 |
| 20200602000000 | 2020-06-02 00:01:00 |
..
```

3系

```sql
mysql> select * from migration_versions;
+------------------------------------------+---------------------+----------------+
| version                                  | executed_at         | execution_time |
+------------------------------------------+---------------------+----------------+
| DoctrineMigrations\Version20200601000000 | 2020-06-01 00:01:00 |             11 |
| DoctrineMigrations\Version20200602000000 | 2020-06-02 00:01:00 |             12 |
..
```

`doctrine:migrations:migrate` または `doctrine:migrations:execute` 実行時に、
3系のマイグレーションテーブルへ更新されます

`execution_time` カラムが追加されるだけではなく、
適用済みのマイグレーションの `version` の値についても、 FQCN への更新がされます!

1からマイグレーションが再実行されるわけではないのでご安心を。


以上で、『マイグレーションテーブルの更新』まわりでのメジャーバージョンアップ対応は完了になります。

ほとんどのケースで、これで対応完了になるのではないかと思っております :thought_balloon: 


---


### マイグレーションファイルでサービスコンテナを利用している場合

ここからは、少しレアケースで、なんらかの理由でマイグレーションファイルでサービスコンテナを利用している場合、
追加で対応してあげる必要があります

これまでは、以下のように `ContainerAwareInterface` を implements して `use ContainerAwareTrait` を宣言すれば、
自動でサービスコンテナがセットされていました。

が、3系からは自動でセットされません :confused: 

```php
class Version20200601000000 extends AbstractMigration implements ContainerAwareInterface
{
    use ContainerAwareTrait;
```

対応方法は[こちら](https://github.com/goetas/DoctrineMigrationsBundle/blob/b4685e0a85db6c468c034f179181386d1d174c3e/Resources/doc/index.rst#migration-dependencies)の `Migration Dependencies` に記載されています

マイグレーション実行時にマイグレーションインスタンスの生成を 
`MigrationFactory` 経由で生成しているところを、これを装飾した `MigrationFactoryDecorator` 経由に変更する（エイリアスをはる）ことで、
デコレータの装飾部分で `ContainerAwareInterface` のインスタンスであれば、サービスコンテナをセットするといった感じです。

```yaml
        doctrine_migrations:
            services:
                 'Doctrine\Migrations\Version\MigrationFactory': 'App\Migrations\Factory\MigrationFactoryDecorator'
                 # 👆 MigrationFactory として MigrationFactoryDecorator を使用するように変更
```