Лучший способ передать класс в функцию обратного вызова

Я использую класс журналирования 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 (отделенная регистрация от ошибки обработки)
  • Легко расширяемые обработчики ошибок (вы можете вставлять другие вещи, а не только регистратор)
Другие вопросы по тегам