ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

❥(^_-) Yii2框架源码解析之容器类Container.php

2021-09-06 19:34:14  阅读:236  来源: 互联网

标签:definition Container reflection _- 源码 params dependencies return class


容器概述

网上总有些人把php框架中的容器说的很高大上。php中的容器其实很简单。

首先,php中的容器是为了解决类和类之间的依赖关系的。举个栗子:

存在三个类:

class Group
{
    public static $a = 0;

    function __construct($a)
    {
        static::$a = $a;
    }
}


class User
{
    public function __construct(Group $group)
    {

    }

}


class UserList
{
    public function __construct(User $user)
    {

    }

    public function getUserList()
    {
        echo "this is the user-list";
    }
}

如果我们要调用UserList类里面的getUserList()方法,通常的做法是:

$group = new Group(1);

$user = new User($group);

$userList = new UserList($user);

$userList->getUserList();

如果我们使用容器的话,需要这样做:

$container = new Container();
$container->set('Group', 'app\service\Group');
$container->set('User', 'app\service\User');
$container->set('UserList', 'app\service\UserList');
$lister = $container->get('UserList');
$lister->getUserList();

我们发现:我们只需要把所有的相关联的类,全部注入到容器中,不需要关心他们之间的依赖关系。(容器帮我们完成了依赖关系的处理)

怎么实现的呢?通过php的反射

php的反射可以获取类的相关信息。那么我们首先通过

$reflection = new \ReflectionClass($className)获取目标类的反射类。再通过
$reflection->getConstructor()获取反射类的构造方法类,再通过
$constructorParameters = $constructor->getParameters()获取构造方法所需参数类。(是一个数组)

再通过参数类的 isDefaultValueAvailable() 方法判断,参数是否有可用默认值。如果没有,则断言它是一个对象,我们就创建它。具体实现看代码:

源码分析

<?php

namespace yii\di;

use ReflectionClass;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;

class Container extends Component
{
 
    private $_singletons = [];
    
    private $_definitions = [];

    private $_params = [];

    private $_reflections = [];
   
    private $_dependencies = [];

