Предложение нового механизма closure и слушателей событий в bootstrap неймспейсов MODX с приоритетами и именами
Хочу поделиться идеей/потенциальным изменением ядра MODX 3, которое даст возможность создавать пакеты без плагинов, да и в целом расширить функционал управления слушателями событий, и попросить вас посмотреть, что думаете.
Началось с того, что я пытаюсь продумать для себя максимально простой способ разработки и сборки пакетов под MODX, и параллельно внедрения архитектурных решений по стилю Laravel.
В процессе пришла идея «а зачем создавать плагины в пакете, если сейчас MODX 3 подключает bootstrap.php файл расширения, и по сути мы в этом бутстрапе можем зарегистрировать слушателей событий без плагинов вовсе».
Для этого так кстати есть метод
Попробовал и обнаружил забавный нюанс — в бутстрап addEventListener работать по сути не будет, поскольку подключение бустрапов происходит ДО основной загрузки карты событий, и эта карта перезатирается потом полностью.
Даже если мы пойдем дальше и в бутстрапе попробуем обхитрить MODX, заранее создав карту:
Проблема как таковая останется, потому что вроде как плагин должен при отсутствии в кеше браться из базы данных напрямую, но почему то у меня так не сработало, возможно это ошибка в логике получения плагина из бд, в которой ковыряться не очень хочется. Поэтому решил посмотреть, как это реализовано в ядре modX.php.
Собственно немного там поковыряв, получилось такое решение — пулл-реквест на гитхабе.
Пример тестирования достаточно подробно описан в пулл-реквесте, меняется там только modX файл — предлагается просто закинуть его в проект-черновик и потестить.
Хотелось бы обратную связь сообщества:
Началось с того, что я пытаюсь продумать для себя максимально простой способ разработки и сборки пакетов под MODX, и параллельно внедрения архитектурных решений по стилю Laravel.
В процессе пришла идея «а зачем создавать плагины в пакете, если сейчас MODX 3 подключает bootstrap.php файл расширения, и по сути мы в этом бутстрапе можем зарегистрировать слушателей событий без плагинов вовсе».
Для этого так кстати есть метод
$modx->addEventListener
Попробовал и обнаружил забавный нюанс — в бутстрап addEventListener работать по сути не будет, поскольку подключение бустрапов происходит ДО основной загрузки карты событий, и эта карта перезатирается потом полностью.
Даже если мы пойдем дальше и в бутстрапе попробуем обхитрить MODX, заранее создав карту:
if ($modx->eventMap === null) {
$modx->eventMap = $modx->getEventMap('web');
}
$modx->addEventListener('OnWebPagePrerender', 33);
Проблема как таковая останется, потому что вроде как плагин должен при отсутствии в кеше браться из базы данных напрямую, но почему то у меня так не сработало, возможно это ошибка в логике получения плагина из бд, в которой ковыряться не очень хочется. Поэтому решил посмотреть, как это реализовано в ядре modX.php.
Собственно немного там поковыряв, получилось такое решение — пулл-реквест на гитхабе.
- Возможность добавлять runtime-события через addEventListener(), которые живут в отдельном свойстве $modx и не затираются при инициализации eventMap() из БД.
- 🔥 Новый метод addEventListenerClosure(...) — чтобы навешивать слушатели-замыканя (closures) с указанием приоритета и имени.
- Универсальный метод удаления removeEventListenerClosure(...), который позволяет удалять слушателей по событию, приоритету или имени, либо глобально.
- Всё сделано с сохранением обратной совместимости, чтобы плагины и существующие события продолжали работать как раньше.
Пример тестирования достаточно подробно описан в пулл-реквесте, меняется там только modX файл — предлагается просто закинуть его в проект-черновик и потестить.
Хотелось бы обратную связь сообщества:
- Насколько вы считаете эту идею полезной?
- Есть ли юз-кейсы, которые не покрываются предлагаемой схемой?
- Какие риски или неудобства видите? (например, производительность, совместимость, сложность).
- Есть ли предложения, как сделать API ещё удобнее?
- ...
Комментарии: 8
Что делать если нужно изменить порядок выполнения плагинов? Если будет использоваться смешанный режим плагинов: твой + стандартный — в каком порядке будут выполняться плагины, особенно если у них одно событие?
Вижу в примерах на git что есть параметр priority:
$modx->addEventListenerClosure(
'OnWebPagePrerender',
function (array $params, \MODX\Revolution\modX $modx) {
$output = &$modx->resource->_output;
$output .= "\n<!-- TEST CLOSURE: " . date('Y-m-d H:i:s') . " -->";
return null;
},
priority: 5,
name: 'appendTestComment'
);
видимо, управлять приоритетом можно.
Я имел ввиду как управлять порядком выполнения, если плагины принадлежат разным компонентам? Сейчас открыл список плагинов и перетасовал приоритеты как надо, а тут списка плагинов не будет. Как искать какие плагины срабатывают на то или иное событие? Как менять их порядок?
И после просмотра примера возник новый вопрос: а если у меня будет большая функция или несколько функций или я хочу использовать целый класс в плагине, как тогда?
И после просмотра примера возник новый вопрос: а если у меня будет большая функция или несколько функций или я хочу использовать целый класс в плагине, как тогда?
так, а в чем проблема во втором абзаце, не очень понял?
$modx->addEventListenerClosure(
'OnWebPagePrerender',
function (\MODX\Revolution\modX $modx) {
(new \Namespace\YourClass($modx))->doSomething();
// $class = $modx->services->get('yourservice'); $class->doSomething();
// \Namespace\YourClass::doSomething();
// my_global_function($modx);
return null;
},
priority: 10,
name: 'appendTestComment',
replace: false
);
Да, но это приоритет выполнения именно Closure слушателей — они выполняются уже после плагинов из бд
с обычными плагинами — я пока это не учитывал, но вопрос логичный. НО это не учитывается и в оригинальном методе addEventListener, так что текущая логика по сути не меняется. Оригинал:
public function addEventListener($event, $pluginId, $propertySetName = '') {
$added = false;
$pluginId = intval($pluginId);
if ($event && $pluginId) {
if (!isset($this->eventMap[$event]) || empty ($this->eventMap[$event])) {
$this->eventMap[$event]= [];
}
$this->eventMap[$event][$pluginId]= $pluginId . (!empty($propertySetName) ? ':' . $propertySetName : '');
$added = true;
}
return $added;
}
Проблема в том что карта у них строится без какой либо доп информации о плагинах, просто событие->список ID плагинов, уже отсортированных в sql запросе. То есть пересортировать ее без дополнительного запроса к базе не получится (ну или надо сохранять расширенную карту из базы при инициализации и потом при добавлении нового плагина пересобирать итоговую карту). Сейчас реализовано так, что сначала выполняются плагины из бд — и подвязанные к событию в админке, и подвязанные к событию в коде, потом все closure плагины. Вообще это больше не для того чтобы кто-то в админке вручную тыкал, а чтобы например при разработке пакета вы спокойно свой функционал раскидали по нужным событиям и не парились с созданием плагинов и привязкой/отвязкой событий при обновлении например.
Но вообще выглядит прикольно. Прям клево, взять и подписаться на событие в своем коде без создания плагинов — удобно должно быть.
Вот да, я тоже из этого исхожу)
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.