provide字段声明本包可替代指定虚拟包(如psr/cache-implementation),用于满足其他包的require依赖;须置于composer.json顶级provide对象中,键为标准虚拟包名,值为版本号(如"*"),根项目声明无效。

虚拟包不是真实存在的包,而是 Composer 用来表达“能力契约”的占位符
Composer 里没有 virtual-package 这种安装对象,所谓“虚拟包”,是通过 provide 字段在 composer.json 中声明的逻辑存在。它不对应任何代码仓库或 ZIP 包,只告诉依赖解析器:“我(当前包)能提供某项能力,等效于安装了某个包”。比如 "symfony/polyfill-mbstring": "^1.0" 在很多包里被 provide 出来,实际并不下载,只是让依赖树认为“mbstring 功能已就绪”。
-
provide是单向承诺:A 提供 B,不代表 A 实现了 B 的全部接口,只表示“满足 B 的最低兼容性要求” - 它不触发下载、不生成 autoload 映射、不写入
vendor/目录,纯属 SAT 求解器阶段的逻辑变量 - 常见于 polyfill、驱动抽象层(如
psr/log)、或框架适配器(如laravel/framework声明提供illuminate/contracts)
provide 如何参与依赖解析?看 SAT 求解器怎么把它当“真包”用
Composer 的依赖解析器把每个 provide 条目转成一个“虚拟命题”,和其他真实包版本一样纳入 CNF 公式。例如,当某包 require psr/log:^2.0,而另一个包在 provide 里写了 "psr/log": "^2.0",求解器就会判定该约束已被满足,无需再拉取真实 psr/log 包。
- 冲突检测照常进行:如果两个包分别
provide了互斥版本(如^1.0和^2.0),仍会报 conflict - 版本范围必须严格匹配:写
"psr/log": "2.0.0"无法满足^2.0,得写成"psr/log": "^2.0" - 不能覆盖
replace或conflict规则:即使 Aprovides B,若 Cconflicts B,A 和 C 依然不可共存
为什么 composer show 看不到虚拟包?它们根本不在 vendor 里
运行 composer show psr/log 返回 “No package found”,不是因为没装对,而是因为它压根没被当作可安装目标处理。Composer 只在 require 列表中看到真实包名时才去下载、解压、autoload;provide 只在解析阶段起作用,install 阶段直接跳过。
- 验证是否生效:用
composer why-not psr/log:2.0.0,若输出为空或显示“provided by xxx”,说明已被满足 - 调试技巧:加
-vvv运行composer update,搜索provides关键字,能看到求解器如何将提供关系纳入决策 - 注意陷阱:某些包把
provide写错成requires,或漏写版本号(如"psr/log": "*"),会导致解析失败而非静默忽略
真实项目中哪些地方最容易误用 provide?
最常出问题的是自己写 SDK 或封装底层驱动时,想用 provide 替代真实依赖,结果运行时报 Class not found——因为 provide 不生成 autoload,也不复制文件。
- 错误做法:在
my-redis-driver里provide"predis/predis": "^1.1",但没实现任何 Predis 接口,也没映射命名空间 - 正确做法:只
provide抽象能力(如"psr/cache": "^3.0"),并确保自身代码真正实现了Psr\Cache\CacheInterface,且 autoload 配置覆盖对应命名空间 - 更安全的替代:优先用
conflict+ 文档说明代替provide,尤其当“提供能力”只是临时 workaround 时
虚拟包的本质是契约而非实现,它省掉的是下载和安装动作,不是编码和测试责任。一旦写进 composer.json,就得按所声明的能力边界交付代码。


















