Composer 導入で生じる問題
このシリーズのその1では Drupal の依存関係管理に Composer を用いるための具体的なステップを説明しました。またそのポストの最後に触れたとおり、Composer を導入することで、いくつかの問題が発生します。今回はそれらに対する解決策・解決案を提示したいと思います。
また、Drupal はバージョン6 の時代から大規模な開発プロジェクトにも積極的に利用されるようになり、今では毎月数百万のページビューを獲得する大企業や政府のサイトにも頻繁に利用されています(利用例が https://www.drupal.com/ に掲載されています)。Drupal 8 にてコアに導入された Configuration Management(構成管理)や、柔軟性や効率性を高めたキャッシュ機能などはその大規模開発案件のニーズを色濃く反映しています。このポストでは、そのような中・大規模のユースケースを考慮して、継続的デリバリーのプロセスの一部としてのビルドとデプロイを見据えた Composer の利用、またそれに関連する問題とその解決策などについて触れたいと思います。
なお、このポストを書くにあたり、以下の手法を試される方が バージョン管理に Git を利用していることを前提としています。
Composer によるファイルの上書き
Drupal サイトを開発するにあたり**「コアに含まれるファイルは絶対変更すべきでない」*という鉄則があります。以下は Wordpress のコーデックスに含まれる画像ですが、Drupal コミュニティ内でも頻繁に引用されています。英文には「あなたがコアをハックする度、神が子猫を殺める」*とあります。個人的にはどーもくんが使われているのがヒットです:
ここでいう「ハック」とは、コードを書き換えることを意味します。ハックすることでアップデートを適用できなくなったり、様々な問題が生じます。
例外的にこのルールに該当しないのは、robots.txt
と .htaccess
の2つのファイルです。サイトの規模が大きくなるにつれ、この2つのファイルを変更する必要性は高くなる傾向にあります。しかし Composer は composer update
実行の際に、この2つのファイルを上書きして初期状態に戻してしまいます。
小規模のサイトで、Drupal コアや拡張モジュールなどを全て一つの Git レポジトリに格納して管理している場合、git checkout robots.txt .htaccess
を手動で実行すればよいのですが、忘れてしまうと最悪の場合サイトが機能しなくなる可能性があります。
Linux / OSX 上であれば、これを解決するのに必要なのは一つの bash スクリプトと、それを Composer に自動実行させるための数行の指示を composer.json
に追加するのみです(また、このポストの末尾付近にも"preserve-path":
を利用する方法についての説明があります)。以下のファイルは [drupal root]/scripts/custom/revert-files.sh
に格納していることを前提にしています。また、実際に利用する場合はファイルが実行可能になっていることを確認してください:
ファイルを復元するスクリプト
#!/bin/bash
git checkout -- web/.htaccess web/robots.txt
上記のスクリプトを実行することを Composer に知らせるために、以下をcomposer.json
に追加します:
"scripts": {
"post-update-cmd": "scripts/custom/revert-files.sh"
}
前回のポストのとおりの手順で Conposer を利用して Drupal を設置した場合、"scripts": {. . .}
が既に存在するので、そこに上記のキーと値のみを追加してください。
試しに robots.txt と .htaccess に変更を加えてコミットした後に、composer update
を実行してみてください。Composer による上書きが、レポジトリにコミットされたバージョンに復元されれば成功です。
Drupal を用いた中・大規模開発に Composer を取り入れる場合の留意点
ビルドとデプロイに際しての問題と提案
ここでは、今後 CI ツールと Composer を使ってビルドおよびデプロイを行うことを検討されている方に対し、問題点と解決策の提案をしたいと思います。
Jenkins や Travis などの CI ツールと drush make を使ってビルドやデプロイ作業をされている方にとっては、Composer を絡めた Drupal サイトのビルドおよびデプロイは特に目新しいテーマではないかもしれません。また、CI ツールを必要としない小規模サイトのみを扱っている方にはあまり価値の無い情報かもしれません。
レポジトリに何を格納するか
中・大規模なサイトの場合、上記のファイル上書き問題を自動的に解決することは不可欠です。また、そもそも Composer によって CI サーバー上でビルドを行えるようにしているにも関わらず、コアやモジュールなどの全てのファイルをレポジトリに格納するのは色々な問題の温床になります。
コアやモジュールファイルを Composer ファイルと共に一つのレポジトリに格納する問題点
まず第一に、Composer のドキュメンテーションには vendor
ディレクトリ内のファイルなどの依存関係を composer.json
と一緒のレポジトリにコミットするべきではないと明記されており、その説明も記されています。以下、挙げられている3点を和訳した上で、特に大事な点について説明します:
- 理由1: レポジトリサイズおよび diff のサイズが膨大になる
- 理由2: VCS 内に依存関係の履歴が重複されるため
- 理由3: 依存関係を git レポジトリから取得した場合、それらが submodule として取り扱われてしまう。それらは実際には submodule ではないため、後に問題が起きる
「理由1」は特に重要なので詳述すると、コアや拡張モジュールのアップデート、または新しいモジュールのインストールを行う際には少なくとも数十、多ければ数千行のコードが追加されます。チームで開発をしている場合、これに加えて多くの追加・変更がメンバーによって行われます。master などのブランチにマージする前にプルリクエスト等を使ってコードレビューをする場合、以下のポイントがレビューされるべきです:
- コアのバージョンに変更があったか
- 拡張モジュールのバージョンに変更があったか
- カスタムコードの追加・変更の詳細
情報過多によるレビューの非効率化
コアや拡張モジュールをレポジトリに格納してしまうと、幾つものファイルにまたがる数百・数千行の変更すべてがプルリクエストに混入してしまいます。またこの状態では、上記に述べた鉄則をメンバーの誰かが破ってコアや拡張モジュールに変更を加えた場合、それを発見するのはほぼ不可能でしょう。
適度な情報によるレビューの効率化
一方、コアや拡張モジュールをレポジトリに格納せず、composer.json
を利用して管理した場合、プルリクエストの際にはバージョンなどのメタ情報のみが差分として現れるため、どのような変更が加えられたかが一目瞭然です。これらの理由から、ベストプラクティスを実践するためにはコアやモジュールのファイルはレポジトリに追加せず、CI サーバー上でのビルドの際にダウンロードすることをお勧めします。
どうしてもコアや拡張モジュールを変更する必要がある場合
上記に関連して、コアや拡張モジュールに存在する既知のバグを修正したい場合など、どうしてもコアに変更を加えなければならないケースがあります。その際推奨される標準の手法は、cweagans/composer-patches を利用することです。readme.md にあるように、"cweagans/composer-patches": "~1.0",
を composer.json
の require:{. . .}
に追加することで利用可能になります。
以下の例はここで紹介されているもので、domain_conf モジュールにこのパッチを当てるものです:
{
"extra": {
"patches": {
"drupal/domain": {
"Domain_conf permissions": "https://www.drupal.org/files/issues/domain-conf-450688-%2310.patch"
}
}
}
}
これでパッチの内容自体はプルリクエストには含まれないため、diff には「何のモジュールにどのパッチが当てられたか」ということだけが表示されるようになります。もちろん、パッチの URL を使えば、実際のコードを確認することが可能です。
その他の留意点
Drupal コアにはそもそも composer.json
ファイルが同梱されています。Drupal ルートには Symphony などの依存関係が記述されており、[Drupal ルート]/core/composer.json
には、Drupal が依存するコアモジュールなどについての記載があります。前者のファイルに拡張モジュールの依存関係を記述していくことも可能なのですが、これでは Drupal コア自体はビルドの対象に入っていないためレポジトリにコアを格納する必要が出てしまい、先述のベストプラクティスを実践することができません。その理由から、このポストではその方法については割愛しています。
また、前回のポストで紹介した Drupal Packagist を利用してインストールしている Drupal は、ディレクトリ構造や composer.json
の内容が異なるため、厳密に言うとフォークということになります。ただし、Pressflow のような、コミュニティ有志が責任をもって管理しているフォークなので、アップデートの不備などの心配は無用でしょう。(Pressflow とは、Varnish を利用可能にしたり、その他パフォーマンスを向上するためのパッチが当てられた Drupal 6 コアのことです)
レポジトリの構成
ローカルの開発環境でビルドした場合、コアやモジュールのディレクトリおよびそれらに格納されているファイルを .gitignore
に追加して、レポジトリには追加しないよう設定します。しかし、カスタムコードやテーマはどうすべきでしょうか。選択肢は2つあります。以下、それぞれの選択肢のメリットとデメリットを挙げます:
1. Composer の勧告に従い、別のレポジトリにカスタムコードを格納する
-
メリット:
- 上記ドキュメンテーションに挙げられている問題を回避することができる
- 依存関係をコミットしないため、
.gitignore
内のルールが単純になる
-
デメリット:
- 複数のレポジトリの管理が必要になる
- 複数のレポジトリからコードをチェックアウトする必要があるため、
git subtree
やgit submodule
を利用した複雑なコード管理を要する。 - ローカルの開発環境を構築したり、ローカルでコードを管理する際、git に詳しくない開発者が多くのサポートを要することになる
2. カスタムコードと composer.json
を同一レポジトリに格納する
-
メリット:
- ローカルの開発環境でのビルドや、レポジトリの管理が容易
- 管理するレポジトリが一つで済む
- 全てのコードが一元管理されているため、コードレビューが容易になる
-
デメリット:
-
sites/all/modules/contrib
等、特定のディレクトリを git に無視('ignore')させつつ、カスタムコードが格納されているディレクトリは無視させない('unignore')、など.gitignore
内に複雑なルールを記述する必要がある
-
どちらの方法を採用するかは、プロジェクトの規模やチームメンバーの好みなどにより決定するべきだと思います。私は主にレポジトリ管理の容易さから後者を利用・推薦しています。.gitignore
のルールは複雑になるものの、最初に正しく設定してしまえば、後は気にする必要がないので、全体的な手間は減ると考えています。
方法だけ説明しても具体性がないので、以下に私が開発に関わった Drupal 7 サイトに利用した composer.json
や .gitignore
を多少加工したものをサンプルとして共有します: https://github.com/dokumori/composer-json-drupal7
このサンプルで特筆に値する点は以下です:
-
["preserve-paths":](https://github.com/derhasi/composer-preserve-paths)
を使って、Composer に上書きされてはいけないカスタムコード等の重要なディレクトリやファイルを指定している
"preserve-paths": [
"web/sites/all/modules/features",
"web/sites/all/modules/custom",
"web/sites/all/themes/custom_theme",
"web/sites/all/drush",
"web/sites/default/settings.php",
"web/sites/default/files",
"web/profiles/custom_profile"
]
-
.gitignore
でカスタムコードが格納されるディレクトリを unignore することで、カスタムコードを同一レポジトリで管理している
上記2つにより、ビルドの際にカスタムコードや重要なディレクトリ・ファイルがプロテクトされます。
。。。と、ここまで書いて初めて気づいたのですが、robots.txt と .htaccess もここに入れておけばよかったのですね。とりあえずscripts/custom/revert-files.sh
は「こんなこともできます」という参考ということで、そのまま残しておきます。
それから、このサンプルの元になったサイトはあまりミッションクリティカルではないため、composer.json
内でコアやモジュールのバージョン指定が厳密に行われていません。中・大規模なサイトの構築の場合、これが致命的な問題につながる可能性も充分にあるので、バージョン指定は必ず行うようにしてください。
Composer の利用に関するコミュニティの取り組み
ここまで Composer を利用した Drupal サイトの設置とメンテナンスについて説明してきましたが、実は現時点においてコミュニティ内では Composer を利用していくことでは同意があるものの、その細部は決まっておらず、今でも活発な議論が交わされています。
Drupal の世界では、コミュニティが独自に開発してきたソリューション("Invented Here")が数多く存在します。例えばモジュールやテーマの情報が記述されている .info ファイル、ビルドツールの drush make、テーマエンジンの phptemplateなどです。これらは新たに Drupal を採用しようとする開発者の障壁となってきました。Drupal 8 からは、これらをいかに PHP のスタンダードと置き換えていくかというのが大きなテーマ([1] [2])になっており、Composer にまつわる議論の多くもその延長線上にあります。
-
Composer の利用に関するメタスレッド: https://www.drupal.org/node/2551607
-
json ファイルの配信およびバージョニングについて: https://www.drupal.org/node/2576285
ここでの議論の要点: 他のプロジェクトではバージョンナンバーは銘々振ってあるが、Drupal ではモジュールのバージョンナンバーがコアとの互換性を表現しているため Drupal 特有であり、他の PHP プロジェクトと異なる。これをどのように解決するか -
Drupal プロジェクトが packagist.org を利用することについての是非: https://www.drupal.org/node/2547617
ここでの議論の要点:- Drupal のパッケージ数は多すぎる上、Drupal 以外の PHP プロジェクトでは利用できないため迷惑を被るのではないか
- packagist.org の信頼性は保証されていないので、ダウンした際にビルドに影響する危険がある
- 数多くの Drupal サイトのパッケージ取得におけるリファレンスとなるため、セキュリティ上の不安が残る
- drupal.org が
packages.json
をサーブすることに関しての是非
-
*モジュールやテーマの .info.yml ファイルを composer.json に置き換える: https://www.drupal.org/node/1398772
- yml.info ファイルを composer.json に置き換えるとサイトは機能しなくなる。後方互換性などを考慮しつつ、Drupal 8.1 / 9 など、どのタイミングで切り替えるか
- テーマの region などの情報まで
"extra":
下で扱うべきではない。yml.info
とcomposer.json
は1対1の関係にはならない
この他にも色々と興味深い議論が交わされています。
まとめ
Composer を利用することで、ビルドのプロセスをスムーズにすることが可能ですが、細かい問題も出てくるので、一つ一つをプロジェクトのニーズに合った方法で解決する必要があります。上記の説明やサンプルを参考にしてください。
また、このポストでは触れていませんが、Composer Manager による各拡張モジュールの依存関係の管理など、まだ Drupal における Composer というトピックに関してはカバーすべきものが残っています。
Drupal プロジェクトにおける Composer 利用についての議論が展開されているスレッドをフォローしている限りでは、様々な案やそれぞれの利点・欠点などについて意見がわかれているものの、Drupal プロジェクトが依存関係管理を Composer に統一していくことでは意見が一致しています。
このように未定の部分が多い現時点で Composer を採用することが時期尚早とは感じませんが、上述のとおりバージョニング、json ファイルの配信方法、yml ファイルと composer.json の使い分け等について、詳細が決定するまでにはおそらくまだ時間がかかる印象を受けます。
Jenkins などの CI ツールを利用されている方は、今の時点から Composer をベースにしたビルドのワークフローを構築しても差し支えないでしょう。ただし、Drupal プロジェクトにおける Composer の利用のベストプラクティスが明らかになるのはこれからです。将来ワークフローに変更を加える必要が出ることを前提に、今後の議論の行方を追っておくべきでしょう。