Рубрики
Uncategorized

Вход в Zend Framework с помощью менеджера событий

В прошлом году я работал над обновлением нашей клиентской панели управления. Панель управления клиентом представляет собой веб-интерфейс… Помеченный php.

В прошлом году я работал над обновлением нашей клиентской панели управления. Панель управления клиентом – это веб-приложение, которое позволяет нашим пользователям получать представление об их различных услугах и кампаниях вместе с нами. Целью проекта было создание совершенно новой панели управления с использованием компонентов Zend Framework 3, а также обновление до последней версии PHP и использование компонентов Material Design для интерфейса.

В нашей предыдущей версии панели управления было очень мало инфраструктуры для ведения журнала. Использование ведения журнала было непоследовательным, и использовались различные библиотеки ведения журнала, в зависимости от того, какой разработчик работал над этим разделом сайта.

После некоторых исследований мы сузили круг до трех потенциальных вариантов:

  1. Используйте класс Регистратор со статическими методами для каждого из потенциальных уровней журнала. Мы использовали этот подход в нескольких небольших приложениях, но посчитали, что он не подойдет для такого крупного проекта, как этот. Хотя этот подход прост в использовании и создании, мы не хотели загрязнять нашу кодовую базу сотнями статических вызовов, над которыми нельзя издеваться.

  2. Внедрите экземпляр регистратора, реализующего LoggerInterface , в любой класс, в котором мы хотели регистрировать информацию. В то время казалось, что это добавляет ненужных накладных расходов на строительство каждого объекта.

  3. Используйте Zend event-менеджер . Отправляйте события журнала ( Событие журнала ) и прикрепляйте Прослушиватель журнала к менеджеру событий. Затем Прослушиватель журнала регистрирует информацию о событии, используя экземпляр Интерфейса регистратора .

Третий подход казался простым в реализации и понимании, поэтому мы решили использовать его в нашем приложении. В этом посте будет рассказано о том, как мы это реализовали, а также о рассмотрении этого подхода теперь, когда мы потратили более года на его использование в производстве.

Создание события журнала

Это наше Событие журнала ниже. Событие будет отправлено классом, желающим зарегистрировать информацию, а затем поймано слушателем, подключенным к нашему менеджеру событий. Мы определяем общедоступные константы, которые сопоставляются с уровнями журнала из PSR-3 . Мы могли бы использовать уровни PSR-3 напрямую, однако мы хотели обеспечить уровень абстракции между нашим LogEvent и любой библиотекой ведения журнала, которую мы использовали. Этот шаг необязателен, так как большинство людей будут использовать регистраторы, совместимые с PSR-3.

declare(strict_types=1);

namespace Application\Event;

use Psr\Log\LogLevel;
use Zend\EventManager\Event;

class LogEvent extends Event
{
    public const DEBUG     = LogLevel::DEBUG;
    public const INFO      = LogLevel::INFO;
    public const NOTICE    = LogLevel::NOTICE;
    public const WARNING   = LogLevel::WARNING;
    public const ERROR     = LogLevel::ERROR;
    public const CRITICAL  = LogLevel::CRITICAL;
    public const ALERT     = LogLevel::ALERT;
    public const EMERGENCY = LogLevel::EMERGENCY;
}

Создание и регистрация слушателя

Теперь, когда у нас есть наше мероприятие, нам нужно создать слушателя и зарегистрировать его у менеджера мероприятий. Прослушиватели подключаются к менеджеру событий, который затем отвечает за реагирование на события, генерируемые приложением.

Наш слушатель реализует интерфейс Listeneraggregate , который требует двух методов: attach для подключения одного или нескольких слушателей и отсоединения для удаления этих слушателей. Мы используем агрегатную характеристику слушателя для реализации метода отсоединения .

declare(strict_types=1);

namespace Application\Listener;

use Application\Event\LogEvent;
use Application\Model\LoggerAwareInterface as LoggerAware;
use Psr\Log\LoggerInterface;
use Zend\EventManager\EventInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\ListenerAggregateTrait;

