如何理解 Laravel 的 Ioc 容器

学习laravel快小一年了,到如今才去研究laravel 的核心 '容器 IOC' 这些概念. 写项目的时候有大概看看关于IOC 文章, 可是没有深刻理解,只是有个概念,赶着写代码, 虽然代码也写的很菜 · - ·php

这几天花了点时间研究了一下laravel 的核心 ‘服务容器’ 而后理解了一下关于IOC 的概念. 不敢说百分百掌握了,可是比以前是有必定加深. 因此决定把本身理解的分享一下, 把本身的第一次博文献给laravel. 理解不到位的还请各位大牛多多指正.laravel

1.依赖

IOC( inversion of controller )叫作控制反转模式,也能够称为(dependency injection ) 依赖注入模式。要理解依赖注入的概念咱们先理解下什么依赖c#

//支付宝支付
class Alipay {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by alipay';
      }
}
//微信支付
class Wechatpay {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by wechatpay';
      }
}
//银联支付
class Unionpay{
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by unionpay';
      }
}

//支付帐单
class PayBill {

      private $payMethod;

      public function __construct( )
      {
          $this->payMethod= new Alipay ();
      }

      public function  payMyBill()
      {
           $this->payMethod->pay();
      }
}


$pb = new PayBill ();
$pb->payMyBill();

经过上面的代码咱们知道,当咱们建立一个class PayBill 的实例的时候, PayBill 的构造函数里面有{ $this->payMethod= new Alipay (); }, 也就是实例化了一个class Alipay . 这个时候依赖就产生了, 这里能够理解为当我想用支付宝支付的时候, 那我首先要获取到一个支付宝的实例,或者理解为获取支付宝的功能支持. 当用咱们完 new 关键字的时候, 依赖其实已经解决了,由于咱们获取了Alipay 的实例. 数组

其实在我知道ioc概念以前,个人代码中大部分都是这种模式 ~ _ ~ . 这种有什么问题呢, 简单来讲, 好比当我想用的不是支付宝而是微信的时候怎么办, 你能作的就是修改Payment 的构造函数的代码,实例化一个微信支付Wechatpay. 微信

若是咱们的程序不是很大的时候可能还感受不出什么,可是当你的代码很是复杂,庞大的时候,若是咱们的需求常常改变,那么修改代码就变的很是麻烦了。因此ioc 的思想就是不要在 class Payment 里面用new 的方式去实例化解决依赖, 并且转为由外部来负责,简单一点就是内部没有new 的这个步骤,经过依赖注入的方式一样的能获取到支付的实例.闭包

2.依赖注入

依赖咱们知道了是什么意思,那依赖注入又是什么意思呢,咱们把上面的代码拓展一下app

//支付类接口
interface Pay
{
    public function pay();
}


//支付宝支付
class Alipay implements Pay {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by alipay';
      }
}
//微信支付
class Wechatpay implements Pay  {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by wechatpay';
      }
}
//银联支付
class Unionpay implements Pay  {
      public function __construct(){}

      public function pay()
      {
          echo 'pay bill by unionpay';
      }
}

//付款
class PayBill {

      private $payMethod;

      public function __construct( Pay $payMethod)
      {
          $this->payMethod= $payMethod;
      }

      public function  payMyBill()
      {
           $this->payMethod->pay();
      }
}

//生成依赖
$payMethod =  new Alipay();
//注入依赖
$pb = new PayBill( $payMethod );
$pb->payMyBill();

上面的代码中,跟以前的比较的话,咱们加入一个Pay 接口, 而后全部的支付方式都继承了这个接口而且实现了pay 这个功能. 可能你们会问为何要用接口,这个咱们稍后会讲到.框架

当咱们实例化PayBill的以前, 咱们首先是实例化了一个Alipay,这个步骤就是生成了依赖了,而后咱们须要把这个依赖注入到PayBill 的实例当中,经过代码咱们能够看到 { $pb = new PayBill( payMethod ); }, 咱们是经过了构造函数把这个依赖注入了PayBill 里面. 这样一来 $pb 这个PayBill 的实例就有了支付宝支付的能力了. 函数

把class Alipay 的实例经过constructor注入的方式去实例化一个 class PayBill. 在这里咱们的注入是手动注入, 不是自动的. 而Laravel 框架实现则是自动注入.学习

3.反射

在介绍IOC 的容器以前咱们先来理解下反射的概念(reflection),由于IOC 容器也是要经过反射来实现的.从网上抄了一段来解释反射是什么意思

"反射它指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助咱们构建复杂,可扩展的应用。其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP语言"

举个简单的例子

class B{

}


class A {

    public function __construct(B $args)
    {
    }

    public function dosomething()
    {
        echo 'Hello world';
    }
}

//创建class A 的反射
$reflection = new ReflectionClass('A');

$b = new B();

//获取class A 的实例
$instance = $reflection ->newInstanceArgs( [ $b ]);

