Composer虚拟包是通过provide字段声明的逻辑别名,用于满足对PSR接口等抽象依赖,本身无源码;只能通过真实包声明或Satis的package类型定义实现。

什么是 Composer 虚拟包(virtual package)
虚拟包不是真实存在的代码仓库,而是通过 provide 字段在 composer.json 中声明的“逻辑别名”,用来满足其他包对某接口、抽象层或标准的依赖,比如 psr/log、php-http/async-client-implementation。它本身不提供源码,只起占位和兼容作用。
为什么私有仓库里不能直接 push 一个虚拟包
因为 Composer 不允许发布没有实际源码的包——composer publish 不存在,所有包最终都必须对应一个可拉取的 dist 或 source。虚拟包只能靠两种方式“落地”:
- 在真实私有包的
composer.json中写"provide": { "vendor/interface": "1.0" },然后把这个包正常发布到你的私有仓库(VCS 或 Satis) - 用
package类型手动定义虚拟包,仅适用于 Satis 仓库:在satis.json的repositories数组里加一项,type 为package,内容含完整 name/version/provide 字段
注意:package 类型无法用于 VCS 或 path 源,只被 Satis 支持;且一旦用了,就必须确保 Satis 构建时启用 "require-all": true 或显式 "require": { "vendor/interface": "*" },否则不会出现在生成的 packages.json 中。
在 Satis 中定义虚拟包的实操要点
下面是一个合法的 package 声明示例,放在 satis.json 的 repositories 数组内:
{
"type": "package",
"package": {
"name": "myorg/logging-interface",
"version": "1.0.0",
"provide": {
"psr/log": "^1.0 || ^2.0 || ^3.0"
}
}
}
关键约束:
-
version必须是规范语义化版本(不能是dev-main或stable) -
name不能与任何真实包重名,否则 Satis 构建会静默覆盖或报错 - 若该虚拟包需被其他私有包
require,则那些包的composer.json中也得把myorg/logging-interface写进require,而不是直接写psr/log——Composer 不会自动解析 provide 链路去反向匹配 - Satis 构建后,检查输出的
packages.json是否真包含该包条目,否则说明配置未生效
常见失败现象与绕过陷阱
项目执行 composer install 仍报 Could not find package psr/log,不是因为没定义虚拟包,而是:
- 客户端项目
composer.json的repositories没指向你的 Satis 仓库(type: "composer"),或者顺序靠后被 packagist.org 兜底了 - 没运行
composer config --global repo.packagist false,导致 Composer 仍尝试访问公网源校验 - 虚拟包的
name和version在packages.json中拼写错误,比如多了一个空格或大小写不一致 - 你改了
satis.json但忘了重新运行php bin/satis build,静态文件根本没更新
最易忽略的一点:虚拟包本身不解决 autoload,它只解决依赖声明。如果你靠它来满足 class_exists('Psr\Log\LoggerInterface'),那还得确保实际加载的是某个真实实现——虚拟包不带任何 PHP 文件。











