Laravel框架 - IOC容器详解

IOC 容器代码

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

php 复制代码
<?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);
    }
 
}
 

依赖注入

复制代码
创建接口 + 实现接口:

1,定义一个名为"Pay"的接口,接口定义了实现类必须提供的方法pay()
2,创建一个实现类Alipay,在这个例子中,Alipay类实现了Pay接口,意味着它必须实现接口中定义的所有方法。
php 复制代码
 
//支付类接口
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();
      }
}

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

php 复制代码
$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(); 

好了,当我们把容器的概念理解了之后,我们就可以理解下为什么要用接口这个问题了. 如果说我不想用支付宝支付,我要用微信支付怎么办,too easy.

php 复制代码
$app->bind("Pay", "Wechatpay");
$app->bind("tryToPayMyBill", "PayBill");
$paybill = $app->make("tryToPayMyBill"); 
$paybill->payMyBill();

是不是很简单呢, 只要把绑定从'Alipay' 改成 'Wechatpay' 就行了,其他的都不用改. 这就是为什么我们要用接口. 只要你的支付方式继承了pay 这个接口,并且实现pay 这个方法,我们就能够通过绑定正常的使用. 这样我们的程序就非常容易被拓展,因为以后可能会出现成百上千种的支付方式.

逻辑描述

当我们实例化一个Container得到 $app 后, 我们就可以向其中填充东西了

php 复制代码
$app->bind("Pay", "Alipay");
$app->bind("tryToPayMyBill", "PayBill"); 

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

php 复制代码
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 存在依赖

php 复制代码
//$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 的实例

php 复制代码
if(is_null($constructor)) {
    return new $concrete;
}

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

php 复制代码
$instances = $this->getDependencies($dependencies);

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

php 复制代码
  array:1 [0 =>Alipay
      {#380}
 ]

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

php 复制代码
 return $reflector->newInstanceArgs($instances);

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

相关推荐
JaguarJack10 小时前
FrankenPHP 原生支持 Windows 了
后端·php·服务端
BingoGo10 小时前
FrankenPHP 原生支持 Windows 了
后端·php
JaguarJack1 天前
PHP 的异步编程 该怎么选择
后端·php·服务端
BingoGo1 天前
PHP 的异步编程 该怎么选择
后端·php
JaguarJack2 天前
为什么 PHP 闭包要加 static?
后端·php·服务端
ServBay3 天前
垃圾堆里编码?真的不要怪 PHP 不行
后端·php
用户962377954483 天前
CTF 伪协议
php
BingoGo5 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack5 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo6 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php