    public function get($class, $params = [], $config = [])
    {
        // 这里是获取一个类的实例。
        //$class 第一个参数是类的名称或者别名,当然,你得先使用set()方法将改类注册到容器中,才能使用get方法获取。
        //$params 第二个参数是该类的构造方法所需要的参数,请在数组中按照顺序传入。
        //$config 第三个参数是该类中属性的初始值得设置,是键值对形式的数组。

        // Instance表示对容器中对象的引用
        // 如果获取的当前类属于instance, 唯一标识就变为当前类中的id属性。
        if ($class instanceof Instance) {
            $class = $class->id;
        }

        // singleton属性是一个容器的注册树,如果说当前实例已经存在注册树上,就直接返回。
        if (isset($this->_singletons[$class])) {
            // singleton
            return $this->_singletons[$class];
        } elseif (!isset($this->_definitions[$class])) {
            // 如果不存在定义就创建实例,并返回(创建中会将其注册到全局树上)
            return $this->build($class, $params, $config);
        }

        // 存在$definition的逻辑
        // 这个属性其实就是注册类的时候(调用set()方法),传入的第二个参数。他一共有三种类型。
        // 1.回调函数
        // 2. 数组
        // 3. 字符串。
        $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));
        }

        // 如果是使用setSingleton()方法注册的对象,那么将singletons赋值。
        if (array_key_exists($class, $this->_singletons)) {
            // singleton
            $this->_singletons[$class] = $object;
        }

        return $object;
    }

    public function set($class, $definition = [], array $params = [])
    {
        // 将类注册到容器中。
        // 设置类的定义,类的参数。
        // 注销已经存在类实例。(因为重新注册之后,如果不销毁之前的实例,当类的参数不一样的时候,会导致获取的对象是之前注册的类)
        // 但是有个新的问题:同时创建同一个类的两个不同实例(类参数不同),是无法实现的 0.0  或许我们需要借助一个新容器?那么这样做岂非太麻烦了?
        $this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
        $this->_params[$class] = $params;
        unset($this->_singletons[$class]);
        return $this;
    }

    public function setSingleton($class, $definition = [], array $params = [])
    {
        // 这个方法和set()方法差不多,唯一的区别就是,使用set()方法注册的类,每次get()的时候,都是重新生成一个新对象。
        // 而setSingleton()方法永远存储第一次生成的对象,并将其保存在 $this->_singletons属性中。每次调用get()方法,都是获取第一次生成的实例。
        $this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
        $this->_params[$class] = $params;
        $this->_singletons[$class] = null; // 保存键,get()方法中会根据array_key_exists()来判断
        return $this;
    }

    public function has($class)
    {
        return isset($this->_definitions[$class]);
    }

    public function hasSingleton($class, $checkInstance = false)
    {
        // 是否存在实例,第二个参数表示是否已经实例化。
        return $checkInstance ? isset($this->_singletons[$class]) : array_key_exists($class, $this->_singletons);
    }


    public function clear($class)
    {
        // 移除
        unset($this->_definitions[$class], $this->_singletons[$class]);
    }

  
    protected function normalizeDefinition($class, $definition)
    {
        // 将类的定义规范化。

        // $definition为空,返回类名称就是入参$class。注意类名要包含命名空间哦。
        if (empty($definition)) {
            return ['class' => $class];
        } elseif (is_string($definition)) {
            // $definition是字符串,则就是完整的正确的类名称。(包含命名空间的)
            return ['class' => $definition];
        } elseif ($definition instanceof Instance) {
            return ['class' => $definition->id];
        } elseif (is_callable($definition, true) || is_object($definition)) {
            return $definition;
        } elseif (is_array($definition)) {
            if (!isset($definition['class']) && isset($definition['__class'])) {
                $definition['class'] = $definition['__class'];
                unset($definition['__class']);
            }
            if (!isset($definition['class'])) {
                if (strpos($class, '\\') !== false) {
                    $definition['class'] = $class;
                } else {
                    throw new InvalidConfigException('A class definition requires a "class" member.');
                }
            }

            return $definition;
        }

        throw new InvalidConfigException("Unsupported definition type for \"$class\": " . gettype($definition));
    }


    public function getDefinitions()
    {
        return $this->_definitions;
    }

    protected function build($class, $params, $config)
    {
        // 整个容器最关键的方法:创建一个类的实例。发现类的依赖关系,解决依赖。实例化依赖的相关类,并注入。

        /* @var $reflection ReflectionClass */

        // 这里只是获取需要生成实例的参数
        list($reflection, $dependencies) = $this->getDependencies($class);

        // 配置中存在“__construct()”键,覆盖实例化的初始值。
        if (isset($config['__construct()'])) {
            foreach ($config['__construct()'] as $index => $param) {
                $dependencies[$index] = $param;
            }
            unset($config['__construct()']);
        }

        // 参数中存在值,覆盖实例化的初始值。
        foreach ($params as $index => $param) {
            $dependencies[$index] = $param;
        }

        // 这里处理准备生成实例的参数。如果参数是对象,则获取实际需要的具体对象。
        $dependencies = $this->resolveDependencies($dependencies, $reflection);
        if (!$reflection->isInstantiable()) {
            throw new NotInstantiableException($reflection->name);
        }
        if (empty($config)) {
            // 根据参数生成新的实例
            return $reflection->newInstanceArgs($dependencies);
        }

        $config = $this->resolveDependencies($config);

        if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) {
            // set $config as the last parameter (existing one will be overwritten)
            // 实现了接口:yii\base\Configurable,则将配置设置为最后一个参数(覆盖写),然后生成实例。
            $dependencies[count($dependencies) - 1] = $config;
            return $reflection->newInstanceArgs($dependencies);
        }

        // 其他情况,$config则是生成实例类中的属性。
        $object = $reflection->newInstanceArgs($dependencies);
        foreach ($config as $name => $value) {
            $object->$name = $value;
        }

        return $object;
    }


    protected function mergeParams($class, $params)
    {
        // 这个方法主要是将类的构造方法中的参数和用户传入的参数合并。

        // $this->_params是容器中所有类的构造方法中参数的全局注册树。
        if (empty($this->_params[$class])) {
            return $params;
        } elseif (empty($params)) {
            return $this->_params[$class];
        }

        $ps = $this->_params[$class];
        foreach ($params as $index => $value) {
            $ps[$index] = $value;
        }

        return $ps;
    }

    protected function getDependencies($class)
    {
        // 这个方法返回了类的所有依赖。
        // 返回的第一个参数是当前类的反射类,第二个参数是相关依赖。

        // 如果已经存在当前类的反射,就直接返回
        if (isset($this->_reflections[$class])) {
            return [$this->_reflections[$class], $this->_dependencies[$class]];
        }

        $dependencies = [];
        try {
            $reflection = new ReflectionClass($class);
        } catch (\ReflectionException $e) {
            throw new InvalidConfigException('Failed to instantiate component or class "' . $class . '".', 0, $e);
        }

        $constructor = $reflection->getConstructor();
        if ($constructor !== null) {
            foreach ($constructor->getParameters() as $param) {
                if (version_compare(PHP_VERSION, '5.6.0', '>=') && $param->isVariadic()) {
                    // php版本大于等于5.6,并且参数是可变的,直接返回。(可变参数是最后一个参数。)
                    break;
                } elseif ($param->isDefaultValueAvailable()) {
                    // 默认值可用,就将默认值存储。
                    $dependencies[] = $param->getDefaultValue();
                } else {
                    $c = $param->getClass();
                    // 没有默认值,获取当前类名称,生成instance实例
                    $dependencies[] = Instance::of($c === null ? null : $c->getName());
                }
            }
        }

        // 赋值并返回。
        $this->_reflections[$class] = $reflection;
        $this->_dependencies[$class] = $dependencies;

        return [$reflection, $dependencies];
    }

    protected function resolveDependencies($dependencies, $reflection = null)
    {
        // 这个方法的作用,就是将目标类中的构造方法中,依赖类的参数,替换为实际的对象实例。
        // 如果当前类不属于引用对象类instance,那么就不做处理。
        foreach ($dependencies as $index => $dependency) {
            if ($dependency instanceof Instance) {
                if ($dependency->id !== null) {
                    // 首先,如果存在id属性,那么就继续从容器中获取依赖类。
                    $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;
    }

    public function invoke(callable $callback, $params = [])
    {
        // 解析函数参数中的依赖,然后调用回调函数。
        return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params));
    }


    public function resolveCallableDependencies(callable $callback, $params = [])
    {
        if (is_array($callback)) {
            $reflection = new \ReflectionMethod($callback[0], $callback[1]);
        } elseif (is_object($callback) && !$callback instanceof \Closure) {
            $reflection = new \ReflectionMethod($callback, '__invoke');
        } else {
            $reflection = new \ReflectionFunction($callback);
        }

        $args = [];

        // 是否是关联数组
        $associative = ArrayHelper::isAssociative($params);

        foreach ($reflection->getParameters() as $param) {
            $name = $param->getName();
            if (($class = $param->getClass()) !== null) {
                $className = $class->getName();
                if (version_compare(PHP_VERSION, '5.6.0', '>=') && $param->isVariadic()) {
                    // 参数可变,合并参数。并打断。
                    $args = array_merge($args, array_values($params));
                    break;
                } elseif ($associative && isset($params[$name]) && $params[$name] instanceof $className) {
                    $args[] = $params[$name];
                    unset($params[$name]);
                } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) {
                    $args[] = array_shift($params);
                } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) {
                    $args[] = $obj;
                } else {
                    // If the argument is optional we catch not instantiable exceptions
                    try {
                        $args[] = $this->get($className);
                    } catch (NotInstantiableException $e) {
                        if ($param->isDefaultValueAvailable()) {
                            $args[] = $param->getDefaultValue();
                        } else {
                            throw $e;
                        }
                    }
                }
            } elseif ($associative && isset($params[$name])) {
                $args[] = $params[$name];
                unset($params[$name]);
            } elseif (!$associative && count($params)) {
                $args[] = array_shift($params);
            } elseif ($param->isDefaultValueAvailable()) {
                $args[] = $param->getDefaultValue();
            } elseif (!$param->isOptional()) {
                $funcName = $reflection->getName();
                throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\".");
            }
        }

        foreach ($params as $value) {
            $args[] = $value;
        }

        return $args;
    }

    public function setDefinitions(array $definitions)
    {
        // 实现了 set() 的队列。
        foreach ($definitions as $class => $definition) {
            if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition && is_array($definition[1])) {
                $this->set($class, $definition[0], $definition[1]);
                continue;
            }

            $this->set($class, $definition);
        }
    }


    public function setSingletons(array $singletons)
    {
        // 实现了 setSingleton() 的队列。
        foreach ($singletons as $class => $definition) {
            if (is_array($definition) && count($definition) === 2 && array_values($definition) === $definition) {
                $this->setSingleton($class, $definition[0], $definition[1]);
                continue;
            }

            $this->setSingleton($class, $definition);
        }
    }
}

上边我贴出了完整的容器类以及相关的源码分析。其中比较重要的几个方法是:get(),set(),build()。

真正的实例的创建的方法是build(),而它是在get()方法中进行调用。set()方法只是设置需要实例化它的参数。

标签:definition,Container,reflection,_-,源码,params,dependencies,return,class
来源: https://blog.csdn.net/qq_39545346/article/details/120142085

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有