Общая информация о принципе действия Joomla 6. Жизненный цикл Приложений Joomla

от автора

Как работает ядро Joomla 6 на уровне Приложения (Application)? Сколько видов Приложений на самом деле есть в Joomla и в чём между ними разница? Какой жизненный цикл каждого Приложения Joomla, включая маршрутизацию, диспетчеризацию и события плагинов? На эти и другие вопросы попытается дать ответ эта статья, опирающаяся на кодовую базу Joomla 6.1.0.

Joomla 6 строит запрос вокруг объекта приложения, которое инициализирует окружение, разбирает запрос, передаёт управление компоненту, собирает документ и затем отправляет ответ. Плагины вмешиваются в работу в строго определённых точках этого цикла.

Для Joomla 3 хочу упомянуть статью Дмитрия Рекуна «Общая информация о принципе действия Joomla», которая довольно подробно описывает как Joomla работала раньше.

Что происходит в Joomla при создании HTML-страницы?

Обычный запрос HTML в Joomla начинается в index.php в корне сайта (если речь идёт о пользовательской части).

Сначала входной файл:

  • проверяет минимальную версию PHP;

  • определяет _JEXEC, чтобы внутренние файлы Joomla нельзя было выполнять напрямую;

  • подключает defines.php из корня сайта (если он существует): его используют для предварительного определения или переопределения констант с путями при нестандартной структуре каталогов (Joomla находится за пределами web root);

  • подключает includes/defines.php, где задаются основные JPATH_*-константы;

  • передаёт управление в includes/app.php.

В includes/app.php Joomla сохраняет время старта и память, ещё раз поднимает определения путей, проверяет наличие обязательных зависимостей и подключает includes/framework.php. Во framework.php загружается libraries/bootstrap.php, проверяется состояние установки, читается configuration.php, настраиваются ошибки, JDEBUG и базовые параметры окружения.

После этого начинается уже загрузка Приложения Joomla 6:

  • берётся DI-контейнер через Factory::getContainer();

  • для сайта настраиваются алиасы веб-сессии;

  • из контейнера создаётся SiteApplication;

  • приложение записывается в Factory::$application;

  • вызывается $app->execute().

Дальше управление переходит к CMSApplication::execute(): проверяются опасные системные переменные, настраивается журнал, строится карта пространств имён расширений (которая потом кэшируется в autoload_psr4.php), загружаются плагины behaviour и system, вызывается onBeforeExecute, затем приложение выполняет свой основной цикл.

Для HTML-страницы сайта этот цикл кратко выглядит следующим образом:

index.php  -> includes/defines.php  -> includes/app.php  -> includes/framework.php  -> DI-контейнер  -> SiteApplication  -> CMSApplication::execute()  -> onBeforeExecute  -> initialiseApp()  -> onAfterInitialise  -> route()  -> onAfterRoute  -> dispatch()  -> onAfterInitialiseDocument  -> компонент  -> onAfterDispatch  -> render()  -> onBeforeRender  -> сборка HTML-документа и шаблона  -> onAfterRender  -> compress() [если включён gzip]  -> onAfterCompress [если включён gzip]  -> onBeforeRespond  -> respond()  -> onAfterRespond

Для каждого типа приложений Joomla используется своя точка входа. А REST API свой рендер — JSON, вместо HTML — и свой роутинг. У CLI — вывод в консоль. Ниже эти различия разобраны подробнее.

Типы приложений

В Joomla 6 есть несколько типов Приложений, каждое из которых создано для определённого круга задач.

Приложение административной части

Класс: Joomla\CMS\Application\AdministratorApplication.

Отвечает за /administrator, вход администратора, меню панели управления, формы редактирования и внутренние сервисы панели управления.

Приложение публичной части сайта

Класс: Joomla\CMS\Application\SiteApplication.

Создаёт привычные нам страницы сайта: статьи, категории, меню, модули, шаблон сайта, мультиязычность, SEF URL и т.д.

API-приложение

Класс: Joomla\CMS\Application\ApiApplication.

Joomla REST API с точкой входа в /api. Тут согласуется формат ответа, роуты, проверяются права доступа к API.

Консольное приложение

Класс: Joomla\CMS\Application\ConsoleApplication.

CLI интерфейс Joomla, точка входа: cli/joomla.php. Здесь нет HTML, применяется обычно для «тяжёлых задач» и для «тру админов».

Демон

Класс: Joomla\CMS\Application\DaemonApplication.

Это долгоживущее консольное приложение для фоновых процессов. Вы можете запустить его с php как серверный процесс, чтоб он батрачил сутками, выполняя какую-нибудь работу. В обычной разработке расширений встречается редко.

Daemon приложение Joomla наследуется от CliApplication, а не от CMSApplication, поэтому у него свой жизненный цикл. В ядре Joomla готовых примеров самого DaemonApplication нет. Но это хороший повод для экспериментов. CliApplication — устаревший класс, в дальнейшем Daemon скорее всего будет наследоваться от нового ConsoleApplication, но пока что это только прогноз, который базируется на комментарии в коде класса CliApplication:

@deprecated  4.0 will be removed in 6.0.  Use the ConsoleApplication instead

Иерархия классов Application Joomla

  • Joomla\Application\AbstractApplication

    • Joomla\Application\AbstractWebApplication

      • Joomla\CMS\Application\WebApplication

        • Joomla\CMS\Application\CMSApplication

          • Joomla\CMS\Application\SiteApplication

          • Joomla\CMS\Application\AdministratorApplication

          • Joomla\CMS\Application\ApiApplication

          • Joomla\CMS\Installation\Application\InstallationApplication

    • Joomla\Console\Application

      • Joomla\CMS\Application\ConsoleApplication

      • Joomla\CMS\Installation\Application\CliInstallationApplication

    • Joomla\CMS\Application\CliApplication

      • Joomla\CMS\Application\DaemonApplication

Контракты приложений Joomla

  • Joomla\CMS\Application\CMSApplicationInterface

    • Joomla\CMS\Application\CMSApplication

    • Joomla\CMS\Application\ConsoleApplication

    • Joomla\CMS\Application\CliApplication

    • Joomla\CMS\Installation\Application\CliInstallationApplication

  • Joomla\CMS\Application\CMSWebApplicationInterface

    • Joomla\CMS\Application\CMSApplication

      • Joomla\CMS\Application\SiteApplication

      • Joomla\CMS\Application\AdministratorApplication

      • Joomla\CMS\Application\ApiApplication

      • Joomla\CMS\Installation\Application\InstallationApplication

Обычные веб-клиенты Joomla, то есть siteadministrator и api, наследуются через цепочку WebApplication → CMSApplication.

ConsoleApplication реализует CMS-контракт, но не является веб-приложением.

DaemonApplication не наследуется от CMSApplication; он идёт через CliApplication, поэтому у него отдельный жизненный цикл.

Пример порядка выполнения веб-приложения

Ниже описан общий цикл для SiteApplication и AdministratorApplication. Для API он почти такой же, но со своим роутингом.

Подготовка к выполнению приложения

Точка входа (index.phpadministrator/index.php или api/index.php) выполняет базовую загрузку:

  • определяет константы путей;

  • поднимает автозагрузку;

  • создаёт DI-контейнер (подключение зависимостей);

  • через сервис-провайдеры собирает объект приложения и внедряет в него диспетчер событий, логгер, сессию и другие сервисы;

  • вызывает $app->execute().

С этого момента жизненным циклом управляет уже само приложение.

Общий цикл CMSApplication::execute()

Базовая логика живёт в libraries/src/Application/CMSApplication.php::execute().

