yii2 随笔(七)依赖注入——(3)yii2的依赖注入

原文地址:

p=124" target="_blank">http://ivhong.com/?

p=124(ivhong.com
是我的博客主址)

yii2的依赖注入的核心代码在 yii\di。在这个包(目录)以下有3个文件。各自是Container.php(容器),Instance.php(实例),ServiceLocator(服务定位器),如今我们讨论一下前两个。服务定位器能够理解一个服务的注冊表。这个不影响我们讨论依赖注入,它也是依赖注入的一种应用。

我们还是从代码開始解说yii2是怎么使用依赖注入的。

// yii\base\application
//这个是yii2的依赖注入使用入口,參数的解释请參考源代码,这里不多解释
public static function createObject($type, array $params = [])
{
    if (is_string($type)) {//type 是字符串的话。它就把type当做一个对象的“原材料”。直接把它传给容器并通过容器得到想要的对象。
        return static::$container->get($type, $params);
    } elseif (is_array($type) && isset($type['class'])) {//type 是数组,而且有class的键,经过简单处理后,得到对象的“原材料”,然后把得到的“原材料”传给容器并通过容器得到想要的对象。
        $class = $type['class'];
        unset($type['class']);
        return static::$container->get($class, $params, $type);
    } elseif (is_callable($type, true)) {//假设type是可调用的结构。就直接调用
        return call_user_func($type, $params);
    } elseif (is_array($type)) {//假设type是array,而且没有'class'的键值,那么就抛出异常
        throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
    } else {//其它情况。均抛出还有一个异常,说type不支持的配置类型
        throw new InvalidConfigException("Unsupported configuration type: " . gettype($type));
    }
}

通过阅读上面代码。Yii::createObject()是把合格的“原材料”,交给“容器($container)”,来生成目标对象的,那么容器就是我们“依赖注入”生产对象的地方。那么$container是什么时候引入的呢(注意这里用的是 static::$container, 而不是 self::$container)?还记得在首页导入yii框架时的语句么?

//导入yii框架
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

代码例如以下

//引入主要的yii框架
require(__DIR__ . '/BaseYii.php');
//仅仅是做了继承,这里给我们留了二次开发的余地,尽管非常少能用到
class Yii extends \yii\BaseYii
{
}
//设置自己主动载入
spl_autoload_register(['Yii', 'autoload'], true, true);
//注冊 classMap
Yii::$classMap = require(__DIR__ . '/classes.php');
//注冊容器
Yii::$container = new yii\di\Container();

你看的没错!就是最后一句话,yii2 把 yii\di\Container 的实现拿给自己使用。

接下来,我们讨论一下容器是怎么实现的?

接着上面的 static::$container->get() 的方法,在解说get方法之前。我们要先了解一下容器的几个属性。这将有助于理解get的实现

$_singletons; // 单例数组,它的键值是类的名字,假设生成的对象是单例,则把他保存到这个数组里,值为null的话。表示它还没有被实例化
$_definitions;// 定义数组,它的键值是类的名字,值是生成这个类所需的“原材料”,在set 或 setSingleton的时候写入
$_params; // 參数,它的键值是类的名字,值是生成这个类所需的额外的“原材料”。在set 或 setSingleton的时候写入
$_reflections; //反射。它的键值是类的名字,值是要生成的对象的反射句柄,在生成对象的时候写入
$_dependencies;//依赖,它的键值是类的名字,值是要生成对象前的一些必备“原材料”,在生成对象的时候。通过反射函数得到。

ok。假设你够细心地话。理解了上面的几个属性,预计你就对yii2的容器有个大概的了解了,这里还是从get開始。

