Yii2 理解di

    • 链接
    • 版本
    • 简述
    • Container
    • Instance TOP
    • 示例说明 TOP

0 链接

http://alex-my.xyz/web/Yii2-理解di

1 版本

// yii\BaseYii\getVersion
public static function getVersion()
{
    return '2.0.10';
}

2 简述

简单的说就是di把类的构造函数分析好存储起来,配上给定的参数,创建实例。

// 类名给定后通过php的反射机制获取构造函数信息
// set的第二个参数是构造函数需要使用的参数值
$container->set('yii\db\Connection', [
        'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
        'username' => 'root',
        'password' => '123456',
        'charset' => 'utf8',
    ]);

3 Container

\yii\di\Container

$_singletons, 保存单例
键: 类名,接口名,别名
值: 类的示例, null表示未初始化
$_definitions, 保存依赖定义
键:类名,接口名,别名
值:函数, 数组(一定要含有class元素)
$_params, 保存构造函数的参数
键:类名,接口名,别名
值:数组
$_reflections, 缓存类反射实例
键:类名,接口名,别名
值:类反射实例
$_dependencies, 缓存依赖信息
键:类名,接口名,别名
值:

normalizeDefinition处理依赖定义, 这里的$definition会影响到$class的构造函数参数值
返回一个数组,且数组中必须含有class元素
示例说明见set, setSingleton的后面

protected function normalizeDefinition($class, $definition)
{
    // 则直接返回,$class构造函数没有参数
    if (empty($definition))
    {
        return ['class' => $class];
    }
    // 如果是字符串,则认为$definition是所依赖的类名,接口名,或者别名
    elseif (is_string($definition))
    {
        return ['class' => $definition];
    }
    // 认为$class是别名,$definition能得到一个对象实例
    elseif (is_callable($definition, true) || is_object($definition))
    {
        return $definition;
    }
    elseif (is_array($definition))
    {
        // $definition除了class元素外,都是$class类构造函数的参数值
        if (!isset($definition['class']))
        {
            // 如果没有包含'class'元素,则$class一定是一个完整路径的类名/接口名
            // 比如$class为yii\db\Connection
            if (strpos($class, '\\') !== false)
            {
                $definition['class'] = $class;
            }
            else
            {
                throw new InvalidConfigException("A class definition requires a \"class\" member.");
            }
        }
        return $definition;
    }
    else
    {
        throw new InvalidConfigException("Unsupported definition type for \"$class\": " . gettype($definition));
    }
}

set, setSingleton,注册依赖
前者注册的之后每次get都会得到新的实例
后者注册的之后每次get都是得到第一次实例化的对象
二者的区别将在get函数中体现

public function set($class, $definition = [], array $params = [])
{
    $this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
    $this->_params[$class] = $params;
    unset($this->_singletons[$class]);
    return $this;
}
public function setSingleton($class, $definition = [], array $params = [])
{
    $this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
    $this->_params[$class] = $params;
    $this->_singletons[$class] = null;
    return $this;
}

使用示例, set, setSingleton, normalizeDefinition相关
函数原型:
normalizeDefinition($class, $definition)

// 在normalizeDefinition中,$class为真正的类名
$container->set('yii\db\Connection');

// 在normalizeDefinition中, $definition才是真正将来实例化的类
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');

// 相当于注册了一个别名foo, 因为$_definitions, $_params, $_singletons都是用$class做为键
// 真正的class信息会存储在$definition['class']中
$container->set('foo', 'yii\db\Connection');

// $_definitions直接保存了$definition这些依赖信息($class构造函数的参数信息)
$container->set('yii\db\Connection', [
    'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
    'username' => 'root',
    'password' => '123456',
    'charset' => 'utf8',
    ]);

// 前面一个不方便使用,可以定义别名,可以使用$container->get('db')
$container->set('db', [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
    'username' => 'root',
    'password' => '123456',
    'charset' => 'utf8',
    ]);

