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');