Yii2 理解di

简介: 链接版本简述ContainerInstance TOP示例说明 TOP0 链接http://alex-my.xyz/web/Yii2-理解di1 版本// yii\BaseYii\getVersionpublic static function getVersion(){ return '2.0.10';}


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

相关文章
|
4月前
|
设计模式 测试技术 数据库连接
Entity Framework Core 中的依赖注入超厉害!DI 与 DbContext 完美结合,提升开发效率
【8月更文挑战第31天】依赖注入(DI)是一种软件设计模式,用于将对象的依赖关系与其创建过程解耦,从而提升代码的可测试性、可维护性和可扩展性。在Entity Framework Core中使用DI能够提高可测试性,便于替换DbContext实现以进行单元测试;增强可维护性,使代码模块化并清晰展示组件间的依赖关系;提升可扩展性,方便添加新服务和功能而不需修改现有代码。通过Microsoft.Extensions.DependencyInjection等依赖注入容器,可将DbContext注册并注入到需要使用的类中,简化数据库管理和测试流程。
131 0
|
设计模式 Java 容器
DI依赖注入篇
依赖注入(Dependency Injection,DI)是一种设计模式,它用于解耦组件之间的依赖关系。在DI中,组件不再负责自己的依赖对象的创建和管理,而是由外部容器负责将依赖对象注入到组件中。
168 1
|
缓存 安全 数据处理
Yii2相对于Yii1有哪些改进?
Yii2相对于Yii1有哪些改进?
|
Oracle 关系型数据库 MySQL
Yii2的基本要求是什么?
Yii2的基本要求是什么?
160 0
|
缓存 开发框架 安全
Yii2是什么?
Yii2是什么?
306 0
|
容器
DI——依赖注入
DI——依赖注入
161 0
|
Web App开发 前端开发 容器
MVC 5 + EF6 完整教程15 -- 使用DI进行解耦
原文:MVC 5 + EF6 完整教程15 -- 使用DI进行解耦 如果大家研究一些开源项目,会发现无处不在的DI(Dependency Injection依赖注入)。 本篇文章将会详细讲述如何在MVC中使用Ninject实现DI 文章提纲 场景描述 & 问题引出 第一轮重构 引入Ninject 第二轮重构 总结 场景描述 & 问题引出 DI是一种实现组件解耦的设计模式。
975 0
|
容器 Java Spring
什么是IoC和DI?DI是如何实现的?
IoC叫控制反转,是Inversion of Control的缩写,DI(Dependency Injection)叫依赖注入,是对IoC更简单的诠释。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。
3412 0