Лучший способ передать класс в функцию обратного вызова
Я использую класс журналирования 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
класс, чтобы я мог использовать что-то вроде:
$logger = Logger::getInstance();
Хотя я видел много очень резких высказываний о паттерне синглтона, некоторые даже называли его "анти-паттерном". Я использую внедрение зависимостей (так хорошо, как я могу) для остальной части проекта.
Я пропускаю другой вариант или есть "правильный" способ сделать это?
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.
Шаблон нулевого объекта используется, когда вам нужен объект без поведения, но вы хотите придерживаться контракта.
Так как мы называем log()
метод на Logger в нашем Error Handler, нам нужен 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 Logger, вам просто нужно немного настроить подсказки типов и вызовы методов в регистраторе. Но принципы остаются прежними.
Выгоды
Выбирая этот тип реализации, вы получаете следующие преимущества:
- Легко заменяемые регистраторы для обработчика ошибок
- Даже легко заменяемые обработчики ошибок
- Loose Couplding (отделенная регистрация от ошибки обработки)
- Легко расширяемые обработчики ошибок (вы можете вставлять другие вещи, а не только регистратор)