yii2源码分析之组件实例化流程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了yii2源码分析之组件实例化流程相关的知识,希望对你有一定的参考价值。

读本篇文章,建议先看看我之前的文章php依赖注入


到此,现在我们正式开始分析yii2框架组件构造流程


我们先从yii\di\ServiceLocator(服务定位器)入手吧!!让我们先看个实例:

use yii\di\ServiceLocator;
use yii\caching\FileCache;

$locator = new ServiceLocator;

// 通过一个可用于创建该组件的类名,注册 "cache" (缓存)组件。
$locator->set('cache', 'yii\caching\ApcCache');

// 通过一个可用于创建该组件的配置数组,注册 "db" (数据库)组件。
$locator->set('db', [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=localhost;dbname=demo',
    'username' => 'root',
    'password' => '',
]);


我们直接打开ServiceLocator的set方法:

//这个函数很简单,就是定义一个id对应$definition,并保存到$this->_definitions数组中
public function set($id, $definition)
{
    if ($definition === null) {
        unset($this->_components[$id], $this->_definitions[$id]);
        return;
    }

    unset($this->_components[$id]);

    if (is_object($definition) || is_callable($definition, true)) {
        // an object, a class name, or a php callable
        $this->_definitions[$id] = $definition;
    } elseif (is_array($definition)) {
        // a configuration array
        if (isset($definition['class'])) {
            $this->_definitions[$id] = $definition;
        } else {
            throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");
        }
    } else {
        throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));
    }
}


现在我们有set方法,如果我们需要指定类名的对象呢,这时候需要看看ServiceLocator的get方法:

public function get($id, $throwException = true)
{
    if (isset($this->_components[$id])) {
        return $this->_components[$id];
    }

    if (isset($this->_definitions[$id])) {
        $definition = $this->_definitions[$id];
        //如果是一个对象,则直接返回
        if (is_object($definition) && !$definition instanceof Closure) {
            return $this->_components[$id] = $definition;
        } else {
            //Yii::createObject创建对象实例(重点方法)
            return $this->_components[$id] = Yii::createObject($definition);
        }
    } elseif ($throwException) {
        throw new InvalidConfigException("Unknown component ID: $id");
    } else {
        return null;
    }
}


跟踪Yii::createObject方法

public static function createObject($type, array $params = [])
{
    if (is_string($type)) {
        //static::$container就是yii\di\Container对象(依赖注入容器)
        return static::$container->get($type, $params);
    } elseif (is_array($type) && isset($type['class'])) {
        $class = $type['class'];
        unset($type['class']);
        return static::$container->get($class, $params, $type);
    } elseif (is_callable($type, true)) {
        return call_user_func($type, $params);
    } elseif (is_array($type)) {
        throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
    } else {
        throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
    }
}


我们来看看yii2的依赖注入容器怎么实现的,打开yii\di\Container的get方法:

public function get($class, $params = [], $config = [])
{
    //$this->_singletons保存单例对象
    if (isset($this->_singletons[$class])) {
        // singleton
        return $this->_singletons[$class];
    } elseif (!isset($this->_definitions[$class])) {
        //通过php反射机制实现$class的实例化
        return $this->build($class, $params, $config);
    }
    //代表已经使用过set方法定义过$class
    $definition = $this->_definitions[$class];

    if (is_callable($definition, true)) {
        $params = $this->resolveDependencies($this->mergeParams($class, $params));
        $object = call_user_func($definition, $this, $params, $config);
    } elseif (is_array($definition)) {
        $concrete = $definition['class'];
        unset($definition['class']);

        $config = array_merge($definition, $config);
        $params = $this->mergeParams($class, $params);

        if ($concrete === $class) {
            $object = $this->build($class, $params, $config);
        } else {
            $object = $this->get($concrete, $params, $config);
        }
    } elseif (is_object($definition)) {
        return $this->_singletons[$class] = $definition;
    } else {
        throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition));
    }

    if (array_key_exists($class, $this->_singletons)) {
        // 单例对象
        $this->_singletons[$class] = $object;
    }

    return $object;
}


我们打开这个$this->build方法:

