美文网首页
Laravel IOC容器深度剖析

Laravel IOC容器深度剖析

作者: wymhuster | 来源:发表于2018-10-08 20:44 被阅读0次

    1、基础知识预备

    1.1 PHP 反射详解

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

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

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

    <?php
    class Person{
        public $name;
        public $gender;
        private $age;
    
        public function __construct($name,$gender){
            $this->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容器是自动注入的。

    <?php
    class Train{
        public function go(){
            echo "move by train \n";
        }
    }
    
    class Airplane{
        public function go(){
            echo "move by airplane \n";
        }
    }
    
    class StupidTraveller{
        private $trafficTool;
        public function __construct(){
            $this->trafficTool = new Train();
        }
    
        public function travel(){
            $this->trafficTool->go();
        }
    }
    $mike = new StupidTraveller();
    $mike->travel();
    
    
    <?php
    interface TrafficTool{
        public function go();
    }
    
    class Train implements TrafficTool{
        public function go(){
            echo "move by train \n";
        }
    }
    
    class Airplane implements TrafficTool{
        public function go(){
            echo "move by airplane \n";
        }
    }
    
    class Traveller{
        private $trafficTool;
        public function __construct(TrafficTool $tool){
            $this->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框架关键技术解析》

          <?php
    // 容器类,将接口与实现绑定
    class Container
    {
        // 保存与接口绑定的闭包,
        // 闭包必须能够返回接口的实例。
        protected $bindings = [];
    
        // 为某个接口绑定一个实现,有两种情况:
        //
        // 第一种是绑定接口的实现的类名;
        // 第二种是绑定一个闭包,这个闭包应该返回接口的实例。
        // 不管哪种情况,实例化操作都是调用 build() 方法完成。
        // 第二种情况,主要是为了能够在实例化前后进行额外的操作,
        // 实例化的逻辑就书写在闭包中。
        public function bind($abstract, $concrete = null)
        {
            // 如果提供的参数不是闭包,而是一个类,
            // 则构建一个闭包,直接调用 build() 方法进行实例化
            if (! $concrete instanceof Closure) {
                // 调用闭包时,传入的参数是容器本身,即 $this
                $concrete = function ($c) use ($concrete) {
                    return $c->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

    <?php
    require __DIR__ . '/Container.php';
    
    interface  TrafficTool
    {
        public function go();
    }
    
    class Train implements TrafficTool
    {
        public function go()
        {
            // TODO: Implement go() method.
            echo "move by train";
        }
    }
    
    class Airplane implements TrafficTool
    {
        public function go()
        {
            // TODO: Implement go() method.
            echo "move by airplane\n";
        }
    
    }
    
    class Destination
    {
    
        public function mark()
        {
            echo "I am here";
        }
    
    }
    
    class Traveller
    {
        protected $trafficTool;
        protected $destination;
    
        public function __construct(TrafficTool $tool, Destination $destination)
        {
            $this->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容器,里面已经进行了一系列的绑定。具体的绑定如下。

    <?php
    
    $app = new Illuminate\Foundation\Application(
        realpath(__DIR__.'/../')
    );
    
    
    $app->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容器深度剖析

          本文链接:https://www.haomeiwen.com/subject/jukhaftx.html