Порядок работы такой:

  • Проверка опасных системных переменных.

  • Настройка журнала.

  • Построение карты пространств имён (неймспейсов) расширений.

  • Загрузка плагинов группы behaviour (в ядре Joomla это: плагин обратной совместимости, версионность и поддержка тегов com_tags).

  • Загрузка плагинов группы system.

  • Вызов onBeforeExecute.

  • Выполнение прикладной части через doExecute().

  • Если создан документ, вызов render().

  • При включённом GZIP-сжатии в конфиге Joomla — вызов compress() и затем onAfterCompress.

  • Вызов onBeforeRespond.

  • Отправка ответа через respond().

  • Вызов onAfterRespond.

Итого:

  • onBeforeExecute — самая ранняя общая точка для системных плагинов;

  • onBeforeRespond — последняя точка до отправки заголовков и тела ответа;

  • onAfterRespond — заключительный этап после отдачи заголовков и тела ответа браузеру: журналирование, отладка, побочные действия.

Отдельно важно: в WebApplication существует ещё onAfterExecute, но в обычном запросе Joomla CMS он не вызывается, потому что CMSApplication переопределяет execute() своим циклом.

Инициализация

Инициализация проходит внутри CMSApplication::initialiseApp(), а у SiteApplication и AdministratorApplication есть свои дополнения.

На этом этапе Joomla:

  • в приложениях сайта и административной части уточняет язык и, при необходимости, группы доступа пользователя;

  • создаёт объект языка и загружает его в приложение;

  • подключает файлы локализации (ini-файлы языков);

  • выбирает редактор для текущего пользователя;

  • вызывает onAfterInitialise.

Важно не смешивать этот этап с более ранней сборкой приложения. Объекты сессии, диспетчера событий, журнала и другие сервисы внедряются ещё при создании приложения в сервис-провайдере libraries/src/Service/Provider/Application.php. Текущий пользователь подтягивается из сессии через WebApplication::afterSessionStart(), а не создаётся внутри initialiseApp().

Именно после onAfterInitialise можно считать, что у приложения уже есть в рабочем состоянии:

  • сессия;

  • язык;

  • пользователь;

  • конфигурация;

  • диспетчер событий.

Это хороший момент для ранней настройки среды: язык уже выбран, редактор известен, базовые сервисы доступны, но к рендеру компонента Joomla ещё не приступила, поэтому логика, зависящая от url, здесь ещё преждевременна.

Маршрутизация и проверка доступа

Публичная часть

В SiteApplication::route():

  • создаётся или берётся текущий Uri;

  • роутер разбирает адрес;

  • найденные переменные записываются во входные данные приложения;

  • вызывается onAfterRoute;

  • выполняется проверка доступа к текущему Itemid (id пункта меню) через authorise().

После этого Joomla уже знает, какой компонент, представление, шаблон, макет и пункт меню связаны с запросом, и может сразу отреагировать на запрет доступа.

Административная часть

В AdministratorApplication::route() логика проще:

  • при необходимости выполняется перевод на HTTPS;

  • проверяется состояние страниц многофакторной проверки;

  • вызывается onAfterRoute.

В административной части маршрутизация в меньшей степени завязана на человекопонятные адреса, но событие то же самое.

API

В ApiApplication::route() порядок другой:

  • загружаются плагины группы webservices;

  • вызывается onBeforeApiRoute;

  • маршрутизатор API подбирает маршрут;

  • Joomla согласует формат ответа по заголовку Accept;

  • переменные маршрута записываются во входные данные;

  • вызывается onAfterApiRoute;

  • при необходимости выполняется вход пользователя для закрытых маршрутов.

Для API важен именно onBeforeApiRoute: через него можно добавить или поправить роуты REST API Joomla до собственно роутинга.

Отдельно: как работает DaemonApplication

На демон-процессы обычно вешают задачи, которые должны жить долго, работать в фоне и не зависеть от HTTP-запроса или разового запуска CLI-команды.

Типичные задачи:

  • постоянная обработка очереди: email, SMS, webhooks, экспорт/импорт, генерация файлов;

  • воркеры фоновых заданий, где нужно быстро подхватывать новые задачи без запуска PHP с нуля каждый раз;

  • слушатели внешних событий: сокеты, очереди сообщений, брокеры, long polling, системные события;

  • периодическая фоновая работа с собственным циклом: очистка, синхронизация, пересчёт агрегатов, мониторинг;

  • процессы, которым нужно держать состояние в памяти: кэш подключений, открытые соединения, подготовленные ресурсы;

  • интеграции с внешними сервисами, где нужно постоянно принимать или отправлять события;

  • сервисные процессы, где важны PID-файл, сигналы SIGTERM/SIGHUP, мягкая остановка и перезапуск.

Для Joomla-практики чаще всего это были бы не обычные задачи расширений, а инфраструктурные вещи: воркер очереди, постоянный импорт, синхронизация с CRM/маркетплейсом, обработчик вебхуков из очереди, индексация, массовая рассылка, мониторинг состояния. Если задача может спокойно запускаться раз в минуту по cron и завершаться, демон обычно не нужен. Демон оправдан, когда процесс должен быть постоянно запущен, быстро реагировать, держать ресурсы открытыми или управляться через сигналы и PID.

Поэтому DaemonApplication живёт по другому сценарию, нежели сайт, админка или API. Это не разовый запрос-ответ, а долгоживущий процесс, который:

  • стартует из CLI;

  • при необходимости уходит в фон;

  • пишет PID-файл;

  • подключает обработчики Unix-сигналов;

  • бесконечно гоняет doExecute() в цикле.

Требования к окружению для демона на базе Joomla

Конструктор DaemonApplication проверяет наличие расширения PCNTL и наличие функций POSIX. Если их нет, приложение падает ещё в __construct().

Какие параметры он готовит?

Метод loadConfiguration() нормализует и подготавливает служебные параметры демона:

  • author_name

  • author_email

  • application_name

  • application_description

  • application_executable

  • application_directory

  • application_pid_file

  • application_uid

  • application_gid

  • application_require_identity

  • max_execution_time

  • max_memory_limit

По умолчанию DaemonApplication строит PID-файл в абсолютном системном Unix-каталоге /tmp, по шаблону /tmp/<application_name>/<application_name>.pid. Это не временная папка конкретной Joomla-установки. Если очень нужно, путь можно переопределить через конфигурационный параметр application_pid_file.

Как выглядит жизненный цикл демона на Joomla?

Порядок в DaemonApplication::execute() такой:

  • onBeforeExecute

  • включение сборки мусора gc_enable()

  • попытка перейти в режим демона через daemonize()

  • если переход удался:

    • включение declare(ticks=1) для мониторинга сигналов

    • бесконечный цикл:

      • gc()

      • usleep(1000)

      • doExecute()

  • onAfterExecute вызывается только если запуск не удался или если выполнение вообще вышло из цикла

На заметку: для DaemonApplication событие onAfterExecute существует реально, но при нормальной бесконечной работе демона до него обычно не доходят.

Это важное отличие Joomla Daemon от веб-цикла Joomla:

  • у демона нет onAfterInitialiseonAfterRouteonAfterDispatchonBeforeRenderonAfterRenderonBeforeRespondonAfterRespond;

  • у него есть только ранний onBeforeExecute, поздний onAfterExecute и специфические onFork и onReceiveSignal.

Что делает daemonize()

