PHP中的依赖注入:实战
依赖注入(DI)是SOLID原则的关键之一,它允许编写灵活、可测试且易于维护的代码。在本文中,我们将通过实际示例,而非抽象理论,来解析如何在PHP中实现依赖注入。您将了解如何消除类之间的硬耦合,使代码随时应对变化。
什么是依赖注入,为什么需要它?
假设您有一个与数据库交互的类 UserController。没有DI时,您很可能直接在控制器内部创建数据库连接对象:
class UserController { private $db; public function __construct() { $this->db = new MySQLConnection('localhost', 'user', 'pass'); }}这种方法造成了紧耦合。如果您想切换到PostgreSQL或使用模拟对象进行测试,就必须修改控制器代码。依赖注入解决了这个问题:依赖关系从外部传入,而不是在类内部创建。
DI的主要优势:
- 降低代码耦合度
- 简化测试(易于替换依赖)
- 提高类的可复用性
- 清晰的架构并遵循依赖反转原则(DIP)
PHP中依赖注入的方式
主要有三种依赖注入方式。我们逐一举例说明。
1. 构造函数注入
这是最常见且推荐的方式。依赖通过构造函数参数传入并保存在类的属性中。
<?phpinterface DatabaseInterface { public function query(string $sql): array;}
class MySQLDatabase implements DatabaseInterface { public function query(string $sql): array { // 与MySQL交互的实际逻辑 return ['id' => 1, 'name' => 'John']; }}
class UserRepository { private DatabaseInterface $db; public function __construct(DatabaseInterface $db) { $this->db = $db; } public function getUser(int $id): array { return $this->db->query("SELECT * FROM users WHERE id = $id"); }}
// 使用方式:$db = new MySQLDatabase();$repo = new UserRepository($db);$user = $repo->getUser(1);?>请注意:我们使用了接口 DatabaseInterface。这使得替换实现变得容易——例如,替换为测试桩。
2. Setter注入
当依赖在对象创建阶段不是必需的,或者可能在运行过程中发生变化时使用。
<?phpclass MailService { private ?MailerInterface $mailer = null; public function setMailer(MailerInterface $mailer): void { $this->mailer = $mailer; } public function send(string $to, string $subject, string $body): void { if ($this->mailer === null) { throw new \\RuntimeException('Mailer not configured'); } $this->mailer->send($to, $subject, $body); }}
// 使用方式:$mailService = new MailService();$mailService->setMailer(new SmtpMailer());$mailService->send('user@example.com', 'Hello', 'Body');?>这种方法的缺点是:您无法保证在调用 send() 方法之前依赖已被设置。因此,对于必需的依赖,构造函数注入更优。
3. 方法注入
依赖直接传递给需要它的方法。适用于不常用或上下文相关的依赖。
<?phpclass ReportGenerator { public function generate(DataFetcherInterface $fetcher, string $reportType): array { $data = $fetcher->