class LogListener implements ListenerAggregateInterface
{
    use ListenerAggregateTrait;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @param LoggerInterface $logger
     */
    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * @inheritdoc
     */
    public function attach(EventManagerInterface $events, $priority = 1)
    {
        $sharedManager = $events->getSharedManager();
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::DEBUG, [$this, 'addDebug'], $priority);
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::INFO, [$this, 'addInfo'], $priority);
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::NOTICE, [$this, 'addNotice'], $priority);
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::WARNING, [$this, 'addWarning'], $priority);
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::ERROR, [$this, 'addError'], $priority);
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::CRITICAL, [$this, 'addCritical'], $priority);
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::ALERT, [$this, 'addAlert'], $priority);
        $this->listeners[] = $sharedManager->attach(LoggerAware::class, LogEvent::EMERGENCY, [$this, 'addEmergency'], $priority);
    }

    private function addRecord(string $loggerLevel, EventInterface $event)
    {
        $logMessage   = $event->getTarget();
        $logContext   = $event->getParams();

        $this->logger->log($loggerLevel, $logMessage, $logContext);
    }

    public function addDebug(EventInterface $event)
    {
        return $this->addRecord(LogEvent::DEBUG, $event);
    }

    public function addInfo(EventInterface $event)
    {
        return $this->addRecord(LogEvent::INFO, $event);
    }


    // Methods must be added for each log level listed in the LogEvent
}

Мы специально используем SharedEventManager , поскольку его можно использовать, когда мы хотим подключить слушателей, еще не имея экземпляра класса, составляющего EventManager . В нашем случае это будет любой класс, который желает испускать Событие входа в систему . Как вы можете себе представить, прикрепление Прослушивателя журнала к каждому отдельному классу, излучающему события, было бы громоздким.

Мы подключаем прослушиватель и определяем метод для каждого уровня журнала, определенного в нашем Событие входа в систему . Некоторые из методов были опущены в приведенном выше примере для удобства чтения. Это позволяет нам регистрировать события на разных уровнях в зависимости от константы, заданной классом излучения.

Например:

// This event will be logged as INFO due to the constant specified
$this->getEventManager()->trigger(LogEvent::INFO, 'Example log message', ['context']);

// Whereas this event will be logged as CRITICAL
$this->getEventManager()->trigger(LogEvent::CRITICAL, 'All connections down', ['context']);

Интерфейс LoggerAwareИнтерфейс

Поскольку мы используем SharedEventManager , нам необходимо указать идентификатор при подключении прослушивателя. Идентификатор используется для идентификации классов, которые могут испускать Регистрируйте событие и поэтому его следует прослушивать. Мы используем Интерфейс LoggerAwareИнтерфейс псевдоним Программное обеспечение для регистрации как наш идентификатор для слушателя. Он расширяет интерфейс EventManagerAwareИнтерфейс как для запуска тот Регистрируйте событие , класс-эмитент должен иметь доступ к экземпляру диспетчера событий. Интерфейс EventManagerAwareInterface требует реализации двух методов: seteventmanager и geteventmanager .

Для любых классов, полученных с помощью диспетчера служб Zend, которые реализуют интерфейс EventManagerAwareInterface , автоматически будет установлен менеджер событий. Это делает отправку событий очень простой, но в ней есть определенное “волшебное” качество, которое может быть непонятно разработчикам, которые раньше не работали с менеджером событий.

declare(strict_types=1);

namespace Application\Model;

use Zend\EventManager\EventManagerAwareInterface;

interface LoggerAwareInterface extends EventManagerAwareInterface
{

}

Например, класс Foo реализует интерфейс LoggerAwareИнтерфейс и имеет метод bar . Когда вызывается метод bar ,/| Событие журнала генерируется в диспетчере событий и обрабатывается любыми слушателями, подключенными к Событие входа в систему . Мы сами внедрили методы setEventManager и getEventManager . В методе setEventManager мы используем имя класса, а также любые интерфейсы, которые он реализует, в качестве идентификаторов событий. Вы помните, что ранее мы использовали Интерфейс LoggerAwareInterface в качестве нашего идентификатора.

class Foo implements LoggerAwareInterface {
    private $events;

    public function bar(): void
    {
        $this->getEventManager()->trigger(LogEvent::INFO, 'Example log message', ['context']);
    }

    /**
     * @inheritDoc
     */
    public function setEventManager(EventManagerInterface $events)
    {
       $className = get_class($this);

       $nsPos = strpos($className, '\\') ?: 0;
       $events->setIdentifiers(array_merge(
           [
               __CLASS__,
               $className,
               substr($className, 0, $nsPos),
           ],
           array_values(class_implements($className))
       ));

       $this->events = $events;

       return $this;
    }

    /**
     * @inheritDoc
     */
    public function getEventManager()
    {
       if (!$this->events) {
           $this->setEventManager(new EventManager());
       }

       return $this->events;
    }
}

Интерфейс управления событиями

