0

0

如何在yii2框架的di容器源码中了解反射的作用

不言

不言

发布时间:2018-07-13 15:29:51

|

2124人浏览过

|

来源于php中文网

原创

这篇文章主要介绍了关于如何在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调用函数

    与光AI
    与光AI

    一站式AI视频工作流创作平台

    下载
  • 数组情况下,获取值与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方法返回实例化对象实现其中的注入关系。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

Laravel开发环境部署之homestead 安装配置过程(windows系统)

相关专题

更多
高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

41

2026.01.16

全民K歌得高分教程大全
全民K歌得高分教程大全

本专题整合了全民K歌得高分技巧汇总,阅读专题下面的文章了解更多详细内容。

73

2026.01.16

C++ 单元测试与代码质量保障
C++ 单元测试与代码质量保障

本专题系统讲解 C++ 在单元测试与代码质量保障方面的实战方法,包括测试驱动开发理念、Google Test/Google Mock 的使用、测试用例设计、边界条件验证、持续集成中的自动化测试流程,以及常见代码质量问题的发现与修复。通过工程化示例,帮助开发者建立 可测试、可维护、高质量的 C++ 项目体系。

23

2026.01.16

java数据库连接教程大全
java数据库连接教程大全

本专题整合了java数据库连接相关教程,阅读专题下面的文章了解更多详细内容。

35

2026.01.15

Java音频处理教程汇总
Java音频处理教程汇总

本专题整合了java音频处理教程大全,阅读专题下面的文章了解更多详细内容。

16

2026.01.15

windows查看wifi密码教程大全
windows查看wifi密码教程大全

本专题整合了windows查看wifi密码教程大全,阅读专题下面的文章了解更多详细内容。

56

2026.01.15

浏览器缓存清理方法汇总
浏览器缓存清理方法汇总

本专题整合了浏览器缓存清理教程汇总,阅读专题下面的文章了解更多详细内容。

15

2026.01.15

ps图片相关教程汇总
ps图片相关教程汇总

本专题整合了ps图片设置相关教程合集,阅读专题下面的文章了解更多详细内容。

9

2026.01.15

ppt一键生成相关合集
ppt一键生成相关合集

本专题整合了ppt一键生成相关教程汇总,阅读专题下面的的文章了解更多详细内容。

26

2026.01.15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.3万人学习

Git 教程
Git 教程

共21课时 | 2.7万人学习

MySQL 教程
MySQL 教程

共48课时 | 1.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号