mcpField
D
drupal-commerce-9-to-10

drupal-commerce-9-to-10

github.com/madsnorgaard開発4948 回インストール6 週間前に更新
リポジトリ

drupal-commerce-9-to-10 について

Drupal Commerce サイトを Drupal 9 から Drupal 10 にアップグレードする保守担当者向けの手順スキルです。事前調査、Composer の変更、データベース更新と Commerce 固有の障害復旧を扱います。

主な機能

  1. 01モジュールとマイグレーションの調査
  2. 02Drupal と Commerce の依存関係更新
  3. 03データベース更新フックの障害診断
  4. 04Stripe とエンティティメタデータの復旧

活用シーン

  1. 01Commerce サイトのメジャーアップグレード計画
  2. 02Commerce 更新中の失敗への対処
  3. 03マイグレーションモジュールの互換性確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/madsnorgaard/drupal-agent-resources --skill drupal-commerce-9-to-10

インストール後、Claude Code で/drupal-commerce-9-to-10を入力して読み込まれたか確認します。

詳細

Drupal Commerce 9→10 アップグレードエキスパート

Drupal Commerce サイトを Drupal 9 から Drupal 10 にアップグレードするエキスパートです。Commerce 固有の更新フックの失敗の診断と復旧も扱います。

このエキスパートが対応する状況

  • Drupal 9 の Commerce サイトを Drupal 10 にアップグレードする
  • Commerce モジュールに関連する drush updatedb の失敗を解決する
  • commerce_stripe、dblog、system.schema の更新失敗をデバッグする
  • Commerce、Stripe、決済モジュールを使用しているサイトで、Drupal のメジャーバージョンアップグレードを計画する

アップグレードのフェーズ

アップグレードは次の 4 つのフェーズで進めます:インベントリ → Composer アップグレード → データベース更新と復旧 → 切り替え。フェーズ 3 では障害が発生することを想定してください。以下のレシピは例外的なケースではなく、主要な復旧手順です。


フェーズ 1:インベントリ

Composer を操作する前に、現在の状態を調査します。