protected function build($class, $params, $config)
{
    /* 
        $reflection为反射类
        $denpendencies为$class类构造函数参数,如果构造函数参数里面有依赖别的类,举例:
        [new Instance(id属性=该类名),new Instance(id属性=该类名)]
    */
    list ($reflection, $dependencies) = $this->getDependencies($class);

    //将$params的key和值加到$class构造函数参数数组列表中
    foreach ($params as $index => $param) {
        $dependencies[$index] = $param;
    }
        //将构造函数参数中有yii\di\Instance的id属性不为空,则将继续递归调用$this->get将所依赖的类实例化
    $dependencies = $this->resolveDependencies($dependencies, $reflection);
    if (empty($config)) {
        return $reflection->newInstanceArgs($dependencies);
    }

    if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) {
        // set $config as the last parameter (existing one will be overwritten)
        $dependencies[count($dependencies) - 1] = $config;
        return $reflection->newInstanceArgs($dependencies);
    } else {
        $object = $reflection->newInstanceArgs($dependencies);
        //将$config数组赋值到实例化对象的属性上
        foreach ($config as $name => $value) {
            $object->$name = $value;
        }
        return $object;
    }
}


继续追$this->getDependencies方法:

//得到[$reflection, $dependencies];
protected function getDependencies($class)
{
    if (isset($this->_reflections[$class])) {
        return [$this->_reflections[$class], $this->_dependencies[$class]];
    }

    $dependencies = [];
    $reflection = new ReflectionClass($class);

    $constructor = $reflection->getConstructor();
    if ($constructor !== null) {
        foreach ($constructor->getParameters() as $param) {
            if ($param->isDefaultValueAvailable()) {
                $dependencies[] = $param->getDefaultValue();
            } else {
                $c = $param->getClass();
                $dependencies[] = Instance::of($c === null ? null : $c->getName());
            }
        }
    }
    //缓存$reflection反射类,以及$class类构造函数参数数组
    $this->_reflections[$class] = $reflection;
    $this->_dependencies[$class] = $dependencies;

    return [$reflection, $dependencies];
}


我们打开$this->resolveDependencies方法:

protected function resolveDependencies($dependencies, $reflection = null)
{
    foreach ($dependencies as $index => $dependency) {
        if ($dependency instanceof Instance) {
            if ($dependency->id !== null) {
                $dependencies[$index] = $this->get($dependency->id);
            } elseif ($reflection !== null) {
                $name = $reflection->getConstructor()->getParameters()[$index]->getName();
                $class = $reflection->getName();
                throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
            }
        }
    }
    return $dependencies;
}


最后来个yii\di\Instance类的具体内容(跟我之前那个类代码基本一样,多了个ensure方法):

namespace yii\di;

use Yii;
use yii\base\InvalidConfigException;

class Instance
{
    public $id;


    protected function __construct($id)
    {
        $this->id = $id;
    }

    public static function of($id)
    {
        return new static($id);
    }

    /**
     *
     * ```php
     * use yii\db\Connection;
     *
     * // returns Yii::$app->db
     * $db = Instance::ensure('db', Connection::className());
     * // returns an instance of Connection using the given configuration
     * $db = Instance::ensure(['dsn' => 'sqlite:path/to/my.db'], Connection::className());
     * ```
     */
    public static function ensure($reference, $type = null, $container = null)
    {
        if (is_array($reference)) {
            $class = isset($reference['class']) ? $reference['class'] : $type;
            if (!$container instanceof Container) {
                $container = Yii::$container;
            }
            unset($reference['class']);
            return $container->get($class, [], $reference);
        } elseif (empty($reference)) {
            throw new InvalidConfigException('The required component is not specified.');
        }

        if (is_string($reference)) {
            $reference = new static($reference);
        } elseif ($type === null || $reference instanceof $type) {
            return $reference;
        }

        if ($reference instanceof self) {
            $component = $reference->get($container);
            if ($type === null || $component instanceof $type) {
                return $component;
            } else {
                throw new InvalidConfigException('"' . $reference->id . '" refers to a ' . get_class($component) . " component. $type is expected.");
            }
        }

        $valueType = is_object($reference) ? get_class($reference) : gettype($reference);
        throw new InvalidConfigException("Invalid data type: $valueType. $type is expected.");
    }

    public function get($container = null)
    {
        if ($container) {
            return $container->get($this->id);
        }
        if (Yii::$app && Yii::$app->has($this->id)) {
            return Yii::$app->get($this->id);
        } else {
            return Yii::$container->get($this->id);
        }
    }
}


以上是关于yii2源码分析之组件实例化流程的主要内容,如果未能解决你的问题,请参考以下文章

Spring源码分析非懒加载的Bean实例化过程(下篇)

yii2 源码分析Behavior类分析

spring源码分析——bean的实例化流程

yii2 源码分析1从入口开始

Yii2之组件的注册与创建

vue源码分析之目录架构