21 KiB
Справка по системе событий EventManager
Общее описание
EventManager — это сервис для работы с событиями в системе «Бизнес.Точка». Он является обёрткой над встроенной системой событий CodeIgniter 4 и предоставляет два типа подписок на события:
- moduleOn() — обработчик выполняется только при наличии активной подписки на модуль
- systemOn() — обработчик выполняется всегда, без проверки статуса подписки
EventManager используется для создания интеграций между модулями, когда действия в одном модуле должны автоматически вызывать события в другом. Например, при создании клиента в CRM может автоматически создаваться задача в модуле Tasks.
Подключение EventManager
EventManager подключается как сервис через service('eventManager'):
$eventManager = service('eventManager');
Доступно в контроллерах через BaseController:
// В контроллере:
$em = service('eventManager');
Основные методы
forModule() — привязка к модулю
Метод forModule() привязывает все последующие вызовы moduleOn() к указанному модулю. Это означает, что подписки на события будут создаваться только если организация имеет активную подписку на этот модуль.
$em = service('eventManager');
// Привязываем события к модулю CRM
$em->forModule('crm');
После вызова forModule() все события, добавленные через moduleOn(), будут проверять подписку организации на модуль CRM. Если подписка не активна — обработчик не будет выполнен.
// Цепочка вызовов
service('eventManager')
->forModule('crm')
->moduleOn('client.created', function($client) {
// Этот код выполнится только если подписка на CRM активна
});
Важно: Метод forModule() необходимо вызвать перед moduleOn(), иначе будет выброшено исключение.
moduleOn() — подписка с проверкой модуля
Метод moduleOn() создаёт подписку на событие, которая выполняется только при соблюдении условий:
- Модуль существует в конфигурации
BusinessModules - Модуль глобально включён в настройках
- Организация имеет активную подписку на модуль
$em = service('eventManager');
$em->forModule('crm');
$em->moduleOn('client.created', function($client) {
// Обработчик выполнится только если CRM подписка активна
log_message('debug', 'Клиент создан: ' . $client['name']);
});
Сигнатура метода:
public function moduleOn(
string $event, // Имя события
callable $callback, // Обработчик события
int $priority = 100 // Приоритет выполнения
): bool
Возвращаемое значение:
true— подписка создана, обработчик будет выполненfalse— подписка не создана (модуль не активен, отключён или не существует)
Параметр $callback:
Обработчик события получает параметры, переданные при вызове события:
$em->forModule('crm');
$em->moduleOn('client.created', function($client, $userId) {
echo 'Создан клиент ' . $client['name'] . ' пользователем ' . $userId;
});
// Где-то в коде:
Events::trigger('client.created', $clientData, $currentUserId);
Приоритет выполнения:
Параметр $priority определяет порядок выполнения обработчиков. Меньшее значение — более высокий приоритет:
// Выполнится раньше (приоритет 50)
$em->moduleOn('client.created', function($client) {
// Логирование
}, 50);
// Выполнится позже (приоритет 100, значение по умолчанию)
$em->moduleOn('client.created', function($client) {
// Отправка уведомлений
});
systemOn() — подписка без проверки модуля
Метод systemOn() создаёт подписку на событие без проверки статуса подписки. Обработчик будет выполнен всегда, независимо от того, какие модули активированы у организации.
Используется для системных событий, которые должны работать для всех организаций:
$em = service('eventManager');
// Этот обработчик выполнится для всех организаций
$em->systemOn('user.login', function($user) {
log_message('info', 'Пользователь вошёл: ' . $user['email']);
});
// Для отправки email-уведомлений при любом действии
$em->systemOn('email.send', function($to, $subject, $body) {
// Логирование отправки
});
Сигнатура метода:
public function systemOn(
string $event,
callable $callback,
int $priority = 100
): void
off() — отписка от события
Метод off() удаляет подписку на событие:
$em = service('eventManager');
// Удаление всех обработчиков события
$em->off('client.created');
// Удаление конкретного обработчика
$em->off('client.created', $specificCallback);
currentModuleActive() — проверка статуса модуля
Метод currentModuleActive() возвращает true если текущий модуль (установленный через forModule()) активен для организации.
Используется внутри обработчиков для проверки:
$em->service('eventManager');
$em->forModule('tasks');
$em->moduleOn('deal.won', function($deal) {
// Проверяем, активен ли модуль Tasks
if ($em->currentModuleActive()) {
// Создаём задачу
createTaskForDeal($deal);
}
});
getCurrentModuleCode() — получение кода модуля
Метод getCurrentModuleCode() возвращает код модуля, установленного через forModule():
$em = service('eventManager');
$em->forModule('crm');
$code = $em->getCurrentModuleCode(); // Вернёт 'crm'
Встроенные события системы
События пользователя
// После успешной регистрации пользователя
Events::trigger('user.registered', $user);
// После подтверждения email
Events::trigger('user.verified', $user);
// При каждом входе в систему
Events::trigger('user.login', $user);
// При выходе из системы
Events::trigger('user.logout', $user);
// При смене пароля
Events::trigger('user.passwordChanged', $user);
// При изменении профиля
Events::trigger('user.profileUpdated', $user, $oldData);
События организации
// При создании организации
Events::trigger('organization.created', $organization);
// При изменении данных организации
Events::trigger('organization.updated', $organization, $changes);
// При удалении организации (до удаления)
Events::trigger('organization.deleting', $organization);
// После удаления организации
Events::trigger('organization.deleted', $organizationId);
// При присоединении пользователя к организации
Events::trigger('organization.userJoined', $organization, $user, $role);
// При выходе пользователя из организации
Events::trigger('organization.userLeft', $organization, $user);
// При изменении роли пользователя
Events::trigger('organization.userRoleChanged', $organization, $user, $oldRole, $newRole);
События модуля Клиенты
// При создании клиента
Events::trigger('client.created', $client, $userId);
// При обновлении клиента
Events::trigger('client.updated', $client, $changes, $userId);
// При удалении клиента
Events::trigger('client.deleted', $clientId, $userId);
// При импорте клиентов
Events::trigger('client.imported', $clients, $userId);
Примеры интеграции модулей
Пример 1: Создание задачи при создании клиента
// В модуле Tasks — файл bootstrap или Config/Events.php
service('eventManager')
->forModule('tasks')
->moduleOn('client.created', function($client, $userId) {
// Создаём задачу на первичный контакт
$taskModel = new \App\Modules\Tasks\Models\TaskModel();
$taskModel->insert([
'organization_id' => $client['organization_id'],
'title' => 'Первый контакт с клиентом: ' . $client['name'],
'description' => 'Необходимо связаться с клиентом для уточнения потребностей',
'assigned_to' => $userId,
'status' => 'todo',
'priority' => 'medium',
'due_at' => date('Y-m-d H:i:s', strtotime('+1 day')),
'created_by' => $userId,
]);
});
Пример 2: Автоматический переход сделки при завершении задачи
// В модуле CRM
service('eventManager')
->forModule('crm')
->moduleOn('task.completed', function($task, $userId) {
if ($task['related_type'] === 'deal' && $task['related_id']) {
$dealModel = new \App\Modules\CRM\Models\DealModel();
// Получаем сделку
$deal = $dealModel->find($task['related_id']);
if ($deal && $deal['stage'] === 'proposal') {
// Переводим сделку на следующий этап
$dealModel->update($deal['id'], [
'stage' => 'negotiation',
'updated_at' => date('Y-m-d H:i:s'),
]);
// Логируем переход
log_message('info', 'Сделка #' . $deal['id'] . ' переведена на этап переговоров после завершения задачи');
}
}
});
Пример 3: Уведомление при записи на приём
// В модуле Booking
service('eventManager')
->forModule('booking')
->moduleOn('booking.created', function($booking, $client, $userId) {
// Отправляем уведомление клиенту
$emailService = service('email');
$emailService->send(
$client['email'],
'Подтверждение записи',
'Уважаемый ' . $client['name'] . ',
Ваша запись на ' . $booking['service_name'] . ' подтверждена на ' .
date('d.m.Y в H:i', strtotime($booking['starts_at']))
);
// Создаём задачу для менеджера
$taskModel = new \App\Modules\Tasks\Models\TaskModel();
$taskModel->insert([
'organization_id' => $booking['organization_id'],
'title' => 'Подготовка к записи: ' . $client['name'],
'description' => 'Клиент записан на услугу ' . $booking['service_name'],
'assigned_to' => $booking['staff_id'],
'status' => 'todo',
'priority' => 'normal',
'due_at' => $booking['starts_at'],
]);
});
Пример 4: Создание проекта Proof при выигрыше сделки
// В модуле CRM
service('eventManager')
->forModule('crm')
->moduleOn('deal.won', function($deal, $userId) {
// Проверяем, активен ли модуль Proof
if (service('moduleSubscription')->isModuleActive('proof')) {
$projectModel = new \App\Modules\Proof\Models\ProjectModel();
$projectModel->insert([
'organization_id' => $deal['organization_id'],
'client_id' => $deal['client_id'],
'title' => 'Проект по сделке #' . $deal['id'],
'description' => 'Автоматически создан при выигрыше сделки',
'status' => 'active',
'created_by' => $userId,
]);
}
});
Правила именования событий
Для консистентности системы используйте следующие правила именования событий:
Формат: сущность.действие
| Сущность | Действия | Пример события |
|---|---|---|
| user | registered, verified, login, logout, passwordChanged, profileUpdated | user.login |
| organization | created, updated, deleting, deleted, userJoined, userLeft, userRoleChanged | organization.created |
| client | created, updated, deleted, imported | client.created |
| deal | created, updated, deleted, won, lost | deal.won |
| booking | created, updated, cancelled, completed | booking.created |
| task | created, updated, deleted, started, completed | task.completed |
| project | created, updated, deleted, archived | project.created |
| file | uploaded, deleted, approved, rejected | file.uploaded |
| send, sent, failed | email.send |
Группы событий модулей
- CRM:
client.*,deal.*,pipeline.* - Booking:
booking.*,service.*,staff.* - Proof:
project.*,file.*,comment.* - Tasks:
task.*,board.*,comment.*
Вызов событий в коде
Для вызова события используйте Events::trigger() из CodeIgniter 4:
use CodeIgniter\Events\Events;
// Простой вызов
Events::trigger('client.created', $clientData);
// С несколькими параметрами
Events::trigger('deal.won', $deal, $userId);
// С именованными параметрами (начиная с CI 4.3+)
Events::trigger('client.updated', [
'client' => $clientData,
'changes' => $changes,
'userId' => $userId,
]);
Порядок инициализации событий
События модулей должны инициализироваться в файле app/Config/Events.php:
<?php
use CodeIgniter\Events\Events;
use CodeIgniter\Shield\Exceptions\RuntimeException;
/*
* --------------------------------------------------------------------
* Application Events
* --------------------------------------------------------------------
* Events::on() methods to register listeners for application events.
*/
// Загрузка событий модулей
if (is_file(APPPATH . 'Modules/Tasks/Config/Events.php')) {
require_once APPPATH . 'Modules/Tasks/Config/Events.php';
}
if (is_file(APPPATH . 'Modules/CRM/Config/Events.php')) {
require_once APPPATH . 'Modules/CRM/Config/Events.php';
}
if (is_file(APPPATH . 'Modules/Booking/Config/Events.php')) {
require_once APPPATH . 'Modules/Booking/Config/Events.php';
}
if (is_file(APPPATH . 'Modules/Proof/Config/Events.php')) {
require_once APPPATH . 'Modules/Proof/Config/Events.php';
}
Каждый модуль создаёт свой файл Config/Events.php:
<?php
// app/Modules/CRM/Config/Events.php
use CodeIgniter\Events\Events;
use App\Services\EventManager;
if (!function_exists('register_crm_events')) {
function register_crm_events(): void
{
$em = service('eventManager');
// Интеграция CRM → Tasks
$em->forModule('crm');
$em->moduleOn('client.created', function($client, $userId) {
// Логика создания задачи
});
// Интеграция CRM → Proof
$em->moduleOn('deal.won', function($deal, $userId) {
// Логика создания проекта
});
}
}
register_crm_events();
Логирование событий
EventManager автоматически логирует информацию о подписках и выполнении событий:
- Подписка создана:
"EventManager: Subscribed to event 'client.created' for module 'crm'" - Модуль отключён:
"EventManager: Module 'crm' is disabled globally" - Подписка не активна:
"EventManager: Organization subscription not active for module 'crm'" - Системное событие:
"EventManager: System event subscribed: 'user.login'"
Уровень логирования — debug для информации и error для ошибок конфигурации.
Тестирование событий
Ручное тестирование в разработке
// В контроллере для тестирования
public function testEvent()
{
$testClient = [
'id' => 999,
'name' => 'Тестовый клиент',
'email' => 'test@example.com',
'organization_id' => session()->get('active_org_id'),
];
// Вызываем событие напрямую
Events::trigger('client.created', $testClient, session()->get('user_id'));
return 'Событие вызвано, проверьте логи';
}
Отладка подписок
// Получение всех обработчиков события
$handlers = Events::listeners('client.created');
foreach ($handlers as $handler) {
log_message('debug', 'Handler: ' . print_r($handler, true));
}
Типичные ошибки и их устранение
Ошибка: "Module code not set"
// Неправильно:
$em->moduleOn('client.created', $callback);
// Правильно:
$em->forModule('crm')->moduleOn('client.created', $callback);
Событие не срабатывает
Возможные причины:
- Модуль не активирован для организации
- Модуль отключён глобально в конфигурации
- Ошибка в имени события
- Исключение в обработчике блокирует выполнение
Проверка:
// Проверка статуса модуля
$em = service('eventManager');
$em->forModule('crm');
if ($em->currentModuleActive()) {
echo 'Модуль активен';
} else {
echo 'Модуль не активен';
}
Конфликты приоритетов
При использовании нескольких обработчиков одного события убедитесь в корректном порядке выполнения:
// Сначала сохраняем данные
$em->moduleOn('deal.won', function($deal) {
saveDealToArchive($deal);
}, 10); // Выполнится первым
// Затем отправляем уведомления
$em->moduleOn('deal.won', function($deal) {
sendDealWonNotification($deal);
}, 100); // Выполнится вторым
Сводка методов
| Метод | Описание | Возвращает |
|---|---|---|
forModule($code) |
Привязать к модулю | $this |
moduleOn($event, $callback, $priority) |
Подписка с проверкой | bool |
systemOn($event, $callback, $priority) |
Системная подписка | void |
off($event, $callback) |
Отписка | void |
currentModuleActive() |
Проверка модуля | bool |
getCurrentModuleCode() |
Код модуля | string|null |