Drupal と PHP のバージョンを確認する
ddev drush status --fields=drupal-version,php-version,db-driver
インストール済みモジュールとバージョンを一覧表示する
ddev drush pm:list --status=enabled --format=table
ddev composer show drupal/* --format=table
非推奨または互換性のないモジュールを特定する
# Check for Drupal 10 compatibility
ddev exec vendor/bin/drupal-check -ad modules/contrib/
ddev exec vendor/bin/drupal-check -ad modules/custom/

drupal-check がインストールされていない場合:

ddev composer require --dev mglaman/drupal-check
現在の設定をエクスポートする
ddev drush config:export -y

設定のエクスポートをコミットします。これはロールバックの基準点になります。

移行モジュールとグループを監査する

Commerce サイトでは、初期データのインポート、継続的なフィード、または過去の D7→D9 移行で有効になった移行モジュールを使用していることがよくあります。アップグレード前に、これらをすべて確認する必要があります。

# List all enabled migration modules
ddev drush pm:list --status=enabled --type=module | grep -i migrat

# List all registered migration groups
ddev drush migrate:status --group=all 2>/dev/null || ddev drush migrate:status

migrate_tools がインストールされている場合は、すべての移行グループとその状態を一覧表示します:

ddev drush sqlq "SELECT id, label FROM config WHERE name LIKE 'migrate_plus.migration_group.%';" | cat

Commerce 固有の移行モジュールを確認します:

モジュール目的D10 での対応
commerce_migrateUbercart/Commerce 1.x → Commerce 2.xD10 対応リリースを確認します。移行が完了している場合は削除します
migrate_drupal_commerceD7 Commerce → D9/D10 Commerce互換性を確認します。D10 向けのパッチが必要な場合があります
commerce_feeds商品フィードのインポートD10 リリースを確認します。migrate_plus への置き換えを検討します
migrate_plus設定ベースの移行、グループD10 対応バージョン(^6)に更新します
migrate_tools移行用の Drush コマンドD10 対応バージョン(^6)に更新します
migrate_fileファイル移行の処理D10 対応バージョンに更新します
進行中または停止した移行を確認する

「Importing」状態のまま残っている移行は、updatedb を妨げます:

# Show migration status — look for "Importing" or "Stopping" states
ddev drush migrate:status 2>/dev/null

# Reset any stuck migrations before upgrading
ddev drush migrate:reset-status <migration_id>

停止した移行が多数ある場合:

-- Find all migrations in a non-idle state
SELECT m.name, m.value FROM key_value m
WHERE m.collection = 'migrate_status'
  AND m.value != 'i:0;';

-- Reset all to idle (value 0)
UPDATE key_value
SET value = 'i:0;'
WHERE collection = 'migrate_status';
移行モジュールを残すか削除するか決める

元の移行が完了している場合(すべてのデータが D9 にあり、検証済みの場合):

# Uninstall migration modules cleanly before upgrading
ddev drush pm:uninstall commerce_migrate migrate_drupal migrate_drupal_ui -y
ddev drush cr

D10 へのアップグレード前にアンインストールすると、D10 リリースがない移行モジュールによる更新フックの失敗を防げます。

移行がまだ有効な場合(継続的なフィード、定期的なインポートなど):

モジュールを残しますが、すべてのモジュールに D10 対応リリースがあることを確認します。drupal.org で各モジュールを確認してください:

  • D10 では migrate_plus ≥ 6.0
  • D10 では migrate_tools ≥ 6.0
  • commerce_migrate — D10 の対応状況を issue queue で確認します
移行グループの設定をエクスポートする

移行モジュールを残す場合は、アップグレード後に復元できるように設定をエクスポートします:

# Export migration group and migration configs
ddev drush config:export --destination=/tmp/migration-config -y
ls /tmp/migration-config/migrate_plus.migration_group.* 2>/dev/null
ls /tmp/migration-config/migrate_plus.migration.* 2>/dev/null

これらは別途バックアップしてください。updatedb によって移行設定が変更または削除される可能性があります。

スキーマバージョンを記録する
ddev drush php:eval '$schemas = \Drupal::keyValue("system.schema")->getAll(); ksort($schemas); foreach ($schemas as $name => $version) { echo $name . "\t" . $version . PHP_EOL; }' | cat

この出力を保存します。フェーズ 3 で孤立したスキーマエントリーを診断する際に必要になります。


フェーズ 2:Composer アップグレード

開始前にデータベースのスナップショットを作成する
ddev snapshot --name=pre-d10-upgrade
composer.json の制約を更新する

コアの制約を変更します:

ddev composer require drupal/core-recommended:^10 drupal/core-composer-scaffold:^10 drupal/core-project-message:^10 --no-update
Commerce および決済モジュールを更新する
ddev composer require drupal/commerce:^2 drupal/commerce_stripe:^1 --no-update

フェーズ 1 で確認した composer show の出力に基づいて、モジュールの制約を調整します。すべてのコントリビュートモジュールに D10 対応リリースが必要です。

フルアップデートを実行する
ddev composer update -W

-W(または --with-all-dependencies)フラグは必須です。これにより、Composer が推移的な依存関係を含む依存関係ツリー全体を解決できるようになります。

Composer が失敗する場合は、制約を1つずつ解決します。

  • D10版がないモジュールを削除します(ddev composer remove drupal/module_name)
  • drupal.org でD10対応版またはパッチを確認します
  • カスタムモジュールの場合は、.info.yml ファイルの core_version_requirement を ^9 || ^10 に更新します
コードベースの状態を確認する
ddev drush cr
ddev drush status

この時点ではサイトでエラーが発生する場合がありますが、想定内です。データベーススキーマはまだ更新されていません。


フェーズ3:データベースの更新と復旧

Commerce サイトでは、単純な Drupal アップグレードとは異なる問題が発生します。updatedb を実行し、失敗することを想定してください。

ddev drush updatedb -y

正常に完了した場合は、フェーズ4に進んでください。失敗した場合は、エラー出力から失敗した更新フックを特定し、該当する以下の手順を適用してください。


ブロッカー1:DefaultTableMapping の __PHP_Incomplete_Class

症状:

PHP Fatal error: Uncaught Error: Cannot use __PHP_Incomplete_Class as array
... in Drupal\Core\Entity\Sql\DefaultTableMapping

多くの場合、commerce_stripe_update_8102 または admin_toolbar_update_8003 の実行中に発生しますが、根本的な破損はどちらのフックにも依存しません。解消するまで、以降の更新でも問題が発生し続けます。

原因: entity.definitions.installed / commerce_payment_method.field_storage_definitions 配下の key_value 行に、アップグレード前のコードベースからシリアライズされたエンティティ定義のスナップショットが格納されています。D10の更新実行中に Drupal がこれをアンシリアライズしようとしますが、参照先のクラスは削除または名前変更されているため、__PHP_Incomplete_Class オブジェクトが生成されます。その後、DefaultTableMapping がそれを配列として参照しようとします。Commerce 層ではサイトが壊れているように見えますが、根本原因は古いエンティティメタデータのキャッシュです。

復旧:

# Backup the row before deleting it
ddev mysql -e "
CREATE TABLE IF NOT EXISTS key_value_backup LIKE key_value;
INSERT INTO key_value_backup
SELECT * FROM key_value
WHERE collection='entity.definitions.installed'
  AND name='commerce_payment_method.field_storage_definitions';
DELETE FROM key_value
WHERE collection='entity.definitions.installed'
  AND name='commerce_payment_method.field_storage_definitions';
"

# Re-run updates; Drupal rebuilds the entity definition from live class code
ddev drush updb -y

確認:

ddev drush updatedb-status
ddev drush php:eval "var_export(\Drupal::keyValue('entity.definitions.installed')->get('commerce_payment_method.field_storage_definitions') !== NULL);"
# Expected: true (a fresh definition has been rebuilt)

__PHP_Incomplete_Class エラーが続く場合は、破損した field_storage_definitions 行を持つエンティティタイプがほかにないか確認してください。同様の問題は commerce_order、commerce_product_variation、その他の Commerce エンティティにも発生する可能性があります。クエリの範囲を広げ、影響を受ける各行についてバックアップを取ってから削除してください。


ブロッカー2:commerce_stripe_update_8102 — 列がすでに存在する

症状:

[error] SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'stripe_customer_id'

更新フックは commerce_payment_method に stripe_customer_id 列を追加しようとしますが、以前の不完全な実行またはスキーマの不整合により、列はすでに作成されています。

原因: スキーマ更新が部分的に実行され(列は作成済み)、スキーマバージョンは完了として記録されていません。updatedb を再実行すると、列をもう一度追加しようとします。

復旧:

列がすでに存在することを確認します。

SHOW COLUMNS FROM commerce_payment_method LIKE 'stripe_customer_id';

存在する場合は、keyValue サービスを使ってスキーマバージョンを 8102 より先に進めます。これにより、値が整数として正しくシリアライズされ(i:8102;)、壊れやすい手書きの PHP 文字列形式を避けられます。

ddev drush php:eval "\Drupal::keyValue('system.schema')->set('commerce_stripe', 8102);"

drush を利用できない場合の生 SQL の代替方法:

UPDATE key_value
SET value = 'i:8102;'
WHERE collection = 'system.schema'
  AND name = 'commerce_stripe';

確認:

ddev drush updatedb-status
ddev drush php:eval "echo \Drupal::keyValue('system.schema')->get('commerce_stripe'), PHP_EOL;"
# commerce_stripe should no longer list 8102 as pending and should report 8102 or higher

ブロッカー 3:commerce_stripe_update_8104 — 支払い方法のメタデータ欠落によるフックレベルのエラー

症状:

[error] Undefined array key "method_id"
[error] Call to a member function getType() on null

サイトの commerce_stripe_update_8104 実行中に ddev drush updb -y を実行すると発生します。このフックは、欠落しているか、フックが想定する形式とは異なる支払い方法のメタデータを参照します。

原因: 保存されたメタデータがフックの想定する構造と一致しない支払い方法に遭遇した場合、commerce_stripe のアップデートフック内部で発生する PHP レベルのバグです。テーブルやカラムの不足が原因ではありません。フック自体が実行途中で致命的なエラーを発生させます。

復旧(最終手段の回避策。後続対応が必要です。下記参照):

commerce_stripe の現在の system.schema 行をバックアップしてから、スキーマバージョンを 8104 より先に進め、残りのアップデートフックと post_update の処理を実行できるようにします。これは失敗したフックを修復するのではなく、スキップする方法です。

ddev mysql -e "
CREATE TABLE IF NOT EXISTS key_value_backup LIKE key_value;
INSERT INTO key_value_backup
SELECT * FROM key_value
WHERE collection='system.schema'
  AND name='commerce_stripe';
"
ddev drush php:eval "\Drupal::keyValue('system.schema')->set('commerce_stripe', 8106);"
ddev drush updb -y

確認:

ddev drush updatedb-status
ddev drush php:eval "echo \Drupal::keyValue('system.schema')->get('commerce_stripe'), PHP_EOL;"

本番環境へのデプロイ前に必要な後続対応:

8104 をスキップすると、このフックが更新するはずだったデータ形式は実際には移行されていません。次回の本番デプロイ前に、データベースのスキップ前のバックアップを復元し、以下を実施してください。

  1. フックのソースを確認します。modules/contrib/commerce_stripe/commerce_stripe.install または drupal.org で commerce_stripe_update_8104 を調べ、どのデータを読み書きするのかを確認します。
  2. このサイトで対象となるデータ形式が使われているか判断します。フックが扱う Stripe の支払い方法や機能をサイトで使っていない場合、スキップしても問題ありません。その旨を書面に記録してください。
  3. データが使われている場合は、スキップしたままにせず、入力データを修正してフックを再実行します。key_value_backup のスキップ前の値に system.schema を戻し、整理済みの入力データに対して updatedb で 8104 を実行します。
  4. 決済を再確認します。テストモードで実際の Stripe トランザクションを実行し、commerce_payment_method の行が正しいこと、および保存済みの顧客の支払い方法で引き続き承認できることを確認します。

監督者なしで、このスキップを本番環境に適用しないでください。


ブロッカー 4:dblog_update_10100 — ALTER TABLE watchdog が MySQL のタイムアウトを超過

症状:

[error] SQLSTATE[HY000]: General error: 2006 MySQL server has gone away

dblog_update_10100 が ALTER TABLE watchdog ... BIGINT を実行しているときに、ddev drush updb -y の実行中に発生します。頻度は低いものの、wid の値が旧 INT の範囲を超えているサイトでは、代わりに変更処理が SQLSTATE[22003]: Numeric value out of range で失敗します。

原因: D10 では、watchdog テーブルの wid カラムが BIGINT に拡張されます。watchdog テーブルが大きいサイトでは、インプレースの ALTER TABLE が MySQL の接続アイドル時間の上限(wait_timeout、またはローカル DDEV の max_allowed_packet)を超えて実行され、処理の途中で MySQL が接続を切断します。数値オーバーフローの場合も根本的な状況は同じです(変換中に旧カラムで値を保持できません)。同じ復旧方法で解消できます。

復旧(最速の方法。本番環境で実施した方法):

アップデートを実行する前に dblog をアンインストールし、カラムの変更処理が実行されないようにしてから、後で再度有効化します。watchdog には運用ログのみが保存されるため、この方法は安全です。

ddev drush pm:uninstall dblog -y
ddev drush updb -y
ddev drush en dblog -y
ddev drush cr

代替方法(dblog をインストールしたままにして、先にテーブルを縮小する):

最近の watchdog エントリを保持する必要がある場合は、テーブルを整理して、ALTER TABLE が MySQL のタイムアウト時間内に終わるようにします。

SELECT COUNT(*) FROM watchdog;

-- Keep only the last 100,000 entries
CREATE TABLE watchdog_backup AS SELECT * FROM watchdog ORDER BY wid DESC LIMIT 100000;
TRUNCATE TABLE watchdog;
INSERT INTO watchdog SELECT * FROM watchdog_backup;
DROP TABLE watchdog_backup;

その後、再実行します。

ddev drush updb -y

確認:

ddev drush updatedb-status
# dblog should no longer list 10100 as pending
ddev drush watchdog:show --count=5

ブロッカー 5:孤立した system.schema エントリ

症状:

[error] Module 'some_removed_module' not found.

または、コードベースに存在しなくなったモジュールのアップデートフックを実行しようとして updatedb がクラッシュします。

原因: Drupal を通じて先にアンインストールせずに、Composer でモジュールを削除した(または modules/ から削除した)ことが原因です。key_value テーブルにはそのモジュールの system.schema エントリが残っているため、Drupal はモジュールがインストール済みだと判断し、アップデートしようとします。

復旧:

-- List all orphaned schema entries (modules in schema but not in codebase)
SELECT name FROM key_value
WHERE collection = 'system.schema'
  AND name NOT IN ('system')
ORDER BY name;
-- Compare this list against: ddev drush pm:list --status=enabled --field=machine_name

-- Remove specific orphaned entries
DELETE FROM key_value
WHERE collection = 'system.schema'
  AND name = 'some_removed_module';

Drupal がモジュールを読み込もうとしないように、core.extension 設定もクリーンアップします。

# Check if the module is still listed in core.extension
ddev drush config:get core.extension module.some_removed_module 2>/dev/null

# If listed, remove it by editing core.extension directly
ddev drush php:eval "\$config = \Drupal::configFactory()->getEditable('core.extension'); \$modules = \$config->get('module'); unset(\$modules['some_removed_module']); \$config->set('module', \$modules)->save();"
ddev drush cr

確認:

ddev drush updatedb-status
# No errors about missing modules
ddev drush cr

更新後:移行モジュールグループの整合性を確認する

すべての更新フックが正常に完了したら、移行モジュールとそのグループがアップグレード後も完全な状態で保持されていることを確認します。

# Check migration module status
ddev drush pm:list --status=enabled | grep -i migrat

# List all migration groups
ddev drush migrate:status 2>/dev/null

updatedb によって移行設定が削除された場合:

フェーズ 1 でバックアップした移行グループ設定を再インポートします。

ddev drush config:import --partial --source=/tmp/migration-config -y
ddev drush cr
ddev drush migrate:status

migrate_plus または migrate_tools の更新フックによってグループ構造が変更された場合:

# List current migration group configs
ddev drush config:list | grep migrate_plus.migration_group

# Verify each group still contains its expected migrations
ddev drush migrate:status --group=<group_name>

よくある問題 — 移行マップテーブルの名前が変更された、または削除された場合:

D10 のスキーマ変更は、migrate_map_* および migrate_message_* テーブルに影響する可能性があります。有効な移行用のマップテーブルがまだ存在することを確認します。

SHOW TABLES LIKE 'migrate_map_%';
SHOW TABLES LIKE 'migrate_message_%';

マップテーブルが見つからない場合、移行は一度も実行されていないかのように動作します。継続中のインポートでは、次回の実行時にコンテンツが重複することになります。バックアップからマップテーブルを再作成するか、ロールバックして再インポートしてください。


フェーズ 4:切り替え

すべての更新フックが正常に完了したら:

設定を再エクスポートする
ddev drush config:export -y

差分を確認します。すべての変更が D9→D10 のスキーマまたはデフォルト値の更新として説明できる必要があります。

完全なステータスチェックを実行する
ddev drush status
ddev drush core:requirements --severity=2
Commerce の機能を確認する
# Check that Commerce entity types are intact
ddev drush entity:updates
ddev drush sqlq "SELECT COUNT(*) FROM commerce_order;" | cat
ddev drush sqlq "SELECT COUNT(*) FROM commerce_product;" | cat
ddev drush sqlq "SELECT COUNT(*) FROM commerce_payment_method;" | cat
すべてのキャッシュをクリアしてテストする
ddev drush cr
ddev launch

Commerce の重要なフローを手動で確認します。

  • 商品表示ページ
  • カートに追加 → チェックアウトのフロー
  • 決済処理(Stripe のテストモードを使用)
  • 注文管理ページ(/admin/commerce/orders)
最終状態をコミットする
ddev drush config:export -y
git add -A
git commit -m "Drupal 10 upgrade complete"

クイックリファレンス:スキーマバージョンのオーバーライド

失敗した更新フックをスキップする場合は、keyValue サービスを優先してください。整数値が正しくシリアライズされ、手書きのシリアライズに伴うバグを回避できます。

ddev drush php:eval "\Drupal::keyValue('system.schema')->set('commerce_stripe', 8106);"
ddev drush php:eval "\Drupal::keyValue('system.schema')->set('dblog', 10101);"

生 SQL を使う方法もあります。スキーマバージョンは文字列ではなく、シリアライズされた整数(i:<version>;)として保存されます。s:<length>:"<version>"; を書き込む方法は、長さのプレフィックスを数え間違えると system.schema が気づかないうちに破損するため、壊れやすい方法です。

UPDATE key_value SET value = 'i:8102;' WHERE collection = 'system.schema' AND name = 'commerce_stripe';
UPDATE key_value SET value = 'i:8106;' WHERE collection = 'system.schema' AND name = 'commerce_stripe';
UPDATE key_value SET value = 'i:10101;' WHERE collection = 'system.schema' AND name = 'dblog';

警告: 更新フックをスキップするのは、そのフックが適用するスキーマ変更がすでに存在するか、サイトに不要であることを確認した場合に限ってください。更新を無条件にスキップすると、データの損失や実行時エラーが発生します。原因を特定していないフックについては、スキップを最終手段の回避策として扱い、本番環境への展開前に修復計画を立ててください(具体例についてはブロッカー 3 を参照)。

アップグレード前のチェックリスト

  • データベースのスナップショットを取得済み(ddev snapshot)
  • 設定をエクスポートしてコミット済み
  • system.schema のバージョンを記録済み
  • すべてのカスタムモジュールと Contrib モジュールに対して drupal-check を実行済み
  • Contrib モジュールについて、drupal.org で D10 対応リリースを確認済み
  • カスタムモジュールの .info.yml ファイルを core_version_requirement: ^9 || ^10 に更新済み
  • ステージング/開発環境で実施。本番環境で最初に実行しないこと

参考資料

コメント