ThinkPHP 5.1 的容器用到了设计模式中的单例模式和注册树模式。整个容器是一个单例,然后在容器中注册上需要用到的类。

看一下入口文件:

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------

// [ 应用入口文件 ]
namespace think;

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

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

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

入口文件中就取出了容器中的app 。这里的app 是一个别名。

get方法

  /**
     * 获取容器中的对象实例
     * @access public
     * @param  string        $abstract       类名或者标识
     * @param  array|true    $vars           变量
     * @param  bool          $newInstance    是否每次创建新的实例
     * @return object
     */
    public static function get($abstract, $vars = [], $newInstance = false)
    {
        return static::getInstance()->make($abstract, $vars, $newInstance);
    }

单例

   public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }

        return static::$instance;
    }

创建类的实例 make

第一遍创建

/**
     * 创建类的实例
     * @access public
     * @param  string        $abstract       类名或者标识
     * @param  array|true    $vars           变量
     * @param  bool          $newInstance    是否每次创建新的实例
     * @return object
     */
    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];
        }

        if (isset($this->bind[$abstract])) {
            $concrete = $this->bind[$abstract];

            if ($concrete instanceof Closure) {
                $object = $this->invokeFunction($concrete, $vars);
            } else {
                $this->name[$abstract] = $concrete;
                return $this->make($concrete, $vars, $newInstance);
            }
        } else {
            $object = $this->invokeClass($abstract, $vars);
        }

        if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }

创建类的实例的时候有三个参数,第一个参数是类或者类的标识,第二个参数是创建类的时候用到的参数,也就是类的构造函数的参数,第三个参数是是否创建新实例。

拿入口函数来说

Container::get('app')->run()->send();

只传入了一个参数 app,没有给类传参数。

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

先说一下容器的几个属性:

/**
 * 容器对象实例
 * @var Container
 */
protected static $instance;

/**
 * 容器中的对象实例
 * @var array
 */
protected $instances = [];

/**
 * 容器绑定标识
 * @var array
 */
protected $bind = [
    'app'                   => App::class,
    'build'                 => Build::class,
    'cache'                 => Cache::class,
    'config'                => Config::class,
    'cookie'                => Cookie::class,
    'debug'                 => Debug::class,
    'env'                   => Env::class,
    'hook'                  => Hook::class,
    'lang'                  => Lang::class,
    'log'                   => Log::class,
    'middleware'            => Middleware::class,
    'request'               => Request::class,
    'response'              => Response::class,
    'route'                 => Route::class,
    'session'               => Session::class,
    'template'              => Template::class,
    'url'                   => Url::class,
    'validate'              => Validate::class,
    'view'                  => View::class,
    'rule_name'             => route\RuleName::class,
    // 接口依赖注入
    'think\LoggerInterface' => Log::class,
];

/**
 * 容器标识别名
 * @var array
 */
protected $name = [];

$instance 是容器。

$instances=[] 这个数组存放的是容器中对象的实例。

$bind 这个数组里面存的是系统自定义的几个别名。可以看到 app 对应的类是 App::classApp 这个类在自动加载里面注册的是facade\App::classfacade\App 是一个门脸模式,最终定位到 think\App 这个类。

$name 这个数组存的是容器标志的别名。就是说,容器中存入一个对象的时候会在$name 里面存入别名,$instances 里面存入对象的实例。

接着往下:

$abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
if (isset($this->instances[$abstract]) && !$newInstance) {
    return $this->instances[$abstract];
}

先看别名里面存的有没有,然后看有没有这个对象的实例。如果有并且不用创建新实例就直接返回。app 当前肯定是没有的,继续往下执行。

if (isset($this->bind[$abstract])) {
        $concrete = $this->bind[$abstract];

        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            $this->name[$abstract] = $concrete;
            return $this->make($concrete, $vars, $newInstance);
        }
    }