Метод daemonize() последовательно:

  • проверяет через isActive(), не запущен ли уже такой демон;

  • сбрасывает внутренние флаги процесса;

  • если не передан флаг -f, вызывает detach() и уходит в фон;

  • если -f передан, остаётся на переднем плане;

  • пишет PID-файл через writeProcessIdFile();

  • пытается сменить пользователя и группу через changeIdentity();

  • подключает обработчики сигналов через setupSignalHandlers();

  • меняет рабочую директорию на application_directory.

Что значит флаг -f

Этот флаг не позволяет уйти демону в фоновый процесс. Если у CLI-ввода есть флаг -f, демон продолжает работать с консолью и выводить информацию (если предполагается) в неё. Это удобно для отладки, запуска под внешним диспетчером процессов, различных видов тестирования.

Как работает fork

Уход в фон делается в detach(), а само разделение процесса — в fork().

Ключевая деталь ядра:

  • вызывается pcntl_fork();

  • и в родительском, и в дочернем процессе вызывается postFork();

  • postFork() отправляет событие onFork.

То есть:

onFork срабатывает после разделения процесса и проходит в обоих потоках выполнения, родительском и дочернем.

Это хорошая точка для post-fork инициализации:

  • переподключить сокеты;

  • заново открыть файловые дескрипторы;

  • переинициализировать соединения, которые нельзя безопасно делить после fork.

Как работает onReceiveSignal

Статический обработчик DaemonApplication::signal($signal):

  • пишет сигнал в журнал;

  • берёт текущий экземпляр приложения из static::$instance;

  • отправляет событие onReceiveSignal;

  • только потом выполняет встроенную реакцию ядра.

Это значит, что обработчик плагина получает сигнал до штатной реакции класса.

Какие сигналы обрабатываются встроенно

Ядро по умолчанию вешает обработчик на длинный список POSIX-сигналов из static::$signals.

Практически важные встроенные ветки такие:

  • SIGINTSIGTERM — мягкое завершение через shutdown();

  • SIGHUP — завершение с последующим перезапуском через shutdown(true);

  • SIGCHLDSIGCLD — уборка завершившихся дочерних процессов через pcntlWait().

Для остальных сигналов ядро только отправляет onReceiveSignal, а специального встроенного поведения не добавляет.

Как он определяет, что демон уже работает?

Метод isActive():

  • читает PID-файл;

  • проверяет, что PID корректен;

  • делает posix_kill($pid, 0) как проверку живого процесса;

  • если процесс не отвечает, удаляет устаревший PID-файл.

То есть здесь используется именно проверка существования процесса, а не простое наличие файла.

Что делает shutdown()

shutdown():

  • ставит флаг завершения;

  • если демон ещё не был полностью поднят, просто завершает текущий процесс;

  • если это главный процесс, читает PID из файла;

  • удаляет PID-файл;

  • при restart=true перезапускает ту же команду;

  • иначе завершает процесс.

Отдельная хронология для DaemonApplication

Если собрать всё вместе, жизненный цикл выглядит так:

Запуск CLI  -> new DaemonApplication(...)  -> проверка PCNTL и POSIX  -> loadConfiguration()  -> execute()  -> onBeforeExecute  -> daemonize()  -> isActive()  -> detach() / foreground mode  -> fork()  -> onFork  -> writeProcessIdFile()  -> changeIdentity()  -> setupSignalHandlers()  -> declare(ticks=1)  -> while (true)       -> gc()       -> usleep(1000)       -> doExecute()  -> при поступлении сигнала       -> onReceiveSignal       -> shutdown()/restart()/reap children  -> onAfterExecute

Важные детали

Для демона Joomla ядро даёт всего четыре действительно системных точки:

  • onBeforeExecute

  • onAfterExecute

  • onFork

  • onReceiveSignal

Но именно для фоновых процессов это и есть главные точки расширения.

При этом Daemon приложение Joomla само не импортирует какие-либо группы плагинов по умолчанию (на данный момент — Joomla 6.1). Поскольку это в любом случае довольно специфичный сценарий — вы импортируете нужные группы плагинов самостоятельно. Также помните, что вы можете здесь использовать и классы фреймворка Joomla и библиотек, входящих в состав ядра Joomla.

Диспетчеризация

После маршрутизации приложение передаёт управление компоненту.

Публичная и административная часть

В SiteApplication::dispatch() и AdministratorApplication::dispatch() порядок такой:

  • создаётся документ;

  • настраиваются метаданные, шаблон и реестры ресурсов;

  • вызывается onAfterInitialiseDocument;

  • компонент отрисовывается через ComponentHelper::renderComponent();

  • результат кладётся в буфер документа;

  • вызывается onAfterDispatch.

onAfterInitialiseDocument — это точка до выполнения компонента: документ уже существует, но компонент ещё не отработал. Здесь удобно править тип документа, метаданные, реестры стилей и сценариев. Помните, что это событие появилось только в Joomla 5.0.0.

onAfterDispatch — это точка после выполнения компонента. Здесь уже можно работать с буферами документа и итогом работы компонента.

API

В ApiApplication::dispatch() смысл тот же:

  • создаётся документ;

  • вызывается onAfterInitialiseDocument;

  • вызывается диспетчер компонента веб-служб.

  • вызывается onAfterDispatch.

Сборка документа

Для фронтенда (SiteApplication) и админки это обычно HTML. Хотя нужно упомянуть, что внутри компонента за формат рендера отвечает View и View может отдавать и txt, и json, и XML — что угодно, не только HTML. Но рассмотрим стандартный путь, когда сборка ответа в HTML делает CMSApplication::render().

Порядок такой:

  • заполняются параметры шаблона;

  • выполняется $this->document->parse(...);

  • вызывается onBeforeRender;

  • документ формирует итоговую строку ответа;

  • строка записывается в тело ответа через setBody();

  • вызывается onAfterRender.

Здесь есть важная деталь: во время сборки HTML-заголовка libraries/src/Document/Renderer/Html/MetasRenderer.php вызывает onBeforeCompileHead.

То есть:

  • onBeforeRender — общий поздний этап перед окончательной сборкой страницы;

  • onBeforeCompileHead — специальная точка именно для <head>;

  • onAfterRender — этап, когда вся HTML-строка уже собрана и её можно переписать целиком.

Вывод ответа

После сборки документа CMSApplication::execute() завершает запрос:

  • при включённом сжатии вызывает onAfterCompress;

  • вызывает onBeforeRespond;

  • отправляет заголовки и тело;

  • вызывает onAfterRespond.

onAfterCompress важно понимать буквально. В CMSApplication::execute() он вызывается только после $this->compress() и только если включён параметр gzip, при этом PHP не использует zlib.output_compression и текущий output_handler не равен ob_gzhandler. Это событие относится к CMS-циклу SiteApplicationAdministratorApplication и ApiApplication; у базового WebApplication::execute() сжатие тоже есть, но события onAfterCompress после него нет.

На практике:

  • onBeforeRespond — последняя безопасная точка для заголовков и тела ответа;

  • onAfterRespond — не место для изменения ответа, а место для побочных действий после того, как HTML отдан в браузер.

Схема цикла выполнения

Публичная часть

index.php  -> SiteApplication::execute()  -> onBeforeExecute  -> initialiseApp()  -> onAfterInitialise  -> route()  -> onAfterRoute  -> dispatch()  -> onAfterInitialiseDocument  -> renderComponent()  -> onAfterDispatch  -> render()  -> onBeforeRender  -> onBeforeCompileHead  -> onAfterRender  -> compress() [если включён gzip]  -> onAfterCompress [если включён gzip]  -> onBeforeRespond  -> respond()  -> onAfterRespond

Админка Joomla

