conflict 在 composer install/update 时,当解析到版本匹配冲突声明才报错;不触发于已锁版本或手动 require 兼容包;需用标准版本语法精确声明,如 "symfony/console": "=9.0"。

Composer 的 conflict 配置不是“防止安装”的开关,而是声明“明确不兼容”的契约——它只在你试图安装被冲突规则覆盖的包版本时触发错误,且仅对直接依赖或间接升级路径生效。
conflict 什么时候真正起作用?
它只在 composer install 或 composer update 过程中,当依赖解析器发现某个包(无论直接还是传递引入)的版本号落在 conflict 声明的范围内时,才中断并报错。它不阻止手动 require 一个已存在的兼容包,也不影响已锁死的 composer.lock 中的旧版本(除非你运行 update)。
- ✅ 触发场景:你执行
composer require some/package:^3.0,而你的composer.json中有"conflict": { "some/package": "^2.5 || ^3.0" } - ❌ 不触发场景:你已安装
some/package:2.4,且 lock 文件里固定了它;此时加 conflict 并不会回滚或报错 - ⚠️ 注意:conflict 不会自动排除替代方案——如果冲突包是某依赖的子依赖,Composer 可能尝试降级父依赖来绕过,而不是直接报错
如何写精准的 conflict 版本约束?
必须用 Composer 的版本约束语法(类似 require),不能写正则或模糊描述。常见误区是把 conflict 当成黑名单通配符,但它只匹配包名 + 版本号组合。
- 用
"symfony/console": " 表示所有低于 3.4 的版本都冲突(含 3.3.9、2.x、1.x) - 用
"guzzlehttp/guzzle": "^7.0.0"表示所有 7.x 兼容版本都冲突(即 7.0.0 到 7.999.999) - 多个包用逗号分隔:
"conflict": { "ext-xdebug": "*", "phpunit/phpunit": ">=9.0 - 不要写
"conflict": { "laravel/framework": "dev-master" }—— 分支名不参与版本解析,这条无效
conflict 和 replace / provide / require 的关系
conflict 是独立检查项,但它的实际效果受其他字段影响。例如:
- 如果 A 包
replace了 B 包,而你在conflict中写了 B,则 Composer 会检查被 replace 的 B 的“逻辑版本”,而非 A 的版本 - 如果某包
provide了psr/log-implementation,这和conflict无关——conflict 只认真实包名,不认虚拟包名 -
require和conflict同时存在时,Composer 优先满足 require,再校验 conflict;若无法同时满足(如 require "^2.0", conflict "^2.3"),则报错
调试 conflict 是否生效?
最可靠的方式是模拟一次会触发它的操作,并观察错误信息是否包含 conflict 关键字:
composer require monolog/monolog:^2.8
假设你的项目中有:
"conflict": {
"monolog/monolog": "^2.8"
}
你会看到类似输出:
Your requirements could not be resolved to an installable set of packages.
...
- Root composer.json requires monolog/monolog ^2.8 -> satisfiable by monolog/monolog[2.8.0].
- monolog/monolog 2.8.0 conflicts with your project's conflict rule ("monolog/monolog": "^2.8").
注意:错误里明确写出 “conflicts with your project's conflict rule”,这是确认生效的关键信号。
真正容易被忽略的是:conflict 不会主动扫描已安装包,也不会在 CI 环境中静默跳过——它只在依赖解析阶段介入。如果你的 lock 文件早已包含冲突版本,那它就一直“合法”到你下次 update 或删 lock 重装为止。










