关于 drupal-commerce-9-to-10
面向 Drupal Commerce 站点维护者的 Drupal 9 升级到 Drupal 10 指南,涵盖升级准备、Composer 依赖调整及数据库更新故障恢复,重点说明 Commerce 与支付模块常见问题。
主要功能
- 01升级前盘点模块与迁移
- 02调整 Drupal 与 Commerce 依赖
- 03诊断数据库更新钩子故障
- 04恢复 Stripe 与实体元数据问题
适用场景
- 01规划 Commerce 站点的主版本升级
- 02处理 Commerce 更新过程中的失败
- 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_migrate | Ubercart/Commerce 1.x → Commerce 2.x | 检查是否有兼容 D10 的版本;如果迁移已完成,则移除 |
migrate_drupal_commerce | D7 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,适用于 D10migrate_tools≥ 6.0,适用于 D10commerce_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 意味着该钩子原本要更新的数据结构实际上并未迁移。下次部署到生产环境之前,请在恢复的跳过前数据库备份上执行以下操作:
- 阅读钩子源码。在
modules/contrib/commerce_stripe/commerce_stripe.install或 drupal.org 上查找commerce_stripe_update_8104。了解它读取和写入哪些数据。 - 判断本网站是否在使用目标数据结构。如果网站未使用此钩子涉及的 Stripe 支付方式或功能,则跳过是安全的。请以书面形式记录这一点。
- 如果正在使用这些数据,则应修复输入数据并重新运行钩子,而不是保留跳过操作。将
system.schema重置为key_value_backup中跳过前的值,然后让updatedb使用清理后的输入数据执行8104。 - 重新验证支付功能。在测试模式下运行一笔真实 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 - 使用预发布/开发环境 — 绝不要先在生产环境运行