administrator/index.php  -> AdministratorApplication::execute()  -> onBeforeExecute  -> initialiseApp()  -> onAfterInitialise  -> route()  -> onAfterRoute  -> dispatch()  -> onAfterInitialiseDocument  -> renderComponent()  -> onAfterDispatch  -> render()  -> onBeforeRender  -> onBeforeCompileHead  -> onAfterRender  -> compress() [если включён gzip]  -> onAfterCompress [если включён gzip]  -> onBeforeRespond  -> respond()  -> onAfterRespond

REST API Joomla

api/index.php  -> ApiApplication::execute()  -> onBeforeExecute  -> initialiseApp()  -> onAfterInitialise  -> route()  -> onBeforeApiRoute  -> parseApiRoute()  -> onAfterApiRoute  -> dispatch()  -> onAfterInitialiseDocument  -> component dispatcher  -> onAfterDispatch  -> render()  -> compress() [если включён gzip]  -> onAfterCompress [если включён gzip]  -> onBeforeRespond  -> respond()  -> onAfterRespond

Полная хронологическая карта триггеров Joomla во время запроса

Ниже уже не укрупнённая схема, а именно хронология. Важно помнить две вещи:

  • не все ветви выполняются в каждом запросе;

  • часть событий внутри формирования HTML зависит от конкретного шаблона и от того, в каком месте шаблон вызывает jdoc:include.

Самый первый триггер в жизненном цикле Joomla

Ещё до onBeforeExecute ядро успевает загрузить группы behaviour и system.

Для каждого плагина, который загружается через PluginHelper::importPlugin(), перед созданием экземпляра и сразу после него проходят:

  • onBeforeExtensionBoot

  • onAfterExtensionBoot

Это касается:

  • плагинов behaviour и system в начале запроса;

  • плагинов webservices в API;

  • плагинов contentuserinstallerextensionfinderprivacyquickiconsampledata и других в более поздних ветках;

  • компонентов и модулей, когда они загружаются через bootComponent() и bootModule().

Поэтому onBeforeExtensionBoot и onAfterExtensionBoot — самые ранние реально доступные точки вмешательства в жизненный цикл расширения.

Полный порядок для обычной HTML-страницы сайта

Ниже порядок для типового запроса фронтенда с HTML-документом.

Базовая последовательность

  • Для каждого загружаемого плагина группы behaviouronBeforeExtensionBoot -> onAfterExtensionBoot

  • Для каждого загружаемого плагина группы systemonBeforeExtensionBoot -> onAfterExtensionBoot

  • onBeforeExecute

  • onAfterInitialise

  • onAfterRoute

  • onAfterInitialiseDocument

  • Для загружаемого компонента: onBeforeExtensionBoot -> onAfterExtensionBoot

  • Внутренняя логика компонента

  • onAfterDispatch

  • onBeforeRender

  • Внутренняя сборка документа и шаблона

  • onAfterRender

  • При включённом gzip и совместимых настройках PHP: compress() -> onAfterCompress

  • onBeforeRespond

  • onAfterRespond

Что может произойти внутри пункта «внутренняя логика компонента»

Если компонент использует HTML-представление на базе Joomla\CMS\MVC\View\HtmlView, то внутри его display() дополнительно идут:

  • onBeforeDisplay

  • загрузка шаблона представления

  • onAfterDisplay

Но и это ещё не всё. Многие компоненты вызывают свои события до parent::display().

Например, у com_content в представлении статьи порядок такой:

  • для каждого плагина группы contentonBeforeExtensionBoot -> onAfterExtensionBoot

  • onContentPrepare

  • onContentAfterTitle

  • onContentBeforeDisplay

  • onContentAfterDisplay

  • onBeforeDisplay

  • onAfterDisplay

У категорий и тегов логика похожая, но события onContentPrepareonContentAfterTitleonContentBeforeDisplayonContentAfterDisplay могут вызываться по каждому элементу списка, а не один раз на страницу.

Что может произойти внутри пункта «внутренняя сборка документа и шаблона»

Вот здесь порядок уже зависит от шаблона.

Если шаблон первым делом выводит <head>, то раньше всего сработает onBeforeCompileHead. Если шаблон затем выводит позиции модулей, то для первой же позиции Joomla запустит такую ветку:

  • onPrepareModuleList

  • onAfterModuleList

  • onAfterCleanModuleList

  • для каждого загружаемого модуля: onBeforeExtensionBoot -> onAfterExtensionBoot

  • для каждого модуля: onRenderModule

  • внутренняя логика модуля

  • для каждого модуля: onAfterRenderModule

  • после завершения всей позиции: onAfterRenderModules

При этом внутренняя логика конкретного модуля тоже может загрузить свои плагины и вызвать свои события. Пример из ядра: mod_articles_news внутри вывода модуля вызывает:

  • onContentPrepare

  • onContentAfterTitle

  • onContentBeforeDisplay

  • onContentAfterDisplay

То есть в одной HTML-странице события content могут сработать:

  • в компоненте;

  • в одном или нескольких модулях;

  • в произвольном порядке относительно позиций модулей, потому что это уже зависит от шаблона.

Полный порядок триггеров плагинов для HTML-страницы в админке Joomla

Базовый порядок почти тот же:

  • для плагинов behaviouronBeforeExtensionBoot -> onAfterExtensionBoot

  • для плагинов systemonBeforeExtensionBoot -> onAfterExtensionBoot

  • onBeforeExecute

  • onAfterInitialise

  • onAfterRoute

  • onAfterInitialiseDocument

  • для компонента: onBeforeExtensionBoot -> onAfterExtensionBoot

  • внутренняя логика компонента и представления

  • onAfterDispatch

  • onBeforeRender

  • события головы страницы, модулей и меню в зависимости от шаблона

  • onAfterRender

  • при включённом gzip и совместимых настройках PHP: compress() -> onAfterCompress

  • onBeforeRespond

  • onAfterRespond

Но в административной части есть ещё несколько условных точек, которые часто забывают.

Если строится меню для модулей меню в панели администратора Joomla, то во время обработки узлов меню могут вызываться onPreprocessMenuItems. Причём это событие может пройти несколько раз, по уровням дерева. Контексты в ядре com_menus.administrator.module и administrator.module.mod_submenu.

Если открыт список пунктов меню в com_menus, Перед выводом представления Items вызывается onBeforeRenderMenuItems. Если модель собирает типы меню дополнительно может вызваться onAfterGetMenuTypeOptions. То есть для административной части полная хронология часто содержит не только системные и модульные события, но и отдельные меню-события внутри конкретных экранов.

Полный порядок для API-запроса

У API жизненный цикл другой, и это важно.

Базовая последовательность

  • Для плагинов behaviouronBeforeExtensionBoot -> onAfterExtensionBoot

  • Для плагинов systemonBeforeExtensionBoot -> onAfterExtensionBoot

  • onBeforeExecute

  • onAfterInitialise

  • Для плагинов webservicesonBeforeExtensionBoot -> onAfterExtensionBoot

  • onBeforeApiRoute

  • разбор маршрута API

  • onAfterApiRoute

  • onAfterInitialiseDocument

  • Для компонента API: onBeforeExtensionBoot -> onAfterExtensionBoot

  • логика контроллера, модели и представления API

  • onAfterDispatch

  • render() через переопределённый ApiApplication::render()

  • при включённом gzip и совместимых настройках PHP: compress() -> onAfterCompress

  • onBeforeRespond

  • onAfterRespond

Чего здесь обычно нет

В обычном API-запросе через ApiApplication нет общего HTML-цикла:

  • нет onBeforeRender из CMSApplication::render();

  • нет onAfterRender;

  • нет onBeforeCompileHead;

  • нет модульных событий формирования вывода.

