Yii2 理解di


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

Yii 2.0 权威指南 本教程的发布遵循 Yii 文档使用许可. 版权所有 2014 (c) Yii Software LLC. 介绍 已定稿 关于 Yii 已定稿 从 Yii 1.1 升级 入门 已定稿 安装 Yii 已定稿 运行应用 已定稿 第一次问候 已定稿 使用 Forms 已定稿 玩转 Databases 已定稿 用 Gii 生成代码 已定稿 更上一层楼 应用结构 已定稿 结构概述 已定稿 入口脚本 已定稿 应用 已定稿 应用组件 已定稿 控制器(Controller) 已定稿 视图(View) 已定稿 模型(Model) 已定稿 过滤器 已定稿 小部件(Widget) 已定稿 模块(Module) 已定稿 前端资源(Asset) 已定稿 扩展(extensions) 请求处理 已定稿 运行概述 已定稿 引导(Bootstrapping) 已定稿 路由(Route)引导与创建 URL 已定稿 请求(Request) 已定稿 响应(Response) 已定稿 Sessions(会话)和 Cookies 已定稿 错误处理 已定稿 日志 关键概念 已定稿 组件(Component) 已定稿 属性(Property) 已定稿 事件(Event) 已定稿 行为(Behavior) 已定稿 配置(Configurations) 已定稿 类自动加载(Autoloading) 已定稿 别名(Alias) 已定稿 服务定位器(Service Locator) 已定稿 依赖注入容器(DI Container) 配合数据库工作 编撰中 数据访问对象(DAO) - 数据库连接、基本查询、事务和模式操作 编撰中 查询生成器(Query Builder) - 使用简单抽象层查询数据库 编撰中 活动记录(Active Record) - 活动记录对象关系映射(ORM),检索和操作记录、定义关联关系 编撰中 数据库迁移(Migration) - 在团体开发中对你的数据库使用版本控制 待定中 Sphinx 待定中 Redis 待定中 MongoDB 待定中 ElasticSearch 接收用户数据 编撰中 创建表单 已定稿 输入验证 编撰中 文件上传 待定中 多模型同时输入 显示数据 编撰中 格式化输出数据 待定中 分页(Pagination) 待定中 排序(Sorting) 编撰中 数据提供器 编撰中 数据小部件 编撰中 主题 安全 编撰中 认证(Authentication) 编撰中 授权(Authorization) 编撰中 处理密码 待定中 客户端认证 待定中 安全领域的最佳实践 缓存 已定稿 概述 已定稿 数据缓存 已定稿 片段缓存 已定稿 分页缓存 已定稿 HTTP 缓存 RESTful Web 服务 已定稿 快速入门 已定稿 资源 已定稿 路由 已定稿 格式化响应 已定稿 授权验证 已定稿 速率限制 已定稿 版本化 已定稿 错误处理 已定稿 测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值