?(^_-) Yii2框架源码解析之容器类Container.php
2021/9/6 20:07:26
本文主要是介绍?(^_-) Yii2框架源码解析之容器类Container.php,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
容器概述
网上总有些人把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()方法只是设置需要实例化它的参数。
这篇关于?(^_-) Yii2框架源码解析之容器类Container.php的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23怎么实现安卓+php 热更新方案?-icode9专业技术文章分享
- 2024-11-22PHP 中怎么实现判断多个值是否为空、null 或者为 false?-icode9专业技术文章分享
- 2024-11-11开源 PHP 商城项目 CRMEB 二次开发和部署教程
- 2024-11-09怎么使用php在kaufland平台刊登商品?-icode9专业技术文章分享
- 2024-11-05PHP的抽象类和接口是什么,有什么区别-icode9专业技术文章分享
- 2024-11-01开源 PHP 商城项目 CRMEB 安装和使用教程
- 2024-11-01用php和mysql写无限分类,有哪几种方法-icode9专业技术文章分享
- 2024-10-31php数据分表导出时部分数据无法导出什么原因-icode9专业技术文章分享
- 2024-10-30有经验的 PHP 开发者学习一门新的编程语言,有哪些推荐的有前景的语言-icode9专业技术文章分享
- 2024-10-21php 检测图片是否篡改过-icode9专业技术文章分享