Причина простая: ApiApplication переопределяет render() и не использует HTML-сборку документа.

Какие API-события добавляются внутри представления и сериализации

Если ответ строится через JsonApiView и сериализатор Joomla, дополнительно могут идти:

  • onApiGetFields

  • onGetApiAttributes

  • onGetApiRelation

Это уже не системная часть жизненного цикла приложения, а события слоя сериализации API.

Хронология при открытии и отправке формы Joomla

Открытие формы редактирования

Типовой порядок:

  • общий цикл приложения до onAfterRoute

  • загрузка компонента

  • onContentPrepareData

  • onContentPrepareForm

  • onBeforeDisplay

  • onAfterDisplay

  • onAfterDispatch

  • дальше общая HTML-сборка страницы

Отправка формы и сохранение записи

Типовой порядок:

  • общий цикл приложения до маршрута и диспетчеризации

  • onContentNormaliseRequestData

  • onContentBeforeValidateData

  • onTableBeforeLoad и onTableAfterLoad, если запись не новая

  • onTableBeforeBind

  • onTableAfterBind

  • onTableCheck

  • onContentBeforeSave

  • onTableBeforeStore

  • onTableAfterStore

  • onContentCleanCache

  • onContentAfterSave

  • обычное завершение запроса через onBeforeRespond и onAfterRespond

Если это удаление:

  • onTableBeforeLoad

  • onTableAfterLoad

  • onContentBeforeDelete

  • onTableBeforeDelete

  • onTableAfterDelete

  • onContentAfterDelete

  • onContentCleanCache

Если это смена состояния (опубликовано, не опубликовано, в архиве, в корзине):

  • onContentBeforeChangeState

  • onTableBeforePublish

  • onTableAfterPublish

  • onContentChangeState

  • onContentCleanCache

Хронология триггеров плагинов Joomla для входа, выхода и восстановления доступа пользователя

Эти события не являются обязательной частью каждого запроса, но если запрос делает именно это действие, порядок будет таким.

Вход пользователя

  • для плагинов useronBeforeExtensionBoot -> onAfterExtensionBoot

  • onUserAuthenticate

  • onUserAuthorisation

  • при отказе: onUserAuthorisationFailure

  • при продолжении входа: onUserLogin

  • при успехе: onUserAfterLogin

  • при неудаче: onUserLoginFailure

Выход пользователя

  • для плагинов useronBeforeExtensionBoot -> onAfterExtensionBoot

  • onUserLogout

  • при успехе: onUserAfterLogout

  • при неудаче: onUserLogoutFailure

Напоминание логина пользователя

onUserAfterRemind — Вызывается при отправке формы «забыли логин» в com_users Joomla.

Запрос на сброс пароля

События вызываются при отправке формы «забыли пароль» в com_users Joomla:

  • onUserBeforeResetRequest

  • сохранение пользователя (там свои события onUserAfterSave и т.д.)

  • onUserAfterResetRequest

Завершение сброса пароля

  • onUserBeforeResetComplete

  • сохранение пользователя

  • onUserAfterResetComplete

Хронология установки расширения Joomla через штатный установщик

Если запрос идёт через стандартный установщик Joomla, то внутри него порядок событий такой:

  • для плагинов группы installeronBeforeExtensionBoot -> onAfterExtensionBoot

  • onInstallerBeforeInstallation

  • при скачивании пакета по url адресу с сайта разработчика: onInstallerBeforePackageDownload

  • onInstallerBeforeInstaller

  • для плагинов extensiononBeforeExtensionBoot -> onAfterExtensionBoot

  • onExtensionBeforeInstall

  • установка расширения

  • onExtensionAfterInstall

  • onInstallerAfterInstaller

Для обновления:

  • onInstallerBeforePackageDownload, если пакет нужно скачать

  • onExtensionBeforeUpdate

  • обновление

  • onExtensionAfterUpdate

Для удаления:

  • onExtensionBeforeUninstall

  • удаление

  • onExtensionAfterUninstall

Карта уровней событий Joomla

Не все события Joomla находятся в верхнем цикле Application. Часть событий возникает при загрузке расширений, часть — внутри компонента, формы, модели, таблицы или шаблона. Поэтому точку внедрения в ход работы Joomla лучше выбирать не по названию события, а по уровню, на котором уже есть нужные данные.

Уровень

Где возникает

Примеры событий

Когда туда идти

Приложение

Общий цикл CMSApplicationSiteApplicationAdministratorApplicationApiApplication

onBeforeExecuteonAfterInitialiseonAfterRouteonAfterDispatchonBeforeRenderonAfterRenderonBeforeRespondonAfterRespond

Когда задача относится ко всему запросу: окружение, маршрут, документ, заголовки, финальное тело ответа

Загрузка расширений

Когда Joomla создаёт плагин, компонент или модуль через механизм загрузки расширений

onBeforeExtensionBootonAfterExtensionBoot

Когда нужно отследить или повлиять на момент создания расширения, а не на его бизнес-логику

Компонент и представление

Внутри MVC-компонента и HTML-представления

onBeforeDisplayonAfterDisplay, а также собственные события компонента

Когда данные уже выбраны компонентом, но страница ещё не собрана целиком

Контент

В com_content, модулях материалов и вспомогательной обработке текста. Эти же события могут вызываться и в других компонента, следующих «Joomla way» в разработке.

onContentPrepareonContentAfterTitleonContentBeforeDisplayonContentAfterDisplay

Когда нужно менять текст, вставки вокруг материала или результат вывода статьи

Формы и данные формы

При построении формы и перед валидацией отправленных данных

onContentPrepareDataonContentPrepareFormonContentNormaliseRequestDataonContentBeforeValidateData

Когда нужно добавить поля, изменить XML-форму или подготовить сырые данные до проверки

Модель сохранения

В AdminModel и наследниках перед записью, после записи, при удалении и смене состояния

onContentBeforeSaveonContentAfterSaveonContentBeforeDeleteonContentAfterDeleteonContentBeforeChangeStateonContentChangeState

Когда нужно применить бизнес-правило компонента или отменить действие на уровне модели

Таблица

В Table и Nested прямо перед низкоуровневой операцией с записью

onTableBeforeBindonTableAfterBindonTableCheckonTableBeforeStoreonTableAfterStoreonTableBeforeDeleteonTableAfterDelete

Когда нужна самая близкая к базе точка и уже есть объект таблицы

Модули

При сборке списка модулей, выводе отдельного модуля и позиции

onPrepareModuleListonAfterModuleListonAfterCleanModuleListonRenderModuleonAfterRenderModuleonAfterRenderModules

Когда нужно подменить список модулей, атрибуты модуля или HTML всей позиции

Пользователи и вход

В аутентификации, авторизации входа, сохранении и удалении пользователя

onUserAuthenticateonUserAuthorisationonUserLoginonUserAfterLoginonUserBeforeSaveonUserAfterSave

Когда задача относится к подлинности, разрешению входа, пользовательской сессии или данным пользователя

Установка и обновление

В com_installerInstallerHelper и Installer

onInstallerBeforePackageDownloadonInstallerBeforeInstallationonExtensionBeforeInstallonExtensionAfterInstallonExtensionBeforeUpdateonExtensionAfterUpdate

Когда нужно вмешаться в скачивание, установку, обновление или удаление расширения

Административное меню

В отдельных экранах и модулях административной части

onPreprocessMenuItemsonBeforeRenderMenuItemsonAfterGetMenuTypeOptions

