[TOC] #### 1. 什么是依赖注入 ---- 依赖注入:把对象所依赖的资源,从外部注入进去,而不是在内部自己创建。 PHP 依赖注入(Dependency Injection,简称 DI)是一种设计模式,其核心思想是将对象的依赖关系从 “类内部创建” 转移到 “外部传入”,从而降低类之间的耦合度,提升代码的可测试性和可维护性。(依赖:一个类可能需要另一个类的功能) 举一个直观的例子(不使用依赖注入,`UserService` 强依赖邮件服务类 `EmailService`,无法替换消息通知类): ```php // 1. 消息通知接口 interface Notice { public function send(); } // 2. 邮件通知类实现消息通知接口 class EmailService implements Notice { public function send() { echo '发送邮件逻辑'; } } // 3. 用户服务类 class UserService { private $emailService; public function __construct() { $this->emailService = new EmailService; } public function register() { $this->emailService->send(); } } // 4. 客户端调用 (new UserService)->register(); ``` 使用依赖注入(消息通知类作为参数传入,也可以替换为 `Notice` 接口的其他实现,只要它们遵循相同的接口): + 优点:解耦,可替换依赖,更易测试 ```php // 1. 消息通知接口 interface Notice { public function send(); } // 2.1 邮件通知类实现消息通知接口 class EmailService implements Notice { public function send() { echo "发送邮件逻辑\n"; } } // 2.2 短信通知类实现消息通知接口 class SmsService implements Notice { public function send() { echo "发送短信逻辑\n"; } } // 3. 用户服务类 class UserService { private $notification; public function __construct(Notice $notification) { $this->notification = $notification; } public function register() { $this->notification->send(); } } // 4. 客户端调用 $uesr1 = new UserService(new EmailService()); $uesr2 = new UserService(new SmsService()); $uesr1->register(); // 发送邮件逻辑 $uesr2->register(); // 发送短信逻辑 ``` #### 2. 依赖注入的实现方式 ---- ##### 构造函数注入 这是最常见的方式,通过在类的构造函数中直接传入依赖,保证对象一旦创建就是完整且可用的,例如: ```php class UserService { private $notification; public function __construct(Notice $notification) { $this->notification = $notification; } public function register() { $this->notification->send(); } } $uesr = new UserService(new EmailService()); $uesr->register(); ``` ##### Setter 方法注入 通过 `setter` 方法传入依赖,适用于可选依赖或后期动态修改依赖的场景 ```php class UserService { public $notification; public function setNotice(Notice $notification) { $this->notification = $notification; } } $uesr = new UserService(); $uesr->setNotice(new EmailService()); ``` ##### 接口注入 这种方式在 PHP 中比较少见(在 Java 中更常见)。它要求类必须实现一个特定接口,该接口定义了注入依赖的方法 ```php // 1. 定义依赖类 class Logger { public function log($msg) { echo "日志:" . $msg . "\n"; } } // 2. 定义注入接口 // 谁实现了我,谁就必须提供 setLogger 方法,用来接收 Logger interface LoggerAwareInterface { public function setLogger(Logger $logger); } // 3. 业务类实现接口 // 注意:没有构造函数注入、依赖是通过接口方法塞进来的 class UserService implements LoggerAwareInterface { private $logger; // 实现注入逻辑 public function setLogger(Logger $logger) { $this->logger = $logger; } public function register() { $this->logger->log("用户注册"); } } // 4. 容器负责注入(关键点) $logger = new Logger(); $userService = new UserService(); // 检查是否实现接口 -> 自动注入 if ($userService instanceof LoggerAwareInterface) { $userService->setLogger($logger); } $userService->register(); ``` #### 3. ThinkPHP 框架的依赖注入 ---- 在 ThinkPHP 中,依赖注入 = 容器(Container) + 反射自动解析(invokeClass / invokeMethod) + 特点:随用随注入 + 自动解析方法参数 控制器构造函数注入: ```php use app\Request; class UserController { protected $request; public function __construct(Request $request) { $this->request = $request; } } ``` ThinkPHP 自动帮你: ```php new UserController(new \app\Request()); ``` 方法参数自动注入: ```php use app\Request; class Index extends BaseController { public function index($id, Request $request) { dump($id, $request); } } ``` 核心实现原理: + ThinkPHP 的核心在这个类:think\Container(vendor/topthink/framework/src/think/Container.php) + 入口方法 make、核心方法 invokeClass 用反射创建对象、bindParams 方法绑定参数,是依赖解析核心 + 本质就是:反射 -\-> 获取参数 -\-> 递归 make() -\-> 注入 方法注入是怎么实现的 ?(ThinkPHP 的特色:不只是构造函数,普通方法也支持依赖注入) 核心方法:invokeMethod() ```php public function invokeMethod($method, $vars = []) { $reflect = new ReflectionMethod(...); $args = $this->bindParams($reflect, $vars); return $reflect->invokeArgs($object, $args); } ``` 举例: ```php // 识别 UserService,由容器创建。识别 $id,从路由参数获取。 public function index(UserService $service, $id) { } ``` 你要能说出来一个完整执行流程,ThinkPHP 做的事: + 找到控制器 `UserController@index` + 用 `Container::invokeClass()` 创建控制器 + 用 `invokeMethod()` 调用方法 + 用反射解析参数(类 -\-> 容器 make(),普通参数 -\-> 路由参数) + 执行方法 一句话总结(面试直接用): ThinkPHP 的依赖注入是基于容器和反射实现的,通过 make、invokeClass 和 invokeMethod 自动解析构造函数和方法参数,递归创建依赖对象,并支持接口绑定、单例和方法级注入,从而实现自动装配。