Сигналы и слоты в PHP. Такие же как в Qt. Ну почти

в 23:53, , рубрики: just like in Qt, open source, packagist, php, php5, qt, signals and slots, slot, Программирование

Сигналы и слоты — подход, используемый в некоторых языках программирования и библиотеках (например, Boost и Qt) который позволяет реализовать шаблон «наблюдатель», минимизируя написание повторяющегося кода. Концепция заключается в том, что компонент (часто виджет) может посылать сигналы, содержащие информацию о событии (например: был выделен текст «слово», была открыта вторая вкладка). В свою очередь другие компоненты могут принимать эти сигналы посредством специальных функций — слотов. Система сигналов и слотов хорошо подходит для описания Графического интерфейса пользователя. Также механизм сигналов/слотов может быть применён для асинхронного ввода-вывода (включая сокеты, pipe, устройства с последовательным интерфейсом, др.) или уведомления о событиях. В библиотеке Qt благодаря Метаобъектному компилятору (англ.)русск. отпадает необходимость писать код регистрации/дерегистрации/вызова, так как эти шаблонные участки кода генерируются автоматически.

Говорит нам Википедия.

В php приложениях нет никакого event loop. Получили запрос, отдали ответ. И все тут. Однако между «получили запрос» и «отдали ответ» есть какой-то жизненный цикл приложения, соответственно не все потеряно и можно попробовать применить этот механизм в веб приложениях. В студенчестве я программировал на C++/Qt и его реализация сигналов и слотов очень тогда нравилась. Отличная ведь идея. Ближайший родственник — шаблон «Наблюдатель». Цель одна, однако реализации совершенно разные. Пришла мне в голову мысль реализовать данный механизм для php в виде небольшой библиотеки.

Сигналы и слоты

Сигнал — это нечто, что может сообщить внешнему Миру о внутреннем состоянии объекта. Телефон может звонить, кот может «мяукать». Звонки и «мяу» являются сигналами телефона и кота соответственно. Они сообщают нам об изменении внутреннего состояния объектов: телефон из состояния покоя перешел в состояние «Вам звонят», кот из состояния «сытый кот» перешел в состояние «голодный кот».

Вы можете реагировать на эти сигналы каким либо способом. Например: снять трубку или накормить наконец-таки своего кота. Ваши реакции на сигналы — это слоты.

В программировании возникают точно такие же ситуации, когда нам нужно реагировать на изменение состояния какого-либо объекта. Для этого и используется механизм сигналов и слотов — для обеспечения коммуникации между объектами. На мой взгляд, в отличии от шаблона «Наблюдатель» он проще и намного прозрачнее в использовании (хотя «Наблюдатель» сам по себе тоже очень простой шаблон).

Немного поразмыслив, я написал библиотеку connector для php, реализующую механизм сигналов и слотов. Посмотрим на нее?

Установка

Connector доступен как composer пакет, соответственно для установки необходимо выполнить

composer require fluffy/connector

или добавить зависимость на библиотеку в Ваш composer.json файл

"require": {
    ...
    "fluffy/connector": "^1.0"
}

и выполнить

composer update

Использование

1. Сигналы

Если мы хотим, чтобы объект мог испускать сигналы, то нам нужно имплементировать интерфейс SignalInterface и использовать соответствующий трейт SignalTrait. Например, у нас есть логер класс и мы хотим, чтобы он высылал сигнал somethingIsLogged всякий раз, когда он заканчивает логирование:

<?php

/**
 * @file
 * Contains definition of Logger class.
 */

use FluffyConnectorSignalSignalInterface;
use FluffyConnectorSignalSignalTrait;

/**
 * Class Logger.
 */
class Logger implements SignalInterface {
    use SignalTrait;

    public function log() 
    {
        // Do logging stuff.
        ...

        // Emit signal about successfull logging.
        $this->emit('somethingIsLogged', 'Some useful data');
    }
}

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

2. Слоты

Слот — это обычный метод класса, который начинается с ключевого слова slot. Давайте создадим класс со слотом.

<?php

/**
 * @file
 * Contains definition of Receiver class.
 */

/**
 * Class Receiver.
 */
class Receiver
{

  public function slotReactOnSignal($dataFromSignal) {
    echo "Received data: $dataFromSignal";
  }

}
3. Сигнально слотовые соединения

Итак, у нас есть Logger класс с сигналом и Receiver класс со слотом. Для того, чтобы реагировать на сигнал, необходимо подключить к нему слот.

use FluffyConnectorConnectionManager;

$logger = new Logger();
$receiver = new Receiver();

ConnectionManager::connect($logger, 'somethingIsLogged', $receiver, 'slotReactOnSignal');

$logger->log();

Вызвав ConnectionManager::connect($sender, $signalName, $receiver, $slotName) мы тем самым подключили сигнал объекта логера к слоту объекта получателя. Это значит, что всякий раз при вызове $logger->log() будет высылаться сигнал логера somethingIsLogged, на который будет реагировать слот slotReactOnSignal объекта получателя. Мы можем определить столько сигнально слотовых соединений, сколько нам нужно. Работают все возможные виды соединений:

  • Один сигнал к одному слоту
  • Один сигнал к нескольким слотам
  • Несколько сигналов к нескольким слотам
  • Несколько сигналов к одному слоту

При чем неважно, будь то сигналы от одного объекта или от разных, будь то слоты одного объекта или разных.

4. Типы соединений

По умолчанию метод ConnectionManager::connect() создает перманентное соединение. Это значит, что соединение не будет разорвано после первой высылки сигнала. Однако есть возможность создать одноразовое соединение, передав пятым параметром константу — тип соединения. Например:

use FluffyConnectorConnectionManager;

$logger = new Logger();
$receiver = new Receiver();

ConnectionManager::connect($logger, 'somethingIsLogged', $receiver, 'slotReactOnSignal', ConnectionManager::CONNECTION_ONE_TIME);

$logger->log();

// Log once again.
$logger->log();

После второго вызова Logger::log() ничего не произойдет так как слот будет отключен от сигнала после первой его высылки.

5. Disconnect

Если мы не хотим больше слушать определенный сигнал, то просто отключаемся от него

ConnectionManager::disconnect($logger, 'somethingIsLogged', $receiver, 'slotReactOnSignal');

Если нам необходимо сбросить все существующие соединения, то нужно вызвать

ConnectionManager::resetAllConnections()
Для чего это нужно?

  • Мне просто нравится механизм сигналов и слотов и его реализация в Qt. Возникло желание реализовать нечто подобное для php.
  • Это удобнее чем «Наблюдатель».

Репозиторий проекта: fluffy/connector

Надеюсь, было интересно дойти до этой строки. Критика и мысли в слух приветствуются.

UPD

Как заметил symbix использование наследования для реализации функционала сигнала не удобно. Учтено. Теперь реализовано на трейте (см. пункт 1)

Автор: FluffyMan

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js