Когда задача касается дерева меню, списка пунктов меню или выбора типа пункта меню

API-сериализация

Внутри API-представления и сериализатора

onApiGetFieldsonGetApiAttributesonGetApiRelation

Когда роут API уже обработан и нужно изменить поля, атрибуты или связи JSON:API-ответа

Аварийная ветка — обработка ошибок

При исключении внутри CMSApplication::execute()

onError

Когда нужна реакция на ошибку до передачи исключения обработчику

Технически часть этих событий вызывается из трейтов или базовых классов, но для выбора точки вмешательства полезнее мыслить уровнем жизненного цикла. Например, onBeforeExtensionBoot относится к загрузке расширения, onTableBeforeStore — к уровню таблицы, а onContentBeforeSave — к уровню модели и бизнес-правил компонента.

Аварийная ветка

Если в основном цикле до отправки ответа вылетает исключение, CMSApplication::execute() вызывает onError. После этого управление передаётся обработчику исключений. Это не штатная ветка, но это тоже часть хронологии запроса.

Как в Joomla 6 устроены триггеры плагинов

Как плагины загружаются

Базовый механизм находится в libraries/src/Plugin/PluginHelper.php.

Когда ядро вызывает PluginHelper::importPlugin('system') или PluginHelper::importPlugin('content'), происходит следующее:

  • из таблицы #__extensions выбираются только включённые плагины;

  • выборка сортируется по полю ordering;

  • плагин загружается и создаётся;

  • его обработчики регистрируются в диспетчере событий.

Отсюда следует первое правило порядка:

если приоритет слушателей не задан явно, раньше будет вызван тот плагин, который раньше загружен, а загружается он по ordering

Как задаётся порядок вызова

В Joomla 6 работают сразу два слоя порядка.

Порядок загрузки плагинов группы

Он определяется ordering в таблице расширений.

Приоритет слушателя

Если плагин реализует SubscriberInterface, он может вернуть карту событий из getSubscribedEvents() и указать приоритет:

<?phpnamespace Acme\Plugin\System\Example\Extension;use Joomla\Event\Priority;use Joomla\Event\SubscriberInterface;final class Example implements SubscriberInterface{    public static function getSubscribedEvents(): array    {        return [            'onAfterRoute'  => ['onAfterRoute', Priority::HIGH],            'onAfterRender' => ['onAfterRender', Priority::LOW],        ];    }}

Что это значит для разработчика:

  • Priority::HIGH — выполнить раньше большинства;

  • Priority::NORMAL — обычный порядок;

  • Priority::LOW — выполнить ближе к концу.

Если у нескольких обработчиков одинаковый приоритет, сохранится порядок их регистрации, то есть учитывается обычно снова ordering.

Какой объект приходит в обработчик

В Joomla 6 ядро почти везде передаёт не россыпь позиционных аргументов, а объект события $event.

Пример:

<?phpnamespace Acme\Plugin\System\Example\Extension;use Joomla\CMS\Event\Application\AfterRouteEvent;final class Example{    public function onAfterRoute(AfterRouteEvent $event): void    {        $app = $event->getApplication();    }}

Старый стиль с методами вроде onContentPrepare($context, &$item, &$params, $page = 0) ещё поддерживается через прослойку совместимости, но это уже устаревающий путь. Поэтому разработчикам рекомендуется провести рефакторинг своих плагинов и поднимать им потихоньку системные требования.

Что в аргументах можно менять

Это самая важная часть для разработчика плагинов.

1. Можно менять переданный объект

Если событие несёт объект, Joomla потом использует этот же объект дальше по цепочке.

Чаще всего это:

  • приложение;

  • документ;

  • форма;

  • таблица;

  • роутер;

  • объект материала / контакта / товара и т.д.;

  • объект ответа аутентификации.

Именно поэтому формально неизменяемое событие всё равно позволяет менять поведение ядра: вы меняете не сам контейнер события, а состояние объекта внутри него.

2. Для массивов и строк ищите update*()

Во многих типизированных событиях есть специальные методы:

  • updateData();

  • updateAttributes();

  • updateContent();

  • updateModules();

  • updateUrl();

  • updateHeaders().

Если они есть, пользоваться нужно именно ими.

3. Для собираемого результата используется result

Многие события накапливают результат не прямой заменой аргумента, а через список значений.

Типичные случаи:

  • onContentAfterTitleonContentBeforeDisplayonContentAfterDisplay собирают строки;

  • onContentBeforeSaveonUserLoginonUserLogout собирают логические значения;

  • наличие хотя бы одного false часто останавливает дальнейшее действие ядра.

4. Не рассчитывайте на произвольный setArgument()

Большая часть событий ядра наследуется от AbstractImmutableEvent. Это значит:

  • произвольная замена аргументов запрещена;

  • менять нужно объект внутри аргумента или использовать предусмотренный update*();

  • старые трюки с прямой подменой аргументов надо считать устаревшими.

Быстрое правило выбора точки вмешательства

Если нужно:

Задача

Нужная точка

Подправить окружение до основного цикла

onBeforeExecute

Реагировать на уже разобранный адрес

onAfterRoute

Добавить стили, сценарии, метаданные

onAfterInitialiseDocumentonBeforeCompileHeadonBeforeRender

Переписать готовый HTML целиком

onAfterRender

Изменить форму редактирования

onContentPrepareForm

Изменить данные формы до проверки

onContentNormaliseRequestDataonContentBeforeValidateData

Проверить или отменить сохранение

onContentBeforeSaveonUserBeforeSave

Вмешаться на самом низком уровне записи в таблицу

onTableBeforeBindonTableCheckonTableBeforeStore

Добавить вставки вокруг текста материала

onContentAfterTitleonContentBeforeDisplayonContentAfterDisplay

Изменить список модулей позиции

onPrepareModuleListonAfterModuleListonAfterCleanModuleList

Основные группы событий и реальные точки вызова

Системные события приложения

Событие

Где вызывается

Когда

Аргументы

Что реально менять

Зачем применять

onBeforeExecute

CMSApplication::execute()

После загрузки behaviour и system, до основного цикла

subject = приложение, в CMS ещё container

Состояние приложения, сервисы контейнера, ранние проверки

Очень ранняя инициализация

onAfterInitialise

CMSApplication::initialiseApp()

После выбора языка, загрузки библиотечных языковых файлов и выбора редактора

subject = приложение

Состояние приложения, язык, редактор, пользовательские состояния

Ранняя настройка среды

onAfterRoute

SiteApplication::route()AdministratorApplication::route()

После разбора маршрута; в site ещё до проверки доступа к Itemid

subject = приложение

Входные данные, перенаправления, языковую логику, кеширование

Логика, зависящая от маршрута

onBeforeApiRoute

ApiApplication::route()

До разбора маршрута API

subject = приложение, router

Сам маршрутизатор API

Регистрация и правка маршрутов API

onAfterApiRoute

ApiApplication::route()

После разбора маршрута API, но до API-логина для непубличного маршрута

subject = приложение

Входные данные и подготовку перед API-логином; саму проверку доступа запускает ApiApplication::route() после события

Поздняя подготовка API-запроса

onAfterInitialiseDocument

SiteApplication::dispatch()AdministratorApplication::dispatch()ApiApplication::dispatch()

Документ уже создан, компонент ещё не отработал

subject = приложение, document

Документ, метаданные, ресурсы, тип документа

Подготовка документа

onAfterDispatch

dispatch() в приложениях

Компонент уже дал вывод, буферы заполнены

subject = приложение

Буферы документа, состояние документа

Постпроцессор компонента

onBeforeRender

