虚拟包是Composer中用于声明能力契约的占位符名称,如psr/log,本身不提供代码,由真实包通过provide字段声明实现;它解决依赖抽象问题,但不可直接安装,需配合真实接口包与具体实现使用。

什么是虚拟包(virtual package)
虚拟包不是真实存在的包,而是一个“占位符名称”,用来声明某个能力或接口契约,比如 psr/log、php-fig/http-message-impl。它本身不提供代码,但允许其他包通过 provide 字段声明自己实现了该契约。Composer 用它解决“依赖抽象而非具体实现”的问题。
如何用 provide 声明自己实现了虚拟包
如果你开发了一个日志适配器,想告诉别人“我兼容 PSR-3”,就在你的 composer.json 里加:
{
"name": "acme/my-logger",
"provide": {
"psr/log": "^3.0"
}
}
这样别人在 require psr/log 时,Composer 就会认为你这个包能“满足”该依赖——前提是它被显式安装或作为子依赖被拉入。
-
provide不会自动触发安装,只用于依赖求解时的匹配 - 不能在
require中写"psr/log": "*"然后指望 Composer 自动选一个实现;必须明确require某个真实包(如monolog/monolog),它内部provide了psr/log - 多个包
provide同一个虚拟包时,Composer 不会报错,但最终生效的是实际被安装的那个
为什么 require 虚拟包会失败
直接运行 composer require psr/log 会报错:Could not find package psr/log。因为 Packagist 上没有名为 psr/log 的真实包——它只是 PHP-FIG 定义的接口规范,由 psr/log 这个 Composer 包(含接口类)提供,但该包本身是可安装的。注意区分:
-
psr/log是一个真实存在的包(含LoggerInterface等接口定义),可以require -
psr/log-implementation才是典型的虚拟包名(无真实发布),靠其他包provide来满足 - 很多文档里说的“require psr/log”其实指的就是装那个接口包,不是“装虚拟名”
在项目中安全使用抽象依赖的实操要点
你想让项目只依赖接口,不绑定具体实现?按这个顺序做:
- 在
require中写真实接口包:"psr/log": "^3.0"(这是必需的,提供类型提示和 IDE 支持) - 在
require中再写一个具体实现:"monolog/monolog": "^3.0"(否则无法运行) - 如果想换实现,删掉
monolog/monolog,改用zendframework/zend-log或其他provide了psr/log的包 - CI 或部署时别加
--no-dev到接口包上——psr/log是运行时依赖,不是 dev-only
最容易被忽略的一点:虚拟包机制只在依赖求解阶段起作用,它不改变自动加载行为。接口类仍需通过真实包(如 psr/log)的 autoload 配置才能被加载;provide 本身不带任何文件。


















