虚拟包是Composer中用于表达接口契约的占位符(如psr/log),无实际代码,仅通过provide字段声明实现能力;它不触发安装或自动加载,仅参与依赖求解,确保符合版本约束的提供者被纳入依赖图。

虚拟包不是真实存在的包,而是 Composer 用来表达“能力”的占位符
虚拟包(如 psr/log、psr/container)本身不能被安装——它没有源码、没有 composer.json、不托管在 Packagist 上。它只是一个命名+版本约束的抽象契约,代表一组接口定义和行为规范。
Composer 用它解决 PHP 没有原生“接口依赖”机制的问题:你无法写 "require": {"psr/log": "^1.0"} 并期望自动装上某个日志实现,因为 psr/log 包只含接口,不提供具体功能。
- 真正起作用的是其他包通过
provide声明“我实现了这个契约”,比如monolog/monolog的"provide": {"psr/log": "^1.0 || ^2.0"} - Composer 在解析依赖时,会把所有声明提供
psr/log的包(monolog、symfony/debug、php-di/php-di等)都纳入候选范围 - 最终选谁,取决于整个依赖图的版本兼容性,而非
provide的先后顺序或权重
provide 字段只声明能力,不触发安装,也不影响自动加载
provide 是纯声明式字段,它不会让 Composer 去下载、解压或注册任何东西。它唯一作用是在 require 阶段参与依赖求解——就像告诉 SAT 求解器:“如果项目需要 psr/log,这些包可作为合法实现。”
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
- 如果你只在自己包里写
"provide": {"psr/log": "^1.0"},但没人require它,那它完全不生效 - 即使别人
require "psr/log",你的包也**不会自动被装上**,除非它同时被其他依赖显式引入,或用户手动composer require your/package -
provide不改变autoload行为:类自动加载仍由你自己的autoload配置(如psr-4)控制,跟是否provide无关 - 错误示例:
"provide": {"mycompany/log": "1.0.0"}—— 如果mycompany/log是个真实包名,这不是虚拟包,不该用provide,该用replace或直接require
多个包 provide 同一虚拟包时,Composer 怎么选?
没有“优先级”,也没有“默认实现”。Composer 只保证:只要存在至少一个满足版本约束的提供者,且它能被拉入依赖树(即不被 conflict 排除、不因版本冲突被剪枝),整个安装就成功。
- 如果 A 和 B 都
provide "psr/log": "^1.0",而项目同时require A和require B,那两者都会被装——但运行时只会用到其中一个的实现,取决于你代码中new或容器注入的是哪个 - 如果项目只
require "psr/log"(没指定具体实现),Composer 会报错:Your requirements could not be resolved,因为虚拟包不能单独安装,必须有具体提供者被拉进来 - 常见绕过方式:加一个“兜底实现”包,比如
composer require psr/log-implementation:dev-main(某些组织维护的空实现),或更实际地,在require-dev中显式引入一个如monolog/monolog - 注意版本号语义:
"psr/log": "^1.1"表示你完整支持 PSR-3 v1.1.0 规范,若只实现部分方法却写"^1.0",会导致下游调用失败但 Composer 无法检测
provide 和 replace 混用是高级技巧,但别为了炫技而用
当你既想对外声明“我能当 psr/container 用”,又想接管旧生态(比如替代已废弃的 container-interop/container-interop),才需要组合使用。
-
provide解决“谁能用”的问题;replace解决“谁不该再出现”的问题 - 典型配置:
"provide": {"psr/container": "^1.0 || ^2.0"}, "replace": {"container-interop/container-interop": "*"} - 危险点:
replace会彻底屏蔽被替换包——哪怕其他依赖硬性写了"require": {"container-interop/container-interop": "^1.2"},Composer 也会拒绝安装并报冲突 - 更安全的做法是限制
replace版本,例如"container-interop/container-interop": "^1.2",避免误杀未来可能的新版
最常被忽略的一点:虚拟包机制的价值不在“多实现可选”,而在于解耦。一旦你习惯写 require psr/log 而非 require monolog/monolog,你的组件就能被任何符合契约的日志库集成——这比搞清楚 provide 怎么写重要得多。

















