PHP 中的依赖注入简介
现代 PHP 开发离不开面向对象设计原则。其中一个能够构建灵活、可测试和可维护应用程序的关键模式是 Dependency Injection (DI) 或 依赖注入。
该模式的本质很简单:我们不直接在类内部创建依赖对象,而是从外部传入(注入)它们。这从根本上改变了应用程序的架构,使类独立于具体实现,并简化了测试过程。
在本文中,我们将探讨 PHP 中的 Dependency Injection 是什么,有哪些实现方式,以及如何使用 DI 容器来自动化这一过程。
问题:为什么没有 DI 不好?
来看一个典型示例。UserController 类负责处理与用户相关的请求。它通过 MysqlDatabase 类使用数据库:
<?php
class MysqlDatabase { public function query(string $sql): array { // ... 连接到 MySQL 并执行查询 return []; }}
class UserController { private MysqlDatabase $database;
public function __construct() { // 硬绑定到具体实现 $this->database = new MysqlDatabase(); }
public function listUsers(): array { return $this->database->query('SELECT * FROM users'); }}
$controller = new UserController();这段代码有什么问题?
- 高耦合度:
UserController硬绑定到MysqlDatabase。如果我们想切换到 PostgreSQL 或使用文件存储,就必须修改控制器的代码。 - 测试困难: 在单元测试中无法将数据库替换为模拟对象。每个测试都会访问真实数据库,速度慢且不方便。
- 违反单一职责原则: 控制器不应该知道如何创建数据库连接。它的职责是处理请求。
解决这个问题的方法是应用控制反转(IoC)原则并使用 Dependency Injection。
PHP 中依赖注入的方式
有三种主要方式可以将依赖注入到类中:
1. 构造函数注入(Constructor Injection)
这是最常见且推荐的方式。依赖通过构造函数参数传入,通常保存为类的私有属性。
<?php
interface DatabaseInterface { public function query(string $sql): array;}
class MysqlDatabase implements DatabaseInterface { public function query(string $sql): array { // MySQL 的实现 return []; }}
class PostgresDatabase implements DatabaseInterface { public function query(string $sql): array { // PostgreSQL 的实现 return []; }}
class UserController { private DatabaseInterface $database;
// 依赖通过构造函数注入 public function __construct(DatabaseInterface $database) { $this->database = $database; }
public function listUsers(): array { return $this->database->query('SELECT * FROM users'); }}
// 使用$mysqlDb = new MysqlDatabase();$controller = new UserController($mysqlDb);
// 轻松切换到另一个数据库$postgresDb = new PostgresDatabase();$controller2 = new UserController($postgresDb);优点: 依赖关系清晰可见,对象在有效状态下创建(依赖是必需的)。
2. Setter 注入(Setter Injection)
适用于可选依赖或当依赖