Дублирование setEventManager и getEventManager в каждом классе, из которого мы хотим выйти, это будет кошмаром для обслуживания, а также увеличит “визуальный долг”. Мы разделили эти методы на Признак EventManagerAware , который может использоваться в любом классе, который хочет генерировать события.

declare(strict_types=1);

namespace Application\Model;

use Zend\EventManager\EventManager;
use Zend\EventManager\EventManagerInterface;

trait EventManagerAwareTrait
{
    private $events;

    /**
     * Set the event manager instance used by this context
     *
     * @param  EventManagerInterface $events
     * @return $this
     */
    public function setEventManager(EventManagerInterface $events)
    {
        ...
    }

    /**
     * Retrieve the event manager
     *
     * Lazy-loads an EventManager instance if none registered.
     *
     * @return EventManagerInterface
     */
    public function getEventManager()
    {
        ...
    }
}

Это упрощает наш пример до этого:

use Application\Model\EventManagerAwareTrait;

class Foo implements LoggerAwareInterface {
    use EventManagerAwareTrait;

    public function bar(): void
    {
        $this->getEventManager()->trigger(LogEvent::INFO, 'Example log message', ['context']);
    }
}

Следует отметить, что вам не нужно будет использовать Функция EventManagerAware при отправке событий с контроллера Zend, поскольку в нем уже реализованы Seteventmanager и geteventmanager .

Прикрепление прослушивателя к начальной загрузке приложения

Почти на месте. Есть еще одна вещь, которую нужно сделать, прежде чем мы сможем начать регистрацию. Нам необходимо убедиться, что наш Прослушиватель журналов подключен к менеджеру событий на этапе начальной загрузки приложения. Мы можем сделать это в Module.php для модуля Приложения .

declare(strict_types=1);

namespace Application;

use Application\Listener\LogListener;
use Zend\EventManager\EventInterface;
use Zend\ModuleManager\Feature\BootstrapListenerInterface;
use Zend\ModuleManager\Feature\ConfigProviderInterface;

class Module implements ConfigProviderInterface, BootstrapListenerInterface
{
    /**
     * @inheritdoc
     */
    public function getConfig()
    {
        return include __DIR__ . '/../config/module.config.php';
    }

    /**
     * @inheritdoc
     */
    public function onBootstrap(EventInterface $e)
    {
        $serviceManager     = $e->getApplication()->getServiceManager();
        $eventManager       = $e->getApplication()->getEventManager();
        $sharedEventManager = $eventManager->getSharedManager();

        // Logging Listener
        $logListener = $serviceManager->get(LogListener::class);
        $logListener->attach($eventManager);
    }
}

Отлично! Теперь мы можем легко запустить Войдите в систему из любого класса, просто реализовав интерфейс LoggerAwareИнтерфейс и используя Интерфейс управления событиями .

Ведение Журнала на основе Событий: 1 Год На

Новая панель управления имела успех и стала огромным шагом вперед по сравнению с предыдущей версией, как для разработчиков, так и для наших пользователей. Ведение журнала работает отлично и позволило нам лучше видеть наше приложение, обеспечивая при этом согласованный опыт для разработчиков.

Однако этот подход в значительной степени связал наше приложение с компонентом Zend event manager. Если бы мы провели рефакторинг нашего приложения для использования другой платформы, это заняло бы гораздо больше времени из-за связи. Эта проблема не была бы столь распространенной в других реализациях, которые я перечислил в начале этого поста.

В прошлом году мы также приняли в команду несколько новых разработчиков. Эти разработчики занимали младшие должности и раньше не имели опыта работы с такими приложениями, как наша панель управления. Система управления событиями обладает большим количеством “волшебного” поведения, которое непонятно новым разработчикам, особенно тем, которые не знакомы с архитектурой, управляемой событиями. Я думаю, что важно, чтобы такая жизненно важная вещь, как ведение журнала, немедленно отлаживалась всеми членами команды, независимо от уровня квалификации.

Если бы я снова начал этот проект, я бы передал экземпляр Интерфейса регистратора в любой класс, который этого требует. Этот подход может быть более “ручным”, но он гораздо более четкий и не привязывает нас к какой-либо конкретной структуре. Если вы уже работаете в системе , управляемой событиями, вы можете решить, что связь с компонентом event manager не является проблемой. Речь идет об использовании правильного инструмента для работы.

Если у вас есть какие-либо вопросы или отзывы по этой статье, вы можете написать мне по адресу ethan@messagecloud.com .

Оригинал: “https://dev.to/ethanbray/logging-in-zend-framework-using-the-event-manager-2a4e”