依赖注入和容器的整理总结

前言

依赖注入,也叫控制反转。简单的来说就是:一个类中用到其他类的实例时,我们不在该类中创建实例,而是在类外创建实例后把实例作为参数传入类中。
当需要创建大量的类的实例的时候,我们为了方便管理,把类实例化的过程分离出来,并存储起来统一管理,这就叫 容器

初级实现

我们通过实现一个缓存模块,来展示依赖注入和容器的基本使用。
首先,建立一个容器类Di.php

<?php   
class Di
{
    protected  $_definitions= [];//存储依赖实例
    public function set($name, $definition)
    {
        $this->_definitions[$name] = $definition;
    }
    public function get($name)
    {
        if (isset($this->_definitions[$name])) {
            $definition = $this->_definitions[$name];
        } else {
            throw new Exception("class not exist");
        }
        if (is_object($definition) || is_callable($definition)) {
            $instance = call_user_func($definition);
        }else{
            throw new Exception("class not obj");
		}
    }
}

缓存类型有file、Db、Redis,我们建立这三种类型对应的操作类

interface BackendInterface{
    public function find($key, $lifetime);
    public function save($key, $value, $lifetime);
    public function delete($key);
}
class redis implements BackendInterface
{
    public function find($key, $lifetime) { }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
}

class db implements BackendInterface
{
    public function find($key, $lifetime) { }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
}

class file implements BackendInterface
{
    public function find($key, $lifetime) { }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
}

建立Cache类,即被注入类

class cache
{
    protected $_di;

    protected $_options;

    protected $_connect;

    public function __construct($options)
    {
        $this->_options = $type;
    }

    public function setDI($di)
    {
        $this->_di = $di;
         $options = $this->_options;
        if (isset($options['connect'])) {
            $service = $options['connect'];
        } else {
            $service = 'redis';
        }
		//根据参数,从容器中找出对应的实例
        $this->_connect = $this->_di->get($service);
    }


    public function get($key, $lifetime)
    {
        $connect = $this->_connect;
        return $connect->find($key, $lifetime);
    }

    public function save($key, $value, $lifetime)
    {
        $connect = $this->_connect;
        return $connect->save($key, $lifetime);
    }

    public function delete($key)
    {
        $connect = $this->_connect;
        $connect->delete($key, $lifetime);
    }
}
/*****************调用cache************************/
$di = new Di();
//  往Di容器中注入需要用到的实例
$di->set('redis', function() {
     return new redisDB([
         'host' => '127.0.0.1',
         'port' => 6379
     ]);
});
$di->set('cache', function() use ($di) {
     $cache = new cache([
         'connect' => 'redis'
     ]);
     $cache->setDi($di);
     return $cache;
});

// 调用
$cache = $di->get('cache');

高级实现

以上过程实现了简单的依赖注入和容器,但是我们发现,上面的依赖关系只有一层。如果此时Db类又依赖了其他类时,上面的代码可能要这么改

class db implements BackendInterface
{
	public $_options;
	public $_di;
	public __construct($options){
		 if (isset($options['type'])) {
            $service = $options['type'];
        } else {
            $service = 'mysql';
        }
		//根据参数,从容器中找出对应的实例
        return $this->_di->get($service);
	}
	public function setDI($di)
    {
        $this->_di = $di;
    }
    public function find($key, $lifetime) {
		$connect = $this->di->connect();
		return $connect->find();
	 }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
    }
}
    /*************************调用***********************************/
    $di = new Di();
//  往Di容器中注入需要用到的实例
$di->set('mysql', function() {
     return new mysql([
         'host' => '127.0.0.1',
         'port' => 6379
     ]);
});
$di->set('db', function() use ($di) {
    $db = new db([
         ‘type’=> 'mysql',
     ]);
     $db->setDi($di);
     return $db;
});


$di->set('cache', function() use ($di) {
     $cache = new cache([
         'connect' => 'db'
     ]);
     $cache->setDi($di);
     return $cache;
});
$cache = $di->get('cache');
}

上面代码:缓存使用了db方式即数据库方式存储,然后db类型使用的mysql。所以我们先要把mysql类注入到容器,然后再把db类注入到容器,最后把cache注入容器并调用,而且顺序必须按照上面的样子,否则后面的类在注入时找不到依赖的类会报错。

当依赖层级较多的时候,一个个的注入不仅不方便,一旦顺序错误也会造成错误。这个时候我们就需要使用php的ReflectionClass反射机制,构建一个自动注入且不需要关心注入顺序的Di容器。

上面的例子中,我们需要先把依赖的对象注入到Di容器中,在被注入的类中需要用到时从Di容器中取。自动注入则是在被注入函数实例化或者方法被调用时,根据构造函数或方法中指定的参数类型,把对象类型的参数实例化后再传入被注入类中。而要获取类的各种信息,就需要用到反射类 ReflectionClass。改造后Di代码如下:

class Di {
	protected $_definitions=[];
    // 获得类的对象实例
    public static function getInstance($className) {
		if(isset($this->_definitions[$className]))	return $this->_definitions[$className];
        $paramArr = self::getMethodParams($className);
        $this->_definition[$className] = (new ReflectionClass($className))->newInstanceArgs($paramArr);
        return $this->_definition[$className];
    }

    //直接调用类的方法
    public static function make($className, $methodName, $params = []) {
        $instance = self::getInstance($className);
        // 获取方法所需要依赖注入的参数
        $paramArr = self::getMethodParams($className, $methodName);
        return $instance->$methodName(array_merge($paramArr, $params));
    }