// $definition可以是函数,每次使用的时候都会调用这个函数生成新的实例
$container->set('db', function ($container, $params, $config) {
        return new \yii\db\Connection($config);
    });

// $definition可以是实例,每次调用都会使用这个实例, 相当于setSingleton
$container->set('db', new \yii\db\Connection($config))

getDependencies($class)解析依赖信息,主要是获取类的构造函数的信息,这样才能调用构造函数创建实例
参数$class是类名

protected function getDependencies($class)
{
    // 如果已有缓存的依赖信息,则直接使用
    if (isset($this->_reflections[$class]))
    {
        return [$this->_reflections[$class], $this->_dependencies[$class]];
    }

    $dependencies = [];
    // 使用PHP5的反射来获取类的信息
    // 通过ReflectionClass,可以获取$class的以下信息:
    // 属性,函数,常量,静态属性,命名空间等
    $reflection = new ReflectionClass($class);
    // 获得$class这个类的构造函数信息
    $constructor = $reflection->getConstructor();
    if ($constructor !== null)
    {
        // 解析构造函数的参数
        foreach ($constructor->getParameters() as $param)
        {
            // 如果参数有默认值,则直接使用该默认值
            if ($param->isDefaultValueAvailable())
            {
                $dependencies[] = $param->getDefaultValue();
            }
            // 用Instance封装参数, Instance::id存储着类名,在build中会进行实例化
            else
            {
                $c = $param->getClass();
                $dependencies[] = Instance::of($c === null ? null : $c->getName());
            }
        }
    }
    // 缓存起来,供下次使用
    $this->_reflections[$class] = $reflection;
    $this->_dependencies[$class] = $dependencies;

    return [$reflection, $dependencies];
}

resolveDependencies实例化依赖,也就是创建构造函数的参数对象

protected function resolveDependencies($dependencies, $reflection = null)
{
    foreach ($dependencies as $index => $dependency)
    {
        // 在解析依赖信息的getDependencies中,有部分参数没有默认值,而是创建了Instance对象
        // 这里会将这些Instance对象实例化对真正的构造函数的参数对象
        if ($dependency instanceof Instance)
        {
            if ($dependency->id !== null)
            {
                // 从di中获取真正的示例对象
                $dependencies[$index] = $this->get($dependency->id);
            }
            ...
        }
    }
    return $dependencies;
}

真正创建对象的是通过build, $class是类名,而不是别名,接口名

protected function build($class, $params, $config)
{
    // 类的信息$reflection
    // 类的构造函数信息
    list ($reflection, $dependencies) = $this->getDependencies($class);
    // 用$params的内容补充,覆盖到构造函数信息中
    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);
    }
    // 如果是实现了接口Configurable,需要将配置放到构造函数参数列表的最后一个
    if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable'))
    {
        $dependencies[count($dependencies) - 1] = $config;
        return $reflection->newInstanceArgs($dependencies);
    }
    else
    {
        $object = $reflection->newInstanceArgs($dependencies);
        foreach ($config as $name => $value)
        {
            $object->$name = $value;
        }
        return $object;
    }
}

我们最终会用到的是get函数

public function get($class, $params = [], $config = [])
{
    // 单例,且存在,则直接使用
    if (isset($this->_singletons[$class]))
    {
        return $this->_singletons[$class];
    }
    // 没有使用set/setSingleton注册的,直接创建实例
    elseif (!isset($this->_definitions[$class]))
    {
        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))
    {
        // $class可能是类名,也可能是别名
        // $concrete只能是类名
        $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
        {
            // 如果$class是别名,则传入类名递归
            $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))
    {
        $this->_singletons[$class] = $object;
    }

    return $object;
}

3 Instance TOP

\yii\di\Instance
用于存储Container中$class构造函数的参数,延迟实例化

$id, 组件id, 可以是类名,接口名,或者别名。

4 示例说明 TOP

