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


Best way to pass a class into a callback function

我正在使用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::getInstance();

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

我是否错过了另一个选项,或者是否有"正确"的方法可以做到这一点?

通过在此处使用单例,您将隐藏记录器的依赖项。您不需要全局访问点,并且由于您已经在尝试遵守 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(接口)并允许传递 null。

<?php

interface Logger {
    public function log( $message );
}
class ConcreteLogger implements Logger {

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

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

空对象模式

您的标准之一如下:

请注意,记录器

可能会也可能不会启动,其想法是能够以某种方式轻松定义另一个记录器。

当您

需要一个没有行为但想要遵守协定的对象时,将使用 Null 对象模式。

由于我们在错误处理程序中调用了记录器上的 log() 方法,因此我们需要一个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记录器,您只需稍微调整记录器上的类型提示和方法调用。但原则保持不变。

好处

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

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