繁体   English   中英

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

[英]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();

虽然我已经看到了很多非常恶劣的事情说一下Singleton模式,有的甚至称这是“反模式”。 我正在为项目的其余部分使用依赖项注入(尽我所能)。

我是否缺少其他选择,还是有“正确”的方法来做到这一点?

通过在此处使用单例,您将隐藏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()函数的需求,我们通过定义合同来确保它的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分配VoidLogger的实例。 它违反了DI的原理,但是在这种情况下完全可以,因为我们使用了特定的模式。

空对象模式

您的条件之一是:

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

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

由于我们在ErrorHandler的Logger上调用了log()方法,因此我们需要一个Logger实例(我们不能一无所获地调用方法)。 但是没有人禁止我们创建不执行任何操作的Logger的具体实现。 而这正是Null Object模式。

<?php

class VoidLogger implements Logger {

    public function log( $message ){}

}

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

用法

<?php 

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

echo $notDefined;

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

优点

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM