【问题标题】:Best way to pass a class into a callback function将类传递给回调函数的最佳方法
【发布时间】:2015-02-04 04:33:59
【问题描述】:

我正在使用 PSR-3 日志记录类,并尝试将它与 set_error_handler() 结合使用。我的问题是我如何正确“抓取”日志对象?

快速示例:

我的ErrorHandler.php

set_error_handler(function ($errno, $errstr , $errfile , $errline , $errcontext) {
    // This error code is not included in error_reporting
    if (!(error_reporting() & $errno)) {
        return;
    }

    $logger->log(/* How? */);

});

我的Logger.php

class Logger extends PsrLogAbstractLogger implements PsrLogLoggerInterface { 
    public function log($level, $message, array $context = array()) { 
        // Do stuff
    }
}

请注意,Logger 可能会或可能不会启动,其想法是可以轻松地以某种方式定义另一个 Logger。

我想到我至少有两个选项,即简单地使用名为 $logger 或类似名称的全局变量,并使用它(即使 Logger 对象不会在全局范围内初始化在我的特定示例中),或者使用“仅此一次”的单例模式,我将在 Logger 类中定义一个静态方法,以便我可以使用以下内容:

$logger = Logger::getInstance();

虽然我看到很多关于单例模式的非常苛刻的说法,有些人甚至称其为“反模式”。我在项目的其余部分使用依赖注入(尽我所能)。

我错过了另一个选择,还是有“正确”的方法来做到这一点?

【问题讨论】:

    标签: php oop logging psr-3


    【解决方案1】:

    在这里使用单例可以隐藏 Logger 的依赖关系。您在这里不需要全局访问点,而且由于您已经在尝试遵守 DI,您可能不想弄乱代码并使其无法测试。

    确实有更简洁的方法来实现它。让我们来看看吧。

    set_error_handler 接受对象

    您不需要将闭包或函数名称传递给set_error_handler 函数。以下是文档状态:

    具有以下签名的回调。可以改为传递 NULL,以将此处理程序重置为其默认状态。除了函数名,还可以提供一个包含对象引用和方法名的数组。

    知道了这一点,您就可以使用专门的对象来处理错误。对象上的handler方法会在set_error_handler中这样调用

    set_error_handler([$errorHandler, 'handle']);
    

    其中$errorHandler 是对象,handle 是要调用的方法。

    错误处理程序

    ErrorHandler 类将负责您的错误处理。我们通过使用类获得的好处是我们可以轻松地使用 DI。

    <?php
    
    interface ErrorHandler {
    
        public function handle( $errno, $errstr , $errfile = null , $errline = null , $errcontext = null );
    
    }
    
    
    class ConcreteErrorHandler implements ErrorHandler {
    
        protected $logger;
    
        public function __construct( Logger $logger = null )
        {
            $this->logger = $logger ?: new VoidLogger();
        }
    
        public function handle( $errno, $errstr , $errfile = null , $errline = null , $errcontext = null )
        {
            echo "Triggered Error Handler";
            $this->logger->log('An error occured. Some Logging.');
        }
    
    }
    

    handle() 方法无需进一步讨论。它的签名符合set_error_handler() 函数的需要,我们通过定义一个合约来确保它。

    这里有趣的部分是构造函数。我们在这里输入一个Logger(接口)并允许传递null。

    <?php
    
    
    interface Logger {
    
        public function log( $message );
    
    }
    
    class ConcreteLogger implements Logger {
    
    
        public function log( $message )
        {
            echo "Logging: " . $message;
        }
    
    }
    

    传递的Logger 实例将被分配给相应的属性。但是,如果没有传递任何内容,则分配一个 VoidLogger 的实例。它违反了 DI 的原则,但在这种情况下完全没问题,因为我们使用了特定的模式。

    空对象模式

    您的标准之一如下:

    请注意,Logger 可能会或可能不会启动,其想法是可以轻松地以某种方式定义另一个 Logger。

    当您需要一个没有行为但又想遵守合同的对象时,可以使用空对象模式。

    由于我们在 ErrorHandler 的 Logger 上调用 log() 方法,我们需要一个 Logger 实例(我们不能在任何情况下调用方法)。但是没有人禁止我们创建一个什么都不做的 Logger 的具体实现。这正是 Null Object 模式。

    <?php
    
    class VoidLogger implements Logger {
    
        public function log( $message ){}
    
    }
    

    现在,如果您不想启用日志记录,请不要在实例化期间将任何内容传递给错误处理程序或自己传递 VoidLogger

    用法

    <?php 
    
    $errorHandler = new ConcreteErrorHandler(); // Or Pass a Concrete Logger instead
    set_error_handler([$errorHandler, 'handle']);
    
    echo $notDefined;
    

    要使用您的 PSR 记录器,您只需稍微调整记录器上的类型提示和方法调用。但原则保持不变。

    好处

    通过选择这种类型的实现,您可以获得以下好处:

    • 错误处理程序的可轻松更换记录器
    • 甚至是易于更换的错误处理程序
    • 松耦合(将日志记录与处理错误分离)
    • 易于扩展的错误处理程序(您可以注入其他内容,而不仅仅是记录器)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-29
      相关资源
      最近更新 更多