PHP Composer报错Your requirements could not be resolved to an installable set of versions的排查方法(composer why-not定位冲突、平台约束与版本约束调整)

改完composer.json跑composer update,终端刷出一长串红字,末尾一句是Your requirements could not be resolved to an installable set of versions。这句本身没有信息量,线索全在它上面那十几行「- xxx requires yyy」里。从倒数第二行往上读,才是排查的起点。

完整的报错块大致长这样:

Problem 1
  - laravel/framework v10.48.22 requires monolog/monolog ^2.9 -> satisfiable by monolog/monolog[2.9.0].
  - Only one of these can be installed: monolog/monolog[2.9.0, 3.5.0].
  - some/old-sdk 2.1.0 requires monolog/monolog ^1.0 -> satisfiable by monolog/monolog[1.27.1].
  - Root composer.json requires some/old-sdk ^2.1 -> satisfiable by some/old-sdk[2.1.0].

这里some/old-sdk就是堵点,它把monolog钉在1.x,而框架要2.9以上。Composer不会替人做降级或者升级的决定,版本区间没有交集就直接放弃,没有任何商量余地。

先分清是逻辑冲突还是平台约束

两种报错长得像,处理方式完全不同。逻辑冲突的每一行都是包名加版本区间,平台约束里出现的是php、ext-、lib-、composer-plugin-api、composer-runtime-api这几个前缀:

- some/old-sdk 2.1.0 requires php ^7.4 -> your php version (8.2.15) does not satisfy that requirement.
- Root composer.json requires ext-gd * -> it is not present.

平台约束别去动版本区间,改不动。它说的是运行环境不达标,要么换环境,要么换包。

why-not与why,一个问谁拦着,一个问谁引进来

composer why-not monolog/monolog 2.9.0
composer why some/old-sdk
composer show --tree some/old-sdk

why-not的列里每一行都是「哪个包要求的是什么范围」,排在最后的那个通常是真正的堵点。有些版本里这个命令叫composer prohibits,两个名字等价,prohibits是老写法。

有一点必须注意:why-not后面要跟精确版本号,实测写成^2.9这种区间会直接返回空,之前在这上面白折腾过一次。连该试哪个版本都不知道时,先跑composer show -a monolog/monolog把可用版本列出来。

show --tree把依赖树逐层展开,比why更能看清整条链子。见过一个项目里A依赖B、B又依赖A的旧版本,绕成了一个圈,靠why-not看不出来,只有树能一眼看到。

平台约束的四种处理

1、先确认真实环境。composer check-platform-reqs列出当前PHP版本、扩展与composer.json要求的对照表。这里有个容易漏的点:CLI和PHP-FPM读的不是同一个php.ini,之前遇到过php -m里有gd、网页里没有的情况,查的时候得看网页那边的phpinfo。

2、只在本机临时绕过。composer update --ignore-platform-req=ext-gd只忽略这一项。加个+号表示只忽略上界,--ignore-platform-req=php+允许装在更高的PHP上,但低于下界仍然失败。整体忽略是--ignore-platform-reqs,线上别用,装上去也跑不了。

3、把目标环境写进配置。config.platform里声明php和扩展的版本,让依赖解析按目标机器算而不是按本机算。目标机是PHP 8.2、本机是PHP 8.4时,这个配置能省掉大量误报。

4、包自己要求了已经不存在的扩展,比如ext-mcrypt这种PHP 7.1就移除掉的,只能换包或者降版本,没有别的路。

install与update不是一回事

composer install严格按composer.lock装,不重新解依赖;update才重新算。改完composer.json直接跑install,多半会先撞上一条The lock file is not up to date with the latest changes in composer.json,那是lock里的content-hash和json对不上,属于提示不是冲突。composer update --lock只刷新哈希、不动任何版本,适合「json改了但一个包都不想动」的场合。

删掉composer.lock重来是最常见的野路子,也是最该先停下来的那一步。lock是之前跑通过的那一套版本组合,删掉等于放弃已知可用的结果。现场遇到过删完lock重装,连带把十几个包升到不兼容的大版本,回滚都费劲。真要重解,先备份lock,再跑一次composer update --dry-run看会动多少包。

只更新一个包时,-w和-W的区别

composer update vendor/pkg默认只动这一个包,它的依赖不动。-w(--with-dependencies)连带更新它的依赖,但跳过composer.json里直接声明的包;-W(--with-all-dependencies)连直接声明的包一起动。排障时先加-w,还不够再加-W,一步到位上-W最容易把lock的diff搞到几百行。

composer update vendor/pkg --dry-run
composer update vendor/pkg -w
composer update vendor/pkg -W --minimal-changes

--minimal-changes(-m)只做必要变动,配合-w/-W用,能把diff压到最小。临时降级用--with:

composer update --with vendor/pkg:2.0.1

这个临时约束必须是现有区间的子集,composer.json不会被改动,只在本次生效。

minimum-stability与repositories导致的「包找不到」

另一类看着像冲突的报错是:包在Packagist上明明有,Composer却说找不到。查两处。一是minimum-stability默认是stable,dev-、beta、RC版本全被过滤掉,composer show -a vendor/pkg能看到全部版本并标出哪些被稳定性挡了。二是项目里的repositories字段,一旦自己写了,全局镜像会被整个跳过,包括阿里云和腾讯云的镜像,哪怕只加了一条私有Git地址。

composer config -g repos.packagist composer https://mirrors.aliyun.com/composer/
composer config -g --unset repos.packagist
composer show -a vendor/pkg

镜像延迟也要算进去,刚打的tag在Packagist上同步需要时间,国内镜像还要再晚一些,老项目赶时间发版时容易被这个拖住。

跑得慢与内存不够,容易被误判成冲突

依赖解析是纯内存计算,大项目在默认memory_limit下会直接报Allowed memory size exhausted,那个报错长得很像依赖问题,其实跟冲突无关。前面加COMPOSER_MEMORY_LIMIT=-1就能放开限制。装了Xdebug时解析会慢好几倍,实测一个中等规模的项目从20秒变成将近3分钟,php -m里看一眼,开发机上临时关掉就行。

conflict、replace与provide这几个字段

包在composer.json里写conflict时,冲突不会体现在版本区间上,why-not查得到但读起来很绕。遇到过一个包在conflict里锁死了整个symfony系列的某个版本,报错块里完全看不到这个约束,只有composer why symfony/console才把它露出来。replace和provide则会让Composer认为某个包已经被替代,表现是「装不上但其实已经装了」。

版本约束该怎么写

^1.2.3允许到2.0之前,~1.2.3只允许到1.3之前,两个符号经常被混用。写*或者dev-master,半年后回头装就是薛定谔的版本,之前见过一个composer.json里写了"*",两年后新机器装出来完全跑不起来。直接依赖写清楚区间,传递依赖交给lock固定住,比事后排查省事得多。

版本与工具链的现状

Composer 2.10.0、2.10.3,是现在的主线,2.10.0发布于2026年5月28日;Composer 2.9.0、2.9.8,与Composer 2.8.0、2.8.12,两条线已经先后停止支持。2.2这条LTS线支持到2026年12月31日,老机器上还在用的可以开始排迁移。Composer 1在2026年5月30日彻底停止支持。PHP这边,常见的PHP 7.4、8.0、8.1、8.2、8.3、8.4、8.5,里头7.4早就不再维护,老项目composer.json里写着"php": "^7.4"时,换台机器装包基本都会撞上平台约束。

顺带一句,audit自2.4起内置,update之后跑一次composer audit --locked,能看出刚升上来的版本有没有已知漏洞。文中版本数据截至2026年10月。