public function get($class, $params = [], $config = [])
{
    if (isset($this->_singletons[$class])) {//查看将要生成的对象是否在单例里,假设是,则直接返回
        // singleton
        return $this->_singletons[$class];
    } elseif (!isset($this->_definitions[$class])) {//假设没有要生成类的定义,则直接生成,yii2自身大部分走的是这部分,并没有事先在容器里注冊什么。那么配置文件是在哪里注冊呢?还记的文章最開始的时候的"服务定位器"么?我们在服务定位器里讲看到这些。
        return $this->build($class, $params, $config);
    }
    //假设已经定义了这个类。则取出这个类的定义
    $definition = $this->_definitions[$class];

    if (is_callable($definition, true)) {//假设定义是可调用的结构
        //先整合一下參数,和$_params里是否有这个类的參数,假设有则和传入的參数以传入覆盖定义的方式整和在一起
        //然后再检查整合后的參数是否符合依赖,就是说是否有必填的參数,假设有直接抛出异常。否则返回參数。检查依赖的时候,须要推断是否为实例(Instance),假设是,则要实现实例。注意:这里出现了Instance。
        $params = $this->resolveDependencies($this->mergeParams($class, $params));
        //把參数专递给可调用结果。返回结果
        $object = call_user_func($definition, $this, $params, $config);
    } elseif (is_array($definition)) {//假设定义是一个数组
        //把代表要生成的class取出
        $concrete = $definition['class'];
        //注销这个键值
        unset($definition['class']);
        //把定义 和 配置整合成新的定义
        $config = array_merge($definition, $config);
        //整合參数
        $params = $this->mergeParams($class, $params);
        //假设传入的$class 和 定义里的class全然一样。则直接生成。build第一个參数确保为真实的类名,而传入的$type可能是别名
        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;
}

研究到这里,我们发现 get 函数不过个“入口”而已,基本的功能在build里

//创建对象
protected function build($class, $params, $config)
{
    //通过类名得到反射句柄,和依赖(依赖就是所需參数)
    //所曾经面提到,传输buile的第一个參数必须为有效的“类名”否则,会直接报错
    list ($reflection, $dependencies) = $this->getDependencies($class);
    //把依赖和參数配置,由于依赖可能有默认參数,这里覆盖默认參数
    foreach ($params as $index => $param) {
        $dependencies[$index] = $param;
    }
    //确保依赖没问题。全部原材料是否都ok了,否则抛出异常
    $dependencies = $this->resolveDependencies($dependencies, $reflection);
    if (empty($config)) {//假设config为空,则返回目标对象
        return $reflection->newInstanceArgs($dependencies);
    }

    if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) {//假设目标对象是 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);
        foreach ($config as $name => $value) {
            $object->$name = $value;
        }
        return $object;
    }
}

好了,build到这里就结束了。以下我们一起看看容器是怎么得到反射句柄和依赖关系的