CMSApplication::render()

Перед окончательной сборкой документа

subject = приложение

Документ, буферы, активы

Последняя общая точка до сборки HTML в site и administrator

onBeforeCompileHead

Document\\Renderer\\Html\\MetasRenderer::render()

Во время сборки <head>

subject = приложение, document

Только содержимое документа и <head>

Подключение ресурсов и метаданных

onAfterRender

CMSApplication::render()

После сборки итоговой строки ответа

subject = приложение

Тело ответа через объект приложения

Полная перепись HTML в site и administrator

onAfterCompress

CMSApplication::execute()

После compress(), только если включён gzip и PHP не сжимает вывод сам

subject = приложение

Тело уже прошло сжатие; править его здесь обычно не нужно

Диагностика, служебные действия

onError

CMSApplication::execute()

Только при исключении

subject = исключение, application = приложение

Обычно журналирование, подмена исключения, аварийная реакция

Обработка ошибок

onBeforeRespond

CMSApplication::execute()

Прямо перед отправкой ответа

subject = приложение

Заголовки, cookies, тело ответа

Последняя точка перед отправкой

onAfterRespond

CMSApplication::execute()

Сразу после отправки

subject = приложение

Ответ клиенту уже не изменить

Журналирование, отладка

События вывода материала

Главная точка для статьи: components/com_content/src/View/Article/HtmlView.php::display().

Порядок там такой:

  • onContentPrepare

  • onContentAfterTitle

  • onContentBeforeDisplay

  • вывод основного содержимого материала

  • onContentAfterDisplay

Общий набор аргументов:

  • context — строка вида com_content.article;

  • subject — объект материала;

  • params — параметры;

  • page — номер страницы или смещение.

Событие

Где вызывается

Что можно менять

Для чего нужно

onContentPrepare

com_content и HTML\\Helpers\\Content::prepare()

Сам объект материала, прежде всего text

Подстановка меток, замена шорткодов вида {SHORT_CODE}, автосвязи, фильтрация текста

onContentAfterTitle

com_content

Возвращаемые строки через result

Вставка блока сразу после заголовка

onContentBeforeDisplay

com_content

Возвращаемые строки через result

Вставка перед основным текстом

onContentAfterDisplay

com_content

Возвращаемые строки через result

Вставка после текста

Например, если вам нужно:

  • изменить сам текст статьи, используйте onContentPrepare;

  • добавить отдельный HTML-блок до или после текста, используйте result-события;

  • трогать поля записи, категории, изображения и другие свойства материала, меняйте объект subject.

События подготовки формы и данных

Эта группа отвечает не за показ страницы как таковой, а за подготовку формы редактирования и начальных данных.

Событие

Где вызывается

Аргументы

Что можно менять

Для чего нужно

onContentPrepareData

FormBehaviorTrait::preprocessData(), обычно из loadFormData() конкретной модели

contextdata, служебный subject

data через updateData() или по ссылке совместимости

Предварительно заполнить или поправить исходные данные формы

onContentPrepareForm

FormBehaviorTrait::preprocessForm()

subject = Formdata

Сам объект формы

Добавить, убрать, скрыть, переименовать поля и вкладки

onContentNormaliseRequestData

FormController::normalizeRequestData()

contextdatasubject = Form

Объект data

Нормализация сырых данных формы до проверки

onContentBeforeValidateData

FormModel::validate()

subject = Formdata

data через updateData()

Последняя правка перед фильтрацией и правилами

Важно понимать, что при открытии формы обычно сначала компонент вызывает onContentPrepareData, потом строит XML-форму и вызывает onContentPrepareForm. А при отправке формы в FormController::save() порядок такой:

  • строится объект формы;

  • вызывается onContentNormaliseRequestData;

  • вызывается onContentBeforeValidateData;

  • форма фильтрует и проверяет данные;

  • затем уже вызывается сохранение модели.

Это очень полезное разделение:

  • onContentNormaliseRequestData — для приведения сырых данных к нужному виду;

  • onContentBeforeValidateData — для правки данных прямо перед правилами формы;

  • onContentPrepareForm — для изменения самой формы, а не значений.

События сохранения и удаления на уровне модели

Основная логика находится в libraries/src/MVC/Model/AdminModel.php.

Порядок при сохранении записи

Если запись уже существует, типовой путь такой:

  • onTableBeforeLoad

  • Table::load()

  • onTableAfterLoad

  • onTableBeforeBind

  • Table::bind()

  • onTableAfterBind

  • prepareTable()

  • Table::check(), внутри базовой реализации — onTableCheck

  • onContentBeforeSave

  • Table::store(), внутри него — onTableBeforeStore и onTableAfterStore

  • onContentCleanCache

  • onContentAfterSave

Ключевой нюанс:

к моменту onContentBeforeSave объект Table уже связан с входными данными и уже прошёл check().

То есть:

  • для работы с ещё не связанными данными лучше перехватывать форму и валидацию;

  • для работы с итоговой строкой записи лучше брать onContentBeforeSave или onTableBeforeStore.

Порядок при удалении записи

В AdminModel::delete():

  • onTableBeforeLoad

  • Table::load()

  • onTableAfterLoad

  • onContentBeforeDelete

  • onTableBeforeDelete

  • Table::delete()

  • onTableAfterDelete

  • onContentAfterDelete

  • onContentCleanCache

Порядок при смене состояния

В AdminModel::publish():

  • onContentBeforeChangeState

  • onTableBeforePublish

  • Table::publish()

  • onTableAfterPublish

  • onContentChangeState

  • onContentCleanCache

Основные события модели, наследующей AdminModel

Событие

Аргументы

Что можно менять

Как ядро использует результат

onContentBeforeSave

contextsubject = таблица, isNewdata

Объект таблицы; можно вернуть false через result

При любом false сохранение отменяется

onContentAfterSave

Те же аргументы

Обычно только побочные действия

Результат не отменяет уже выполненное сохранение

onContentBeforeDelete

contextsubject = таблица

Объект таблицы; можно вернуть false

Удаление отменяется

onContentAfterDelete

contextsubject = таблица

Обычно только побочные действия

Это уже постфактум

onContentBeforeChangeState

contextsubject = список первичных ключей, value

Можно вернуть false

Изменение состояния отменяется

onContentChangeState

Те же аргументы

Обычно побочные действия

Состояние уже изменено

onBeforeBatch

Зависит от команды партии

Таблицу и результат

Тонкая настройка пакетных операций в списке элементов в админке

onContentCleanCache

defaultgroupcachebaseresult

Обычно только чтение результата очистки

Удобно для реакций после сброса кеша

События таблиц (классы Table) Joomla

Классы Table нужны для работы с базой данных. Классы Table в Joomla описывают строку / запись в базы данных как объект: загружают запись, связывают входные данные с полями, проверяют, сохраняют, удаляют и меняют состояние. Они нужны как низкоуровневый слой работы с БД под моделями, чтобы компоненты не писали однотипный SQL для CRUD-операций вручную. Самый низкий уровень находится в libraries/src/Table/Table.php и libraries/src/Table/Nested.php.

Здесь события ближе всего к реальной записи в базу. Они особенно полезны, когда нужно вмешаться не в поведение компонента, а в сам объект таблицы.

Здесь нужно учитывать, что объект Table для базы данных может быть использован разными компонентами, библиотеками или плагинами, поэтому внедрение в процесс на данном этапе может проявить себя порой в самых неожиданных местах. Не забывайте грамотно ограничивать действие своих плагинов.

Событие

Где вызывается

Аргументы

Что менять на практике

onTableObjectCreate

