Сигналы и слоты — подход, используемый в некоторых языках программирования и библиотеках (например, 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