protected function getDependencies($class)
{
    if (isset($this->_reflections[$class])) {//是否已经解析过目标对象了
        return [$this->_reflections[$class], $this->_dependencies[$class]];
    }

    $dependencies = [];//初始化依赖数组
    $reflection = new ReflectionClass($class);//得到目标对象的反射。请參考php手冊

    $constructor = $reflection->getConstructor();//得到目标对象的构造函数
    if ($constructor !== null) {//假设目标对象有构造函数。则说明他有依赖
        //解析全部的參数,注意得到參数的顺序是从左到右的,确保依赖时也是依照这个顺序运行
        foreach ($constructor->getParameters() as $param) {
            if ($param->isDefaultValueAvailable()) {//假设參数的默认值可用
                $dependencies[] = $param->getDefaultValue();//把默认值放到依赖里
            } else {//假设是其它的
                $c = $param->getClass();//得到參数的类型,假设參数的类型不是某类,是基本类型的话,则返回null
                //假设,是基本类型,则生成null的实例。假设不是基本类型,则生成该类名的实例。注意:这里用到了实例(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)
{
    //拿到依赖关系
    foreach ($dependencies as $index => $dependency) {
        //假设依赖是一个实例。由于经过处理的依赖,都是Instance的对象
        if ($dependency instanceof Instance) {
            if ($dependency->id !== null) {//这个实例有id,则通过这个id生成这个对象。而且取代原来的參数
                $dependencies[$index] = $this->get($dependency->id);
            } elseif ($reflection !== null) {//假设反射句柄不为空,注意这个函数是protected 类型的,所以仅仅有本类或者本类的衍生类可訪问,可是本类里仅仅有两个地方用到了。一个是 get 的时候。假设目标对象是可调用的结果(is_callable)。那么$reflection===null,另外一个build的时候,$reflection不为空,这个时候代表目标对象有一个必须參数。可是还不是一个实例(Instance的对象)。这个时候代表缺乏必须的“原材料”抛出异常
                //则拿到响应的必填參数名字,而且抛出异常
                $name = $reflection->getConstructor()->getParameters()[$index]->getName();
                $class = $reflection->getName();
                throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
            }
        }
    }

    //确保了全部的依赖后,返回全部依赖,假设目标是is_callable($definition, true),则不会抛出异常,仅仅把Instance类型的參数实例化出来。
    return $dependencies;
}

看到这里,我们就能够了解了yii2是怎么使用容器实现“依赖注入”了,那么有个问题。闭包的依赖怎么保证呢?我想是由于yii2觉得闭包的存在解决的是局限性的问题。不存在依赖性,或者依赖是交给开发人员自行解决的。另外yii2的容器,假设參数是闭包的话,就会出现错误。由于对闭包的依赖。解析闭包參数的时候,会得到$dependencies[]
= Instance::of($c === null ? null : $c->getName());得到的就是一个 Closure 的实例,而后面 实例化这个实例的时候,就会出现故障了,所以用yii2的容器实现对象的时候,被实现的对象不能包括闭包參数,假设有闭包參数,则一定要有默认值,或者人为保证会传入这个闭包參数,绕过自己主动生成的语句。

ok容器的主要函数就有这些了,其它方法。set,setSingleton,has,hasSingleton,clear一看就知道什么意思,另外这些方法基本上没有在框架中使用(能够在这些函数写exit。看看你的页面会不会空白),或者你用容器自己生成一些东西的话,能够自行查看这些函数的使用方法。

最后。我们来看看Instance究竟扮演了什么角色

//yii\di\Instance
//非常诧异吧。就是实例化一个自己,注意这个自己是 static,以后你可能须要用到这个地方
public static function of($id)
{
    return new static($id);
}
[/php]
那么这个函数的构造函数呢?
[php]
//禁止外部实例化
protected function __construct($id)
{
    //赋值id
    $this->id = $id;
}

在容器中。就用到了Instance的这两个方法,说明Instance在实例中,仅仅是确保了依赖的可用性。此外Instance还提供了其它的函数。当中 get 得到的是当前Instance所相应的id的实例化对象,另外。另一个静态函数ensure

//确保 $reference 是 $type类型的,假设不是则抛出异常
//在框架中多次用到,请自行查找
//另外。假设$type==null的时候。他也能够当做依赖注入的入口。用法请自行查看源代码,到如今你应该能够自己看懂这些代码了。
public static function ensure($reference, $type = null, $container = null)
{
    //...
}
时间: 2024-10-06 12:29:08

yii2 随笔(七)依赖注入——(3)yii2的依赖注入的相关文章

Yii2 学习笔记 01 -- 依赖注入在yii2中的应用

什么是依赖注入?                  依赖注入(Dependency Injection)是设计模式的一种.名字比较抽象,但是,要解决的问题却是比较明确.对于给定的应用程序,需要借助一些相对独立的组件来完成功能.一般来说,使用这些组件的过程就包含在应用程序的逻辑语句之中.问题是,当这些组件想要做成类似插件功能,以达到应用程序的业务逻辑不变就能随意的更改组件的实现的效果.这种灵活性取决于应用程序如何组装这些组件.如果说应用程序依赖于这些组件的话,依赖注入就是把这些依赖关系从应用程序的

从头认识Spring-1.1 什么是依赖注入?为什么须要依赖注入?

这一章节我们来讨论一下什么是依赖注入?为什么须要依赖注入? 1.什么是依赖注入? 笔者理解的是:对象的生成不再是通过显示的new,并且到spring容器里面取.对象的创建是使用注入这样的形式 2.为什么须要依赖注入? (1)解耦 (2)易于測试 我们以歌唱家唱歌这个为样例,在没有使用依赖注入情况下的代码: package com.raylee.my_new_spring.my_new_spring.ch01.topic_1_1; public class Song { @Override pub

【Spring】Spring依赖注入IOC的设值注入setter

其实标题中如此高大上的名词,只是一个问题,关于在Spring的applicationContext.xml中的如下语句是什么意思? <property name="aService" ref="aService"/> 这类语句在SSH的配置中会大量存在,因为Spring所谓的核心机制就是Spring依赖注入IOC.下面举一个例子说明这个问题: 先贴上目录结构: 在Eclipse中新建一个Java工程,不是JavaWeb,在这个Java配置好Spring3

SQL注入之mysql显错注入

在我们实际渗透中,明明发现一个注入点,本以为丢给sqlmap就可以了,结果sqlmap只显示确实是注入点,但是数据库却获取不了,如图1所示,这时我们可以使用手工进行注入,判断出过滤规则以及基本过滤情况,然后再选择对应的sqlmap脚本(如果有的话),本文主要是讲述如何通过mysql函数报错来进行注入,另外如何使用手工进行全程注入的利用过程,如果你知道sqlmap里面有对应的脚本的话,烦请告知一下,谢谢!. 图1 获取数据库失败 此时我们可以考虑下是否是显错注入,对于显错注入我们可以使用mysql

Statement和PreparedStatement的区别; 什么是SQL注入,怎么防止SQL注入? (转)

问题一:Statement和PreparedStatement的区别 先来说说,什么是java中的Statement:Statement是java执行数据库操作的一个重要方法,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句.具体步骤: 1.首先导入java.sql.*:这个包. 2.然后加载驱动,创建连接,得到Connection接口的的实现对象,比如对象名叫做conn. 3.然后再用conn对象去创建Statement的实例,方法是:Statement stmt = conn

C++进程注入(通过远程线程注入进程)

需要本文代码请直接跳到文章最底部下载 注入进程的方法有很多,本文主要介绍通过远程线程来注入进程的方法: 我们知道,每个进程都有4GB的地址空间,windows可用的大小大概为1.5GB左右,远程线程注入的方法主要是,打开一个线程以后,将要注入的动态库的地址写入这个地址空间,然后调用开启远程线程的函数,来执行LoadLibraryA或者LoadLibraryW(其实不存在LoadLibrary这个函数,他只是一个宏,如果是UNICODE环境的话会调用LoadLibraryW,否则就是LoadLib

Android中的依赖问题(五种依赖、eclipse、AS、添加第三方库、jar)

这篇文章的主题是: 依赖是什么 eclipse中的依赖 AS中的依赖(有一篇详细的文章讲得非常好,这里就不再写了http://blog.csdn.net/yy1300326388/article/details/46422939) 另外相关的话题有↓,可以自行查看: 安卓的support包的历史 AS中grandle具体的内容 怎么做一个给别人用的Jar包和lib库 (另外,贴图有点麻烦,所以这里图片只放了一张,如果有博友觉得本文有帮助而且希望补充图片,可以留言) 依赖是什么 依赖这个东西在安卓

Spring IOC注入(三)自动注入

前俩篇讲了一下IOC注入的set方式注入和基于构造器的注入,现在就来讲讲自动注入~ 自动注入:容器依照一些规则去装配bean中的一个属性 注意:自动装配只对[对象类型]起作用,对基本类型不起作用. 自动注入有俩种方式: 第一种: 在beans标签中配置装载方式:defadefault-autowire="byType"或defult-autowire="byName" default-autowire="byType"或default-autow

Statement和PreparedStatement的区别; 什么是SQL注入,怎么防止SQL注入?

问题一:Statement和PreparedStatement的区别 先来说说,什么是java中的Statement:Statement是java执行数据库操作的一个重要方法,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句.具体步骤: 1.首先导入java.sql.*:这个包. 2.然后加载驱动,创建连接,得到Connection接口的的实现对象,比如对象名叫做conn. 3.然后再用conn对象去创建Statement的实例,方法是:Statement stmt = conn