虚拟包是Composer的“能力声明协议”,用于解耦接口与实现;通过composer.json中provide字段声明,如"psr/log-implementation": "1.0.0",不下载文件,仅在依赖解析时生效,支持灵活替换日志等实现。

psr/log-implementation 这类包名根本不存在于 Packagist,也不会下载任何文件——它就是 Composer 的虚拟包(Virtual Package)。
虚拟包不是真实代码库,而是一种“能力声明协议”,用来解耦接口与实现,让依赖关系更灵活、更可替换。
虚拟包怎么声明?用 provide 字段
你在自己的包里想告诉 Composer:“我已内置日志功能,能替代其他日志实现”,就在 composer.json 里加:
{
"name": "myorg/my-logger",
"provide": {
"psr/log-implementation": "1.0.0"
}
}
这行不安装任何东西,只在依赖解析时起作用:当另一个包写 "require": {"psr/log-implementation": "^1.0"},Composer 就会认为你的包已满足该需求。
-
provide是单向声明,不引入依赖,也不触发安装 - 版本号可写具体值(如
"1.0.0")或通配(如"*"),但建议写死语义化版本,避免误匹配 - 多个虚拟包可并列声明,例如同时提供
psr/http-message-implementation和psr/container-implementation
为什么不用真实包名做依赖?
假设一个框架硬编码依赖 monolog/monolog,那用户就只能用 Monolog;但若它依赖 psr/log-implementation,你就能自由换成 symfony/debug、slim/php-view 甚至自己写的轻量日志器——只要它们都声明了 provide。
- 真实包名 = 绑定实现;虚拟包名 = 绑定契约
- 微服务间共享基础组件时,靠虚拟包可避免版本冲突(比如 A 服务用 Monolog 2.x,B 服务用 3.x,但都声明提供
psr/log-implementation,上游框架无需感知) - 一旦某虚拟包被多个包同时
provide,Composer 会报错Root package requires psr/log-implementation, but none of the packages provide it—— 实际是“提供了多个,无法选”
常见虚拟包名有哪些?
它们全由 PHP-FIG 定义,不是 Composer 发明的。最常用几个:
-
psr/log-implementation:必须实现Psr\Log\LoggerInterface -
psr/http-message-implementation:必须有RequestInterface、ResponseInterface等 -
psr/container-implementation:必须实现ContainerInterface -
psr/simple-cache-implementation:对应SimpleCacheInterface
注意:psr/* 开头的是规范包(如 psr/log),只含接口定义,本身不提供实现;而带 -implementation 后缀的才是虚拟包,用于运行时选择实现。
require 解析阶段生效,不会出现在 vendor/ 目录里,也查不到源码——调试时如果发现某个类明明没装却能用,第一反应不该是“是不是漏写了 autoload”,而应检查有没有包通过 provide 默默兜底了。










