本番・開発環境でWordpressのDBスキーマを揃える。Phinxでマイグレーションとシード実行

![phinx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/119201/54d79a77-e8a1-483c-aff6-d2d9bcbee4cf.png)



数名で始めた小さな読み物アプリケーションをWordpressで管理をすることになりました。極力Wordpressをコード化してスケールする開発を行いたいと考えている方に参考になればと思い書きました。


## この記事の対象となる読者
- WordpressをDockerで運用し、複数人・複数の環境で開発しようとしている開発者
- WordpressのDBスキーマ管理（マイグレーションツール）の技術選定中の方



## あらまし
本番、開発環境という2つの環境で運用をしたいとなった場合、Wordpressはプラグインの扱いが環境ごとに差分が出やすく悩ましい限りです。
  
なにしろWordpressはプラグインをインストールするとプラグインのファイルだけでなく、テーブルや設定レコードが増えるのですから。

可能な限りGitでプラグインのコードだけでなく、DBの差分も管理して全環境に適用したいと考えました。

主にDBの差分をどう運用するかについて模索して実装しています。



## DBの差分をどうするか
PHPにはRailsのような強力なDBマイグレーションの仕組みがなさそうですが、どうにかしてコードでテーブル構成を管理したいと思いいくつかツールを比較検討した結果、[Phinx](https://phinx.org/) というマイグレーションツールを選定することとなりました。


## 選定理由
- shellやCIから実行しやすいシンプルな作り
- Phinx自体PHPで書かれているのでPHP書ける人ならコードリーディングしやすい
- ドキュメントが充実しているしサクサク読める（ただし英語だ


[Phinxの全体像を掴みたい方はこちらをご覧ください](https://qiita.com/hypermkt/items/b915b8a9fbda2f0c612e)
ツール比較の際、参考にさせていただきました。



## デメリットあり！Wordpressの思想とは若干ずれる運用方法になることを許容できるか？

ツールの選定ができましたが、この場合Wordpressの利用方法が限定されそうで、それこそが最大のデメリットだと思われます。

- プラグインやテーマ管理は開発環境でのみで行わなければならない
- サイト基本設定も開発環境でのみで行わなければならない

理由はDBマイグレーション、設定レコードをコミットしたいからです。
裏返せば、本番環境の管理画面でプラグイン・テーマ操作、設定変更を行うことはNGということになります。
できることは記事追加や画像追加、ページ作成などに限定されます。


しかし納品先があるわけでもなく、自分たちで使うアプリケーションであれば全く問題にはならない内容かもしれません。



## 下準備
利用したい [Dockerfile](https://hub.docker.com/_/wordpress) をとってきて保存している想定です。
私の場合はDockerHubに公開されているWordpressのイメージ(php7.4-fpmなど)を使用し、docker composeでNginx, Wordpress, MySQLなどのコンテナを起動しています。

また、Phinxはcomposer実行によりインストールするためcomposer.jsonを用意しておきます。

```json
{
  "require": {
    "robmorgan/phinx": "*"
  }
}
```

ファイルは以下のような配置になっているとします。

```
├ docker
│  ├ php7.4-fpm-wordpress
│  │  ├ Dockerfile
│  │  ├ ...
│  ├ ...
│
├ docker-compose.yml
├ lib
   └ db
      ├ composer.json
      ├ ...
```


## WordpressのDockerfileを編集する
PHPの設定が終わったタイミングでDockerのレイヤーを差し込みます。

Phinxはcomposerで動かすので先にcomposerを落としてきてPATHが通っている場所に配置しておきます。

```Dockerfile
# composerを落としてくる
RUN curl -sS https://getcomposer.org/installer | php && \
    mv ./composer.phar /usr/local/bin/composer
```
`/usr/local/bin/` に配置したので `composer` で実行できるようになりました。



```Dockerfile
# Phinxをcomposerでインストールする
COPY ./lib/db/composer.json /var/lib/db/
WORKDIR /var/lib/db
RUN composer install && \
    ./vendor/bin/phinx init
```

基本的にこれでDockerを立ち上げれば実行可能になっているはずです。

Dockerを立ち上げてWordpressのコンテナに入ってマイグレーションファイル作成ができれば期待通りです。

```bash
$ docker exec -it your_wordpress_1 bash
$ php /var/lib/db/vendor/bin/phinx create CreateHoge
```


## WordpressのDB情報をどのようにコード化するか
一概にこれが正解とは言えませんが私はこのような手順でコード化しました。

#### STEP1
MySQLのdumpファイル出力します。（CSVではなくSQL)

#### STEP2
dumpのSQLをテーブルごとにマイグレーションを作成して転記します。
マイグレーション作成はコンテナ内で実行する想定です。

```bash
$ php /var/lib/db/vendor/bin/phinx create CreateWpComments
```

```php
<?php
declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class CreateWpComments extends AbstractMigration
{
    public function up(): void
    {
        $query = <<<EOS
CREATE TABLE `wp_comments` (
  `comment_ID` bigint(20) UNSIGNED NOT NULL,
  `comment_post_ID` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
  `comment_author` tinytext COLLATE utf8_general_ci NOT NULL,
  `comment_author_email` varchar(100) COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `comment_author_url` varchar(200) COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `comment_author_IP` varchar(100) COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `comment_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `comment_date_gmt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `comment_content` text COLLATE utf8_general_ci NOT NULL,
  `comment_karma` int(11) NOT NULL DEFAULT '0',
  `comment_approved` varchar(20) COLLATE utf8_general_ci NOT NULL DEFAULT '1',
  `comment_agent` varchar(255) COLLATE utf8_general_ci NOT NULL DEFAULT '',
  `comment_type` varchar(20) COLLATE utf8_general_ci NOT NULL DEFAULT 'comment',
  `comment_parent` bigint(20) UNSIGNED NOT NULL DEFAULT '0',
  `user_id` bigint(20) UNSIGNED NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

ALTER TABLE `wp_comments`
  ADD PRIMARY KEY (`comment_ID`),
  ADD KEY `comment_post_ID` (`comment_post_ID`),
  ADD KEY `comment_approved_date_gmt` (`comment_approved`,`comment_date_gmt`),
  ADD KEY `comment_date_gmt` (`comment_date_gmt`),
  ADD KEY `comment_parent` (`comment_parent`),
  ADD KEY `comment_author_email` (`comment_author_email`(10));
EOS;

        $this->execute($query);
    }

    public function down(): void
    {
        $this->table('wp_comments')->drop()->save();
    }
}
```

ここではdumpからコピペして生クエリを実行するように指定しました。
PhinxにはMySQL PDOにアクセスするAPIがありますが、dumpしたSQLを書き直す意味はないでしょう。


#### STEP3
dumpから設定レコードとプラグインのテーブルのレコードをseedに埋め込む
コンテナ内でseedファイルを生成します。

```bash
$ php /var/lib/db/vendor/bin/phinx seed:create InsertWpUsers
```

生成されたseedファイルを編集します。

```php
<?php
use Phinx\Seed\AbstractSeed;

class InsertWpUsers extends AbstractSeed
{
    public array $WP_USERS = [
        [
            'ID' => 1,
            'user_login' => 'admin',
            'user_pass' => 'xxxxxxxx',
            'user_nicename' => 'admin',
            'user_email' => 'xxxxx@hogefuga.foo',
            'user_url' => '',
            ...
    ];

    public function run()
    {
        $table = $this->table('wp_users');
        $table->insert($this->WP_USERS)->save();
    }
}
```

↑はAPIを使う書き方に直した例。
手間がかかるだけだった。




```php
<?php


use Phinx\Seed\AbstractSeed;

class InsertWpOptions extends AbstractSeed
{
    private function environment()
    {
        return $this->input->getOptions('environment')['environment'];
    }

    public function run()
    {
        $siteUrl = $this->environment() === 'production' ? 'https://your-production-domain.jp' : 'http://localhost:8080';

        $insertion = <<<EOQ
INSERT INTO `wp_options` (`option_id`, `option_name`, `option_value`, `autoload`)
VALUES
	(1, 'siteurl', "${siteUrl}", 'yes'),
	(2, 'home', "${siteUrl}", 'yes'),
	(3, 'blogname', '日刊Magazines', 'yes'),
        ...
EOQ;
        $this->execute($insertion);
    }
}
```

↑のseedファイルでは環境ごとに異なるサイトURLでレコード作成できるように、URLを条件に応じて解決しています。


```bash
$ php /var/lib/db/vendor/bin/phinx seed:run -e production
```
のようにすると `-e ${環境}` で渡された引数をPhinxが環境におうじたDB設定を読み分けてくれます。

Classに生えていた ` $this->input` から引数など取得できるので活用させていただきました。


マスターデータ管理の他、開発デバッグ用のデータも作ることができて良さそうですね。


#### STEP4
マイグレーション及びseederを実行する

以下のようなディレクトリ構成にして整理しています。

```
└ lib
   └ db
      ├ commands
      │  ├ migrate.sh
      │  ├ rollback.sh
      │  ├ run_seeder_development.sh
      │  └ run_seeder_production.sh
      ├ migrations
      │  ├ xxxxx.php
      │  ...
      ├ seeds
      │  ├ xxxxx.php
      │  ...
      ├ templates
      │  ├ xxxxx.php.dist
      │  ...
      │  
      ├ composer.json
      ├ composer.lock
      ├ phinx.yml
      ...
```


```bash
$ docker exec -w /var/lib/db my_wordpress_1 \
sh -c 'php ./vendor/bin/phinx create CreateHogeFuga \
--template="./templates/migration.php.dist"'
```

マイグレーションの場合はこのようにテンプレートを使用して作成しています。

残念ながらseedはテンプレートに対応していないのかうまく動きませんでした。

```bash
$ docker exec -w /var/lib/db my_wordpress_1 sh -c 'php ./vendor/bin/phinx seed:create InsertFooBar'
```

以上でコード化ができたかと思います。


## 終わりに
作成したマイグレーションはマウントして編集したり、composerが用意したファイルをGitに乗せないようにしたりなど、考えることはここに記載している以外に数多くあり、言葉足らずな部分もあるかもしれません。