Laravel IOC容器深度剖析

1、基础知识预备

1.1 PHP 反射详解

什么是反射?直观理解就是根据到达地找到出发地和来源。比如,一个光秃秃的对象,我们可以仅仅通过这个对象就能知道它所属的类,拥有哪些方法。

PHP的反射是指在PHP运行状态过程中,扩展分析PHP程序,导出或提出关于类、方法、属性、参数等详细信息,包括注释。这种动态获取信息以及动态调用对象方法的功能成为反射API

下面是一个PHP反射的一个简单demo。
详细可参考PHP docuement http://php.net/manual/zh/book.reflection.php

name = $name;
        $this->gender = $gender;
    }

    public function __set($key, $value){
        $this->$key = $value;
    }

    public function __get($key){
        if(!isset($this->$key)){
            return "$key not exists";
        }
        return $this->$key;
    }
}
//用ReflectionClass得到A的反射类对象,通过反射类对象可以得到类的各种属性,
//包括类名空间,父类,类名等,使用newInstanceArgs可以传入构造函数的参数创建一个新的类的实例
$reflect = new ReflectionClass("Person");
//注入参数
$student = $reflect->newInstanceArgs(["xiaoming", "male"]);
echo $student->name;
echo "\n";
echo $student->gender;
echo "\n";
echo $student->age;
echo "\n";
$student->age = 20;
echo $student->age;
echo "\n";

output:

xiaoming
male
age not exists
20

1.2 依赖

程序的执行过程就是方法的调用过程,有方法调用,就必然会促使对象和对象之间产生依赖,除非一个对象不参与程序的运行,这样的对象就像一座孤岛,与其他对象没有任何交互,但是这样的对象也就没有任何存在的价值了。因此,在我们的程序代码中,任何一个对象必然会与其他一个甚至更多个对象产生依赖关系。

依赖产生的原因主要有:

  • 方法调用
  • 继承(子类依赖于父类)
  • 传递参数(一个类作为参数传递给另外一个类的成员方法时)
  • 临时变量引入

下面有两段代码。
第一段是直接在代码中引入了Train类,这时候就形成了依赖。但是假如旅客不想坐火车,想坐飞机,只能修改StupidTraveller中的代码。
第二段代码使用依赖注入的方式解除了上面StupidTraveller对Train的依赖。代码的可扩展性变强了很多,如果还有其他的交通工具比如Car,只需要将Car注入Traveller类中即可。此处的依赖注入是手动的,Laravel的IOC容器是自动注入的。

trafficTool = new Train();
    }

    public function travel(){
        $this->trafficTool->go();
    }
}
$mike = new StupidTraveller();
$mike->travel();

trafficTool = $tool;
    }

    public function travel(){
        $this->trafficTool->go();
    }
}
//在这里进行了依赖注入
$mike = new Traveller(new Train());
$mike->travel();

2、IOC容器

2.1 IOC容器是什么?

在理解IOC之前,首先从1.2的例子中理解下什么是DI(Dependency Injection)。Traveller类在初始化的时候注入了TrafficTool类,这就是一个典型的依赖注入的例子。依赖注入有两个好处,一个是解耦,将依赖之间解耦,显然Traveller类要比StupidTraveller要优雅很多;第二个是由于已经解耦了,方便做单元测试,尤其是Mock测试。如果TrafficTool是一个数据库的操作类(打个比方啊),这个时候你做单元测试的时候可以直接Mock一个虚拟的数据库类注入到Traveller中去来做单元测试。

IOC(Inversion of Control) 控制反转是一种面对对象编程的设计原则,用于降低代码之间的耦合度。其基本思想是借助第三方实现具有依赖关系的对象之间的解耦。


混乱的依赖关系
IOC容器

软件系统在没有IOC之前,对象之间相互依赖,那么对象A在初始化或者运行到某一个点的时候,自己必须主动去创建一个对象B,或者使用之间已经创建好的对象B。无论是创建还是使用对象B,控制权都在自己手上。

但是在有了IOC容器后,这种情况改变了,由于IOC的加入,对象之间的直接联系消失了。在对象A运行时需要对象B的时候,IOC会主动创建一个对象B注入到对象A需要的地方。这也是Laravel中实现的IOC容器的神奇之处,自动注入依赖。

2.2 一段比较经典的IOC容器的代码

下面这段代码源于《laravel框架关键技术解析》

      build($concrete);
            };
        }
        $this->bindings[$abstract] = $concrete;
    }

    // 生成指定接口的实例
    public function make($abstract)
    {
        // 取出闭包
        $concrete = $this->bindings[$abstract];
        // 运行闭包,即可取得一个实例
        return $concrete($this);
    }

    public function build($class)
    {
        // 取得类的反射
        $reflector = new ReflectionClass($class);

        // 检查类是否可实例化
        if (! $reflector->isInstantiable()) {
            // 如果不能,意味着接口不能正常工作,报错
            echo $message = "Target [$class] is not instantiable";
        }

        // 取得构造函数的反射
        $constructor = $reflector->getConstructor();

        // 检查是否有构造函数
        if (is_null($constructor)) {
            // 如果没有,就说明没有依赖,直接实例化
            return new $class;
        }

        // 取得包含每个参数的反射的数组
        $parameters = $constructor->getParameters();
        // 返回一个真正的参数列表,那些被类型提示的参数已经被注入相应的实例
        $realParameters = $this->resolveDependencies($parameters);

        return $reflector->newInstanceArgs($realParameters);
    }

    protected function resolveDependencies($parameters)
    {
        $realParameters = [];
        foreach($parameters as $parameter) {
            // 如果一个参数被类型提示为类 foo,
            // 则这个方法将返回类 foo 的反射
            $dependency = $parameter->getClass();
            if(is_null($dependency)) {
                $realParameters[] = NULL;
            } else {
                $realParameters[] = $this->make($dependency->name);
            }
        }

        return (array)$realParameters;
    }
}

下面在用上面的IOC容器来实现先前的Traveller

trafficTool = $tool;
        $this->destination = $destination;
    }

    public function travel()
    {
        $this->trafficTool->go();
        $this->destination->mark();
    }
}


// 实例化容器
$app = new Container();
//绑定某一功能到ioc
//将Train绑定到TrafficTool,abstract 是TrafficTool, concrete是Train
$app->bind('TrafficTool', 'AirPlane');
$app->bind("Destination", "Destination");
$app->bind('travellerA', 'Traveller');
//实例化对象
$tra = $app->make('travellerA');
$tra->travel();

注意$tra = $app->make('travellerA');这里并没有手动的注入依赖TrafficTool。这个依赖在container的resolveDependencies方法中获取,最后在$reflector->newInstanceArgs($realParameters)这行代码中间进行了注入。并且这一系列的注入都不是手动的,注入都是自动完成的。

2.3、laravel中的IOC容器

在laravel初始化的地方index.php中有这样一行代码

/*
|--------------------------------------------------------------------------
| Turn On The Lights
|--------------------------------------------------------------------------
|
| We need to illuminate PHP development, so let us turn on the lights.
| This bootstraps the framework and gets it ready for use, then it
| will load up this application so that we can run it and send
| the responses back to the browser and delight our users.
|
*/

$app = require_once __DIR__.'/../bootstrap/app.php';

开灯了!!!这里的app就是IOC容器,里面已经进行了一系列的绑定。具体的绑定如下。

singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);
return $app;

容器就是在vendor/laravel/framework/src/illuminate/Container/Container.php中实现的,具体的实现代码要比上面的那个简单版复杂很多,但是思想是一样的。

你可能感兴趣的:(Laravel IOC容器深度剖析)