Composerを利用してコントリビュートモジュールにパッチを適用する方法


Composerを利用して、Drupalのコントリビュートモジュールにパッチを適用する方法
Drupal のコントリビュートモジュール(Drupal本体に標準で添付されているコアモジュールではなく、第三者によって開発・公開されているモジュール)で問題が発生したとき、多くの場合はモジュールのIssueページで同様の問題がないかを検索します。
そして、同様の問題が見つかって「Issueパッチ」があれば、それを適用して解決します。
今回はこのパッチ適用をComposerで行う方法と、Issue パッチではどうしても修正できない場合に使う「カスタムパッチ」の作成・適用方法についても解説します。
方法を知っておくと万が一のときにも対応できるので、コントリビュートモジュールを利用している人はぜひご覧ください。
前提条件
Lando環境を前提としていますが、基本的にはDockerやMAMPなどのローカル開発環境でも以下条件が満たされていれば問題ありません。
- プロジェクトがGit管理されている
- Composer(2.X系の方が良い)がインストールされている
- Composerで管理されたDrupalプロジェクト(「drupal/recommended-project」など)
- Drushがインストールされている
また、本記事に記載するコマンドは特筆がない限り、Drupal プロジェクトの composer.jsonのあるディレクトリ(Lando・Dockerの場合は原則コンテナ内)で実行してください。
コンテナ内へのアクセス方法
Landoを使用している場合はlando sshコマンドを使用します。
lando ssh
Dockerを使用している場合はdocker execコマンドを使用します。
docker exec -it コンテナ名 bash
cweagans/composer-patchesのインストール
まずは「cweagans/composer-patches(以降はcomposer-patchesと呼びます)」をインストールします。
これはComposerのパッケージにパッチを当てるためのプラグインです。
例えば、composer installでインストールしたパッケージに対して、なにか修正を加えたとします。
そのあと改めてcomposer installを実行してパッケージを更新したり、他の人がインストールするときに、前に加えた修正はすべて上書きされてしまいます。
これは修正した内容がcomposer.jsonというファイルに反映されていないからです。
このプラグインを使ってパッチの適用をすることで、composer.jsonにパッチの情報も記録され、あとからパッケージの更新をしても、パッチの適用が外れる心配がなくなります。
インストール状況の確認
まず、「composer-patches」がすでにインストールされているか確認します。
下記コマンドを実行して、表示される結果で判断します。
composer show cweagans/composer-patches
以下のような結果が表示された場合は、まだインストールされていません。
[InvalidArgumentException]
Package "cweagans/composer-patches" not found, try using --all (-a) to show all available packages.
以下のような結果が表示された場合はインストール済みのため、インストールはスキップして、その次の設定を行います。
name : cweagans/composer-patches
descrip. : Provides a way to patch Composer packages.
keywords :
versions : * 1.7.2
type : composer-plugin
license : BSD 3-Clause "New" or "Revised" License (BSD-3-Clause) (OSI approved) https://spdx.org/licenses/BSD-3-Clause.html#licenseText
homepage :
source : [git] https://github.com/cweagans/composer-patches.git e9969cfc0796e6dea9b4e52f77f18e1065212871
dist : [zip] https://api.github.com/repos/cweagans/composer-patches/zipball/e9969cfc0796e6dea9b4e52f77f18e1065212871 e9969cfc0796e6dea9b4e52f77f18e1065212871
path : /app/vendor/cweagans/composer-patches
names : cweagans/composer-patches
(以下省略)
composer-patchesのインストール
先ほど確認して、「composer-patches」がないと確認できたので、インストールします。
composer require cweagans/composer-patches
もし以下のように聞かれた場合は「y」を入力してenterで先へ進みます(これは「composer-patches」を信頼して、書き込み権限を与えるかどうかを聞かれています)。
Do you trust "cweagans/composer-patches" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?]
ちなみに、ここで「y」を入力しないと「composer-patches」は使えません。
必要なcomposerの設定を行う
「composer-patches」がインストールできたら、今度は必要なComposerの設定を行います。
以下のコマンドを 1 行ずつ実行します。
composer config extra.enable-patching true
composer config extra.composer-exit-on-patch-failure true
これで「composer-patches」のインストールと設定は完了です。
composer-patchesでパッチの適用方法
「composer-patches」のインストールと設定ができたので、次は実際にパッチを適用する方法についてです。
まず、適用させたいパッチ情報をcomposer.jsonに追加します。
以下のコマンドを利用することで、記法エラーを回避しながらcomposer.jsonに正確に追加してくれます。
必要な記述内容が理解できている場合は composer.json をエディタソフトで直接編集しても問題ありませんが、コマンドの方がラクです。
composer config extra.patches.drupal/{モジュールマシンネーム} --json -m '{"パッチのラベル (Issue のタイトルと、パッチが登録されたコメントの URL など)":"パッチの URL"}'
「モジュールマシンネーム」にはモジュールのURLの「https://www.drupal.org/project/{この部分}」 を入力します。
「パッチのラベル」に関しては特にルールはないので、分かりやすいラベルを入力しましょう。
特にこだわりがない場合は、どのIssueのパッチなのかを明確にするために、Issueのタイトルと、パッチが登録されたコメントのURLをラベルに指定すると管理しやすいです。
コマンドを実行してcomposer.jsonにパッチ情報を追加できたら、composer installを実行します。
ちなみに、composer installはcomposer.jsonの内容をもとにしてパッケージをインストールしたり、更新するコマンドです。
そのときに、先ほど追加したパッチ情報も読み取って、「composer-patches」を利用してパッチを適用してくれます。
composer-patchesを利用して実際にIssueパッチを適用する
実際にパッチ適用の例をお見せした方が分かりやすいので、今回は「Token」モジュールに対して「Issue - Token for entity reference returns default language instead of translation」パッチを適用します。
composer.json に適用したいパッチを記述
まずはcomposer.jsonに、適用したいパッチ情報を追加するために、下記コマンドを実行します。
composer config extra.patches.drupal/token --json -m '{"Token for entity reference returns default language instead of translation: https://www.drupal.org/project/token/issues/2906445#comment-14541432":"https://www.drupal.org/files/issues/2022-05-30/2906445-19.patch"}'
実行するとcomposer.jsonに情報が追加されます。
パッチを適用する
コマンドを実行してcomposer.jsonにパッチ情報が追加されたら、composer installでパッチを適用します。
パッチ適用に成功した場合
以下のように表示された場合はパッチ適用成功です。適用されたパッチについての情報も表示されています。
Gathering patches for root package.
Removing package drupal/token so that it can be re-installed and re-patched.
- Removing drupal/token (1.10.0)
Deleting docroot/modules/contrib/token - deleted
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update <package name>`.
Package operations: 1 install, 0 updates, 0 removals
Gathering patches for root package.
Gathering patches for dependencies. This might take a minute.
- Installing drupal/token (1.10.0): Extracting archive
- Applying patches for drupal/token
https://www.drupal.org/files/issues/2022-05-30/2906445-19.patch (Token for entity reference returns default language instead of translation: https://www.drupal.org/project/token/issues/2906445#comment-14541432)
Package doctrine/reflection is abandoned, you should avoid using it. Use roave/better-reflection instead.
Package webmozart/path-util is abandoned, you should avoid using it. Use symfony/filesystem instead.
Generating autoload files
56 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
また、モジュールのディレクトリ内に「PATCHES.txt」というファイルが生成され、composer-patchesを利用して適用したパッチについての情報が記述されています。
This file was automatically generated by Composer Patches (https://github.com/cweagans/composer-patches)
Patches applied to this directory:
Token for entity reference returns default language instead of translation: https://www.drupal.org/project/token/issues/2906445#comment-14541432
Source: https://www.drupal.org/files/issues/2022-05-30/2906445-19.patch
あとは下記コマンドを順に実行していって、キャッシュクリア、DB 更新確認と更新、構成エクスポートで問題がなければ完了です。
# キャッシュクリア
drush cache:rebuild
# DB 更新確認 (更新対象があれば更新)
drush updatedb
# 構成エクスポート (変更がない時などエクスポートされない場合があります)
drush config:export
パッチ適用に失敗した場合
別のパッチ適用を試みたケースですが、以下のようにパッチ適用に失敗することもあります。
「.rej」という拡張子でパッチ適用失敗時のさまざまなファイルが作られることがありますので、それらを誤ってコミットしないように注意しましょう。
Gathering patches for root package.
Removing package drupal/token so that it can be re-installed and re-patched.
- Removing drupal/token (1.10.0)
Deleting docroot/modules/contrib/token - deleted
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update <package name>`.
Package operations: 1 install, 0 updates, 0 removals
- Downloading drupal/token (1.10.0)
Gathering patches for root package.
Gathering patches for dependencies. This might take a minute.
- Installing drupal/token (1.10.0): Extracting archive
- Applying patches for drupal/token
https://www.drupal.org/files/issues/2022-05-11/2933031-20.patch (Drupal best practices and coding standards: https://www.drupal.org/project/token/issues/2933031#comment-14511383)
Could not apply patch! Skipping. The error was: Cannot apply patch https://www.drupal.org/files/issues/2022-05-11/2933031-20.patch
[Exception]
Cannot apply patch Drupal best practices and coding standards: https://www.drupal.org/project/token/issues/2933031#comment-14511383 (https://www.drupal.org/files/issues/2022-05-11/2933031-20
.patch)!
install [--prefer-source] [--prefer-dist] [--prefer-install PREFER-INSTALL] [--dry-run] [--dev] [--no-suggest] [--no-dev] [--no-autoloader] [--no-progress] [--no-install] [-v|vv|vvv|--verbose] [-o|--optimize-autoloader] [-a|--classmap-authoritative] [--apcu-autoloader] [--apcu-autoloader-prefix APCU-AUTOLOADER-PREFIX] [--ignore-platform-req IGNORE-PLATFORM-REQ] [--ignore-platform-reqs] [--] [<packages>]...
もしパッチ適用に失敗した場合は、下記のようなことを試していきます。
- 類似Issueのパッチ適用を試す
- 新しいバージョンがある場合はアップデートする
- hookや他のAPIを駆使してカバーできるか調査・検証
- 代替モジュールがあれば乗り換える。
- devリリースへ切り替えて検証。
- 適用しようとしたパッチのIssueにバグ報告のコメントを投稿してメンテナーの対応を待つ(コメントは英語で具体的なものが好ましい。必ず回答がもらえるわけではないので内々でリミットを決めておく)
- 最後の手段としてカスタムパッチを作成・適用する
カスタムパッチの作成方法
「コントリビュートモジュールのバグをIssueパッチで修正できない」「代替モジュールがない」「カスタムモジュールでの実装が厳しい」「仕様変更が至極困難」などの理由で、どうしてもモジュールを乗り換えられない場合もあると思います。
そういった場合に限り、「カスタムパッチ」という自作のパッチを作成して「composer-patches」を利用して適用する方法もあります。
ただし、モジュールのアップデート時にパッチ適用が失敗したときは、その都度カスタムパッチのアップデート作業が必要になるのと、より入念なオペレーションチェックが必須になります。
また、安易にカスタムパッチでの対処をおこなうと思わぬバグを誘発する恐れがあります。
基本的にはおすすめできない手法ですので、最後の砦の対応として捉えてください。
モジュール内のファイルを直接修正する
まずは作業前にGitをクリーンな状態(未ステージや未コミットのものがない状態)にします。
そして、修正したいコントリビュートモジュールのファイルを直接修正します。
今回は例として、「docroot/modules/contrib/token/token.module」を簡単に修正します。
/**
* Implements hook_ENTITY_TYPE_presave() for menu_link_content.
*/
function token_menu_link_content_presave(MenuLinkContentInterface $menu_link_content) {
drupal_static_reset('token_menu_link_load_all_parents');
}
+
+/**
+ * Implements hook_cron().
+ */
+function token_cron() {
+ \Drupal::logger('token')->notice('This message has been fixed to be output by a custom patch.');
+}
このとき、コーディングは[Drupal Coding Standards](https://www.drupal.org/docs/develop/standards/coding-standards) に則って行うようにしましょう。
修正内容からカスタムパッチを作成する
ファイルを修正できたら、「git diff」コマンドを使ってカスタムパッチを作成します。
このとき、カスタムパッチ用ディレクトリをモジュール別に準備すると管理しやすくなります。
また、LandoやDockerなどの場合、仮想コンテナ内で実行してもうまくいかないことがあるので、ホストマシンで実行しましょう。
mkdir -p patches/drupal/token/ && (cd docroot/modules/contrib/token/ && git diff --patch --relative) >patches/drupal/token/token_cron.patch
作成したカスタムパッチの中身を確認するには下記コマンドです。
diff --git a/token.module b/token.module
index 2299984dd..a5b2a3205 100644
--- a/token.module
+++ b/token.module
@@ -769,3 +769,10 @@ function token_node_insert(NodeInterface $node) {
function token_menu_link_content_presave(MenuLinkContentInterface $menu_link_content) {
drupal_static_reset('token_menu_link_load_all_parents');
}
+
+/**
+ * Implements hook_cron().
+ */
+function token_cron() {
+ \Drupal::logger('token')->notice('This message has been fixed to be output by a custom patch.');
+}
パッチファイルにはコメントが書けるので、修正内容を忘れないうちに書いておくのがオススメです。
# Cron 実行時にログメッセージを出力する変更です。
diff --git a/token.module b/token.module
index 2299984dd..a5b2a3205 100644
--- a/token.module
+++ b/token.module
@@ -769,3 +769,10 @@ function token_node_insert(NodeInterface $node) {
function token_menu_link_content_presave(MenuLinkContentInterface $menu_link_content) {
drupal_static_reset('token_menu_link_load_all_parents');
}
+
+/**
+ * Implements hook_cron().
+ */
+function token_cron() {
+ \Drupal::logger('token')->notice('This message has been fixed to be output by a custom patch.');
+}
composer-patchesでカスタムパッチを適用する
あとは「composer-patches」でパッチの適用ですが、Issueパッチのときと流れは変わりません。
まずは、composer.jsonに適用したいパッチ情報を追加するために、下記コマンドを実行します。
ただし、今回はカスタムパッチという自分で作成したファイルを指定する必要があるので、URL部分はカスタムパッチのあるパスを指定します。
composer config extra.patches.drupal/token --json -m '{"Custom cron log message":"patches/drupal/token/token_cron.patch"}'
composer.jsonにパッチ情報が追加されたら、composer installでパッチを適用します。
適用に成功したら、キャッシュクリア、DB 更新確認と更新、構成エクスポートを行うのですが、今回の例ではキャッシュクリアだけで問題ありません。
この辺りは実際の修正内容に応じて実行します。
# キャッシュクリア
drush cache:rebuild
# DB 更新確認 (更新対象があれば更新)
drush updatedb
# 構成エクスポート (変更がない時などエクスポートされない場合があります)
drush config:export
次にエンジニアレベルでオペレーションチェックを実施します。
ログを見ながら「Warning」や「Notice」などのエラーが検出されないこと、要件を満たすことを確認して、カスタムパッチと修正したファイルをコミットします。
そして最後に入念なオペレーションチェックを実施して問題なければ完了です。
まとめ
最初は少しややこしく感じるかもしれませんが、コマンドだけで確実にパッチを適用できますし、composer.jsonを見ればどのパッチが適用されているのかもすぐ分かります。
また、カスタムパッチに関しては基本的にはオススメできない手法ですが、存在だけでも知っておけば、いざというときに対応できます。
以上、Composerを利用した、Drupalのコントリビュートモジュールにパッチを適用する方法の解説でした。

逸見 秀行/ バックエンドエンジニア
Drupal のバックエンド実装がメインですが、日々の開発が効率的になるツールを実装していたりもします。
好きな Drupal モジュールは DraggableViews, Allowed Formats です。
週末に家族でドライブしたり、楽器に触れたりすることが私の趣味です。