首先需要了解什么是依赖注入,在这里我只能copy比较不错的定义如下:
依赖注入(Dependency Injection),组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。(引用:控制反转和依赖注入的理解(通俗易懂))
通过阅读yii2的源码,其中Container类使用di技术实现对类的管理(呵呵~),yii2所支持的对一个类的定义(definition)格式比较丰富:
/**
* Registers a class definition with this container.
*
* For example,
*
* ```php
* // register a class name as is. This can be skipped.
* $container->set('yii\db\Connection');
*
* // register an interface
* // When a class depends on the interface, the corresponding class
* // will be instantiated as the dependent object
* $container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');
*
* // register an alias name. You can use $container->get('foo')
* // to create an instance of Connection
* $container->set('foo', 'yii\db\Connection');
*
* // register a class with configuration. The configuration
* // will be applied when the class is instantiated by get()
* $container->set('yii\db\Connection', [
* 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
* 'username' => 'root',
* 'password' => '',
* 'charset' => 'utf8',
* ]);
*
* // register an alias name with class configuration
* // In this case, a "class" element is required to specify the class
* $container->set('db', [
* 'class' => 'yii\db\Connection',
* 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
* 'username' => 'root',
* 'password' => '',
* 'charset' => 'utf8',
* ]);
*
* // register a PHP callable
* // The callable will be executed when $container->get('db') is called
* $container->set('db', function ($container, $params, $config) {
* return new \yii\db\Connection($config);
* });
* ```
*
*/
所谓类的定义(definition)是指class的类名(yii2中利用命名空间确定类名)。
所谓依赖注入,就是不再通过new Class()去显示的创建一个类的实例(当然不是这么肤浅的目的了),而是定义或者是配置过一次,再次获取该类的实例的时候,仅仅需要某个方法就能实现。
在创建一个DB类的实例的过程就是:new DB() ,或者DB类的初始化有依赖项如这样:new DB(string dsn, string username = 'xxx'...),这时,当实例一个DB对象时,就需要给出其构造函数的依赖项的值,依赖项可能是值类型或者某个对象,值类型的话我们给值就行,对于对象的话,当然也是通过di(依赖注入)创建一个(如果没有特别实现的话, 就按照构造函数定义的来), 但是关于对象的类的定义也需要在容器类中缓存,不然容器就找不到依赖对象的定义,从而无法完成DB(打比方)依赖项的实例化。
涉及技术:关于对象的嵌套依赖就用递归的算法实现,而构造函数的参数通过类的反射(ReflectionClass)获取, 而类的实例化通过ReflectionClass::newInstanceArgs($args)方法实现。
目录结构:
/index.php 入口文件
/auto_loader.php 加载类的实现方法文件
/di/Container.php 容器类定义
/common/* 通用类
/web/Application.php 应用程序类
代码如下:
index.php
<?php
use \dhope\web\Application;
require 'auto_loader.php';
spl_autoload_register('auto_loader');
(new Application())->run();
auto_loader.php
<?php
defined('PATH') or define('PATH', __DIR__);
function auto_loader($class)
{
$path = str_replace(
'\\',
DIRECTORY_SEPARATOR,
substr($class, strpos($class, '\\', 1)+1)
);
if(file_exists(PATH.DIRECTORY_SEPARATOR.$path.'.php')) {
require PATH.DIRECTORY_SEPARATOR.$path.'.php';
} else {
throw new Exception(sprintf('not found `%s`', $class));
}
return class_exists($class);
}
web/Application.php
<?php
namespace dhope\web;
use \dhope\di\Container;
class Application
{
public function run()
{
$container = new Container();
$container->set('db', '\dhope\common\DB', [
'dsn' => 'dh.dhope.com',
'port' => 80
]);
$container->set('mysql', '\dhope\common\Mysql', [
'db' => [
'class' => 'db'
],
'mode' => 0
]);
$container->set('t_user', '\dhope\common\TableUser', [
'connect' => [
'class' => 'mysql'
]
]);
$handle = $container->get('t_user');
echo json_encode($handle->showTable(), JSON_UNESCAPED_UNICODE);
}
}
di/Container.php
<?php
namespace dhope\di;
use ReflectionClass;
use Exception;
define('PARAMETER_OBJ', 'class');
define('PARAMETER_VAR', 'var');
/**
* 容器类,支持依赖注入
* @author zhangchao02
* @date 2019-08-11
*/
class Container
{
//class 类的定义
private $_definitions;
//class 类的实例
private $_instances;
//class 类构造函数的初始化参数
private $_params;
//class 类的反射
private $_classReflection;
//class 类的依赖
private $_dependencies;
public function __construct()
{
//变量初始化
$this->_definitions = [];
$this->_instances = [];
$this->_params = [];
$this->_classReflection = [];
$this->_dependencies = [];
}
/**
* 初始化类的定义
* @param $class 类的别名
* @param $definition 类的定义
* @param array $params 类构造函数的初始化值
* @throws Exception
*/
public function set($class, $definition, $params = [])
{
//类别名到类名的映射
$this->_definitions[$class] = $this->normalizeDefinition($class, $definition);
//类构造参数初始值的存储
$this->_params[$class] = $params;
}
/**
* 获取实例
* @param $class 类的别名
* @param bool $cover 是否复用实例
* @return mixed
*/
public function get($class, $cover = false)
{
if($cover) {
if($this->_instances[$class] && is_object($this->_instances[$class])) {
return $this->_instances[$class];
}
}
$instance = $this->build($class);;
if($cover) {
$this->_instances[$class] = $instance;
}
return $instance;
}
/**
* 创建一个实例
* @param $class 类的别名
* @return mixed
* @throws Exception
*/
public function build($class)
{
$params = $this->_params[$class];
$this->setDependencies($class, $params);
$dependencies = $this->_dependencies[$class];
$reflection = $this->_classReflection[$class];
$args = [];
foreach ($dependencies as $index => $dependency) {
if(is_array($dependency)) {
$key = key($dependency);
$value = current($dependency);
if($key == PARAMETER_OBJ) {
$args[$index] = $this->get($value);
} elseif ($key === PARAMETER_VAR) {
$args[$index] = $dependency[$value];
} else {
throw new Exception(sprintf('class `%s` not exists parameter type `%s`', $class, $key));
}
} else {
$args[$index] = $dependency;
}
}
return $reflection->newInstanceArgs($args);
}
/**
* 设置类的依赖
* @param $class
* @param $params
* @throws \ReflectionException
*/
public function setDependencies($class, $params)
{
if(!isset($this->_definitions[$class])) {
throw new Exception(sprintf('not found the definition of class `%s`', $class));
}
$definition = $this->_definitions[$class]['class'];
if(isset($this->_classReflection[$class])) {
$reflection = $this->_classReflection[$class];
} else {
try{
$reflection = new ReflectionClass($definition);
} catch (\ReflectionException $e) {
throw new Exception(sprintf('Failed to instantiate component or class "%s"', $class));
}
}
$constructor = $reflection->getConstructor();
$dependency = [];
if($constructor !== null) {
foreach ($constructor->getParameters() as $param) {
$name = $param->getName();
if(isset($params[$name])) {
if(is_array($params[$name])) {
$key = key($params[$name]);
if($key === PARAMETER_OBJ) {
$dependency[] = [
PARAMETER_OBJ => current($params[$name])
];
} else {
$dependency[] = [
PARAMETER_VAR => current($params[$name])
];
}
} else {
$dependency[] = $params[$name];
}
} elseif ($param->isDefaultValueAvailable()) {
$dependency[] = $param->getDefaultValue();
} else {
throw new Exception(sprintf('class `%s` construct miss parameter `%s`',
$class,
$name
));
}
}
}
$this->_classReflection[$class] = $reflection;
$this->_dependencies[$class] = $dependency;
}
/**
* 支持多种类的定义格式
* @param $class
* @param $definition
* @return array
* @throws Exception
*/
public function normalizeDefinition($class, $definition)
{
$normalize_definition = [];
if(!$definition) {
if(!class_exists($class)) {
throw new Exception(sprintf(
'class `%s` not exists',
$class
));
}
$normalize_definition['class'] = $class;
} elseif (is_string($definition) && class_exists($definition)) {
$normalize_definition['class'] = $definition;
} elseif (is_array($definition)) {
if(isset($definition['class']) && class_exists($definition['class'])) {
$normalize_definition['class'] = $definition['class'];
} else {
throw new Exception(sprintf('class `%s` definition not found', $class));
}
} else {
throw new Exception(sprintf('class `%s` not definition', $class));
}
return $normalize_definition;
}
}
common/DB.php
<?php
namespace dhope\common;
class DB
{
private $dsn;
private $port;
public function __construct(string $dsn, int $port)
{
$this->dsn = $dsn;
$this->port = $port;
}
public function toString()
{
return [
'dsn' => $this->dsn,
'port' => $this->port
];
}
}
common/Mysql.php
<?php
namespace dhope\common;
define('MODE_RA', 0);
define('MODE_RW', 1);
class Mysql
{
private $db;
private $mode;
public function __construct(DB $db, $mode = MODE_RA)
{
$this->db = $db;
$this->mode = $mode;
}
public function toString()
{
return json_encode([
'db' => $this->db->toString(),
'mode' => $this->mode
]);
}
}
common/TableUser.php
<?php
namespace dhope\common;
class TableUser
{
private $handle;
private $table;
public function __construct(Mysql $connect)
{
$this->handle = $connect;
$this->table = 'user';
}
public function showTable()
{
return [
'table' => $this->table,
'handle' => $this->handle->toString()
];
}
}
以上代码实现的就是一个利用容器类+依赖注入技术创建一个类的实例的过程