WordPressをComposerでモダンな運用

# 2020/12 追記

本文中でおすすめした、 [WP Offload Media Lite for Amazon S3, DigitalOcean Spaces, and Google Cloud Storage](https://ja.wordpress.org/plugins/amazon-s3-and-cloudfront/) は現時点でおすすめできません。
S3のメタデータのキャッシュ実装に問題があり、大規模サイトではMySQLに高負荷がかかります。
また、ページ閲覧時におけるパフォーマンス低下があります。

代替として、 [S3 Uploads](https://github.com/humanmade/S3-Uploads) をおすすめします。

# はじめに

モダンPHPの技術を利用して、WordPressをより簡単にデプロイしたいと思いませんか？

通常のWordPressのセットアップでは、サーバに上げれば動作しますが、テーマやプラグインなどがファイルベースでの管理となっているため、Gitでの管理や複数台への展開など大規模構築に構築しようとすると、メリットが辛みになってしまいます。

そこで、モダンPHPのテクニックを利用することで、スケーラブルなWordPressサイトを構築するテクニックをまとめました。
後のほど投稿予定のFargateでWordPressをデプロイするために、まずは、ステートレス（※ここではサーバで状態を管理しないという意味）で管理する上でのポイントにもなっています。

以下の方向けの記事です。

- Git でWordPressを管理したい
- .env や環境変数を利用したデプロイがわかる
- Composer を利用した開発を行っている
- DepOpsを利用して、安定的にバージョンアップできる
- S3を外部ストレージを利用している
- 英語のドキュメントを読むことができる

この投稿では、WordPressの複数台構成と、サーバに状態を持たないことを目標としています。

# 運用と開発を分離しよう

この投稿を実施する場合、最も重要なのは、WordPressのバージョンアップやプラグインの管理をブラウザから行わないということです。
Bedrock の `WP_ENV=production` ではデフォルトでブラウザの管理画面からは以下のことができなくなります。

- WordPressのバージョンアップ
- プラグインのインストール・バージョンアップ・修正
- テーマのインストール・バージョンアップ・修正
- 言語のインストール・バージョンアップ

これで安心して開発と運用を分離できますね！

そのため、プラグインをカジュアルに管理画面から追加したり。テーマを管理画面でゴリゴリ変更している場合には向きません。


# WordPress のインストール

WordPressをComposerで管理するために [Bedrock](https://roots.io/bedrock/) を利用しましょう。

.env やComposerでのプラグイン管理など、基本的なことがすでに揃っています。
また、WordPressのパスワードを bcrypt にしてくれる [wp-password-bcrypt](https://github.com/roots/wp-password-bcrypt) もインストールされます。

インストールは簡単です。

```
$ composer create-project roots/bedrock
```

を実行すると、以下のディレクトリ構成でWordPressがインストールされます。

## ディレクトリ構成

```
├── composer.json
├── config
│   ├── application.php
│   └── environments
│       ├── development.php
│       ├── staging.php
│       └── production.php
├── vendor
└── web
    ├── app
    │   ├── mu-plugins
    │   ├── plugins
    │   ├── themes
    │   └── uploads
    ├── wp-config.php
    ├── index.php
    └── wp
```

LaravelなどのWAFを利用している方には見慣れた構成ですね！


WordPress本体は `web/wp` 以下にインストールされます。そしてこのディレクトリはComposerで管理され、バージョンアップの際には自動的に置き換えられます。

おなじみの `wp-content` はBedrockでは `web/app` になっています。もちろんこれは設定で変更できます。

プラグインはComposerでインストールするため、Gitにプラグイン自体を追加する必要はありません。
Composerでインストールすれば、 `web/app/plugins` 以下にインストールされます。

`web/app/uploads` には画像やプラグインによってアップロードされるファイルがアップロードされます。
このディレクトリだけどうにかできれば、あとはGitで管理できます。

## 環境設定

.env ファイルに `DB_NAME` や `DB_USER` を設定します。
詳細は [Installing Bedrock](https://roots.io/bedrock/docs/installing-bedrock/) を確認しましょう。

もう、 wp-config.php を書き換える必要はありません。

## サーバの設定

サーバの設定は、 [Bedrock Server Configuration](https://roots.io/bedrock/docs/bedrock-server-configuration/) をもとに設定しましょう。

## .gitignore

.gitignore もデフォルトで設定されています。

```plaintext:.gitignore
# Application
web/app/plugins/*
!web/app/plugins/.gitkeep
web/app/mu-plugins/*/
web/app/upgrade
web/app/uploads/*
!web/app/uploads/.gitkeep

# WordPress
web/wp
web/.htaccess

# Dotenv
.env
.env.*
!.env.example

# Composer
/vendor

# WP-CLI
wp-cli.local.yml
```

## 日本語化

本体の日本語化もComposerで行えます。

```json:composer.json
{
    "repositories": [
        {
            "type": "composer",
            "url": "https://wp-languages.github.io"
        }
    ],
    "extra": {
      "dropin-paths": {
        "web/app/languages/": ["vendor:koodimonni-language"],
        "web/app/languages/plugins/": ["vendor:koodimonni-plugin-language"],
        "web/app/languages/themes/": ["vendor:koodimonni-theme-language"]
      }
    }
}
```

```
$ composer require koodimonni-language/ja:*
```

[Composer WordPress language packs 
by Koodimonni](https://wp-languages.github.io/) も確認しましょう。

## WP Cron の実行

WP Cron の実行もcronで行うことで、安定した実行が可能です。
`DISABLE_WP_CRON` を .env で設定し、[WP Cron](https://roots.io/bedrock/docs/wp-cron/) を確認しながら以下を設定しましょう。

```
*/5 * * * * curl http://example.com/wp/wp-cron.php
```

もちろん、CLIから直接実行することも可能です。

```
*/5 * * * * php __BASE_DIR__/web/wp/wp-cron.php
```

## WordPress 本体のバージョンアップ

WordPress本体も Composer でアップデートする場合には、以下のように書き換えることで可能です。

```json:composer.json
    "roots/wordpress": "*",
```

もちろん、5.1系のみで、メジャーバージョンアップは自分でバージョンアップしたい場合には、

```json:composer.json
    "roots/wordpress": "~5.1.0",
```

といったComposerのバージョン指定も可能です。
**ただし、急激なバージョンアップでなければ、メジャーバージョンアップしても大抵のプラグインやテーマは動きます。というより、動くように作られているべきです。**
**古いバージョンのWordPressを使い続けることには危険性しかありません。バージョンを適時上げて行くためにも、PROD / DEV環境を作り、バージョンを適時上げられる運用体制を作ってください。**

WordPressのバージョンナンバリングもドキュメントにあります。
[Version Numbering – Make WordPress Core](https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/)

WordPressのメジャーバージョンアップは、先頭の `5.1` の部分で、 `5` だけではありません。
この点には注意しましょう。

## 準備万端

これで Composer で WordPress を管理する準備は万端です！

# プラグインの管理

WordPressのプラグインはブラウザからのインストールか、ファイルアップロードが前提となっています。
しかし、 [WordPress Packagist](http://wpackagist.org/) で非公式にComposerに対応したミラーがあります。

Bedrock にもドキュメント [Composer](https://roots.io/bedrock/docs/composer/) があります。

すでにBedrockでのインストールを行っていれば、以下のようにインストールできます。
例えば、All in One SEO Pack であれば、以下でインストールできます。

```
$ composer require wpackagist-plugin/all-in-one-seo-pack:*
```

プラグインのSlugはWordPressのサイト内のSlugのままです。
https://ja.wordpress.org/plugins/all-in-one-seo-pack/ の `all-in-one-seo-pack` 部分をそのまま利用できます。

ComposerからインストールしてもWordPressではすぐに有効にはなりません。
WordPressの管理画面から、プラグインを有効にしましょう。


## Composer でのプラグインのバージョン指定

WordPressのプラグインは基本的に最新版を利用しましょう！
決して、古いバージョンを理由なく利用してはいけません。

WordPressのプラグインは定期的に脆弱性が発見されることが多く、古いバージョンを放置することは危険です。

Composer で管理する場合のバージョン指定は、 `*` がおすすめです。
つまり、 `composer update` を利用した場合には常に最新版がインストールされます。

受託でWordPressを利用する方の中には、古いプラグインのバージョンで固定される方もいますが、絶対にこれを行ってはいけません。

プラグインのバージョンアップによって起きるトラブルは、安定していないプラグインや、更新されていない古いプラグインの利用、実装が悪いテーマによって引き起こされます。

WordPressのプラグインは基本的にマイグレーションが発生する場合も、ファイルをアップデートしたあとに管理画面からマイグレーションを実行します。


## プラグインのバージョンアップ

プラグインのバージョンアップも簡単です。

```
$ composer update
```

これだけです。 `composer.lock` でバージョンが固定されているため、どの環境でも `composer install` を行うだけでGitで管理されたバージョンを再現できます。

## プラグインの日本語化

プラグインの日本語化ファイルは、 Composer からインストールしただけではインストールされません。

[Manually adding any language zip to your composer.json](https://github.com/wp-languages/wp-languages.github.io#manually-adding-any-language-zip-to-your-composerjson) を参考に `repositories` を追加することで管理できます。

```json:composer.json
{
    "repositories": [
        {
            "type": "package",
            "package": {
                "name": "koodimonni-plugin-language/jetpack-fr_FR",
                "type": "wordpress-language",
                "version": "3.9.2",
                "dist": {
                    "type": "zip",
                    "url": "https://downloads.wordpress.org/translation/plugin/jetpack/3.9.2/fr_FR.zip",
                    "reference": "master"
                }
            }
        }
    ],
    "require": {
        "koodimonni/composer-dropin-installer": "*",
        "koodimonni-plugin-language/jetpack-fr_FR": ">=3.9.2"
    },
    "extra": {
      "dropin-paths": {
        "htdocs/wp-content/languages/": ["vendor:koodimonni-language"],
        "htdocs/wp-content/languages/plugins/": ["vendor:koodimonni-plugin-language"],
        "htdocs/wp-content/languages/themes/": ["vendor:koodimonni-theme-language"]
      }
    }
}
```

.gitignore にも追加しておきましょう。

```plaintext:.gitignore
web/app/languages
```

もしくは、自動インストールしたければ、[Composer Auto Language Updates](https://github.com/Angrycreative/composer-plugin-language-update) を利用することも可能です。
こちらは、Composerのイベントフックのため、言語ファイルのみがバージョンアップしていてもバージョンアップされない点を注意しましょう。


# uploads の管理

ここまでが理解できていれば、 `uploads` もGitディレクトリの外に出したいと思うでしょう。
S3を利用するのが、最も簡単な方法です。

ここで利用するのが、 [WP Offload Media Lite for Amazon S3, DigitalOcean Spaces, and Google Cloud Storage](https://ja.wordpress.org/plugins/amazon-s3-and-cloudfront/) です。
このプラグインを利用すれば、メディアをS3にアップしてくれます。

既存サイトの移行の場合には、post_metaをどうにか追加する必要があります。
この点は注意してください。

ここまでを理解できていれば、Composerでインストールできますね。

```
$ composer require wpackagist-plugin/amazon-s3-and-cloudfront:*
```

## IAMでS3へアクセス

AWSインフラを利用しているのであれば、AWSのアクセスキーは環境変数で管理せず、IAM Roleを利用しましょう。
IAM Roleの利用は `AS3CF_AWS_USE_EC2_IAM_ROLE` で設定できます。

```plaintext:.env
AS3CF_AWS_USE_EC2_IAM_ROLE=true
```

```php:config/application.php
Config::define('AS3CF_AWS_USE_EC2_IAM_ROLE', env('AS3CF_AWS_USE_EC2_IAM_ROLE'));
```

IAMポリシーを最小限にするには、以下を設定します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::example",
            ]
        },
        {
            "Effect": "Allow",
            "Action": "s3:ListAllMyBuckets",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObjectAcl",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::example/app/uploads/*"
            ]
        }
    ]
}
```

## One more config

もし、WordPressとS3のURLを同じにしたければ、サーバとS3の前にCloudfrontを設置します。

`app/uploads/` 以下のみS3を参照し、それ以外はサーバを参照するように設定しましょう。

# REST API の無効化

ここで、残念なお知らせです。
デフォルトのWordPressでは、API経由でユーザーアカウント名を取得することができます。

以下の形式です。
https://www.example.com/wp-json/wp/v2/users/1

[Disable REST API](https://ja.wordpress.org/plugins/disable-json-api/) でログインしていないユーザーには無効にしておきましょう。

```
$ composer require wpackagist-plugin/disable-json-api:*
```

インストール後、管理画面からプラグインを有効にしましょう。


# メールの送信

AWSインフラを利用している場合には、SESなどを利用することになると思います。

[WP Mail SMTP by WPForms](https://ja.wordpress.org/plugins/wp-mail-smtp/) を利用することで、SMTPサーバを利用することができます。

```
$ composer require wpackagist-plugin/wp-mail-smtp:*
```

設定を .env で管理したい場合には、以下のように設定します。
以下の設定を行うと、WP Mail SMTPの設定は管理画面経由では行えないようになります。

```plaintext:.env
WPMS_ON=true
WPMS_MAILER=smtp
WPMS_SMTP_HOST=email-smtp.us-east-1.amazonaws.com
WPMS_SMTP_PORT=587
WPMS_SSL=tls
WPMS_SMTP_AUTH=true
WPMS_SMTP_AUTOTLS=
WPMS_SMTP_USER=***
WPMS_SMTP_PASS=***
```

```php:config/application.php
Config::define('WPMS_ON', env('WPMS_ON'));
Config::define('WPMS_MAILER', env('WPMS_MAILER'));
Config::define('WPMS_SMTP_HOST', env('WPMS_SMTP_HOST'));
Config::define('WPMS_SMTP_PORT', env('WPMS_SMTP_PORT'));
Config::define('WPMS_SSL', env('WPMS_SSL'));
Config::define('WPMS_SMTP_AUTH', env('WPMS_SMTP_AUTH'));
Config::define('WPMS_SMTP_AUTOTLS', env('WPMS_SMTP_AUTOTLS'));
Config::define('WPMS_SMTP_USER', env('WPMS_SMTP_USER'));
Config::define('WPMS_SMTP_PASS', env('WPMS_SMTP_PASS'));
```

WP Mail SMTP by WPForms のバグで、define経由で設定をするとテストメールが送れないバグがあるので、その点は注意が必要です。



# Sessionの利用

WordPressの管理画面へのログインなどには、PHPのSessionは利用していません。
しかし、一部のプラグインや追加開発などで、複数台でSessionを利用したい場合もあると思います。

複数台でSessionを利用する場合にはDynamoDBがおすすめです。
Amazon DynamoDB オンデマンドキャパシティーモードを利用すれば、オートスケールのしきい値設定などは不要です。

AWS SDKを利用することで、簡単にハンドラを登録できます。

```
$ composer require aws/aws-sdk-php
```

```plaintext:.env
DYNAMODB_SESSION_TABLE_NAME=table_name
```

```php:config/application.php
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\SessionHandler;

$dynamoDBClient = new DynamoDbClient([
    'region' => 'ap-northeast-1',
    'version' => 'latest',
]);
$sessionHandler = SessionHandler::fromClient($dynamoDBClient, [
    'table_name' => env('DYNAMODB_SESSION_TABLE_NAME'),
]);
$sessionHandler->register();
```

# Twig の利用

ここまで興味を持っていただいたエンジニアの方であれば、WordPressでTwigを利用したいと考える方もいるはずです。
テーマをTwigで管理するとよりシンプルに管理することができます。

Twig を使わなくても、WordPressがグローバル変数を利用している `the_post` などをうまくラップしてくれます。
テーマで苦労したくないモダンPHPerはぜひインストールしましょう。

詳細はここでは書きませんが、 [Timber](https://github.com/timber/timber) を利用すると、他にも様々な便利な機能が利用できます。

TimberはWordPressプラグインとしてではなく、直接Composerでインストールしましょう。

```
$ composer require timber/timber
```

# WordPress 本体・プラグインのバージョンアップをどうするか

WordPressのバージョンアップなどを継続的に行うのであれば、CircleCIで自動的にCIを回すなど、継続的にバージョンアップする環境を整えましょう。
自動的にPRを作れば、あとはマージだけですね。

[circleci-composer-update-pr](https://github.com/enomotodev/circleci-composer-update-pr)

会社ではGitLab CIを利用しているので、 [gitlabci-composer-update-mr](https://github.com/enomotodev/gitlabci-composer-update-mr) を利用して定期的にMRを自動的に作成しています。

# あとがき

ここまで、急ぎ足でご紹介しました。
WordPressはレガシーだという声も多いですが、英語のドキュメントなどでは、非常に多くのノウハウや、エコシステムがあります。
ぜひ、モダンPHPの世界でWordPressを利用してみてください！

ここまで対応ができると、Fargateでの運用はこのWordPressをDockerコンテナ化するだけです。
ShifterのようにS3にHTMLを書き出して運用する場合や、固定台数で運用している例は多くありますが、WordPressの豊富なプラグインを利用して動的なサイトを運用したい場合には、苦労が多かったと思います。
FargateでWordPressを運用すると、オートスケール含めて本当に辛みが減ります。
Aurora Serverlessと組み合わせることで、APPもDBもオートスケール対応になります。

このテクニックを利用し、月間数百万PV以上のサイトをFargate化しています。
そちらについても、今後書きたいと思います。
Yahoo砲、Gunosy砲もオートスケールで耐えられました。


コメントで不明な点やご意見、より良いアイデアを教えていただければ幸いです。


Twitterもやってますので、ぜひ、そちらでもお願いします！
[Twitter @t_tsuru](https://twitter.com/t_tsuru)


受託でもやってますので、何かあればTwitterからメッセージを頂ければと思います。


# 追記

多くの閲覧、いいねありがとうございます。
レガシーと言われるWordPressですら、Composerで管理をして、状態をサーバ内に持たないことができることがこの記事からもわかると思います。
自分が作ったものではないソフトウェアの性質を理解し、運用を組み立てることは、職業エンジニアにとっては非常に重要と思います。
WordPressはウェブのシェア30％とも言われており、運用者にとってはどこかで開発されたマイナーなCMSよりも簡単で運用しやすく、ネット上にもノウハウが転がっています。
※[WordPressの市場シェア（2019年）：Kinsta](https://kinsta.com/jp/wordpress-market-share/)
運用者は記事を公開することが目的で、CMSはその道具に過ぎません。
だからこそ、エンジニアとしては、それを安定して支えられるためのフローを追求すべきかなと思います。

この記事を書いたきっかけは、レガシーと否定するだけではなくどのようにモダンフローに適用できるかを追求したことが始まりです。

また、Googleも、WordPress用のオフィシャルプラグインを公開予定です。
これからもWordPressのエコシステムは大きく発展していくと思います。

みなさまにとって、しかたなくつかうWordPressであっても少しでも辛みの少ない運用ができることを願っています。