使用php依赖注入实现

首先需要了解什么是依赖注入,在这里我只能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()
        ];
    }

}

以上代码实现的就是一个利用容器类+依赖注入技术创建一个类的实例的过程

发布了31 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_36557960/article/details/99172917