Dependency injection and container finishing summary

Preface

Dependency injection, also called inversion of control. To put it simply: when an instance of another class is used in a class, we do not create an instance in that class, but pass the instance as a parameter to the class after creating the instance outside the class.
When a large number of instances of a class need to be created, for the convenience of management, we separate the process of class instantiation and store them for unified management. This is calledcontainer

Primary realization

We demonstrate the basic use of dependency injection and containers by implementing a cache module.
First, create a container class 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");
		}
    }
}

The cache types are file, Db, and Redis. We establish operation classes corresponding to these three types.

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) { }
}

Create the Cache class, that is, the injected class

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

Advanced implementation

The above process implements simple dependency injection and containers, but we found that there is only one layer of dependencies above. If the Db class depends on other classes at this time, the above code may need to be changed like this

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

The above code: the cache uses the db method, that is, the database storage method, and then the db type uses mysql. So we must first inject the mysql class into the container, then inject the db class into the container, and finally inject the cache into the container and call it, and the order must be as above, otherwise the following classes will not find the dependent classes during injection. Report an error.

When there are many dependency levels, it is not only inconvenient to inject one by one, but it will also cause errors if the order is wrong. At this time, we need to use PHP's ReflectionClass reflection mechanism to build a Di container that is automatically injected and does not need to care about the order of injection.

In the above example, we need to inject dependent objects into the Di container first, and fetch them from the Di container when needed in the injected class. Automatic injection is when the injected function is instantiated or the method is called, according to the parameter type specified in the constructor or method, the parameter of the object type is instantiated and then passed into the injected class. To get all kinds of information about the class, you need to use the reflection classReflectionClass. The Di code after transformation is as follows:

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;
    }
}

The cache and db code after transformation is as follows

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

After the transformation, we can directly call the instantiation cache and call methods without registering dependencies.

But at this time, the code is very limited. When we need to adjust the cache storage method to redis, we found that it cannot be adjusted by passing in parameters. The code also needs to be optimized, mainly for the automatic resolution of dependencies in Di, and support for the analysis of arrays. And when it involves multi-layer dependency and multi-layer configuration, the registration mechanism must be introduced, but at this time, the registration only specifies the dependency and does not instantiate the dependent object. When Di resolves the dependency, the dependent object can be obtained according to the registered dependency. And registration does not need to be in order at this time.
The modified code is as follows

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

Guess you like

Origin blog.csdn.net/u012830303/article/details/103784012