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

相关文章
|
7天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1917 6
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
5天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
652 111
|
15天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2554 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
7天前
|
人工智能 弹性计算 数据库
阿里云优惠券种类解析:主要券种区别和适用群体及领取和使用指南
2026年阿里云构建了覆盖全用户的七类优惠券,本文逐一拆解了每类优惠券的核心规则、适用人群与使用技巧:大促限定的阶梯满减券分个人、企业双通道,最高可减800元;学生专属300元无门槛券支持全品类通用;按量付费用户可参与消费达标返券形成循环优惠;新用户有低门槛专享满减券尝鲜;老用户可领取系统自动发放的随机福利券;中大型企业迁云可申请最高100万元的专项补贴;云产品通用券还能在活动价基础上实现折上折。不同身份、不同采购场景的用户均可通过精准匹配对应优惠券,最大化享受优惠力度。
460 110
阿里云优惠券种类解析:主要券种区别和适用群体及领取和使用指南
|
13天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
1570 2
|
15天前
|
人工智能 自然语言处理 数据挖掘
Qwen3.8-Max-Preview深度全解析:2.4万亿参数旗舰MoE模型+Token Plan限时优惠完整落地指南
2026年7月,全新旗舰级混合专家大模型Qwen3.8-Max-Preview正式开放抢先体验,作为通义千问Qwen3系列规格最高、综合推理能力顶尖的新一代模型,该模型总参数量达到2.4万亿(2.4T),是当前线上可调用的原生多模态旗舰模型,综合推理水准对标海外顶级Fable 5模型,在复杂工程开发、长文档深度分析、多步骤智能体自治、跨境多语言创作、海量数据挖掘五大高难度业务场景实现跨越式性能提升。
1420 2
|
17天前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
1491 55
|
2天前
Qoder 一周年 × Qwen3.8-Max 正式上线,多重好礼限时领
8月3日,Qwen3.8-Max 正式上线Qoder,迎来Qoder一周年。新老用户可领800次免费调用,下单再赠2000次;夜间(22:00–08:00)调用5折;邀请好友双方得积分与调用额度。
241 0