将 class 传递给回调函数的最佳方式

Best way to pass a class into a callback function

我正在使用 PSR-3 日志记录 class,并且我正尝试将其与 set_error_handler() 结合使用。我的问题是如何 正确地 "grab" 日志对象?

快速示例:

我的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 对象不会在我的特定示例中在全局范围内初始化),或者使用单例模式 "just this one time",我将在 Logger class 中定义一个静态方法,以便我可以使用例如:

$logger = Logger::getInstance();

尽管我已经看到很多关于单例模式的非常 严厉的言论,有些人甚至称其为"Anti-Pattern"。我正在为项目的其余部分使用依赖注入(尽我所能)。

我是否遗漏了另一个选项,或者是否有 "Right" 方法来做到这一点?

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

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

set_error_handler 接受对象

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

A callback with the following signature. NULL may be passed instead, to reset this handler to its default state. Instead of a function name, an array containing an object reference and a method name can also be supplied.

了解这一点,您可以使用专用对象来处理错误。在 set_error_handler

中将像这样调用对象上的处理程序方法
set_error_handler([$errorHandler, 'handle']);

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

错误处理程序

ErrorHandler class 将负责您的错误处理。我们使用 class 获得的好处是我们可以轻松地使用 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 的原则,但在这种情况下完全没问题,因为我们使用了特定的模式。

空对象模式

您的标准之一如下:

Note that the Logger may or may not be initiated, and the idea is that one would be able to easily define another Logger somehow.

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

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

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

好处

通过选择这种实施方式,您可以获得以下好处:

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