    // 获得类的方法参数,把对象类参数实例化
    protected static function getMethodParams($className, $methodsName = '__construct') {

        // 通过反射获得该类的信息
        $class = new ReflectionClass($className);
        $paramArr = []; // 记录参数,和参数类型

        // 判断函数方法名是否存在
        if ($class->hasMethod($methodsName)) {
            $construct = $class->getMethod($methodsName);
            // 判断函数方法是否有参数
            $params = $construct->getParameters();
            if (count($params) > 0) {
                // 判断参数类型
                foreach ($params as $key => $param) {
                    if (is_obj($param)) {
                        // 获得参数类型名称
                        $paramClassName = get_class($param);
                        //递归获取依赖的对象是否依赖其他对象
                        $args = self::getMethodParams($paramClassName);
                        //返回依赖对象的实例作为参数
                        $paramArr[] = (new ReflectionClass($paramClassName))->newInstanceArgs($args);
                    }
                }
            }
        }
        return $paramArr;
    }
}

cache和db代码改造后如下

class cache
{
    protected $_connect;

    public function __construct(db $db)
    {
        $this->_connect= $db;
    }
    public function get($key, $lifetime)
    {
        $connect = $this->_connect;
        return $connect->find($key, $lifetime);
    }

    public function save($key, $value, $lifetime)
    {
        $connect = $this->_connect;
        return $connect->save($key, $lifetime);
    }

    public function delete($key)
    {
        $connect = $this->_connect;
        $connect->delete($key, $lifetime);
    }
}


class db implements BackendInterface
{
	public $_connect;
	public __construct(mysql $mysql){
        $this->_connect = $mysql->connect();
	}
    public function find($key, $lifetime) {
		$connect = $this->_connect();
		return $connect->find();
	 }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
    }
}

//调用
$cache = Di::getInstance('cache');

改造后我们无需注册依赖就可以直接调用实例化cache并调用方法。

但这个这个时候,代码的局限性很大,当我们需要调整cache的存储方式为redis时,发现不能通过传入参数调整。代码还需要优化,主要是Di中自动解析依赖的地方,要支持数组的解析。而且涉及多层依赖多层配置时,还是要引入注册机制,但此时是的注册只指定依赖关系,不实例化依赖对象,当Di解析依赖时,可根据注册的依赖关系获取依赖对象。且此时注册也不需要分先后顺序。
修改后代码如下

class Di {
	protected $_dependencies=[];//存储依赖对象实例
	protected $_definitions=[];//存储注册依赖信息
	protected $_params=[];//存储映射对象的参数
    // 注册依赖关系
    public function set($className,$param) {
		if(isset($this->_definitions[$className]))	return $this->_definitions[$className];
        $paramArr = self::getMethodParams($className);
        $this->_definition[$className] = (new ReflectionClass($className))->newInstanceArgs($paramArr);
		
       $this->_definitions[$className] = $param['class'];
       $this->_params[$className] = $param;
    }

    // 获取依赖
    public function get($className) {
		if(isset($this->_dependencies($className)))	return $this->_dependencies($className));
		// 使用PHP的反射机制来获取类的有关信息,主要就是为了获取依赖信息
        $reflection = new ReflectionClass($this->_definitions[$className]);
		$dependencies = unset($this->params[$className]);
        // 通过类的构建函数的参数来了解这个类依赖于哪些单元
        $constructor = $reflection->getConstructor();
		    if ($constructor !== null) {
		        foreach ($constructor->getParameters() as $param) {
		            if ($param->isDefaultValueAvailable()) {
		                // 构造函数如果有默认值,将默认值作为依赖。即然是默认值了,
		                // 就肯定是简单类型了。
		                $dependencies[] = $param->getDefaultValue();
		            } else {
		                $c = $param->getClass();
		                // 构造函数没有默认值,则为其创建一个引用。
		                // 就是前面提到的 Instance 类型。
		                $dependencies[] = $this->get($c->getName());
		            }
		        }
		    }
			$obj = $reflection->newInstanceArgs($dependencies);
			$this->_dependencies[$className] = $obj;
			return $obj;
    }
}

class cache
{
    protected $_connect;
	//类的依赖信息尽量放在构造函数中,便于在实例化类时解析相关依赖
	//解析依赖时根据指定的实例路径生成映射,我们从上面Di的get代码中看到,获取依赖对象的映射时
	//并不是直接使用的指定路径,而是通过指定的路径从_definitions中获取真实的依赖类信息,这样我们需要用到不同模块时,只要修改配置中的class等信息即可
    public function __construct(BackendInterface $cache)
    {
        $this->_connect= $cache;
    }
    public function get($key, $lifetime)
    {
        $connect = $this->_connect;
        return $connect->find($key, $lifetime);
    }

    public function save($key, $value, $lifetime)
    {
    }

    public function delete($key)
    {
    }
}

class db implements BackendInterface
{
	public $connect;
	public __construct(sql $db){
		 $this->connect = $db;
	}
    public function find($key, $lifetime) {
		return $this->connect->find();
	 }
    public function save($key, $value, $lifetime) { }
    public function delete($key) { }
    }
}
//调用
$di = new Di();
//注册部分先后顺序,因为此时并没有实例化,只是注册了依赖关系
$di->set('cache',['class'=>'\\cache'])
$di->set('BackendInterface ',['class'=>'\\app\\db']);//使用数据库存储缓存数据
//若我们要用redis方式存储缓存数据,可以修改后面的配置信息
//如:$di->set('BackendInterface ',['class'=>'\\app\\redis','host'=>'','name'=>'']);

$di->set('sql',['class'=>'\\sql\\mysql','host'=>'127.0.0.1','name'=>'test']);
//调用时自动解析依赖并实例化
$cache = $di->get('cache');

猜你喜欢

转载自blog.csdn.net/u012830303/article/details/103784012