$instance->dosomething(); //输出 ‘Hellow World’

$constructor = $reflection->getConstructor();//获取class A 的构造函数

$dependencies = $constructor->getParameters();//获取class A 的依赖类

dump($constructor);

dump($dependencies);

dump 的获得的$constructor 和 $dependencies 結果以下

//constructor
ReflectionMethod {#351 ▼
        +name: "__construct" 
        +class: "A" 
        parameters: array:1 [▶] 
        extra: array:3 [▶] 
        modifiers: "public"
}

//$dependencies
array:1 [▼
        0 => ReflectionParameter {#352 ▼
         +name: "args"
          position: 0
          typeHint: "B"
      }
]

经过上面的代码咱们能够获取到 class A 的构造函数,还有构造函数依赖的类,这个地方咱们依赖一个名字为 'args' 的量,并且经过TypeHint能够知道他是类型为 Class B; 反射机制可让我去解析一个类,能过获取一个类里面的属性,方法 ,构造函数, 构造函数须要的参数。 有个了这个才能实现Laravel 的IOC 容器.

4.IOC容器

接下来介绍一下Laravel 的IOC服务容器概念. 在laravel框架中, 服务容器是整个laravel的核心,它提供了整个系统功能及服务的配置, 调用. 容器按照字面上的理解就是装东西的东西,好比冰箱, 当咱们须要冰箱里面的东西的时候直接从里面拿就好了. 服务容器也能够这样理解, 当程序开始运行的时候,咱们把咱们须要的一些服务放到或者注册到(bind)到容器里面,当我须要的时候直接取出来(make)就好了. 上面提到的 bind 和 make 就是注册 和 取出的 两个动做.

5. IOC 容器代码

好了,说了这么多,下面要上一段容器的代码了. 下面这段代码不是laravel 的源码, 而是来自一本书《laravel 框架关键技术解析》. 这段代码很好的还原了laravel 的服务容器的核心思想. 代码有点长, 小伙伴们要耐心看. 固然小伙伴彻底能够试着运行一下这段代码,而后调试一下,这样会更有助于理解.

<?php 

//容器类装实例或提供实例的回调函数
class Container {

    //用于装提供实例的回调函数,真正的容器还会装实例等其余内容
    //从而实现单例等高级功能
    protected $bindings = [];

    //绑定接口和生成相应实例的回调函数
    public function bind($abstract, $concrete=null, $shared=false) {
        
        //若是提供的参数不是回调函数,则产生默认的回调函数
        if(!$concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }
        
        $this->bindings[$abstract] = compact('concrete', 'shared');
    }

    //默认生成实例的回调函数
    protected function getClosure($abstract, $concrete) {
        
        return function($c) use ($abstract, $concrete) {
            $method = ($abstract == $concrete) ? 'build' : 'make';
            return $c->$method($concrete);
        };
        
    }

    public function make($abstract) {
        $concrete = $this->getConcrete($abstract);

        if($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }
        
        return $object;
    }

    protected function isBuildable($concrete, $abstract) {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    //获取绑定的回调函数
    protected function getConcrete($abstract) {
        if(!isset($this->bindings[$abstract])) {
            return $abstract;
        }

        return $this->bindings[$abstract]['concrete'];
    }

    //实例化对象
    public function build($concrete) {

        if($concrete instanceof Closure) {
            return $concrete($this);
        }

        $reflector = new ReflectionClass($concrete);
        if(!$reflector->isInstantiable()) {
            echo $message = "Target [$concrete] is not instantiable";
        }

        $constructor = $reflector->getConstructor();
        if(is_null($constructor)) {
            return new $concrete;
        }

        $dependencies = $constructor->getParameters();
        $instances = $this->getDependencies($dependencies);

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

    //解决经过反射机制实例化对象时的依赖
    protected function getDependencies($parameters) {
        $dependencies = [];
        foreach($parameters as $parameter) {
            $dependency = $parameter->getClass();
            if(is_null($dependency)) {
                $dependencies[] = NULL;
            } else {
                $dependencies[] = $this->resolveClass($parameter);
            }
        }

        return (array)$dependencies;
    }

    protected function resolveClass(ReflectionParameter $parameter) {
        return $this->make($parameter->getClass()->name);
    }

}

上面的代码就生成了一个容器,下面是如何使用容器

$app = new Container();
$app->bind("Pay", "Alipay");//Pay 为接口, Alipay 是 class Alipay
$app->bind("tryToPayMyBill", "PayBill"); //tryToPayMyBill能够当作是Class PayBill 的服务别名

//经过字符解析,或获得了Class PayBill 的实例
$paybill = $app->make("tryToPayMyBill"); 

//由于以前已经把Pay 接口绑定为了 Alipay,因此调用pay 方法的话会显示 'pay bill by alipay '
$paybill->payMyBill();

当咱们实例化一个Container获得 $app 后, 咱们就能够向其中填充东西了

$app->bind("Pay", "Alipay");
$app->bind("tryToPayMyBill", "PayBill");

当执行完这两行绑定码后, $app 里面的属性 $bindings 就已经有了array 值,是啥样的呢,咱们来看下

array:2 [▼
 "App\Http\Controllers\Pay" => array:2 [▼
     "concrete" => Closure {#355 ▼
       class: "App\Http\Controllers\Container" 
       this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
       parameters: array:1 [▼
         "$c" => []
       ] 
       use: array:2 [▼
         "$abstract" => "App\Http\Controllers\Pay"
        "$concrete" => "App\Http\Controllers\Alipay"
       ] 
       file: "C:\project\test\app\Http\Controllers\IOCController.php" line:       "119 to 122"
   } 
   "shared" => false 
 ]

"tryToPayMyBill" => array:2 [▼
     "concrete" => Closure {#359 ▼
         class: "App\Http\Controllers\Container" 
         this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
         parameters: array:1 [▼
               "$c" => []
         ] 
         use: array:2 [▼
               "$abstract" => "tryToPayMyBill" 
               "$concrete" => "\App\Http\Controllers\PayBill"
         ] 
         file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122"
   } 
     "shared" => false 
 ]
]

当执行 $paybill = $app->make("tryToPayMyBill"); 的时候, 程序就会用make方法经过闭包函数的回调开始解析了.

解析'tryToPayBill' 这个字符串, 程序经过闭包函数 和build方法会获得 'PayBill' 这个字符串,该字符串保存在$concrete 上. 这个是第一步. 而后程序还会以相似于递归方式 将$concrete 传入 build() 方法. 这个时候build里面就获取了$concrete = 'PayBill'. 这个时候反射就派上了用场, 你们有没有发现,PayBill 不就是 class PayBill 吗? 而后在经过反射的方法ReflectionClass('PayBill') 获取PayBill 的实例. 以后经过getConstructor(),和getParameters() 等方法知道了 Class PayBill 和 接口Pay 存在依赖

//$constructor = $reflector->getConstructor();
ReflectionMethod {#374 ▼
    +name: "__construct" 
    +class: "App\Http\Controllers\PayBill" 
    parameters: array:1 [▼
          "$payMethod" => ReflectionParameter {#371 ▼
              +name: "payMethod" 
              position: 0 typeHint: "App\Http\Controllers\Pay"
          }
    ]
     extra: array:3 [▼
          "file" => "C:\project\test\app\Http\Controllers\IOCController.php"
          "line" => "83 to 86" 
          "isUserDefined" => true 
      ] 
    modifiers: "public"
}


//$dependencies = $constructor->getParameters();
array:1 [▼
    0 => ReflectionParameter {#370 ▼
        +name: "payMethod" 
        position: 0 
        typeHint: "App\Http\Controllers\Pay"
        }
]

接着,咱们知道了有'Pay'这个依赖以后呢,咱们要作的就是解决这个依赖,经过 getDependencies($parameters), 和 resolveClass(ReflectionParameter $parameter) ,还有以前的绑定$app->bind("Pay", "Alipay"); 在build 一次的时候,经过 return new $concrete;到这里咱们获得了这个Alipay 的实例

if(is_null($constructor)) {
            return new $concrete;
        }

到这里咱们总算结局了这个依赖, 这个依赖的结果就是实例化了一个 Alipay. 到这里还没结束

$instances = $this->getDependencies($dependencies);

上面的$instances 数组只有一个element 那就是 Alipay 实例

array:1 [▼0 =>Alipay
      {#380}
 ]

最终经过 newInstanceArgs() 方法, 咱们获取到了 PayBill 的实例。

return $reflector->newInstanceArgs($instances);

到这里整个流程就结束了, 咱们经过 bind 方式绑定了一些依赖关系, 而后经过make 方法 获取到到咱们想要的实例. 在make中有牵扯到了闭包函数,反射等概念.

好了,当咱们把容器的概念理解了以后,咱们就能够理解下为何要用接口这个问题了. 若是说我不想用支付宝支付,我要用微信支付怎么办,too easy.

$app->bind("Pay", "Wechatpay");
$app->bind("tryToPayMyBill", "PayBill");
$paybill = $app->make("tryToPayMyBill"); 
$paybill->payMyBill();

是否是很简单呢, 只要把绑定从'Alipay' 改为 'Wechatpay' 就好了,其余的都不用改. 这就是为何咱们要用接口. 只要你的支付方式继承了pay 这个接口,而且实现pay 这个方法,咱们就可以经过绑定正常的使用. 这样咱们的程序就很是容易被拓展,由于之后可能会出现成百上千种的支付方式.

好了,到这里不知道小伙伴有没有理解呢,我建议你们能够试着运行下这些代码, 这样理解起来会更快.同时推荐你们去看看 《laravel 框架关键技术解析》这本书,写的仍是不错的.


转载请注明:做者[[哎哟个人巴扎黑]

相关文章
相关标签/搜索