为什么PHP项目需要依赖注入_使用DI容器解耦业务逻辑
依赖注入通过构造函数参数注入解耦业务逻辑,避免手动new导致的硬编码耦合、测试困难与环境切换问题。接口必须显式绑定,自动装配仅解决具体类依赖。解耦旨在无需修改业务类即可替换实现,如切换日志驱动或数据库引擎。
先说几个核心判断:PHP项目不是“需要”依赖注入,而是只要业务逻辑开始跨类协作,手动 new 就会立刻失控。
你写一个 UserService 调用 Mailer 发邮件,再调用 Logger 记日志,再调用 Cache 缓存结果——这四个类如果全靠构造函数里 new 出来,那换数据库驱动、切日志后端、改缓存引擎时,你得翻遍所有服务类去改实例化代码。

当你在类里写 new,就等于锁死了实现
常见错误现象:UserRepository 构造函数直接 new MySQLConnection(...);测试时想 mock 数据库?不可能——new 是硬编码,不是接口契约。
- 接口无法替换:哪怕你写了
DatabaseInterface,只要类内部new了具体实现,接口就形同虚设 - 单元测试失效:无法注入测试替身(mock/stub),只能走真实 DB/网络,CI 流水线慢到崩溃
- 环境切换困难:开发用 SQLite、测试用 MySQL、生产用 PostgreSQL?靠改代码不是运维,是人肉部署脚本
__construct() 参数注入才是解耦起点
这不是“更优雅的写法”,而是把依赖关系从运行时(new)移到编译时(类型提示),让容器能看懂你要什么。
- 必须用类型提示:参数写
DatabaseInterface $db,而不是$db或object $db,否则自动装配失效 - 不要在构造函数里做实际工作:只赋值,不连接、不查询、不初始化外部资源——那些该交给工厂或生命周期回调
- 避免可选依赖:像
LoggerInterface $logger = null会破坏自动装配链,真要可选,用 setter 或容器条件绑定
为什么不能只靠工厂类?
工厂看似解耦,实则把 new 从类里挪到工厂里,问题照旧:工厂本身变成高耦合中心,且无法支持多实例策略(比如单例 vs 每次新建)。
- 工厂难测试:你得 mock 工厂返回值,而工厂又可能依赖其他工厂,形成工厂套娃
- 生命周期失控:工厂无法统一管理单例、作用域(request/scoped)、销毁逻辑(如 PDO 连接关闭)
- 配置分散:每个工厂一个文件,改一个 DB 配置要翻 5 个工厂,而容器配置集中在一个地方
自动装配不是银弹,接口必须显式绑定
PHP-DI、Lara vel 容器默认启用自动装配,但对 LoggerInterface 这类抽象,它不知道你想要 Monolog 还是 Syslog。不绑定,运行时报 No entry or class found。
- 必须提前注册:在容器启动时执行
$container->set(LoggerInterface::class, MonologLogger::class) - 别信“零配置”宣传:自动装配只解决“已知具体类”的依赖链,接口/抽象类永远需要人工指路
- 警惕循环依赖:A 依赖 B,B 又依赖 A —— 容器报错信息往往模糊,建议用
php-di/php-di的debug模式定位
最常被忽略的一点:解耦不是目的,是手段。真正关键的是——你能否在不改任何业务类的前提下,把 FileLogger 换成 SentryLogger,把 MySQLDriver 切到 PostgreSQLDriver,甚至把整个支付模块替换成第三方 SDK。做不到这点,DI 就只是换了个姿势写 new。


































