thinkphp 5.1框架解析(三):容器和依赖注入

在上一篇文章中咱们讲到了 ThinkPHP 如何实现自动加载,若是想看的话能够看
ThinkPHP5.1 源码浅析(二)自动加载机制php

在阅读本篇文章 以前,我但愿你掌握了 IOC 、DI 、Facade的基本知识,若是不了解,请先查看着几篇文章。thinkphp

深刻理解控制反转(IoC)和依赖注入(DI)segmentfault

那么步入正题。cookie

服务调用

基于分析框架的 入口脚本文件index.php闭包

// 加载基础文件
require __DIR__ . '/../thinkphp/base.php';

// 支持事先使用静态方法设置Request对象和Config对象

// 执行应用并响应
Container::get('app')->run()->send();

上面 base.php 中的做用是载入自动加载机制,和异常处理,以及开启日志功能。app

// 执行应用并响应
Container::get('app')->run()->send();

在这里才是使用了 IOC 容器功能,获取 app 这个容器1框架

进入 Container 中以后咱们先介绍他的类属性函数

protected static $instance; // 定义咱们的容器类实例,采用单例模式,只实例化一次
protected $instances = [];    // 容器中的对象实例
protected $bind = [];        // 容器绑定标识
protected $name = [];        // 容器标识别名

$instances 是实现了 注册树模式2,存储值测试

array (
  'think\\App' => App实例,
  'think\\Env' => Env实例,
  'think\\Config' => Config实例,
   ...
)

bind 在初始化时,会载入一堆初始数据,记录一堆类别名和 类名的映射关系。ui

protected $bind = [
      'app' => 'think\\App',
      'build' => 'think\\Build',
      'cache' => 'think\\Cache',
      'config' => 'think\\Config',
      'cookie' => 'think\\Cookie',
        ...
    ]

namebind 属性记录的值都是很相似的,都是 类别名和 类名的映射关系。区别是,name 记录的是 已经实例化后的 映射关系。

进入get方法

public static function get($abstract, $vars = [], $newInstance = false)
{
    return static::getInstance()->make($abstract, $vars, $newInstance);
}

这一段代码没什么好讲的,就是先获取当前容器的实例(单例),并实例化。

进入 make 方法

public function make($abstract, $vars = [], $newInstance = false)
{
    if (true === $vars) {
        // 老是建立新的实例化对象
        $newInstance = true;
        $vars        = [];
    }
    // 若是已经存在而且实例化的类,就用别名拿到他的类
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
    // 若是已经实例化,而且不用每次建立新的实例的话,就直接返回注册树上的实例
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }
    // 若是咱们绑定过这个类,例如 'app' => 'think\\App',
    if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract];
        // 由于ThinkPHP 实现能够绑定一个闭包或者匿名函数进入,这里是对闭包的处理
        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // 记录 映射关系,并按照 类名来实例化,如 think\\App
            $this->name[$abstract] = $concrete;
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // 按照类名调用该类
        $object = $this->invokeClass($abstract, $vars);
    }

    if (!$newInstance) {
        $this->instances[$abstract] = $object;
    }
    // 返回制做出来的该类
    return $object;
}

咱们拆分一下,

if (true === $vars) {
        // 老是建立新的实例化对象
        $newInstance = true;
        $vars        = [];
}

这段代码是 让咱们函数能够 使用 make($abstract, true)的方式调用此函数,使咱们每次获得的都是新的实例。(我以为这种方式不是很好,每一个变量的形成含义不明确)

// 若是已经存在而且实例化的类,就用别名拿到他的类
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
    // 若是已经实例化,而且不用每次建立新的实例的话,就直接返回注册树上的实例
    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }

前面说过,name 中存放的是已经实例化的 别名=> 类名 的映射关系,咱们在这里尝试取出 类名,若是该类实例化,就直接返回。

// 若是咱们绑定过这个类,例如 'app' => 'think\\App',
    if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract];
        // 由于ThinkPHP 实现能够绑定一个闭包或者匿名函数进入,这里是对闭包的处理
        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            // 记录 映射关系,并按照 类名来实例化,如 think\\App
            $this->name[$abstract] = $concrete;
            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        // 按照类名调用该类
        $object = $this->invokeClass($abstract, $vars);
    }

这里是看咱们须要容器加载的类是否之前绑定过别名(咱们也能够直接 bind('classNickName') 来设置一个)

  1. 若是绑定过,那么就来实例化它。
  2. 若是没有,那么就认定他是一个类名,直接调用。3

门面模式

在上面的 IOC 容器中,咱们须要 $ioc->get('test'); 才能拿到 test 类,才能使用咱们的 $user->hello()方法进行打招呼,有了门面以后,咱们能够直接 用 Test::hello() 进行静态调用,下面咱们就来介绍一下这个

在咱们编写代码时常常会用到 facade包下的类来接口的静态调用,咱们在这里举一下官网的例子

假如咱们定义了一个app\common\Test类,里面有一个hello动态方法。

<?php
namespace app\common;

class Test
{
    public function hello($name)
    {
        return 'hello,' . $name;
    }
}

调用hello方法的代码应该相似于:

$test = new \app\common\Test;
echo $test->hello('thinkphp'); // 输出 hello,thinkphp

接下来,咱们给这个类定义一个静态代理类app\facade\Test(这个类名不必定要和Test类一致,但一般为了便于管理,建议保持名称统一)。

<?php
namespace app\facade;

use think\Facade;

class Test extends Facade
{
    protected static function getFacadeClass()
    {
        return 'app\common\Test';
    }
}

只要这个类库继承think\Facade,就可使用静态方式调用动态类app\common\Test的动态方法,例如上面的代码就能够改为:

// 无需进行实例化 直接以静态方法方式调用hello
echo \app\facade\Test::hello('thinkphp');

结果也会输出 hello,thinkphp

说的直白一点,Facade功能可让类无需实例化而直接进行静态方式调用。

Facade工做原理

  1. Facede 核心实现原理就是在 Facade 提早注入IoC容器。
  2. 定义一个服务提供者的外观类,在该类定义一个类的变量,跟ioc容器绑定的key同样,
  3. 经过静态魔术方法__callStatic能够获得当前想要调用的 hello 方法
  4. 使用static::$ioc->make('Test');

为何要使用 Facade

使用Facades其实最主要的就是它提供了简单,易记的语法,从而无需手动注入或配置长长的类名。此外,因为他们对 PHP 静态方法的独特调用,使得测试起来很是容易。


  1. 在这里系统找不到 Container 类的位置,因此会执行自动加载机制去寻找 Container 的位置,并加载它
  2. 把一堆实例挂在树上,须要的时候在拿来用。
  3. 直接调用是使用了反射后的结果,关于反射的知识点在自行查看
相关文章
相关标签/搜索