通过在此处使用单例,您将隐藏 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
这里输入一个(接口)并允许传递 null。
<?php
interface Logger {
public function log( $message );
}
class ConcreteLogger implements Logger {
public function log( $message )
{
echo "Logging: " . $message;
}
}
传递的Logger
实例将被分配给相应的属性。但是,如果没有传递任何内容,VoidLogger
则分配 a 的实例。它违反了 DI 的原则,但在这种情况下完全没问题,因为我们使用了特定的模式。
空对象模式
您的标准之一如下:
请注意,Logger 可能会或可能不会启动,其想法是可以轻松地以某种方式定义另一个 Logger。
当您需要一个没有行为但想要遵守合同的对象时,使用空对象模式。
因为我们log()
在 ErrorHandler 中调用 Logger 上的方法,所以我们需要一个Logger
实例(我们不能在任何东西上调用方法)。但是没有人禁止我们创建一个什么都不做的 Logger 的具体实现。这正是 Null Object 模式。
<?php
class VoidLogger implements Logger {
public function log( $message ){}
}
现在,如果您不想启用日志记录,请不要在实例化期间将任何内容传递给错误处理程序或VoidLogger
自己传递 a。
用法
<?php
$errorHandler = new ConcreteErrorHandler(); // Or Pass a Concrete Logger instead
set_error_handler([$errorHandler, 'handle']);
echo $notDefined;
要使用您的 PSR 记录器,您只需稍微调整记录器上的类型提示和方法调用。但原则保持不变。
好处
通过选择这种类型的实现,您可以获得以下好处:
- 用于错误处理程序的可轻松更换记录器
- 甚至是易于交换的错误处理程序
- 松耦合(将日志记录与处理错误分离)
- 易于扩展的错误处理程序(您可以注入其他东西,而不仅仅是记录器)