从yii2框架中的di容器源码中了解反射的作用
反射简介
参考官方简介的话,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。
YII2框架中示例
对于yii2框架,应该都知道di容器,对于di容器的源码这里也主要讲明Container类,先看看平时怎么使用di,就用yii2框架中注释的示例代码来展示;
container调用示例
namespace app\models;
use yii\base\BaseObject;
use yii\db\Connection;
use yii\di\Container;
interface UserFinderInterface
{
function findUser();
}
class UserFinder extends BaseObject implements UserFinderInterface
{
public $db;
public function __construct(Connection $db, $config = [])
{
$this->db = $db;
parent::__construct($config);
}
public function findUser()
{
}
}
class UserLister extends BaseObject
{
public $finder;
public function __construct(UserFinderInterface $finder, $config = [])
{
$this->finder = $finder;
parent::__construct($config);
}
}
$container = new Container;
$container->set('yii\db\Connection', [
'dsn' => '...',
]);
$container->set('app\models\UserFinderInterface', [
'class' => 'app\models\UserFinder',
]);
$container->set('userLister', 'app\models\UserLister');
$lister = $container->get('userLister');
// 上述操作相当于下列实现
$db = new \yii\db\Connection(['dsn' => '...']);
$finder = new UserFinder($db);
$lister = new UserLister($finder);
上面的示例代码只是实例化了Container类,然后调用set方法注入了其他对象,最后获取到了依赖与其他对象创建的lister对象,既然只调用了set方法与get方法,那就先从调用最多的set开始看Container代码。
set方法
public function set($class, $definition = [], array $params = [])
{
$this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
$this->_params[$class] = $params;
unset($this->_singletons[$class]);
return $this;
}
上面的代码比较简洁,调用了类的normalizeDefinition方法,这个一会再说,先说明在该方法中出现的三个属性的含义
- _definitions数组,保存依赖定义
- _params数组,保存构造函数的参数
- _singletons,保存单例
再看normalizeDefinition方法,该方法主要作用是规范类定义
protected function normalizeDefinition($class, $definition)
{
if (empty($definition)) {
// 为空
return ['class' => $class];
} elseif (is_string($definition)) {
// 为字符串
return ['class' => $definition];
} elseif (is_callable($definition, true) || is_object($definition)) {
// 检验是否为可调用函数或者对象
return $definition;
} elseif (is_array($definition)) {
// 检测是否为数组
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));
}
上述代码中已做了一些判断注释,不难发现最后需要返回的definition变量需要为数组格式,或者可调用函数与对象,注意回到刚开始的调用示例代码,definition变量分别有数组格式不带class键,
数组格式带class键,与字符串类型。到底set方法调用已完毕,从源码中分析基本上看不到反射的影子,也就是些传入参数格式兼容处理再写入类属性,接着来看下示例代码中的get方法吧。
get 方法
public function get($class, $params = [], $config = [])
{
if (isset($this->_singletons[$class])) {
// 直接返回单例
return $this->_singletons[$class];
} elseif (!isset($this->_definitions[$class])) {
// 调用bulid
return $this->build($class, $params, $config);
}
$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)) {
// singleton
$this->_singletons[$class] = $object;
}
return $object;
}
上述代码,简要划分一下,请稍作浏览,后面会继续讲述,先说明属性_definitions集合中不存在的情况,即调用build,这个一会说明,再看如果存在相关class键的情况,下面会做几种情况的处理,
- 可调用函数情况下,调用resolveDependencies方法,再call_user_func调用函数
- 数组情况下,获取值与class比较,相等的情况去调用build方法,不想等重新调用get方法使用该值
- 为对象的化直接存储到_singletons属性集合中去,并直接返回对象,这个不作赘述
下面分别来简要分析一下上述调用的几个方法,bulid与resolveDependencies方法
bulid方法的调用逻辑
先看下build方法调用源码
protected function build($class, $params, $config)
{
// 声明变量分别存储getDependencies方法返回的数组
list($reflection, $dependencies) = $this->getDependencies($class);
// 将params数组的数据mergy并覆盖入变量$dependencies
foreach ($params as $index => $param) {
$dependencies[$index] = $param;
}
// 调用resolveDependencies方法
$dependencies = $this->resolveDependencies($dependencies, $reflection);
// 调用反射类方法,检测类是否可实例化
if (!$reflection->isInstantiable()) {
throw new NotInstantiableException($reflection->name);
}
if (empty($config)) {
// 创建一个类的新实例,变量$dependencies作为参数将传递到类的构造函数。
return $reflection->newInstanceArgs($dependencies);
}
$config = $this->resolveDependencies($config);
// 如果变量$dependencies为空并且class是yii\base\Configurable接口的实现
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);
}
// 创建对象,注入参数
$object = $reflection->newInstanceArgs($dependencies);
// 对象属性赋值
foreach ($config as $name => $value) {
$object->$name = $value;
}
return $object;
}
看了上述源码,也基本了解此方法是为了返回实例化对象,并调用了反射的一些接口函数,这里基本上可以知道反射的一些作用,第一个就是检测类的合法性,例如检测是否为接口实现,是否可实例化,
还有一个就是创造,上述可以看出根据反射创建类的实例,并注入构造函数依赖的参数。下面再了解下该方法里面调用的两个依赖方法,分别为开头的变量声明getDependencies与resolveDependencies
处理变量。
getDependencies方法调用
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 (version_compare(PHP_VERSION, '5.6.0', '>=') && $param->isVariadic()) {
// 检测php版本与构造参数检测是否为可变参数
break;
} elseif ($param->isDefaultValueAvailable()) {
// 检测参数是否是否有默认值,如果有数据保存默认值
$dependencies[] = $param->getDefaultValue();
} else {
// 获取参数的类型提示符,查看是否为null,返回的是reflectClass对象
// 这里再举个例子,例如构造函数为这样__construct(Db $db);这里返回的就是Db类的反射
$c = $param->getClass();
// 创建Instance实例存储类名
$dependencies[] = Instance::of($c === null ? null : $c->getName());
}
}
}
// 存储起来
$this->_reflections[$class] = $reflection;
$this->_dependencies[$class] = $dependencies;
return [$reflection, $dependencies];
}
该方法主要作用为解析依赖信息,主要是获取类的构造函数的信息,这样才能调用构造函数创建实例。
resilveDependencies方法调用
该方法主要是实例化依赖,也就是创建构造函数的参数对象,不作过多赘述
protected function resolveDependencies($dependencies, $reflection = null)
{
foreach ($dependencies as $index => $dependency) {
// 在解析依赖信息的getDependencies中,有部分参数没有默认值,而是创建了Instance对象
// 这里会将这些Instance对象实例化对真正的构造函数的参数对象
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;
}
总结
在上述源码中基本上可以看到几处反射的应用,而反射到底是什么,由什么作用呢?想必看完上文也会有一点点理解,嗯,其实意义如其名,
就是反射类的信息,其作用是获取类的信息,而php的反射类也提供了很多的接口函数以供使用,使用的时候可以去查询官网手册。
上文也看出来yii2框架中的di容器创建对象,在这里还是希望可以稍微讲述下刚开始的示例代码,其先在容器中注入了数据库连接类,finder类,listener类,而finder类构造函数依赖于
数据库连接类,listener类依赖与finder类,由获取依赖信息方法可以知道构造中会去取出依赖对象信息然后调用解析依赖信息重新去调用get方法返回实例化对象实现其中的注入关系。
原文地址:https://segmentfault.com/a/1190000015620290
从yii2框架中的di容器源码中了解反射的作用的更多相关文章
- jQuery中使用 .html() function在IE8和9中显示不正常源码中多出sizset和sizcache
错误原因:在引入jquery的时候,使用了html function,在IE8和IE9下面有可能会出现不兼容 解决办法:在html头部加一句 <meta http-equiv="X-U ...
- Android 网络框架之Retrofit2使用详解及从源码中解析原理
就目前来说Retrofit2使用的已相当的广泛,那么我们先来了解下两个问题: 1 . 什么是Retrofit? Retrofit是针对于Android/Java的.基于okHttp的.一种轻量级且安全 ...
- Django框架rest_framework中APIView的as_view()源码解析、认证、权限、频率控制
在上篇我们对Django原生View源码进行了局部解析:https://www.cnblogs.com/dongxixi/p/11130976.html 在前后端分离项目中前面我们也提到了各种认证需要 ...
- 小白都能看懂的Spring源码揭秘之IOC容器源码分析
目录 前言 IOC 只是一个 Map 集合 IOC 三大核心接口 IOC 初始化三大步骤 定位 加载 注册 总结 前言 在 Spring 框架中,大家耳熟能详的无非就是 IOC,DI,Spring M ...
- 从express源码中探析其路由机制
引言 在web开发中,一个简化的处理流程就是:客户端发起请求,然后服务端进行处理,最后返回相关数据.不管对于哪种语言哪种框架,除去细节的处理,简化后的模型都是一样的.客户端要发起请求,首先需要一个标识 ...
- Android 源码中的设计模式
最近看了一些android的源码,发现设计模式无处不在啊!感觉有点乱,于是决定要把设计模式好好梳理一下,于是有了这篇文章. 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因.如果一个类有多于 ...
- Java 容器源码分析之Map-Set-List
HashMap 的实现原理 HashMap 概述 HashMap 是基于哈希表的 Map 接口的非同步实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.此类不保证映射的顺序 ...
- Java集合框架——jdk 1.8 ArrayList 源码解析
前言:作为菜鸟,需要经常回头巩固一下基础知识,今天看看 jdk 1.8 的源码,这里记录 ArrayList 的实现. 一.简介 ArrayList 是有序的集合: 底层采用数组实现对数据的增删查改: ...
- Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...
随机推荐
- [App Store Connect帮助]七、在 App Store 上发行(3.1)提交至“App 审核”:提交 App 以供审核
将您的 App 提交至“App 审核”,从而开始审核流程,以便使您的 App 在 App Store 上可用. 但是,在您能够上传 App 至“App 审核”前,您必须提供所有必填的元数据并为该版本选 ...
- 用for循环实现的菱形图案
package com.wh.lingxing; import java.util.Scanner; public class LingXing { public static void main(S ...
- javaservlet介绍
servlet 是 serve applet的意思 Java servlet是用Java编写的服务器端程序.其主要功能在于交互式地浏览和修改数据,生成动态Web内容. Servlet运行于支持Jav ...
- 205 Isomorphic Strings 同构字符串
给定两个字符串 s 和 t,判断它们是否是同构的.如果 s 中的字符可以被替换最终变成 t ,则两个字符串是同构的.所有出现的字符都必须用另一个字符替换,同时保留字符的顺序.两个字符不能映射到同一个字 ...
- hdu 1430 魔板 康托展开 + 很好的映射
http://acm.hdu.edu.cn/showproblem.php?pid=1430 如果从start ---> end,每一次都bfs进行,那么就肯定会超时. 考虑到先把start映射 ...
- [转][ASP.NET MVC]如何定制Numeric属性/字段验证消息
本文转自:http://www.cnblogs.com/artech/archive/2012/02/13/NumericPropertyValidation.html 对于一个Numeric属性/字 ...
- 关于java的arrays数组排序示例AJPFX的分享
Java API对Arrays类的说明是:此类包含用来操作数组(比如排序和搜索)的各种方法. 1.对基本数据类型的数组的排序 说明: (1)Arrays类中的sort()使用的是“经过调优的快速排序法 ...
- ssm基础配置
1.导包 <dependencies> <dependency> <groupId>org.springframework</groupId> < ...
- 用css制作圆环图表 (vue,sass)
效果图: 思路 :在一个容器里再放两个矩形,每个矩形都占一半,给这两个矩形都设置溢出隐藏,当去旋转矩形里面的圆形的时候,溢出部分就被隐藏掉了,这样就可以达到想要的效果. 代码-html: <di ...
- OpenGl之旅-—初识opengl
昨天学习了如何使用codeblocks来编译运行一个opengl的项目.在创建一个新的opengl项目时他默认已经写了一个示例,今天我们就上面的例子进行下代码的剖析,以此来敲开opengl的神秘大门. ...