关于 typo3-extension-upgrade
用于将 TYPO3 扩展代码升级到较新 LTS 版本的工作流技能,不用于升级项目或核心。它强调保留已有版本支持,并逐个版本安装和运行测试后再报告结果。
主要功能
- 01仅针对 TYPO3 扩展的升级流程
- 02保留现有版本约束
- 03Rector 和 Fractor 迁移指引
- 04按版本运行 PHPStan 和 PHPUnit
- 05核验实际安装的目标版本
适用场景
- 01将扩展升级到较新的 TYPO3 LTS
- 02维护对多个 TYPO3 版本线的兼容性
- 03诊断迁移和测试失败
- 04报告已测试与未测试的版本线
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ netresearch/ typo3-extension-upgrade-skill --skill typo3-extension-upgrade安装后在 Claude Code 中输入/typo3-extension-upgrade确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
checkpoints.yamlassets/.php-cs-fixer.phpfractor.phpphpstan.neonphpunit.xmlrector.phpreferences/api-changes.mdapi-traps.mdaudit-mode.mddual-compatibility.mdmulti-version-worktrees.mdpre-upgrade.mdreal-world-patterns.mdthird-party-dependency-upgrades.mdtoolchain-output.mdtroubleshooting.mdupgrade-v11-to-v12.mdupgrade-v12-to-v13.mdupgrade-v13-to-v14.mdverification.mdscripts/scan-deprecations.sh详细介绍
TYPO3 扩展升级技能
用于将 TYPO3 扩展升级到较新 LTS 版本的框架。 仅升级扩展代码,不升级项目或核心。
只有在每一条约束所指定的安装版本上,最后运行第 10 步命令并得到 exit=0,才算完成——请以实际运行结果为准,不能想当然。 不能只在一条版本线上通过,而另一条仍失败。不能因为跳过或删除失败的测试就算通过。不能使用 --no-verify 提交。达不到这些要求,就应报告尚未完成,并指出能说明原因的输出。
提交钩子失败,说明仓库要求通过某项检查,而不是遇到了障碍。 阅读钩子运行的命令,亲自运行该命令,修复它报告的问题,然后重新提交:代码风格通常由仓库自带的修复工具(使用其配置运行 php-cs-fixer fix)修正,PHPStan 报告的问题则应在代码中修复。实际情况是:3 个 Agent 在 TYPO3 两条版本线上都通过后,仍用 --no-verify 绕过了代码风格或 PHPStan 钩子;第 4 个则绕过了自己失败的单元测试。
升级工具包
| 工具 | 用途 | 文件 |
|---|---|---|
| Extension Scanner | 诊断已弃用的 API | TYPO3 后台 |
| Rector | 自动化 PHP 迁移 | .php |
| Fractor | 非 PHP 迁移 | FlexForms、TypoScript、YAML、Fluid |
| PHPStan | 静态分析 | .php |
核心工作流
-
完成规划阶段(参阅
references/pre-upgrade.md) -
创建功能分支(确认 Git 工作区干净)
-
更新
composer.json中的版本约束,以适配目标版本。v14 的 LTS 次版本是 3:写^14.3,绝不能写^14.4——若要同时支持两个版本,可写^13.4 || ^14.3。v11.5、v12.4 和 v13.4 会让人误以为14.4是下一个版本;但它并不对应任何已发布版本,因此composer update会因依赖解析失败而以非零状态退出,且不会安装任何内容。各版本组合对应的约束见:references/upgrade-v13-to-v14.md添加一个版本,不等于替换原有版本。 单独写
^14.3会取消对扩展此前支持的所有版本线的支持,对所有安装该扩展的用户来说都是破坏性变更——而这又是让新版本线解析通过的捷径,因此很容易意外发生。“让它兼容当前 LTS”指的是添加新版本线,而不是丢掉旧版本线。 读取现有约束并保留其中的每一条版本线,然后添加目标版本:^12.4 || ^13.4变为^12.4 || ^13.4 || ^14.3;只有原本仅支持 v13 的扩展才会变为^13.4 || ^14.3。 只有在明确要求时才移除某条版本线,并在维护者能看到的地方说明。一个代码库能否兼容所有这些版本,是另一个问题,答案见references/dual-compatibility.md。 -
审查第三方依赖是否有主版本变更(参阅
references/third-party-dependency-upgrades.md) -
运行
rector process --dry-run,然后审查并应用变更 -
运行
fractor process --dry-run,然后审查并应用变更 -
运行
php-cs-fixer fix -
针对每个受支持的依赖版本运行
phpstan analyse,并修复错误 -
运行
phpunit并修复测试。Tests/是升级的一部分,不是升级的后续工作。 v14 已移除的类如果在测试中被引用,会直接导致致命错误,而不是测试失败:如果它出现在导入语句、父类、属性或签名中,PHPUnit 加载测试套件时就会中止,因此完全不会运行任何测试。运行任何命令前,先在两个目录树中搜索已移除的类型:grep -rnE 'TypoScriptFrontendController|StandaloneView|TemplateView|HashService|LocalPreviewHelper|LocalCropScaleMaskHelper|FreezableBackendInterface' Classes/ Tests/每个匹配结果都必须修复,删除use行不算修复:没有导入时,Foo::class会通过当前命名空间解析,因此文件会引用一个从未存在过的类,错误会在之后显现于仍须支持旧版本的代码行中。应修改用法,而不是导入语句。对已移除类调用createMock也不能只通过替换名称来修复——参阅references/upgrade-v13-to-v14.md然后,在运行测试套件之前和完成编辑之后,都要从扩展根目录运行此命令。 这是对上述编辑的检查,整段粘贴即可运行——无需查找路径,也无需绑定变量。输出中的每一行都表示有文件使用了某个已移除类型的短名称,却没有对应的use行;在步骤 10 之前逐一修复。无输出且退出代码为 0 即表示通过——发现问题时会以 1 退出,因此后接&& phpunit不会在存在问题时运行测试套件。types='TypoScriptFrontendController|StandaloneView|TemplateView|HashService|LocalPreviewHelper|LocalCropScaleMaskHelper|FreezableBackendInterface' strip='foreach (token_get_all(file_get_contents($argv[1])) as $t) { if (!is_array($t)) { echo $t; } elseif (!in_array($t[0], [T_COMMENT, T_DOC_COMMENT, T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE], true)) { echo $t[1]; } }' bad=0 for f in $(grep -rlE --include='*.php' "\b($types)\b" Classes Tests 2>/dev/null); do code=$(php -r "$strip" "$f") for t in $(grep -oE "\b($types)\b" <<<"$code" | sort -u); do grep -qE "^use .*\\\\$t;" <<<"$code" || grep -qE "\\\\$t\b" <<<"$code" \ || { echo "$f: $t used without import"; bad=1; } done done test "$bad" = 0首先会剥离注释和字符串字面量:提及已移除类型的文档块不是 PHP 会解析的引用。 同一项检查是此技能的
checkpoints.yaml中的TU-58,对应automated-assessment中的运行器。此处直接给出代码块,是因为脚本必须容易找到,才能成为捷径。在相同案例和模型下测得,此步骤中,有三次试验将运行运行器的指令放入上下文,但没有一次实际运行;而这段代码块原样粘贴后,六次试验中有五次实际运行。 -
安装目标版本,并在该版本上运行测试套件。 已安装版本上的测试套件通过,并不能证明目标版本可用——那只是旧代码通过了旧测试。先分两步安装,再测试:
composer update "typo3/*" --with typo3/cms-core:^14.3 -W --no-install \ && rm -rf vendor && composer install \ && composer show typo3/cms-core | grep '^versions' \ && vendor/bin/phpunit -c Build/phpunit/UnitTests.xml; echo "exit=$?"命令采用链式执行,因此安装失败时会停止,不会在旧目录上运行 PHPUnit;
versions行会显示实际安装的版本,而exit=是最后运行步骤的状态码。 从 v13 升级到 v14 时,单次执行会在过程中失败:Composer 升级typo3/class-alias-loader后,会在新文件上运行旧插件,而锁文件写入后,插件因Class "…\CaseSensitiveToken" not found而崩溃。在旧vendor/上运行composer install也会以同样方式崩溃,所以要先删除vendor/。 软件包参数很重要:如果不加该参数,composer update会升级所有依赖,测试结果就会受到与 TYPO3 无关的升级影响。-W会允许 TYPO3 软件包自身的依赖随之更新。 如果无法安装目标版本,就表示升级未经测试。 请在报告中说明这一点并附上错误信息,不要声称测试套件通过:已安装版本上的通过结果只适用于该版本。 -
对约束保留的每个较旧系列,运行相同的命令——约束为
^13.4时使用--with typo3/cms-core:^13.4,其他系列依此类推。让目标版本通过的迁移可能会导致下一个较低系列的测试失败:实测中,有一个 Agent 让 v14 通过,却让 v13 的测试套件继续失败,并报告称三个系列都兼容。修复后,重新运行每个系列的测试。无法在此处安装的系列——Composer 无法解析或下载的系列——属于未经测试,而非测试失败:请附上 Composer 的错误信息进行报告,并且绝不能因此将该系列从约束中移除(步骤 3)。 -
报告之前,检查每个系列最后一次运行的
exit=行。 对于已成功安装的系列,任何非exit=0的结果都表示工作尚未完成:返回步骤 9。安装失败的系列属于步骤 11 所述的未经测试情形。带着失败的测试套件提交,或使用--no-verify提交,也不算完成。报告应逐个系列说明安装了什么版本以及测试套件是否通过;如果未通过,还要附上 PHPUnit 或 Composer 输出的失败信息。 -
验证成功标准(请参阅
references/verification.md)
只有约束中列出的每个系列都已安装且测试套件通过,才算完成——包括所有可安装的系列;其余系列应报告为未经测试。这不代表扩大了约束,也不代表测试套件在原本已通过的版本上仍为绿色。
经实测:Agent 报告“✅ 719 个测试通过”,但从未安装目标版本;或者看到测试套件三次无法加载;又或者看到 3 个错误和 13 个失败,并使用 --no-verify 提交。运行结果中的 exit= 行会告诉你属于哪种情况;写报告前先看这一行。
已删除的类在何处被引用,决定了问题何时出现。在 import、父类、属性或签名中引用时,PHPUnit 会在加载测试套件时解析它,因此完全不会运行任何测试,失败的表现也与测试失败截然不同。若只在方法体内引用,则会在执行对应测试时失败,而测试套件的其他部分仍会通过——这种失败更不显眼,也更容易在摘要中被忽略。
迁移导致测试失败时
这是难免的。Rector 运行前原本全绿的测试套件,运行后通常会出现几十个错误,逐一处理这些错误就是你的工作。
绝不要为了恢复全绿而回退迁移。 经实测:某个 Agent 运行 Rector,看到 Tests: 719, Errors: 34,便丢弃了 Classes/ 下所有迁移后的文件,得到 OK (719 tests, 1176 assertions),随后提交了 composer.json、ext_emconf.php 和两个构建文件——没有提交任何代码。该提交声称支持一个代码实际上并不支持的版本,这是三种可能结果中最糟糕的一种:升级失败是显而易见的,没有尝试升级则是坦诚的,而这种情况两者都不是。
回退后恢复全绿,与你一开始的状态并无二致。唯一值得认可的全绿,是第 10 步中目标版本已安装且迁移已完成时的全绿。
跳过或删除失败的测试来恢复全绿,本质上也是回退,只不过一次处理一个测试。 有一种跳过是合理的:当代码按版本分支时,只存在于旧版本中的分支测试——if (!class_exists(X::class)) { self::markTestSkipped(...); }——可以在新版本中跳过,前提是新版本的分支也有自己的测试。除此之外,任何跳过、提前返回的版本检查,或删除测试方法,都会让失败悄无声息。使用 --no-verify 提交也一样:它会让本应报告问题的检查静默无声。
也不要停下来询问是否继续。 经实测:某个 Agent 安装目标版本,统计新版本删除的某个类被使用的次数,运行 git reset --hard,然后询问是否应该进行重构。要求让扩展兼容新版本,就等于授权进行这项工作。替换新版本删除的内容,就是升级;无论有多少处使用,都应直接处理——这是你的工作,不是需要汇报的发现。只有请求未明确的事项才需要询问,例如是否放弃对某个受支持版本线的支持(第 3 步)。
不要自动应用的情况
涉及双版本兼容、缺少测试、不明确的变更或复杂 API(DBAL、Extbase)时,不要盲目应用 Rector/Fractor。应手动应用规则,并在每次变更之间进行测试。
第三方依赖升级
当 composer.json 将依赖的版本范围扩大到新的主版本时:枚举 API 的使用情况,对照新的 API 核查,验证模拟对象,针对签名差异使用适配器模式,并按每个主版本运行 PHPStan。参见 references/third-party-dependency-upgrades.md。
快捷命令
rector process --dry-run && rector process # PHP migrations
fractor process --dry-run && fractor process # Non-PHP migrations
php-cs-fixer fix && phpstan analyse && phpunit # Quality checks
资源模板
assets/ 中的配置模板:rector.php、fractor.php、phpstan.neon、phpunit.xml、.php-cs-fixer.php
参考资料
| 参考资料 | 适用场景…… |
|---|---|
references/pre-upgrade.md | 制定计划的检查清单、版本审计、风险评估 |
references/api-changes.md | 检查各 TYPO3 版本中已弃用或已移除的 API |
references/api-traps.md | 跨版本易错点:TCA 限制、启动顺序、绕过 DI、v12+ 中未返回的 redirect();基于 TYPO3/typo3 的某个 ref 核实 API 事实 |
references/upgrade-v11-to-v12.md | 从 TYPO3 v11 升级到 v12 |
references/upgrade-v12-to-v13.md | 从 TYPO3 v12 升级到 v13 |
references/upgrade-v13-to-v14.md | 从 TYPO3 v13 升级到 v14 |
references/dual-compatibility.md | 双版本兼容(v12 + v13) |
references/real-world-patterns.md | 真实迁移示例 |
references/toolchain-output.md | Rector/Fractor 试运行输出 |
references/troubleshooting.md | Rector 破坏代码、PHPStan 错误、测试失败 |
references/third-party-dependency-upgrades.md | TYPO3 以外的依赖项(主版本升级、适配器模式) |
references/verification.md | 成功标准和真实场景测试 |
references/multi-version-worktrees.md | 各 LTS 版本的工作树布局、回移工作流、跨版本 CI 矩阵 |
references/audit-mode.md | 评估/估算:仅针对无法自动化的发现创建工单 |
scripts/scan-deprecations.sh <path> | 确定性 grep 扫描,查找已弃用/已移除的 API 和易错点 |