Laravel底层解读:控制反转与依赖注入、IOC容器和反射
控制器中需要使用一个类A,然后newA 它,然后调用这个类的对象,然而这个类实例化时候需要传递B类的实例化对象,我们就需要先去new B 然后传递,这个是类之间的依赖关系。
我们在控制器方法中new这个类,然后使用它,但是我们发现很多框架中的类,不需要你去实例化,而方法中直接有个形参就可以用。本质上就是框架底层去new 了这个类,然后传递给这个方法形参,所以你在方法里面可以直接用,那么这个原本在方法中new 类创建实例对象,变成了在方法外面new 了 然后传递进来,这个站在这个方法的角度来看,就是一种反转,也就是控制反转,如果站在方法外面来看,我不需要你new 我new好了传递给方法的形参,你直接用就行,那么这个视角,就是依赖注入。其实本质就是一样的东西。
我们理解了概念之后,我们来理解一下什么是IOC容器和反射。
首先我们有这么几个类,之间有依赖关系,看里面构造方法可以得知。这个class A 我们需要在控制器方法中使用,但是我们上面已经知道了我们在方法中使用A 不需要自己去new ,太复杂了,何况 A类有那么多层的依赖关系。所以框架底层帮我们new好 然后给我们使用(控制反转、依赖注入);
/**
* @author zhaoruiqing
* @date 2022-
* @desc 我是类A的注释
*/
class A
{
//我是构造函数的注释
public function __construct(B $b)
{
$this->b = $b;
}
//我是getB的注释
public function getB()
{
$this->b->bMethod();
}
}
class B
{
public function __construct(C $c,D $d)
{
$this->c = $c;
$this->d = $d;
}
public function bMethod()
{
echo "我是B中的方法bMethod()";
}
}
class C{
public function __construct(){
}
public function cMethod(){
echo "我是C中的方法cMethod()";
}
}
class D{
public function __construct(){
}
public function dMethod(){
echo "我是D中的方法dMethod()";
}
}
那么框架中如何,帮我们new A,那我们做一个工厂类,专门帮助我们在方法中实例类,然后给我们使用:如下图

创建了一个IOC工厂类,实例化这些类村粗到数组中,用的时候,然后给我们(工厂单例模式),这个就是控制反转,有个专门的ioc类帮我们实例对象,不要我们实例化,但是上面的代码仍然有问题,我们这个A类存储依赖B类,B类依赖于CD等等类,所以即便帮我们实例化,也需要在夸号中写上依赖关系,(注意上图没有写,这里补充)。
难道我们所有类存在依赖关系,让这个工厂类,去new ,都要封装框架的人去手动填写每个类的依赖关系吗?
当然不是,这个IOC工厂类,会用到PHP的一个反射,其实反射就是PHP提供的一组让我们获取类信息的Api,比如,我有一个类,里面有属性、方法、类名、注释、静态的也好,我们通过反射方法,传递这个类的名称,调用响应的获取方法,就可以得到相应的属性或者成员方法、构造方法、甚至注释。
那也就是说我们IOC工厂类,在框架底层去帮我们实例化类的,时候,不需要认为去告诉哪个类依赖于哪个类,框架可以调用反射方法,去获取相应的类中的构造方法,然后得到这个类需要传递哪个类的实例,也就是知道了这个类所需要的依赖类。
如下几组方法,就是反射。
//获取类的反射信息,也就是类的所有信息
$reflector = new ReflectionClass(类名);
echo $reflector->getDocComment(); 获取类的注释信息
//获取反射类的构造函数信息
$constructor = $reflector->getConstructor();
//获取反射类的构造函数的参数
$dependencies = $constructor->getParameters();
但是A依赖于B ,B依赖于C C依赖于D 等,这样子可以使用递归反射api,就可以得到最终使用A需要实例化哪些类,直接如何传递。
具体代码,如下:
class Ioc
{
protected $instances = [];
public function __construct()
{
}
public function getInstance($abstract){
//获取类的反射信息,也就是类的所有信息
$reflector = new ReflectionClass($abstract);
// echo $reflector->getDocComment(); 获取类的注释信息
//获取反射类的构造函数信息
$constructor = $reflector->getConstructor();
//获取反射类的构造函数的参数
$dependencies = $constructor->getParameters();
if(!$dependencies){
return new $abstract();
}
foreach ($dependencies as $dependency) {
if(!is_null($dependency->getClass())){
$p[] = $this->make($dependency->getClass()->name);
//这里$p[0]是C的实例化对象,$p[1]是D的实例化对象
}
}
//创建一个类的新实例,给出的参数将传递到类的构造函数
return $reflector->newInstanceArgs($p);
}
public function make($abstract)
{
return $this->getInstance($abstract);
}
}
$ioc = new Ioc();
$a = $ioc->make('A');
$a->getB();
到这里我们就可以通过IOC这个类去在控制器之外实例化类(控制反转),实例化过程运用了反射和递归,然后传递给方法形参(依赖注入),给用户使用。
Lavavel把ioc这个工厂类叫做IOC容器。因为实例化类之后存储在变量,变量相当于这些类的容器。名字随便怎么叫啦,理解本质就好。