症状

Drupal core を 11.3 系から 11.4 へ上げようとしたときの話です。composer update が1本の contrib モジュールで止まりました。ファイルストレージを S3 互換のオブジェクトストレージへ向けている S3 File System(drupal/s3fs)です。

このモジュールは外せません。サイトのファイル保存の根幹が乗っています。しかも調べると、コード自体は 11.4 でも動きそうでした。作者がまだ 11.4 を検証していないので上限を切っている、という状態です。

第1の関門: Composer の依存解決

この状況の定番の回避策が mglaman/composer-drupal-lenient です。指定したパッケージの core 制約だけを緩め、Composer の依存解決を通します。

{
  "require": {
    "mglaman/composer-drupal-lenient": "^2"
  },
  "extra": {
    "drupal-lenient": {
      "allowed-list": ["drupal/s3fs"]
    }
  }
}

allowed-list は必ず対象を1本に絞るべきです。ここを広げると、本当に非互換なモジュールが混ざったまま core が上がってしまい、何が原因で壊れているのか分からなくなります。

これで composer update は通り、core 11.4 が入りました。

実際にコードが動かない部分があった

ここで終わりではありませんでした。キャッシュを再構築した時点で、サービスコンテナのコンパイルが失敗します。

  • ArgumentCountError(s3fs.mime_type.guesser サービス)
  • Missing proxy class のコンパイラ警告

core 11.4 が MIME タイプ推定の仕組みを破壊的に変更していたためです。11.4 の MimeTypeGuesser はこうなっています。

// core/lib/Drupal/Core/File/MimeType/MimeTypeGuesser.php
public function __construct(
  protected StreamWrapperManagerInterface $streamWrapperManager,
  protected \Traversable $guessers,
) {}

変更点は2つです。

1. 収集メソッドが消えた。 従来は addMimeTypeGuesser() / addGuesser() を呼んで推定器を後から登録する方式でした。11.4 ではコンストラクタ第2引数に tagged iterator で注入する方式に変わり、収集メソッドそのものが無くなりました。s3fs のコンパイラパスは収集メソッドを呼んでいたので、第2引数が渡らず ArgumentCountError になります。

2. ProxyClass が同梱されなくなった。 そのため lazy: true を指定できません。core 自身の file.mime_type.guesser も非 lazy になっています。

対応は2つのパッチです。1つ目は、11.4 以上のときだけ tagged iterator を注入して lazy を切る形にします。古い core でも動くよう、バージョン分岐を入れておくのが要点です。パッチを当てた状態で core を戻す可能性があるので、片方向にしない。

// s3fs のコンパイラパスに追記
if (version_compare(\Drupal::VERSION, '11.4', '>=')) {
  $consumer->setArgument(1, new TaggedIteratorArgument($tag));
  $consumer->setLazy(FALSE);
  return;
}
// 11.4 未満は従来の収集メソッド方式をそのまま通す

2つ目は services.yml から lazy: true を落とすだけです。

   s3fs.mime_type.guesser:
     class: Drupal\Core\File\MimeType\MimeTypeGuesser
     arguments: [ '@stream_wrapper_manager' ]
-    lazy: true

これでコンテナは通り、実際のファイル I/O・MIME 推定・画像スタイル派生の生成も動くようになりました。

第2の関門: Drupal はまだ「非互換」と言っていた

コードは動いています。それなのに、管理画面のステータスレポートは s3fs を**「互換性のないモジュール」**として表示し続けました。drush updatedb も要件エラーのプロンプトを出します。

ここが本題です。

composer-drupal-lenient が緩めるのは Composer の依存解決だけです。 Drupal 自身のモジュール互換判定は、Composer のメタデータを見ていません。モジュールの .info.yml にある core_version_requirement を実行時に読んでいます。

# s3fs.info.yml
core_version_requirement: ">=8.8 < 10.7 || >=11.0 < 11.2 || >=11.2.3 < 11.4"

上限が < 11.4。だから 11.4.x は範囲外で、非互換と判定されます。composer-drupal-lenient はこの行に何もしません。管轄が違うのです。

つまり互換チェックは二重にあります。

関門判定に使うもの緩める手段
Composer の依存解決パッケージの composer.jsoncomposer-drupal-lenient
Drupal のランタイム判定(status / updb)モジュールの .info.yml の core_version_requirement.info.yml へのパッチ

有名な回避策は片方しか面倒を見てくれません。.info.yml 側もパッチで上限を広げます。

 name: S3 File System
-core_version_requirement: ">=8.8 < 10.7 || >=11.0 < 11.2 || >=11.2.3 < 11.4"
+core_version_requirement: ">=8.8 < 10.7 || >=11.0 < 11.2 || >=11.2.3 < 12"

これで非互換表示も updatedb のプロンプトも消えます。

なお、この順序には意味があります。.info.yml を広げるのは、コードが実際に動くことを確認した後にすべきです。先に広げると、Drupal が出してくれていた唯一の警告を自分で黙らせた状態で、壊れたコードを本番へ運ぶことになります。

おまけの罠: パッチが当たらない

パッチを書いて composer install しても、当たらないことがあります。

cweagans/composer-patches の v1 系は、パッケージのバージョンが変わっていないと新しいパッチを再適用しません。すでに展開済みのモジュールに対して、パッチ定義を1本足しただけでは何も起きません。

対象を消して入れ直す必要があります。

rm -rf web/modules/contrib/s3fs
composer install

「パッチファイルは書いた、composer.json にも登録した、なのに .info.yml が変わっていない」でしばらく悩みました。差分が当たっているかどうかは、composer の出力を信じずに対象ファイルを直接見て確認するのが確実です。

まとめ

  • Drupal の互換チェックは二重にある。 Composer の依存解決(composer.json)と、Drupal ランタイムの判定(.info.yml の core_version_requirement)は別系統
  • composer-drupal-lenient は Composer 側だけを緩める。 composer update が通っても、ステータスレポートと updatedb は .info.yml を読んで「非互換」と言い続ける
  • allowed-list は対象を1本に絞る。 広げると本当に非互換なモジュールを取り込んでも気づけない
  • Composer が通ることと、コードが動くことは別。 core 11.4 は MimeTypeGuesser の収集メソッドを廃して tagged iterator のコンストラクタ注入へ移し、ProxyClass も廃止した(ArgumentCountError と Missing proxy class の原因)
  • contrib へのパッチはバージョン分岐を入れて version-neutral に書く。 core を戻す可能性があるので片方向にしない
  • .info.yml の上限を広げるのは、実機でコードが動くと確認した後。 先に黙らせると、唯一の警告を自分で消した状態で本番へ運ぶ
  • cweagans/composer-patches v1 はパッケージのバージョンが変わらないと新パッチを再適用しない。 rm -rf して composer install する。当たったかは対象ファイルを直接見て確認する

この記事は、事業・Webサイトの売買プラットフォーム RIKKA M&A を開発する中で得た知見です。RIKKA M&A は GitHub リポジトリを解析する技術デューデリジェンスを備え、Drupal 11 / PHP / LLM で構築・運用しています。

サイト・アプリ・AIプロダクトM&Aのプラットフォーム RIKKA M&A