虚拟包本身不提供代码,仅声明实现了某接口规范(如psr/log),是解耦的结果而非工具;真正解耦依赖于接口编程、避免硬编码具体实现类。

虚拟包(Virtual Package)本身不提供代码,只用于声明“我实现了某个接口规范”,比如 psr/log、psr/container。它不是用来“防止耦合”的工具,而是解耦的**结果**——真正起作用的是你是否用接口编程、是否避免硬依赖具体实现。
为什么直接 require 一个日志实现(如 monolog/monolog)会导致耦合
一旦你在业务代码里写 use Monolog\Logger; 或 new Monolog\Logger(),你就和 Monolog 的类名、构造参数、方法签名绑死了。换日志库时,所有调用点都要改。
正确做法是:只依赖抽象,比如 Psr\Log\LoggerInterface,让框架或容器注入具体实现。这时 monolog/monolog 就只是个可插拔的“驱动”,不是业务强依赖。
常见错误现象:
- 项目
require了monolog/monolog,但代码里没用Psr\Log\LoggerInterface,全靠Monolog\*类硬编码 -
composer.json里写了"replace": {"psr/log": "*"},但本地包根本没实现LoggerInterface,只是占位——这会让其他包误以为“日志能力已就绪”,实际运行时报错 - 多个包都
provide同一个虚拟包(如psr/http-message),Composer 可能选中体积更大、API 更旧的那个实现,导致后续升级困难
provide 和 replace 字段怎么用才安全
provide 是声明“我提供了某虚拟包的能力”,常用于轻量适配器或桥接包;replace 是声明“我替代了某包”,会阻止 Composer 安装被替换的包——它更危险,容易引发意外缺失。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
使用建议:
- 自己写的包如果只是封装了
psr/log接口,用"provide": {"psr/log": "^1.0"}即可,别加replace - 不要在应用项目(
"type": "project")的composer.json里写provide或replace—— 这些字段只对库(library)有意义 - 检查冲突时,用
composer show --tree psr/log看谁在提供它;用composer why psr/log看谁在要求它 - 若发现两个包都
provide同一虚拟包,且你只想用其中一个,可在require中显式指定那个实现,例如"monolog/monolog": "^3.0",Composer 会优先满足该约束
如何验证解耦是否真正生效
光看 composer.json 里有没有 provide 没用,关键看运行时行为。
实操步骤:
- 删掉当前日志实现(如
composer remove monolog/monolog),再跑一遍单元测试——如果大量报Class not found或Interface not implemented,说明代码还在直接 new 具体类 - 搜索项目代码里所有
new \Monolog\、new \Psr\Log\(注意后者也不该 new,只能 type-hint 注入) - 检查 DI 容器配置(如 Laravel 的
AppServiceProvider),确认LoggerInterface::class绑定的是接口,而不是Monolog\Logger::class - 运行
composer dump-autoload -o后,检查vendor/composer/autoload_psr4.php里是否还映射着Monolog\命名空间——如果没删干净,自动加载器仍可能偷偷加载它
最难的不是配置 provide,而是让团队所有人习惯写 function handle(LoggerInterface $logger) 而不是 function handle(Logger $logger)。接口命名、类型提示、容器绑定这三步缺一不可,否则虚拟包只是个空壳。

















