PHP中依赖注入(DI)简介
现代PHP开发离不开对组件松耦合原则的理解。依赖注入(DI)是创建灵活、可测试且易于维护的应用的关键设计模式之一。不同于类自身创建依赖(例如数据库对象或日志记录器),依赖是从外部传入的。这从根本上改变了代码的架构。
本文将详细探讨PHP中的依赖注入是什么、有哪些注入方法、DI容器如何工作,以及为什么该模式已成为Laravel、Symfony和Yii2等现代框架的标准。
1. 问题:紧耦合
在讨论解决方案之前,我们先看看问题所在。一个经典例子是UserController类,它在内部直接创建Database对象:
<?phpclass Database { public function query(string $sql): array { // 连接数据库并执行查询 return []; }}
class UserController { private Database $db;
public function __construct() { // 紧耦合依赖——在构造函数内部创建对象 $this->db = new Database(); }
public function getUsers(): array { return $this->db->query('SELECT * FROM users'); }}?>这种方法的缺点:
- 测试困难:无法在单元测试中将
Database替换为模拟对象。 - 违反单一职责原则(SRP):类不仅要负责业务逻辑,还要负责创建依赖。
- 修改困难:如果想将MySQL替换为PostgreSQL,就必须修改所有创建
Database的类中的代码。
2. 解决方案:依赖注入
依赖注入建议从外部传入依赖。主要有三种注入方法:通过构造函数、通过setter方法以及通过接口(方法)。我们逐一来看。
2.1. 构造函数注入
这是最常见且推荐的方法。依赖在对象创建时一次性传入,后续无法更改。
<?phpclass UserController { private Database $db;
// 依赖从外部传入 public function __construct(Database $db) { $this->db = $db; }
public function getUsers(): array { return $this->db->query('SELECT * FROM users'); }}
// 使用方式:$db = new Database();$controller = new UserController($db);?>优点:确保依赖始终可用;对于必需依赖是理想选择。缺点:如果类有多个依赖,构造函数会变得臃肿。
2.2. Setter注入
适用于在对象创建后可设置的可选依赖。
<?phpclass Logger { public function log(string $message): void { echo $message; }}
class UserController { private ?Logger $logger = null;
public function setLogger(Logger $logger): void { $this->logger = $logger; }
public function getUsers(): array { if ($this->logger) { $this->logger->log('正在获取用户...'); } // 获取用户的逻辑 return []; }}
// 使用方式:$controller = new UserController();$controller->setLogger(new Logger());?>优点: