引子
不知道大家开发时有没有好奇过以下这两个问题呢?
- 为什么方法的参数位置①是需要传入 2 个参数的,一个是
Request类型的参数,一个是不定类型的$id参数,但路由只有一个$id参数,那$request参数是哪里来的? UserService的__construct方法明确实例化需要一个Cache类型的参数,但②中并没有传入,为什么能使用呢?③为什么使用new不传参数就会报错呢?Route::get('/{id}','\App\Http\Controllers\IndexController@index');
class IndexController extends Controller{public function index(Request $request, $id)①{app(UserService::class)②->getUserNameById($id);③// TypeError: Too few arguments to function App/Services/UserService::__construct(), 0 passed in Psy Shell code on line 1 and exactly 1 expected(new UserService())->getUserNameById($id);}}
class UserService{public $cache;public function __construct(Cache $cache){$this->cache = $cache;}public function getUserNameById($id){return $this->cache->get('user:id:' . $id);}}
原来这叫
依赖注入,开始也不知道是个啥,那就抱着这两个疑问开始寻找答案。
接下来会围绕这 3 个点来讲
- 依赖注入
- 依赖:谁依赖谁
- 注入:注入什么
- 控制反转
- 控制:谁控制谁
- 反转:反转什么
-
常规代码
```php class Index1Controller { public $userService;
public function __construct() {
/*** 因为我需要(依赖) UserService() 给我提供数据, 所以创建了一个 UserService() 对象** 控制:我 (IndexController) 控制了 UserService() 对象的创建* 反转:我 (IndexController) 绝对控制 UserService() 对象的权利,创建对象的控制权没有发生转移,所以没有反转,一切都是亲力亲为。*/$this->userService = new UserService();
}
public function index() {
// 我 (index) 控制了 UserService() 对象的创建$userService = new UserService();$userName = $userService->getUserName();$userName2 = $this->userService->getUserName();return [$userName, $userName2];
} }
(new IndexController())->index();
<a name="FprSj"></a>### 依赖注入和控制反转```phpclass Index2Controller{public $userService;/*** 因为我需要(依赖) UserService() 给我提供数据, 所以我需要接收一个 UserService 类型的参数* 把依赖从外部传入进来,把需要的依赖传入进来了,就是依赖注入** 控制:调用者控制了 UserService() 对象的创建* 反转:我 (IndexController) 控制 UserService 创建的权利已经没有了(转移了),那转移给谁了?这里的控制权转移给调用者了。*/public function __construct(UserService $userService){$this->userService = $userService;}public function index(){/*** 在方法中创建对象* 我 (index) 控制了 UserService() 对象的创建*/$userService = new UserService();$userName = $userService->getUserName();$userName2 = $this->userService->getUserName();return [$userName, $userName2];}}// __construct() 中创建 new UserService() 转移到了这里$userService = new UserService();// 将 $userService 传入(注入) controller 中(new Index2Controller($userService))->index();
IoC 容器自动注入
上面的 依赖注入和控制反转 并没有解决开头引出的两个问题的答案,依赖还是需要手动创建,然后手动注入,如何实现依赖的自动注入呢?这个时候就需要一个 IoC 容器了
- 如何注入
使用 PHP 提供的 反射 (Reflection) 功能
- 我们需要注入哪里的参数
依赖注入是以构造函数参数的形式传入,所以我们需要自动注入构造函数指定的参数
- 我们需要注入哪些参数
我们只注入类实例,其他参数原样传入
IoC 容器其实就是一个普通的 class 类,实现了某些功能而已,不必想的太复杂。
class Container{/*** ioc容器实现自动依赖注入* @param string $className 类名* @param array $param 参数数组* @return object* @throws ReflectionException*/public static function make($className,$param = []){$reflect = new ReflectionClass($className);// 获取构造函数$construct = $reflect->getConstructor();// 保存实例化需要的参数$args = [];if ($construct){/*** 获取构造函数的参数* array(3) {* [0] => object(ReflectionParameter)#3 (1) {["name"]=> string(10) "userService"}* [1] => object(ReflectionParameter)#4 (1) {["name"]=> string(9) "userModel"}* [2] => object(ReflectionParameter)#5 (1) {["name"]=> string(2) "id"}* }*/$consParams = $construct->getParameters();foreach ($consParams as $k => $v){/*** object(ReflectionClass)#6 (1) {["name"]=>string(10) "UserServer"}*/$class = $v->getClass();if ($class){// $c = $class->getName();// $args[] = new $c;// 如果这样处理依赖的的 UserService() 还有依赖的话则无法兼顾,所以需要递归处理$args[] = self::make($class->getName());}}}// 合并参数$args = array_merge($args, $param);/*** ReflectionClass::newInstanceArgs — 从给出的参数创建一个新的类实例。* 相当于:return new UserController(new UserServer, new UserModel, $id)* IoC 控制反转:* 控制:容器控制了对象的创建* 反转:创建对象的权利已经转移到了容器中来了,不再是 UserController() 中的 __construct() 了。* DI 依赖注入:* 依赖:$args 保存了保存了需要那些依赖* 注入:把 $args 中的依赖作为参数传入(注入),返回实例*/return $reflect->newInstanceArgs($args);}}try {//使用ioc容器可以自动注入 UserServer 和 UserModel 两个依赖,我们只需要传入剩下的参数即可$userController = Container::make(UserController::class, [55]);$userController->index();} catch (ReflectionException $e) {echo $e->getMessage();}class UserController{public $userServer;public $userModel;public $id;//依赖 UserServer 和 UserModelpublic function __construct(UserServer $userServer,UserModel $userModel,$id) {$this->userServer = $userServer;$this->userModel = $userModel;$this->id = $id;}public function index(){echo 'id:'.$this->id.PHP_EOL;$this->userServer->say();$this->userModel->say();}}class UserServer{//依赖 SayServerpublic function __construct(SayServer $sayServer){echo $sayServer->getName().PHP_EOL;}public function say(){echo 'UserServer'.PHP_EOL;}}class UserModel{//依赖 SayServerpublic function __construct(SayServer $sayServer){echo $sayServer->getName().PHP_EOL;}public function say(){echo 'UserModel'.PHP_EOL;}}class SayServer{protected $name;public function __construct(){$this->name = 'sayServer';}public function getName(){return $this->name;}}
回顾问题
- 路由中的
Request $request参数是哪里来的
答:请求进入框架之后,框架解析 url 找到相对应的控制器类,调用容器写好的自动注入方法 (案例中是 make()),进行注入参数,这样就可以愉快又方便的使用啦。
- 使用 app() 和 new 有什么不同
答:其实 laravel 中 app() 就是使用 Container 实例化的一个助手函数
看看 laravel 中的助手函数
function app($abstract = null, array $parameters = []){if (is_null($abstract)) {return Container::getInstance();}return Container::getInstance()->make($abstract, $parameters);}
结尾
这个案例的 Container 中似乎没有太体现出 容器 这个词,因为还没有实现实例化对象的存储,具体可以看看相关的源码。
