Composer 卡住的根本原因是 SAT 求解器在依赖约束图中无限回溯,而非正则引擎回溯死锁;日志中“Resolving dependencies through SAT”明确指向逻辑冲突,需通过精简 require-dev、固化版本、添加 platform 约束等手段缓解。

Composer 本身没有正则引擎回溯死锁
Composer 不使用 PCRE 或其他正则引擎做依赖解析,所谓“正则回溯死锁”是误判。它卡住时 CPU 拉满、内存暴涨,根本原因是 SAT 求解器在约束图中无限回溯——不是 preg_match() 超时,也不是 .*? 写错了。所有日志里出现的 Resolving dependencies through SAT 都指向逻辑冲突,而非正则性能问题。
为什么有人误以为是正则导致的死锁
常见混淆点来自三类现象:
- 看到 Composer 日志里有路径匹配(如
packages/laravel/framework/10.0.0.json),就以为在用正则做路由或文件筛选 —— 实际这些是 HTTP GET 请求路径,由 Guzzle 或 cURL 发起,不经过 PHP 正则 - 某些自定义脚本(如 post-install-cmd 中调用
grep -E或sed)在并发执行时触发系统级正则回溯,但错误堆栈不会出现在composer install主流程里 - IDE 或 LSP 插件(如 PHPStan、Psalm)在后台扫描 vendor/ 时用了贪婪正则,和 Composer 并发抢占 CPU,造成“Composer 卡住”的错觉
真正要盯的回溯点:SAT 求解器的约束尝试痕迹
当 composer update -vvv --dry-run 输出末尾反复出现相同包名组合,就是求解器在闭环里打转:
-
Trying monolog/monolog 2.10.0 → failed出现 ≥5 次,说明某个上游包把它钉死在^1.0 -
Rejecting vendor/a because it requires vendor/b ^3.0, but locked to 2.9和下一行Rejecting vendor/b because it requires vendor/a ^2.0成对出现 → 强循环信号 - 输出中
vendor/a/package-a在不同层级重复展开,且引入路径互为上下游 →composer depends --tree vendor/a/package-a可实锤
绕过死锁不靠改正则,靠切约束面
目标不是“优化正则”,而是让求解器少走弯路:
- 删掉
require-dev下非必要工具(如phpunit/phpunit、friendsofphp/php-cs-fixer),它们常通过 autoload 加载 src/,意外引入循环 - 把模糊约束
"^1.0 || ^2.0"改成精确版本"2.3.0",大幅缩小搜索空间 - 临时加
"platform": {"php": "8.2"}抑制因真实 PHP 版本触发的跨版本兼容试探 - 绝对不要用
--ignore-platform-reqs—— 它会让求解器尝试更多非法组合,死锁更久
真正难处理的是 require-dev 里某个包声明了 "conflict": {"laravel/framework": ">=11"},而你又没运行 composer prohibits laravel/framework:11.0.0 去主动探雷。











