-
简单理解Laravel中的控制反转和依赖注入 (非权威,仅供参考)
2020-07-08 15:17:25首先从概念上来说,依赖注入是控制反转的一种具体实现。 而从PHP面向对象的角度来说,控制反转是想解决,A类依赖于B类,而在一般情况下A类想要使用B类的方法,需要先New一个B类。所以对于B类的控制权在于A类手中。 ...首先从概念上来说,依赖注入是控制反转的一种具体实现。
而从PHP面向对象的角度来说,控制反转是想解决,A类依赖于B类,而在一般情况下A 类想要使用B类的方法,需要先New 一个B类。所以对于B类的控制权在于A类手中。
但是这样的坏处就是,假如B 类又依赖于C 类,这样下去,就会很麻烦,因为互相依赖太多。
而控制反转就可以 使用一个 另外的类,假如说是IOC类,这个类专门用来生成各种对象,然后将这些生成的对象以参数传递的方式放入所需要的类。
而IOC 类的具体实现 在laravel中使用的是 PHP 反射机制,就可以拿到PHP的构造方法或者什么方法 传递过来的所需要的类。
把这些类在进行实例化,如果这个类还依赖于其他类,那么递归调用。
总结一下:
1.依赖注入是控制反转的一种具体实现。
2.laravel中的依赖注入 是利用 PHP的反射机制, 加上递归进行处理的。
先简单记录下。。。供参考。
-
Laravel源码分析之控制反转和依赖注入
2020-02-22 13:59:11控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度, 是一种编程思想,能让我们设计出更为优良的程序。 依赖注入(DI) 是实现IOC的一种方式,...概念
-
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度, 是一种编程思想,能让我们设计出更为优良的程序。
-
依赖注入(DI) 是实现IOC的一种方式,通过该方式可以将存在对象内部的依赖转移到外部,动态的将依赖注入到对象内部,实现代码的解耦。
-
先看下以下代码有什么问题:
/** * 用户类 */ class User { private $name = '张三'; public function name() { return $this->name; } } /** * 应用类 */ class App { protected $user; public function __construct() { $this->user = new User(); } public function printUserName() { echo $this->user->name(); } } $app = new App(); $app->printUserName();
代码中定义了一个
User
类和App
类, 在App
内部使用new User()
创建了一个user实例
,如果我想给User
类名改成复数Users
, 同时还需要去修改App
类中内部的代码, 违反了开放封闭原则,这时候如果我们将依赖转移到外部,则可以不用修改内部代码,如下:<?php /** * 用户类 */ class User { private $name = '张三'; public function name() { return $this->name; } } /** * 应用类 */ class App { protected $user; public function __construct(User $user) { $this->user = $user; } public function printUserName() { echo $this->user->name(); } } $app = new App(new User()); $app->printUserName();
App
类在构造方法中以参数的形式传入,只需要在外部把实例化好的User
对象传入。
反射
-
先来阅读我项目中的
UserController
控制器的部分代码:class UserController extends Controller { public function __construct(UserService $service) { $this->service = $service; } /** * 用户登录 * @param LoginRequest $request * @return false|string */ public function login(LoginRequest $request) { return $this->service->login($request->validated()); } /** * 用户注册 * @param RegisterRequest $request * @return false|string * @throws Exception */ public function register(RegisterRequest $request) { return $this->service->register($request->validated()); } }
可以看到在
控制器
的构造方法中,注入了一个UserService
对象,但是我们根本没有做任何实例化操作,这是Laravel内部帮我们是实现的,那Laravel是如何实现的呢?可能有读者已经想到了,没错,就是反射。 -
反射具有对类、接口、函数、方法和扩展进行反向工程的能力,没使用过的可以参考PHP官方文档-反射。
-
先看一下以下这段反射代码,方便后面理解laravel的ioc。如下:
<?php /** * 用户类 */ class User { private $name; public function __construct($name='ClassmateLin') { $this->name = $name; } public function name() { return $this->name; } } // 获取User的reflectionClass对象 $reflector = new reflectionClass(User::class); // 拿到User的构造函数 $constructor = $reflector->getConstructor(); // 拿到User的构造函数的所有依赖参数, 返回的是一个数组 $dep_params = $constructor->getParameters(); // 创建user对象,不传递参数 $user = $reflector->newInstance(); echo $user->name() . PHP_EOL; // 输出默认值ClassmateLin $name = $dep_params[0]->name; // 拿到构造函数参数数组的第一个参数的参数名。 // 创建user对象,需要传递参数的 $user = $reflector->newInstanceArgs($dep_params=[$name => 'XXX']); echo $user->name() . PHP_EOL; // 输出XXX
可以看出:
- 通过reflectionClass
传入User
类,可以拿到一个反射实例。
- 通过反射实例可以拿到构造函数。
- 通过构造函数可以拿到参数列表。
- 通过反射对象可以创建实例,分为带参数和不带参数两种形式。
简单容器
- 通过了解上文的内容,我们可以创建一个简单的容器来实现对象的实例化。
- 比如你的系统用户登录时,依赖一个日志对象, 通过上述内容,你已经可以写出以下代码:
<?php /** * 日志接口 */ interface Log { public function log($msg); } /** * 文件日志的实现 */ class FileLog implements Log { /** * 将log进行一个简单的输出 * @param $msg * @return Log|void */ public function log($msg) { echo '文件日志记录: ' . $msg . PHP_EOL; } } /** * 数据库日志的实现 */ class DbLog implements Log { public function log($msg) { echo '数据库日志记录:' . $msg . PHP_EOL; } } class User { private $log; /** * @param FileLog $log */ public function __construct(FileLog $log) { $this->log = $log; } /** * 简单的登录操作 * @param string $username */ public function login($username='ClassmateLin') { echo '用户:' . $username . '登录成功!'; $this->log->log('日志: 用户:' . $username . '登录成功!'); } }
该代码中定义了一个日志操作的接口
Log
, 并且提供了文件方式和数据库方式的日志具体实现。
但是你可能注意到User
类中为什么不是注入接口,注入接口Log
的化,那不是既可以传入FileLog
实例,也可以传入DbLog
实例,像这样:class User { private $log; /** * @param FileLog $log */ public function __construct(Log $log) { $this->log = $log; } /** * 简单的登录操作 * @param string $username */ public function login($username='ClassmateLin') { echo '用户:' . $username . '登录成功!'; $this->log->log('日志: 用户:' . $username . '登录成功!'); } } $user = new User(new DbLog()); $user = new User(new FileLog());
原因在于反射是不能动态创建接口的,如果想实现的话可以做一个容器绑定,这会在下一篇文章中进行描述。本文先来实现一个简单的容器。
- 定义一个容器:
Application
, 提供一个make
方法通过传入类名参数,进行实例化:
class Application { function make(string $class_name) { $reflector = new reflectionClass($class_name); // 拿到反射实例 $constructor = $reflector->getConstructor(); // 拿到构造函数 if (is_null($constructor)) { // 如果写构造函数,得到的constructor是null。 return $reflector->newInstance(); // 进行无参数实例化 } // 拿到构造函数依赖的参数 $dependencies = $constructor->getParameters(); // 这时候我们依赖的参数可能也有参数,通过递归的去获取当前类的参数。 $instance = $this->getDependencies($dependencies); // 进行带参数的实例化 return $reflector->newInstanceArgs($instance); } /**获取依赖 * @param $params * @return array */ private function getDependencies($params) { $dependencies = []; // array_walk相等于foreach, for 的作用,据说速度是最快的,我也没去验证,只是喜欢闭包。 array_walk($params, function ($param) use (&$dependencies) { $class_name = $param->getClass()->name; // 获取类名 $dependencies[] = $this->make($class_name); // 调用make函数创建实例 }); return $dependencies; } }
类中定义了两个函数
make
和getDependencies
,通过递归的形式进行拿构造函数参数进行实例化。递归的出口就是当一个类不需要参数的时候。- 创建一个
User
的实例:
$app = new Application(); $user = $app->make('User'); // 通过传入类名,遍得到了一个user实例 $user->login();
完整代码
如果读完这篇文章有所收获,不妨帮忙点个赞,谢谢老板。
<?php /** * 日志接口 */ interface Log { public function log($msg); } /** * 文件日志的实现 */ class FileLog implements Log { /** * 将log进行一个简单的输出 * @param $msg * @return Log|void */ public function log($msg) { echo '文件日志记录: ' . $msg . PHP_EOL; } } /** * 数据库日志的实现 */ class DbLog implements Log { public function log($msg) { echo '数据库日志记录:' . $msg . PHP_EOL; } } class User { private $log; /** * @param FileLog $log */ public function __construct(FileLog $log) { $this->log = $log; } /** * 简单的登录操作 * @param string $username */ public function login($username='ClassmateLin') { echo '用户:' . $username . '登录成功!' . PHP_EOL; $this->log->log('日志: 用户:' . $username . '登录成功!'); } } class Application { function make(string $class_name) { $reflector = new reflectionClass($class_name); // 拿到反射实例 $constructor = $reflector->getConstructor(); // 拿到构造函数 if (is_null($constructor)) { // 如果写构造函数,得到的constructor是null。 return $reflector->newInstance(); // 进行无参数实例化 } // 拿到构造函数依赖的参数 $dependencies = $constructor->getParameters(); // 这时候我们依赖的参数可能也有参数,通过递归的去获取当前类的参数。 $instance = $this->getDependencies($dependencies); // 进行带参数的实例化 return $reflector->newInstanceArgs($instance); } /**获取依赖 * @param $params * @return array */ private function getDependencies($params) { $dependencies = []; // array_walk相等于foreach, for 的作用,据说速度是最快的,我也没去验证,只是喜欢闭包。 array_walk($params, function ($param) use (&$dependencies) { $class_name = $param->getClass()->name; // 获取类名 $dependencies[] = $this->make($class_name); // 调用make函数创建实例 }); return $dependencies; } } $app = new Application(); $user = $app->make('User'); $user->login();
-
-
laravel 浅析依赖倒转、控制反转、IoC 容器、依赖注入
2021-01-19 11:46:50今天看到 浅析依赖倒转、控制反转、IoC 容器、依赖注入 这篇文件,让自己初步了解这些概念。记录一下这篇 重写仿照 laravel 的 IOC 容器和依赖注入 的代码 <?php class Boos{ //领导依赖员工 private $staff; ...参考: https://learnku.com/articles/14145/rely-on-inversion-control-inversion-ioc-container-dependency-injection
今天看到 浅析依赖倒转、控制反转、IoC 容器、依赖注入 这篇文件,让自己初步了解这些概念。记录一下这篇 重写仿照 laravel 的 IOC 容器和依赖注入 的代码<?php class Boos{ //领导依赖员工 private $staff; //老板只需要告诉外部我需要什么样的人就好了,其它什么都不管,具体什么样的人交给外部处理。 //用构造方法方式实现依赖注入 public function __construct(Standard $staff){ $this->staff = $staff; } public function task(){ $this->staff->work(); } } //招聘所设定的标准 interface Standard{ public function work(); } //员工需要依赖的标准 class StaffA implements Standard{ public function work(){ echo '雇员A有能力能够完成老板指定的工作'; } } class StaffB implements Standard{ public function work(){ echo '雇员B有能力能够完成老板指定的工作'; } } class Hr{ private $binds = []; //接受不同员工的简历,并存起来 2、实例化后调用 bind 方法参数 // $contract = 'Standard', $concrete = 'StaffA' public function bind($contract,$concrete){ //等于 $this->binds['Standard'] = 'StaffA'; $this->binds[$contract] = $concrete; } //询问老板选人的标准由哪些,并且从满足的简历中筛选人 private function methodBindParams($className){ // 5、 根据第4步 调用的 methodBindParams 方法 参数 $className = 'Boos', // 这里进行实例化 reflect 类,并且传递参数 $className = 'Boos','__construct' $reflect = new reflect($className,'__construct'); // 7、实例化完成后 调用 reflect 类的 bindParamsToMethod 方法并且返回 return $reflect->bindParamsToMethod(); } //将选好的工作人员交给老板 3、调用 make 方法 参数 'Boos' public function make($className){ // 4、调用 当前类里的 methodBindParams 方法,传递参数为 'Boos' // 根据 第8步,最终 $methodBindParams = ['Boos' => [0 => ['staff', 'Standard']] ]; $methodBindParams = $this->methodBindParams($className); // 9、 这里进行实例化 reflect 类,并且传递参数 $className = 'Boos','__construct' $reflect = new reflect($className,'__construct'); // 10、调用 reflect 类的 make方法, // $this->binds = ['Standard'=> 'StaffA']; // $methodBindParams = ['Boos' => [0 => ['staff', 'Standard']] ]; // 根据11步,这里最终返回的是 Boos的实例化 return $reflect->make($this->binds,$methodBindParams); } } class reflect{ private $className; private $methodName; // 6、根据第5步,实例化 reflect 类,也就是本类, 实例化后会先调用 __construct 构造方法 // 并且传递的参数为 $className = 'Boos',$methodName = '__construct' public function __construct($className,$methodName){ $this->className = $className; $this->methodName = $methodName; } //绑定参数到方法 8、根据第七步的调用 走此方法 public function bindParamsToMethod(){ $params = []; // 8续、实例化一个 ReflectionMethod 类,$this->className,$this->methodName参数根据 // 第5步 实例化时传递的 $className = 'Boos',$methodName = '__construct' // 实际就时获取的 Boos类的 __construct 方法的详情 $method = new ReflectionMethod($this->className,$this->methodName); // 8续、根据上面这行代码 拿到的 __construct 方法详情,然后 getParameters 方法获取 // __construct 方法的参数,按顺序进行获取,返回的是个数组 所以这里进行foreach循环 foreach ($method->getParameters() as $param) { // 8续、 $param->name 是 Boos 类的__construct的参数名称,Boos的 // __construct(Standard $staff) 方法只有一个参数,这里循环只会循环一次 // $param->getClass()->name 获取的是变量的对象声明,类型声明无法获取。 // 最终 $param->name 为 'staff' , $param->getClass()->name 为 'Standard' $params[] = [$param->name,$param->getClass()->name]; } // 8续、然后返回,$this->className 为 'Boos', // $params 为 $params = [ 0 => ['staff', 'Standard'] ]; // 最终为 ['Boos' => [0 => ['staff', 'Standard']] ]; return [$this->className => $params]; } // 11、根据第10步调用的本方法 make 并且传递的参数 // $this->binds = ['Standard'=> 'StaffA']; // $methodBindParams = ['Boos' => [0 => ['staff', 'Standard']] ]; public function make($bind,$methodBindParams){ $args = []; foreach ($methodBindParams as $className => $params) { foreach ($params as $param) { list($paramName,$paramType) = $param; // 11续、 根据传递的参数循环, // new $bind[$paramType]()等于 new $bind['Standard']() // new $bind['Standard']() 等于 new StaffA(); // 这里 $paramName 就是实例对象 $paramName = new $bind[$paramType](); // 然后添加到 $args 数组中 array_push($args, $paramName); } } // 11续、根据第9步,实例化本类的时候传递的参数 $className = 'Boos', // 所以这里获取的是 Boos 类详情 $reflectionClass = new ReflectionClass($this->className); // 11续、这里进行 Boos 类实例化,并且把 $args 参数传递到 Boos类的构造函数 // 根据上面的循环实例化,$args数组中的每个元素都是 一个实现了 Standard 接口的类 // 根据 Boos 类的 __construct(Standard $staff) 类型声明 传递是没问题的 // 最终返回的是 Boos类实例化 return $reflectionClass->newInstanceArgs($args); } } // 1、实例化 Hr 类 $hr = new Hr(); //老板如果需要换工作人员,只需要绑定其它的工作人员即可。 //2、实例化后调用 bind 方法 $staff = $hr->bind('Standard','StaffA'); // 3、调用 make 方法 返回的是Boos类实例化,并且把 StaffA 传递进了Boos类的构造函数 // 根据第11步进行传递的 $boos = $hr->make('Boos'); // 12、终篇 然后这里调用 task() // $this->staff->work(); $this->staff也就是 StaffA 的实例化, // 在第11步进行传递进Boos类的构造函数 $boos->task();
-
针对laravel 依赖注入,控制反转,IOC容器,服务提供者,契约一些自己的理解
2020-07-21 18:00:54在这个过程中,服务B本来可以控制开发者的某个功能,现在开发者不直接调用这个服务了,而是换成了接口A,所以,该服务B对该项功能失去了控制权,而接口A获得了该功能的控制权,这个过程就叫做控制反转laravel 自己本身就是一个IOC容器,也叫服务容器,
服务容器就是管理类的依赖和执行依赖注入的工具
控制反转的作用就是实现模块或对象的解耦,通过借助第三方将具有依赖的模块或对象进行解耦,而这个第三方,就是IOC容器。
容器嘛,就是储存了需要的服务在里面,方便开发者调用。所以,Laravel为了方便管理这些服务(实现解耦)决定,不再直接调用这些服务,例如开发者定义了一个方法1,原本和服务B绑定,通过服务B实现,现在不要B了,而是定义了一个接口A,通过接口A去实现这个服务B。
在这个过程中,服务B本来可以控制开发者的某个功能,现在开发者不直接调用这个服务了,而是换成了接口A,所以,该服务B对该项功能失去了控制权,而接口A获得了该功能的控制权,这个过程就叫做控制反转
好处就在于:如果该服务B不合适了,那么接口A可以找一个适合需求的其他服务替换上去,不用修改开发者的方法1,至于你接口A用的啥服务,我这方法也不用管。这个接口A要实现服务B,那实现服务B的类就得继承这个接口A,在这个B类中实现接口A定义的抽象方法。
这个接口A 也可以被看作该项服务的提供者。laravel中有多个服务提供者,他们组成相应的组件,多个组件形成这样一个laravel框架
既然接口A有了,接口BCDE那么多,要方便管理,就得给规定服务提供者的格式,方法参数,来约束他们的规则,这个概念就叫做:契约
契约的好处:就在于,只要满足约束规则,就可以按需求随意替换,
**Facades,**我们可以叫做门面,其实就是一组静态接口或者代理,能让开发者简单的访问绑定到容器中的各种服务。Laravel 里面自带了一些 Facades,如Cache等。一个 Facade 就是一个类,使用这个类可以访问到来自容器里的一个对象,这个功能就是在 Facade 类里面定义的。Laravel 的 Facades 还有任何你自己定义的 Facades,都会去继承 Facade 这个类。通俗来说,你在类上方使用的use xxxx 都属于门面,详情看链接:https://www.jianshu.com/p/a96715975d4e那么,如果我要实现的服务这里没有怎么办,那就需要将新来的服务进行注册和初始化,只有注册到容器中了,容器才能调用它,这个工作由服务提供者来实现。回归正题
那依赖注入是什么:开发者的这个方法1,定义了一个中间商接口A,这个接口A可以以参数形式,注入方法1所属类1的构造方法中 储存起来,这个过程就叫做依赖注入。
只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入我们要如何做依赖注入呢?很简单: $biller = new StripeBiller(new SmsNotifier);
这就是一个依赖注入。账单类 StripeBiller 不用考虑如何通知用户,我们直接传递给它一个通知实现类 SmsNotifier
的实例。从代码角度来说,这可能只是个微小的变动,但这种设计模式的引入,绝对会使你的整个应用架构焕然一新:因为明确指定了类的职责边界,实现了不同层和服务之间的解耦,你的代码变得更加容易维护;此外,从面向接口编程的角度来看,代码变得更加容易测试,你只需通过模拟注入依赖即可,不同类之间的测试完全可以隔离开来 -
通过laravel理解IoC(控制反转)容器和DI(依赖注入)
2016-04-21 15:49:00原文地址:http://www.insp.top/learn-laravel-container,转载务必...当然,有这样一种容 器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多... -
深入理解Laravel容器概念,DI依赖注入,IOC控制反转
2020-04-03 15:50:27深入理解Laravel容器概念,DI依赖注入,IOC控制反转 IOC - 控制反转 DI - 依赖注入 这两个存在的目的都是为了解耦! 解耦可以理解为,原本紧密结合的两个磁铁,现在我们在他们中间加一层木板,强行将他们分开,却不... -
laravel Ioc Di 依赖注入,控制反转,容器
2018-03-08 16:23:11容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是...IoC 容器, laravel 的核心Laravel 的核心就是... -
Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)
2017-09-24 10:11:14容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是...IoC 容器 —— Laravel 的核心Laravel 的核心就 -
laravel 学习笔记:IoC服务容器(依赖注入与控制反转)
2018-11-08 11:27:51当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。... -
Laravel 服务容器实现原理
2017-03-30 19:20:59前言 通过实现laravel 框架功能,以便...Laravel框架中就是使用服务容器来实现 控制反转 和 依赖注入 。 什么是控制反转(IoC)和依赖注入(DI) 控制反转(IoC) 就是说把创建对象的 控制权 进行转移,以前创建对象... -
Laravel 学习笔记:深入理解控制反转(IoC)和依赖注入(DI)
2018-05-12 08:11:33控制反转(IoC):由外部负责其依赖行为; 例如“超人”类不需要在其内部固化它的“超能力”,而是由外部来产生、组装“超能力”,再通过“超人”的某个接口中植入; 只要“超能力”满足某个接口,就能被超人所... -
laravel 服务容器实现原理
2019-05-08 18:29:22Laravel框架中就是使用服务容器来实现 ** 控制反转 ** 和 ** 依赖注入 **。 什么是控制反转(IoC)和依赖注入(DI) 控制反转(IoC) 就是说把创建对象的** 控制权 进行转移,以前创建对象的主动权和创建时机是由... -
Laravel 之服务容器——深入理解控制反转(IoC)和依赖注入(DI)
2018-06-02 08:51:23容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是...IoC 容器 —— Laravel 的核心Laravel 的核心... -
laravel 核心架构(1)服务容器-深入理解控制反转(IoC)和依赖注入(DI)
2019-12-04 12:02:45laravel 容器 存放的 是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦”、“依赖注入(DI)”。 服务容器的使用 2. 通过案例解析... -
转:Laravel系列--Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)...
2017-02-23 20:08:00http://laravelacademy.org/tutorials/basic友情提示:本文有点长,但绝对都...当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现... -
laravel服务容器-----深入理解控制反转(IoC)和依赖注入(DI),facade(门面)和contracts(契约)联系
2019-08-26 18:01:59控制反转:即IOC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了... -
hyperf依赖注入和控制反转
2020-12-14 13:20:59之前在laravel会用到依赖注入和控制反转,面试也会遇到,一直觉得自己明白的很透彻了,现在hyperf框架又遇到,发现疑问重重,特此整理一下,以免后期再不明白 概念 1. 依赖注入(DI) 对象之间依赖关系由容器在...