Yii2 理解di

来源:互联网 发布:做电力行业的软件 编辑:程序博客网 时间:2024/05/17 23:41

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


0 链接

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

1 版本

// yii\BaseYii\getVersionpublic 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

0 0
原创粉丝点击