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. 02调整 Drupal 与 Commerce 依赖
  3. 03诊断数据库更新钩子故障
  4. 04恢复 Stripe 与实体元数据问题

适用场景

  1. 01规划 Commerce 站点的主版本升级
  2. 02处理 Commerce 更新过程中的失败
  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 大版本升级

升级阶段

升级分为四个阶段:盘点 → 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.x检查是否有兼容 D10 的版本;如果迁移已完成,则移除
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 的版本
检查进行中或卡住的迁移

处于“正在导入”状态的迁移会阻塞 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 上逐一检查:

  • migrate_plus ≥ 6.0,适用于 D10
  • migrate_tools ≥ 6.0,适用于 D10
  • commerce_migrate — 查看问题队列中的 D10 支持情况
导出迁移组配置

如果要保留迁移模块,请导出其配置,以便在升级后恢复:

# 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 失败,请一次解决一个约束:

  • 移除没有 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. 如果正在使用这些数据,则应修复输入数据并重新运行钩子,而不是保留跳过操作。将 system.schema 重置为 key_value_backup 中跳过前的值,然后让 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

在执行 ddev drush updb -y 时,dblog_update_10100 运行 ALTER TABLE watchdog ... BIGINT,于是遇到此问题。较少见的情况是,在 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 会崩溃,因为它尝试运行代码库中已不存在的模块的更新钩子。

**原因:**通过 Composer 移除模块(或从 modules/ 中删除模块)时,没有先通过 Drupal 卸载该模块。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';

同时清理 core.extension 配置,避免 Drupal 尝试加载该模块:

# 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
  • 已在 drupal.org 验证 contrib 模块是否有兼容 D10 的版本
  • 已将自定义模块 .info.yml 文件更新为 core_version_requirement: ^9 || ^10
  • 使用预发布/开发环境 — 绝不要先在生产环境运行

资料来源

评论

支持 Markdown