将类传递给回调函数的最佳方法

时间:2015-02-04 04:33:59

标签: php oop logging psr-3

我正在使用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类中定义一个静态方法,以便我可以使用如下内容: / p>

$logger = Logger::getInstance();

虽然我已经看到很多关于Singleton模式的非常苛刻的事情,但有些甚至称它为“反模式”。我正在为项目的其余部分使用依赖注入(尽我所能)。

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

1 个答案:

答案 0 :(得分:1)

通过在这里使用单例,您可以隐藏Logger的依赖关系。您不需要在此处访问全局访问点,并且由于您已经尝试遵守DI,因此您可能不希望弄乱您的代码并使其无法稳定。

确实有更清洁的方法来实现这一点。我们来看看吧。

set_error_handler接受对象

您不需要将闭包或函数名称传递给set_error_handler函数。这是文档所说的内容:

  

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

了解这一点,您可以使用专用对象来处理错误。对象的处理程序方法将在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(接口)并允许传递空值。

<?php


interface Logger {

    public function log( $message );

}

class ConcreteLogger implements Logger {


    public function log( $message )
    {
        echo "Logging: " . $message;
    }

}

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

空对象模式

您的一个标准如下:

  

请注意,Logger可能会也可能不会启动,而且我们的想法是能够以某种方式轻松定义另一个Logger。

当您需要一个没有行为但希望遵守合同的对象时,使用Null对象模式。

由于我们在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记录器,您只需稍微调整记录器上的类型提示和方法调用。但原则保持不变。

优势

通过选择此类实施,您将获得以下好处:

  • 错误处理程序的易于更换的记录器
  • 即使是易于更换的错误处理程序
  • 松散耦合(从处理错误中解除记录)
  • 易于扩展的错误处理程序(您可以注入其他内容,而不仅仅是记录器)