该示例位于\yii\di\Container中

namespace app\models;

use yii\base\Object;
use yii\db\Connection;
use yii\di\Container;

interface UserFinderInterface
{
 function findUser();
}

class UserFinder extends Object implements UserFinderInterface
{
 public $db;

 public function __construct(Connection $db, $config = [])
 {
     $this->db = $db;
     parent::__construct($config);
 }

 public function findUser()
 {
 }
}

class UserLister extends Object
{
 public $finder;

 public function __construct(UserFinderInterface $finder, $config = [])
 {
     $this->finder = $finder;
     parent::__construct($config);
 }
}

$container = new Container;
// 注册方法1,类名+参数
$container->set('yii\db\Connection', [
 'dsn' => '...',
]);
// 注册方法2,接口名+类名
$container->set('app\models\UserFinderInterface', [
 'class' => 'app\models\UserFinder',
]);
// 注册方法3,别名+类名
$container->set('userLister', 'app\models\UserLister');
// 使用
$lister = $container->get('userLister');

TOP

时间: 2024-10-30 23:18:32

Yii2 理解di的相关文章

Yii2 理解filters

版本 ActionFilter AccessControl AccessRule VerbFilter 其它 1 版本 // yii\BaseYii\getVersion public static function getVersion() { return '2.0.10'; } 2 ActionFilter 动作过滤器的基类, 有两个重要的变量, 这两个变量都是存储action id $except: 过滤器将不会应用于在这里面出现的action,即使出现在$only中,过滤器也不会有效.

Yii2 理解Validator

版本 Validator BooleanValidator CompareValidator DateValidator DefaultValueValidator EachValidator EmailValidator ExistValidator FileValidator FilterValidator ImageValidator IpValidator NumberValidator RangeValidator RegularExpressionValidator Required

Yii2 理解Controller

版本 继承与实现 actions createAction getModules runAction run findLayoutFile render renderContent 参考 1 版本 // yii\BaseYii\getVersion public static function getVersion() { return '2.0.10'; } 2 继承与实现 Controller继承与Component, 并实现了ViewContextInterface接口. 在Control

Yii2 理解Component

版本 继承与实现 event和behaviors behaviors 添加Behavior到Component ensureBehaviors attachBehavior和attachBehaviors detachBehavior和detachBehaviors __get __set __call on和off 1 版本 // yii\BaseYii\getVersion public static function getVersion() { return '2.0.10'; } 2

Yii2 理解Object

版本 继承与实现 构造函数 __construct __get __set method_exists __isset __unset 其余判断函数 1 版本 // yii\BaseYii\getVersion public static function getVersion() { return '2.0.10'; } 2 继承与实现 Object实现了Configurable接口. Configureable要求在构造函数的参数末尾加上$config public function __c

谈谈对Spring IOC的理解

学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring Ioc的理解. 一.分享Iteye的开涛对Ioc的精彩讲解 首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinniansh

如何深入理解DIP、IoC、DI及IoC容器

前言 对于大部分小菜来说,当听到大牛们高谈DIP.IoC.DI以及IoC容器等名词时,有没有瞬间石化的感觉?其实,这些"高大上"的名词,理解起来也并不是那么的难,关键在于入门.只要我们入门了,然后循序渐进,假以时日,自然水到渠成. 好吧,我们先初略了解一下这些概念. 依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念). 控制反转(IoC):一种反转流.依赖和接口的方式(DIP的具体实现方式). 依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式). Io

对IOC和DI的理解

首先说一下什么是IOC和DI,IOC是Inversion of Control(控制反转)的简写,DI是Dependency Injection(依赖注入)的简写,martinfowler对IOC的解释为:"Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion

如何理解IOC 依赖注入的思想(目前见过最好的对DI的描述)

1 IoC理论的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑. 图1:软件系统中耦合的对象 如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针.分针和秒针顺时针旋转,从而在表盘上产生正确的时间.图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务.我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组