虚拟包是无实际文件的逻辑标识符,由实包通过provide字段声明实现,用于表达接口契约(如psr/log),使依赖解耦;它不直接安装,仅参与依赖解析与兼容性判定。

什么是虚拟包(virtual package)
虚拟包不是真实存在的可安装包,而是一个由其他包“提供(provide)”的逻辑标识符,比如 psr/log、php-fig/http-message-impl。它不对应 Packagist 上的某个仓库,只用于表达“兼容性契约”或“能力声明”。Composer 用它来解耦接口与实现,让项目能声明“我需要一个日志实现”,而不绑定到 monolog/monolog 或 psr/log 的具体实现者。
如何用 provide 字段控制依赖准入
你无法直接 composer require psr/log(它没有源码),但可以在自己的包中通过 provide 声明自己“实现了某契约”,从而让其他依赖该契约的包接受你作为合法实现。常见于框架适配器、轻量替代库、或内部封装层。
- 在你自己的
composer.json中添加:"provide": { "psr/log": "^3.0", "myorg/logger-adapter": "self.version" } - 当另一个包写
"require": { "psr/log": "^3.0" }时,Composer 会把你的包识别为满足条件的提供者,允许它被选中参与依赖解析 - 注意:仅靠
provide不会自动安装你的包;必须有其他依赖(显式 require 或传递依赖)把它拉进来,provide只影响“是否允许共存”和“是否满足约束”
用 replace 替换不兼容或废弃的包
replace 更激进:它告诉 Composer “我完全取代某个包”,包括其所有版本。常用于 fork 维护、安全加固版、或移除已弃用的旧包。
- 例如,你想用自研的
myorg/http-client替代guzzlehttp/guzzle的全部功能:"replace": { "guzzlehttp/guzzle": "*" } - 一旦你的包被安装,Composer 就不会再允许任何
guzzlehttp/guzzle的版本存在(哪怕其他依赖显式 require 它),否则报错conflict - 风险点:若未真正实现全部接口或行为,运行时可能崩溃;务必配合严格测试和文档说明
为什么 conflict 比 provide 更适合准入拦截
如果你的目标是“禁止某包进入项目”,conflict 是最直接有效的机制——它不协商、不替代,只拒绝。
- 在项目根目录的
composer.json中写:"conflict": { "laravel/framework": "=4.0.0 - 执行
composer install或composer update时,只要解析出的依赖图里出现被conflict的组合,立刻中断并提示冲突详情 - 比手动检查
composer why或事后审计更早介入,适合 CI 流水线卡点、安全策略强制、或遗留系统隔离
虚拟包本身不下载代码,它的力量在于声明权与排除权。真正起效的前提是:你得让这个声明被 Composer 的依赖解析器看到——要么它在你主动 require 的包里,要么它出现在根项目的 composer.json 中。漏掉 conflict 或误写 provide 版本范围,都可能导致策略静默失效。