看一下传入的$abstract 是不是一个系统预定义的别名,是的话把别名对应的类名存入$concrete 。这里app 就是App::class

还要看传入的是不是一个闭包,闭包的返回对象和类的返回是两种处理方法。

当前传入的是App::class 会重新执行一下make(App::class)

第二遍创建

重新执行的时候,容器已经有了,也即$instance 已经有了。

$abstract=App::class bind 数组中没有这个,所以执行

$object = $this->invokeClass($abstract, $vars);

创建一个对象

if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }

把这个对象存到instances 这个数组里面。

invokeClass

    /**
     * 调用反射执行类的实例化 支持依赖注入
     * @access public
     * @param  string    $class 类名
     * @param  array     $vars  参数
     * @return mixed
     */
    public function invokeClass($class, $vars = [])
    {
        try {
            $reflect = new ReflectionClass($class);

            if ($reflect->hasMethod('__make')) {
                $method = new ReflectionMethod($class, '__make');

                if ($method->isPublic() && $method->isStatic()) {
                    $args = $this->bindParams($method, $vars);
                    return $method->invokeArgs(null, $args);
                }
            }

            $constructor = $reflect->getConstructor();

            $args = $constructor ? $this->bindParams($constructor, $vars) : [];

            return $reflect->newInstanceArgs($args);

        } catch (ReflectionException $e) {
            throw new ClassNotFoundException('class not exists: ' . $class, $class);
        }
    }

这个里面主要有以下几个要说明的:

ReflectionClass($class)

反射类,可以通过这个反射类,得到类的方法,属性等等。

hasMethod

看这个反射类里面有没有某个方法。

getConstructor

获取类的构造函数。

newInstanceArgs($args)

把参数传给构造函数,返回一个新的对象。

ReflectionMethod($class, '__make')

反射方法类,这个就相当于获得类的方法。

isPublic

判断是不是公共方法

isStatic

判断是不是静态方法

invokeArgs(null, $args)

使用数组给方法传送参数,并执行他。第一个参数是 null 表示调用的是静态方法。

总结一下上述代码:

传入的类里面有公共的静态的__make 方法的时候,就执行__make 方法返回对象。

没有的话就执行默认的构造函数返回对象。

bindParams

/**
 * 绑定参数
 * @access protected
 * @param  \ReflectionMethod|\ReflectionFunction $reflect 反射类
 * @param  array                                 $vars    参数
 * @return array
 */
protected function bindParams($reflect, $vars = [])
{
    if ($reflect->getNumberOfParameters() == 0) {
        return [];
    }

    // 判断数组类型 数字数组时按顺序绑定参数
    reset($vars);
    $type   = key($vars) === 0 ? 1 : 0;
    $params = $reflect->getParameters();

    foreach ($params as $param) {
        $name      = $param->getName();
        $lowerName = Loader::parseName($name);
        $class     = $param->getClass();

        if ($class) {
            $args[] = $this->getObjectParam($class->getName(), $vars);
        } elseif (1 == $type && !empty($vars)) {
            $args[] = array_shift($vars);
        } elseif (0 == $type && isset($vars[$name])) {
            $args[] = $vars[$name];
        } elseif (0 == $type && isset($vars[$lowerName])) {
            $args[] = $vars[$lowerName];
        } elseif ($param->isDefaultValueAvailable()) {
            $args[] = $param->getDefaultValue();
        } else {
            throw new InvalidArgumentException('method param miss:' . $name);
        }
    }

    return $args;
}

关键函数:

getNumberOfParameters(); //获取反射函数参数数目
getParameters();//这是一个参数列表,数组形式这个不是一般的数组,是特定的一种格式
getName();//参数名称
getClass();//类名称
array_shift([]);//删除数组的第一个元素,并且返回删除的这个元素
isDefaultValueAvailable();//反射函数是否有默认参数
getDefaultValue();//反射函数的默认参数

本文由 yang 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

楼主残忍的关闭了评论