Конструктор таблицы

subject = таблица

Первичная настройка объекта таблицы

onTableBeforeLoad / onTableAfterLoad

Table::load()

ключи загрузки, флаг reset, затем данные таблицы

Реакция на чтение записи

onTableBeforeBind / onTableAfterBind

Table::bind()

srcignoresubject

Наиболее ранняя правка полей таблицы

onTableCheck

Table::check()

subject = таблица

Проверка и досчёт полей до записи

onTableBeforeStore / onTableAfterStore

Table::store()

updateNulls, ключ, subject

Последняя правка перед записью и реакция после неё

onTableBeforeDelete / onTableAfterDelete

Table::delete()

первичный ключ, subject

Низкоуровневый контроль удаления

onTableBeforePublish / onTableAfterPublish

Table::publish()

список ключей, новое состояние, userId

Реакция на публикацию и снятие с публикации

onTableBeforeCheckout / onTableAfterCheckout

Table::checkout()

пользователь, таблица

Контроль блокировки записи

onTableBeforeCheckin / onTableAfterCheckin

Table::checkin()

таблица

Контроль снятия блокировки

onTableBeforeHit / onTableAfterHit

Table::hit()

таблица

Счётчики просмотров

onTableBeforeMove / onTableAfterMove

Table::move()

смещение и условия

Порядок сортировки

onTableBeforeReorder / onTableAfterReorder

Table::reorder()

запрос и условия

Перестройка порядка

onTableBeforeReset / onTableAfterReset

Table::reset()

таблица

Сброс состояния объекта

onTableSetNewTags

AdminModel::batchTags()

newTagsreplaceTagsremoveTagssubject

Специальная обработка пакетного назначения меток

При работе с событиями Joomla Table:

  • меняйте поля у объекта subject;

  • не рассчитывайте на широкую подмену служебных аргументов;

  • если логика относится к конкретному бизнес-правилу компонента, часто лучше использовать событие модели, а не таблицы.

События модулей

Эта группа событий для плагинов живёт в libraries/src/Helper/ModuleHelper.php и libraries/src/Document/Renderer/Html/ModulesRenderer.php.

Порядок на уровне списка модулей:

  • onPrepareModuleList

  • если событие не дало свой список, Joomla строит список сама

  • onAfterModuleList

  • Joomla чистит список от дублей и недопустимых элементов

  • onAfterCleanModuleList

Порядок на уровне отдельного модуля:

  • onRenderModule

  • отрисовка модуля

  • onAfterRenderModule

Порядок на уровне позиции:

  • все модули позиции отрисованы

  • onAfterRenderModules

Событие

Аргументы

Что можно менять

Для чего нужно

onPrepareModuleList

modules

Полностью заменить список модулей через updateModules()

Свой источник модулей

onAfterModuleList

modules

Править уже собранный список

Фильтрация и перестановка

onAfterCleanModuleList

modules

Править очищенный список

Финальная чистка

onRenderModule

subject = модуль, attributes

Параметры и сам объект модуля

Подмена стиля, атрибутов и содержимого

onAfterRenderModule

subject = уже отрисованный модуль, attributes

Обычно только содержимое модуля

Постобработка одного модуля

onAfterRenderModules

contentattributes

Общую строку позиции через updateContent()

Обёртки, агрегаты, массовая правка HTML

События пользователей и аутентификации

Логика раскидана по:

  • libraries/src/Authentication/Authentication.php;

  • libraries/src/Application/CMSApplication.php;

  • libraries/src/User/User.php.

Порядок при входе пользователя

  • onUserAuthenticate

  • onUserAuthorisation

  • при отказе onUserAuthorisationFailure

  • onUserLogin

  • при успехе onUserAfterLogin

  • при неудаче onUserLoginFailure

Порядок при выходе

  • onUserLogout

  • при успехе onUserAfterLogout

  • при неудаче onUserLogoutFailure

Порядок при сохранении пользователя

В User::save():

  • onUserBeforeSave

  • запись в таблицу пользователя

  • onUserAfterSave

Порядок при удалении пользователя

В User::delete():

  • onUserBeforeDelete

  • удаление из таблицы

  • onUserAfterDelete

Событие

Аргументы

Что можно менять

Для чего нужно

onUserAuthenticate

credentialsoptionssubject = AuthenticationResponse

Объект ответа аутентификации

Подтвердить или отклонить подлинность пользователя

onUserAuthorisation

subject = AuthenticationResponseoptions

Обычно добавляется результат

Решить, можно ли пустить уже проверенного пользователя

onUserLogin

subject = массив ответа аутентификации, options

Возврат false через result отменяет вход

Поднять пользовательскую сессию в своём хранилище

onUserAfterLogin

subjectoptions

Обычно побочные действия

Журналы, внешние сервисы, одноразовые метки

onUserBeforeSave

subject = старые данные пользователя, isNewdata = новые данные

Можно вернуть false

Бизнес-правила перед сохранением

onUserAfterSave

subject = сохранённые данные, isNewsavingResulterrorMessage

Обычно побочные действия

Синхронизация после сохранения

onUserBeforeDelete

subject = массив пользователя

Обычно проверка и исключение

Запрет удаления

onUserAfterDelete

subjectdeletingResulterrorMessage

Обычно побочные действия

Уборка следов пользователя

Здесь есть принципиальное отличие от событий контента:

onUserAuthenticate и onUserAuthorisation работают с объектом AuthenticationResponse, то есть меняют не HTML и не таблицу, а состояние решения о входе.

События установки и обновления расширений

Эта группа особенно важна для систем сопровождения, зеркал, внутренних каталогов расширений и нестандартной доставки пакетов расширений Joomla.

Событие

Где вызывается

Аргументы

Что можно менять

onInstallerBeforePackageDownload

InstallerHelper::downloadPackage()

urlheaders

Адрес и заголовки через updateUrl() и updateHeaders()

onInstallerBeforeInstallation

com_installer

Контекст установки и result

Можно прервать установку

onExtensionBeforeInstall

Installer::install()

объект установщика, пакет

Обычно проверки перед установкой

onExtensionAfterInstall

Installer::install()

объект установщика, eidresult

Реакция после установки

onExtensionBeforeUpdate

Installer::update()

объект установщика, пакет

Проверки перед обновлением

onExtensionAfterUpdate

Installer::update()

объект установщика, eidresult

Реакция после обновления

onExtensionBeforeUninstall

Installer::uninstall()

объект установщика, идентификатор

Проверки перед удалением

onExtensionAfterUninstall

Installer::uninstall()

объект установщика, идентификатор, result

Уборка после удаления

Вместо заключения

В Joomla 6+ система плагинов стала строже и понятнее, чем в старых Joomla 2.5 / Joomla 3:

  • события имеют типизированные классы;

  • аргументы названы явно;

  • для изменения данных всё чаще есть специальные методы;

  • основная точка расширения — не «магия триггера», а точное понимание стадии жизненного цикла.

Именно поэтому хорошая разработка под Joomla начинается не с вопроса «какой есть триггер», а с вопроса «на какой стадии жизненного цикла приложения Joomla мне действительно нужно вмешаться».

Предчувствуя вопрос о смешении нескольких тематик в одной статье, отвечу сразу: у меня не было цели сделать строго академическую статью, которая бы описывала только то, что соответствует тематике. У меня была цель сделать статью-справочник, в которой общая архитектура и хронология работы Joomla была бы связана с тем, как можно правильно внедриться в процессы с помощью плагинов и помочь оценить правильность точки внедрения.

ссылка на оригинал статьи https://habr.com/ru/articles/1055928/