PHP 依赖注入

18 Aug 2016 Category: PHP

考虑一个问题,如果一个web应用需要一个日志服务,日志服务可以是文本,数据库或者邮件的形式, 而且日志需要将获取的信息格式化指定的形式。应用可以根据需要,任意切换日志服务是文本还是数 据库还是邮件。如果以传统的方式,日志记录的代码类似下面的形式:



    class Logger{
        public function formatLog($log)
        {
            //TO-DO format logger
            return $log;
        }

        public function log($str,$type)
        {
            $logTxt = $this->format($str);
            swtich($type)
            {
                case 'database':
                    $this->databaseLog($logTxt);break;
                case 'file':
                    $this->fileLog($$logTxt);break;
                case 'email':
                    $this->emailLog($$logTxt);break;
            }
        }

        public function databaseLog($str)
        {
            $db = new DbHelper();
            $db->save('logger',$str);
        }

        public function fileLog($str)
        {
            $file = new FileHelper();
            $file->save('logger',$str);
        }

        public function emailLog($str)
        {
            $email = new EmailHelper();
            $email->send(ADMIN_EMAIL,$str);
        }
        ...
    }


从上面的代码中可以看出来,logger类依赖数据库操作类DbHelper,依赖文件操作类FileHelper, 依赖邮箱操作类FileHelper。但是这些关系都是写在Logger这个类文件中的,耦合性高。如果项目 中希望在不同的页面将日志记录在不同的文件,数据表,或者发送给不同的管理者邮箱呢?这个时 候就需要对类的依赖在不同的地方进行注入。在百度百科上,依赖注入 说的太高深了,就是解除代码之间的耦合关系。

注入有一下几种方式

    class Logger{
        public $loggerHnadler;
        public __construct($loggerHandler)
        {
            $this->loggerHandler = $this->loggerHandler;
        }

        public function log($str)
        {
            $this->loggerHnadler->log($str);
        }
    }

    class FileLoggerHandler{
        $public $filePath;
        public __construct($filePath)
        {
            $this->filePath = $this->filePath;
        }

        public function log($str)
        {
            file_put_contents($this->filePath,$str);
        }
    }

    class EmailLoggerHandler{
        $public $adminMail;
        public __construct($adminMail)
        {
            $this->adminMail = $this->adminMail;
        }

        public function log($str)
        {
            sendEmail($this->adminMail,$str);
        }
    }

    //调用

    $fileLoggerHandler = new FileLoggerHandler(WEB_ROOT.'/logger/login.log');

    $logger = new Logger(fileLoggerHandler);
    $logger->log('login error'.getError());

    $emailLoggerHandler = new EmailLoggerHandler(DEV_ABB_EMAIL);
    $logger = new Logger(emailLoggerHandler);
    $logger->log('login error'.getError());


    $logger = new Logger();
    $logger->setLoggerHandler(fileLoggerHandler);
    $logger->log();
    $logger->setLoggerHandler(emailLoggerHandler);
    $logger->log();

上面的两种注入方式都需要提前准备依赖的类对象。加速文件日志类依赖文件读写类,依赖 目录操作,权限验证等相关类文件,则需要按依赖顺序构造好相应的类对象,并且对各个对 象进行依赖的注入。所以需要重复很多对象创建,set的操作。因此就有了依赖注入容器 container,用于实现类的依赖对象的管理等操作。github上有一个最最简单的Container Twittee,简单而有效。

    class Container{
        public $cont = array();
        function __set($k, $c) { $this->s[$k]=$c; }
        function __get($k) { return $this->s[$k](); }
    }

    class Logger{
        public $container;

        public __construct()
        {
            $this->container = new Container();
        }

        public function addContainerData($key,$class)
        {
            $this->container->set($key,$class);
        }

        public function log($containerKey,$str)
        {
            $this->container->get($containerKey)->log();
        }
    }


    //使用

    $logger = new Logger();
    $logger->addContainnerData('fileLog','FileLoggerHandler');
    $logger->log('error info');

这个最简单的注入容器解决了依赖对象创建繁杂的问题。但是没有解决注入对象依赖问题。所以 一些框架的依赖注入容器中添加了获取对象依赖的操作:


    class Container{
        public $cont = array();
        function __set($k, $c) { $this->s[$k]=$c; }
        function __get($k) { return $this->s[$k](); }
        function getDepends($key)
        {
            $class = $this->s[$k];
            $dependencies = array();
            $ref = new ReflectionClass($class);//获取对象的实例
            $constructor = $ref->getConstructor();//获取对象的构造方法
            if($constructor !== null)
            {//如果构造方法有参数
                foreach($constructor->getParameters() as $param)
                {//获取构造方法的参数
                    if($param->isDefaultValueAvailable())
                    {//如果是默认 直接取默认值
                        $dependencies[] = $param->getDefaultValue();
                    }
                    else
                    {//将构造函数中的参数实例化
                        $temp = $param->getClass();
                        $temp = ($temp === null ? null : $temp->getName());
                        $temp = Instance::getInstance($temp);//这里使用Instance 类标示需要实例化 并且存储类的名字
                        $dependencies[] = $temp;
                    }
                }
            }
            $this->_reflections[$class] = $ref;
            $this->_dependencies[$class] = $dependencies;
        }
    }

    //使用

    $logger = new Logger();
    $logger->addContainnerData('fileLog','FileLoggerHandler');//这里会自动创建FileLoggerHandler依赖类对象
    $logger->log('error info');