扩展包作者必须显式声明 conflict 字段,否则与laravel 11等不兼容时会导致composer安装卡死在sat求解器;conflict是硬性拦截机制,仅用于强互斥场景,不可替代require约束或文档说明。

扩展包作者必须显式声明 conflict 字段
你写的扩展包一旦被多个项目引入,冲突就不再是你本地的事。如果它和 Laravel 11 的 illuminate/support 不兼容,但没在 composer.json 里写 "conflict": {"laravel/framework": ">=11.0"},那下游项目 composer update 时就会卡死在 SAT 求解器里,报错却找不到源头。
常见错误是把 conflict 当成文档提示,只写在 README 里。它实际是 Composer 解析阶段的硬闸门——只要该组合出现在任何依赖路径中(哪怕只是 require-dev 间接拉入),安装就会立即失败,不给运行时留后患。
-
conflict只用于强互斥场景:比如两个包提供同名类、或 API 完全断裂(如 Guzzle 7 → 8 的构造器签名变更) - 别用它“提醒用户注意版本”,那种需求该靠
require约束或文档 - 测试时务必在最小环境验证:新建空项目,
composer require your/package dev-main,再加一个已知冲突包(如laravel/framework:^11.0),看是否真被拦截
避免用 ^ 或 ~ 声明依赖范围
扩展包的 composer.json 里写 "monolog/monolog": "^2.0",看似宽松,实则放大嵌套不确定性。下游项目若同时依赖另一个锁死 monolog/monolog:1.26.1 的包,Composer 就找不到交集,直接报 Conclusion: don't install monolog/monolog 2.0.0。
真正可控的做法是显性声明兼容边界:
- 若只测过
monolog/monolog:2.8.0–2.10.0,就写"monolog/monolog": ">=2.8.0 - 若完全兼容 2.x 全系列且有 CI 覆盖,才用
"monolog/monolog": "^2.0",但得确保 CI 跑了 2.0.0 和 2.10.0 两个极值版本 - 私有包若调用
guzzlehttp/guzzle8.x 特有方法,就别在require里写"^7.0 || ^8.0",而应分两套发布:v1.x 适配 Guzzle 7,v2.x 适配 Guzzle 8
用 composer why-not 验证约束自洽
发布前不跑 composer why-not,等于把炸弹交给用户拆。比如你声明支持 php: "^8.1",但又在代码里用了 PHP 8.2 才有的 match 表达式新语法,下游项目在 PHP 8.1 下装包不会报错,运行时才崩。
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
正确验证流程:
- 在目标最低 PHP 版本环境(如 PHP 8.1)下,执行
composer why-not your/package:dev-main,确认无阻断链 - 再换高版本(如 PHP 8.3),执行
composer why-not your/package:dev-main --dry-run,看是否能解出路径 - 对关键依赖做反向验证:比如你 require
psr/log:^2.0,就跑composer why-not psr/log:3.0.0,确保没意外拦住未来版本
replace 字段必须真实可替代
有些扩展包用 replace 声明自己“替代”某个官方组件(如 "symfony/polyfill-mbstring": "*" 替代 ext-mbstring),但这不是文字游戏——Composer 会检查被替代项是否真被完全覆盖。
容易踩的坑:
- 若你的包
replace了monolog/monolog,但没实现Monolog\Handler\StreamHandler::write()的全部参数逻辑,下游项目一用就报ArgumentCountError -
replace后必须在autoload里完整映射所有被替代包的命名空间,否则class_exists('Monolog\Logger')会返回false - 最稳妥做法:除非你是 polyfill 类项目,否则别用
replace;用conflict+ 清晰文档说明兼容关系更安全
复杂点在于:扩展包的依赖策略不是写一次就完事,它要经受下游项目各种组合的锤炼。最容易被忽略的是 require-dev 里的包——它们虽不进生产依赖树,但会悄悄锁死 sebastian/exporter 这类间接依赖,导致下游项目升级 phpunit 时全链崩坏。










