Предложение нового механизма closure и слушателей событий в bootstrap неймспейсов MODX с приоритетами и именами

Хочу поделиться идеей/потенциальным изменением ядра MODX 3, которое даст возможность создавать пакеты без плагинов, да и в целом расширить функционал управления слушателями событий, и попросить вас посмотреть, что думаете.

Началось с того, что я пытаюсь продумать для себя максимально простой способ разработки и сборки пакетов под 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(...), который позволяет удалять слушателей по событию, приоритету или имени, либо глобально.
  • Всё сделано с сохранением обратной совместимости, чтобы плагины и существующие события продолжали работать как раньше.
Такой подход даст возможность в в bootstrap’е пакета (да и в целом в любом месте) повесить нужные event listener — например, чтобы модифицировать вывод страницы, добавить логирование, вставить JS и т.п. При этом введение closure-слушателей с именами и приоритетом делает код чище, облегчает написание расширений без создания плагинов в админке.

Пример тестирования достаточно подробно описан в пулл-реквесте, меняется там только modX файл — предлагается просто закинуть его в проект-черновик и потестить.

Хотелось бы обратную связь сообщества:

  • Насколько вы считаете эту идею полезной?
  • Есть ли юз-кейсы, которые не покрываются предлагаемой схемой?
  • Какие риски или неудобства видите? (например, производительность, совместимость, сложность).
  • Есть ли предложения, как сделать API ещё удобнее?
  • ...
Олег
Вчера в 01:40
modx.pro
149
+1

Комментарии: 8

Артур Шевченко
Вчера в 11:26
0
Что делать если нужно изменить порядок выполнения плагинов? Если будет использоваться смешанный режим плагинов: твой + стандартный — в каком порядке будут выполняться плагины, особенно если у них одно событие?
    Наумов Алексей
    Вчера в 12:04
    0
    Вижу в примерах на 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'
    );
    видимо, управлять приоритетом можно.
      Артур Шевченко
      Вчера в 13:45
      0
      Я имел ввиду как управлять порядком выполнения, если плагины принадлежат разным компонентам? Сейчас открыл список плагинов и перетасовал приоритеты как надо, а тут списка плагинов не будет. Как искать какие плагины срабатывают на то или иное событие? Как менять их порядок?

      И после просмотра примера возник новый вопрос: а если у меня будет большая функция или несколько функций или я хочу использовать целый класс в плагине, как тогда?
        Олег
        Вчера в 17:14
        0
        так, а в чем проблема во втором абзаце, не очень понял?
        $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
        );
        Олег
        Вчера в 16:50
        0
        Да, но это приоритет выполнения именно Closure слушателей — они выполняются уже после плагинов из бд
        Олег
        Вчера в 17:07
        0
        с обычными плагинами — я пока это не учитывал, но вопрос логичный. НО это не учитывается и в оригинальном методе 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 плагины. Вообще это больше не для того чтобы кто-то в админке вручную тыкал, а чтобы например при разработке пакета вы спокойно свой функционал раскидали по нужным событиям и не парились с созданием плагинов и привязкой/отвязкой событий при обновлении например.
        Наумов Алексей
        Вчера в 12:05
        0
        Но вообще выглядит прикольно. Прям клево, взять и подписаться на событие в своем коде без создания плагинов — удобно должно быть.
          Олег
          Вчера в 16:47
          0
          Вот да, я тоже из этого исхожу)
          Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
          8