douwenpin0428 2013-03-03 01:30
浏览 46
已采纳

PHP Composer,用于包含带命名空间的建议模块的良好设计模式

I am writing some code (a database abstraction layer, to be used in other of my code modules) that I would like to release as a standalone module that may be included in a project with composer. I would like to include in the composer defintion some suggested modules that would improve the performance of my module, but that are't required.

The problem I have is how to do this in a way that is note completely horrific.

So in this particular example the module is declared in namespace Intahwebz\DB; and then the optional module Intahwebz\Log\Log is tried to be included, which in turn tries to use the optional module Monolog.

What I have so far is the module code ConnectionWrapper.php

namespace Intahwebz\DB;

use Intahwebz\Log\Log;


if(trait_exists("Intahwebz\Log\Log") == false){
    require_once("Log.php");
}


class ConnectionWrapper{

    use Log;

    function __construct(){
        $this->initLog();

        // Rest of constructor code.

        $this->log->trace("ConnectionWrapper has been constructed.");
    }   

    // Lots of other functions here.
}


?>  

Then in Log.php I check to see if Monolog is available and if so include it, otherwise define a really lightweight logger.

<?php

namespace Intahwebz\Log;

if (class_exists('Monolog\Logger') &&
    class_exists('Monolog\Handler\StreamHandler')) {

require_once "MonologWrapper.php";

}
else{

    class Logger{
        public function debug($message, array $context = array()){
            echo $message."
";
        }
        public function log($level, $message, array $context = array()){
            echo $message."
";
        }
        public function info($message, array $context = array()){
            echo $message."
";
        }
        public function notice($message, array $context = array()){
            echo $message."
";
        }
        public function warning($message, array $context = array()){
            echo $message."
";
        }
        public function error($message, array $context = array()){
            echo $message."
";
        }
        public function critical($message, array $context = array()){
            echo $message."
";
        }
        public function alert($message, array $context = array()){
            echo $message."
";
        }
        public function emergency($message, array $context = array()){
            echo $message."
";
        }
    }

    trait Log{

        var $log;
        function initLog(){
            $this->log = new Logger(__CLASS__);
        }
    }
}

If Monolog is available, we use it by including MonologWrapper.php

<?php

namespace Intahwebz\Log;

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

trait Log{

    var $log;

    function    initLog(){
        $this->log = new Logger(__CLASS__);
        //Todo - get log handler from config file automagically.
        $this->log->pushHandler(new StreamHandler(PATH_TO_ROOT.'var/log/Admin.log', Logger::WARNING));
    }
}


?>

The problems with this are:

1) It's incredibly ugly, and requires an extra files per suggested module.

2) It doesn't allow people to switch in a different logger other than monolog without re-writing code.

3) It has duplicate class/trait definitions separated only by if statements, which completely confuses my IDE.

I know that the way Symfony et al solve this problem is by having a service layer. However I can't see how to use that design pattern without forcing the dependency inclusions to be a lot more complicated than they ought to be for just bringing in an optional module.

Can anyone describe a decent design pattern for including optional modules, or replacing them with other compatible modules? Or is this the type of thing that can only be defined nicely within an actual framework?

  • 写回答

1条回答 默认 最新

  • douqiandai4327 2013-03-03 02:25
    关注

    Dependency Injection :)

    For example instead of

    function    initLog(){
        $this->log = new Logger(__CLASS__);
        // ..
    }
    

    use

    function setLogger(Logger $logger) {
        $this->log = $logger;
        // ...
    }
    

    And so on. There are too many of this hardcoded dependencies, that I'm not going to mention everyone.

    class MonologLoggingConnectionWrapperWrapper extends ConnectionWrapper {
        public function __construct (ConnectionWrapper $wrapper, Logger $logger) {
            // ..
        }
    }
    

    Now Monolog is only required, when someones tries to instanciate this class and then it is quite obvious, that something is missing. If you like you can add a test to the file before the class definition, that should (in this case) throw an Exception, or trigger an error.

    (The classname results from the fact, that ConnectionWrapper is not really a wrapper, but a class on it's own: A wrapper must extends the wrapped classes, or implements the corresponding interface, thus it is cleanly exchangeable)

    Worth to mention: Avoid conditional class-, or function-definitions :) One Classname, one class (not more) and when I try to instanciate a class, it either exists, or not, but not "sometimes".

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 CSS实现渐隐虚线边框
  • ¥15 thinkphp6配合social login单点登录问题
  • ¥15 HFSS 中的 H 场图与 MATLAB 中绘制的 B1 场 部分对应不上
  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 截图中的mathematics程序转换成matlab
  • ¥15 动力学代码报错,维度不匹配
  • ¥15 Power query添加列问题