---
title: "Общая информация о принципе действия Joomla 6. Жизненный цикл Приложений Joomla. - WebTolk"
description: "Подробно разбираем жизненный цикл приложений Joomla 6: Site, Administrator, API, Console и Daemon, порядок событий, маршрутизацию, dispatch, render и плагины."
url: "https://web-tolk.ru/blog/obshchaya-informatsiya-o-printsipe-dejstviya-joomla-6-zhiznennyj-tsikl-prilozhenij-joomla"
date: "2026-06-28T13:31:59+00:00"
language: "ru-RU"
---

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

 Автор: Сергей Толкачев Создано: 28 июня 2026 Обновлено: 28 июня 2026 Просмотров: 82    ![Жизненный цикл приложений Joomla 6: Site, Administrator, API, Console и Daemon](https://web-tolk.ru/blog/images/blog/obshchaya-informatsiya-o-printsipe-dejstviya-joomla-6-zhiznennyj-tsikl-prilozhenij-joomla/header-1080.webp)

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

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

Для Joomla 3 хочу упомянуть [статью Дмитрия Рекуна "Общая информация о принципе действия Joomla"](https://jpath.ru/docs/basics/joomla-internals/obshchaya-informatsiya-o-printsipe-dejstviya-joomla), которая довольно подробно описывает как Joomla работала раньше.

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

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

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

1. проверяет минимальную версию PHP;
2. определяет `_JEXEC`, чтобы внутренние файлы Joomla нельзя было выполнять напрямую;
3. подключает `defines.php` из корня сайта (если он существует): его используют для предварительного определения или переопределения констант с путями при нестандартной структуре каталогов (Joomla находится за пределами web root);
4. подключает `includes/defines.php`, где задаются основные `JPATH_*`-константы;
5. передаёт управление в `includes/app.php`.

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

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

1. берётся DI-контейнер через `Factory::getContainer()`;
2. для сайта настраиваются алиасы веб-сессии;
3. из контейнера создаётся `SiteApplication`;
4. приложение записывается в `Factory::$application`;
5. вызывается `$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, то есть `site`, `administrator` и `api`, наследуются через цепочку `WebApplication → CMSApplication`.

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

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

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

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

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

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

1. определяет константы путей;
2. поднимает автозагрузку;
3. создаёт DI-контейнер (подключение зависимостей);
4. через сервис-провайдеры собирает объект приложения и внедряет в него диспетчер событий, логгер, сессию и другие сервисы;
5. вызывает `$app->execute()`.

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

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

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

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

1. Проверка опасных системных переменных.
2. Настройка журнала.
3. Построение карты пространств имён (неймспейсов) расширений.
4. Загрузка плагинов группы `behaviour` (в ядре Joomla это: плагин обратной совместимости, версионность и поддержка тегов `com_tags`).
5. Загрузка плагинов группы `system`.
6. Вызов `onBeforeExecute`.
7. Выполнение прикладной части через `doExecute()`.
8. Если создан документ, вызов `render()`.
9. При включённом GZIP-сжатии в конфиге Joomla - вызов `compress()` и затем `onAfterCompress`.
10. Вызов `onBeforeRespond`.
11. Отправка ответа через `respond()`.
12. Вызов `onAfterRespond`.

Итого:

- `onBeforeExecute` — самая ранняя общая точка для системных плагинов;
- `onBeforeRespond` — последняя точка до отправки заголовков и тела ответа;
- `onAfterRespond` — заключительный этап после отдачи заголовков и тела ответа браузеру: журналирование, отладка, побочные действия.

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

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

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

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

1. в приложениях сайта и административной части уточняет язык и, при необходимости, группы доступа пользователя;
2. создаёт объект языка и загружает его в приложение;
3. подключает файлы локализации (ini-файлы языков);
4. выбирает редактор для текущего пользователя;
5. вызывает `onAfterInitialise`.

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

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

- сессия;
- язык;
- пользователь;
- конфигурация;
- диспетчер событий.

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

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

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

В `SiteApplication::route()`:

1. создаётся или берётся текущий `Uri`;
2. роутер разбирает адрес;
3. найденные переменные записываются во входные данные приложения;
4. вызывается `onAfterRoute`;
5. выполняется проверка доступа к текущему `Itemid` (id пункта меню) через `authorise()`.

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

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

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

1. при необходимости выполняется перевод на HTTPS;
2. проверяется состояние страниц многофакторной проверки;
3. вызывается `onAfterRoute`.

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

### API

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

1. загружаются плагины группы `webservices`;
2. вызывается `onBeforeApiRoute`;
3. маршрутизатор API подбирает маршрут;
4. Joomla согласует формат ответа по заголовку `Accept`;
5. переменные маршрута записываются во входные данные;
6. вызывается `onAfterApiRoute`;
7. при необходимости выполняется вход пользователя для закрытых маршрутов.

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

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

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

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

- постоянная обработка очереди: email, SMS, webhooks, экспорт/импорт, генерация файлов;
- воркеры фоновых заданий, где нужно быстро подхватывать новые задачи без запуска PHP с нуля каждый раз;
- слушатели внешних событий: сокеты, очереди сообщений, брокеры, long polling, системные события;
- периодическая фоновая работа с собственным циклом: очистка, синхронизация, пересчёт агрегатов, мониторинг;
- процессы, которым нужно держать состояние в памяти: кэш подключений, открытые соединения, подготовленные ресурсы;
- интеграции с внешними сервисами, где нужно постоянно принимать или отправлять события;
- сервисные процессы, где важны PID-файл, сигналы SIGTERM/SIGHUP, мягкая остановка и перезапуск.

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

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

1. стартует из CLI;
2. при необходимости уходит в фон;
3. пишет PID-файл;
4. подключает обработчики Unix-сигналов;
5. бесконечно гоняет `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()` такой:

1. `onBeforeExecute`
2. включение сборки мусора `gc_enable()`
3. попытка перейти в режим демона через `daemonize()`
4. если переход удался: 1. включение `declare(ticks=1)` для мониторинга сигналов 2. бесконечный цикл: 1. `gc()` 2. `usleep(1000)` 3. `doExecute()`
5. `onAfterExecute` вызывается только если запуск не удался или если выполнение вообще вышло из цикла

На заметку:

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

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

- у демона нет `onAfterInitialise`, `onAfterRoute`, `onAfterDispatch`, `onBeforeRender`, `onAfterRender`, `onBeforeRespond`, `onAfterRespond`;
- у него есть только ранний `onBeforeExecute`, поздний `onAfterExecute` и специфические `onFork` и `onReceiveSignal`.

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

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

1. проверяет через `isActive()`, не запущен ли уже такой демон;
2. сбрасывает внутренние флаги процесса;
3. если не передан флаг `-f`, вызывает `detach()` и уходит в фон;
4. если `-f` передан, остаётся на переднем плане;
5. пишет PID-файл через `writeProcessIdFile()`;
6. пытается сменить пользователя и группу через `changeIdentity()`;
7. подключает обработчики сигналов через `setupSignalHandlers()`;
8. меняет рабочую директорию на `application_directory`.

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

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

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

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

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

1. вызывается `pcntl_fork()`;
2. и в родительском, и в дочернем процессе вызывается `postFork()`;
3. `postFork()` отправляет событие `onFork`.

То есть:

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

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

- переподключить сокеты;
- заново открыть файловые дескрипторы;
- переинициализировать соединения, которые нельзя безопасно делить после `fork`.

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

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

1. пишет сигнал в журнал;
2. берёт текущий экземпляр приложения из `static::$instance`;
3. отправляет событие `onReceiveSignal`;
4. только потом выполняет встроенную реакцию ядра.

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

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

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

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

- `SIGINT`, `SIGTERM` — мягкое завершение через `shutdown()`;
- `SIGHUP` — завершение с последующим перезапуском через `shutdown(true)`;
- `SIGCHLD`, `SIGCLD` — уборка завершившихся дочерних процессов через `pcntlWait()`.

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

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

Метод `isActive()`:

1. читает PID-файл;
2. проверяет, что PID корректен;
3. делает `posix_kill($pid, 0)` как проверку живого процесса;
4. если процесс не отвечает, удаляет устаревший PID-файл.

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

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

`shutdown()`:

1. ставит флаг завершения;
2. если демон ещё не был полностью поднят, просто завершает текущий процесс;
3. если это главный процесс, читает PID из файла;
4. удаляет PID-файл;
5. при `restart=true` перезапускает ту же команду;
6. иначе завершает процесс.

### Отдельная хронология для `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()` порядок такой:

1. создаётся документ;
2. настраиваются метаданные, шаблон и реестры ресурсов;
3. вызывается `onAfterInitialiseDocument`;
4. компонент отрисовывается через `ComponentHelper::renderComponent()`;
5. результат кладётся в буфер документа;
6. вызывается `onAfterDispatch`.

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

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

### API

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

1. создаётся документ;
2. вызывается `onAfterInitialiseDocument`;
3. вызывается диспетчер компонента веб-служб.
4. вызывается `onAfterDispatch`.

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

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

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

1. заполняются параметры шаблона;
2. выполняется `$this->document->parse(...)`;
3. вызывается `onBeforeRender`;
4. документ формирует итоговую строку ответа;
5. строка записывается в тело ответа через `setBody()`;
6. вызывается `onAfterRender`.

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

То есть:

- `onBeforeRender` — общий поздний этап перед окончательной сборкой страницы;
- `onBeforeCompileHead` — специальная точка именно для `<head>`;
- `onAfterRender` — этап, когда вся HTML-строка уже собрана и её можно переписать целиком.

## Вывод ответа

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

1. при включённом сжатии вызывает `onAfterCompress`;
2. вызывает `onBeforeRespond`;
3. отправляет заголовки и тело;
4. вызывает `onAfterRespond`.

`onAfterCompress` важно понимать буквально. В `CMSApplication::execute()` он вызывается только после `$this->compress()` и только если включён параметр `gzip`, при этом PHP не использует `zlib.output_compression` и текущий `output_handler` не равен `ob_gzhandler`. Это событие относится к CMS-циклу `SiteApplication`, `AdministratorApplication` и `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 во время запроса

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

1. не все ветви выполняются в каждом запросе;
2. часть событий внутри формирования HTML зависит от конкретного шаблона и от того, в каком месте шаблон вызывает `jdoc:include`.

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

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

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

1. `onBeforeExtensionBoot`
2. `onAfterExtensionBoot`

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

- плагинов `behaviour` и `system` в начале запроса;
- плагинов `webservices` в API;
- плагинов `content`, `user`, `installer`, `extension`, `finder`, `privacy`, `quickicon`, `sampledata` и других в более поздних ветках;
- компонентов и модулей, когда они загружаются через `bootComponent()` и `bootModule()`.

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

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

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

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

1. Для каждого загружаемого плагина группы `behaviour`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
2. Для каждого загружаемого плагина группы `system`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
3. `onBeforeExecute`
4. `onAfterInitialise`
5. `onAfterRoute`
6. `onAfterInitialiseDocument`
7. Для загружаемого компонента: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
8. Внутренняя логика компонента
9. `onAfterDispatch`
10. `onBeforeRender`
11. Внутренняя сборка документа и шаблона
12. `onAfterRender`
13. При включённом gzip и совместимых настройках PHP: `compress()` -> `onAfterCompress`
14. `onBeforeRespond`
15. `onAfterRespond`

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

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

1. `onBeforeDisplay`
2. загрузка шаблона представления
3. `onAfterDisplay`

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

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

1. для каждого плагина группы `content`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
2. `onContentPrepare`
3. `onContentAfterTitle`
4. `onContentBeforeDisplay`
5. `onContentAfterDisplay`
6. `onBeforeDisplay`
7. `onAfterDisplay`

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

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

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

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

1. `onPrepareModuleList`
2. `onAfterModuleList`
3. `onAfterCleanModuleList`
4. для каждого загружаемого модуля: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
5. для каждого модуля: `onRenderModule`
6. внутренняя логика модуля
7. для каждого модуля: `onAfterRenderModule`
8. после завершения всей позиции: `onAfterRenderModules`

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

1. `onContentPrepare`
2. `onContentAfterTitle`
3. `onContentBeforeDisplay`
4. `onContentAfterDisplay`

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

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

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

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

1. для плагинов `behaviour`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
2. для плагинов `system`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
3. `onBeforeExecute`
4. `onAfterInitialise`
5. `onAfterRoute`
6. `onAfterInitialiseDocument`
7. для компонента: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
8. внутренняя логика компонента и представления
9. `onAfterDispatch`
10. `onBeforeRender`
11. события головы страницы, модулей и меню в зависимости от шаблона
12. `onAfterRender`
13. при включённом gzip и совместимых настройках PHP: `compress()` -> `onAfterCompress`
14. `onBeforeRespond`
15. `onAfterRespond`

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

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

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

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

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

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

1. Для плагинов `behaviour`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
2. Для плагинов `system`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
3. `onBeforeExecute`
4. `onAfterInitialise`
5. Для плагинов `webservices`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
6. `onBeforeApiRoute`
7. разбор маршрута API
8. `onAfterApiRoute`
9. `onAfterInitialiseDocument`
10. Для компонента API: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
11. логика контроллера, модели и представления API
12. `onAfterDispatch`
13. `render()` через переопределённый `ApiApplication::render()`
14. при включённом gzip и совместимых настройках PHP: `compress()` -> `onAfterCompress`
15. `onBeforeRespond`
16. `onAfterRespond`

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

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

- нет `onBeforeRender` из `CMSApplication::render()`;
- нет `onAfterRender`;
- нет `onBeforeCompileHead`;
- нет модульных событий формирования вывода.

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

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

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

1. `onApiGetFields`
2. `onGetApiAttributes`
3. `onGetApiRelation`

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

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

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

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

1. общий цикл приложения до `onAfterRoute`
2. загрузка компонента
3. `onContentPrepareData`
4. `onContentPrepareForm`
5. `onBeforeDisplay`
6. `onAfterDisplay`
7. `onAfterDispatch`
8. дальше общая HTML-сборка страницы

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

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

1. общий цикл приложения до маршрута и диспетчеризации
2. `onContentNormaliseRequestData`
3. `onContentBeforeValidateData`
4. `onTableBeforeLoad` и `onTableAfterLoad`, если запись не новая
5. `onTableBeforeBind`
6. `onTableAfterBind`
7. `onTableCheck`
8. `onContentBeforeSave`
9. `onTableBeforeStore`
10. `onTableAfterStore`
11. `onContentCleanCache`
12. `onContentAfterSave`
13. обычное завершение запроса через `onBeforeRespond` и `onAfterRespond`

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

1. `onTableBeforeLoad`
2. `onTableAfterLoad`
3. `onContentBeforeDelete`
4. `onTableBeforeDelete`
5. `onTableAfterDelete`
6. `onContentAfterDelete`
7. `onContentCleanCache`

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

1. `onContentBeforeChangeState`
2. `onTableBeforePublish`
3. `onTableAfterPublish`
4. `onContentChangeState`
5. `onContentCleanCache`

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

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

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

1. для плагинов `user`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
2. `onUserAuthenticate`
3. `onUserAuthorisation`
4. при отказе: `onUserAuthorisationFailure`
5. при продолжении входа: `onUserLogin`
6. при успехе: `onUserAfterLogin`
7. при неудаче: `onUserLoginFailure`

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

1. для плагинов `user`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
2. `onUserLogout`
3. при успехе: `onUserAfterLogout`
4. при неудаче: `onUserLogoutFailure`

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

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

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

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

1. `onUserBeforeResetRequest`
2. сохранение пользователя (там свои события `onUserAfterSave` и т.д.)
3. `onUserAfterResetRequest`

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

1. `onUserBeforeResetComplete`
2. сохранение пользователя
3. `onUserAfterResetComplete`

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

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

1. для плагинов группы `installer`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
2. `onInstallerBeforeInstallation`
3. при скачивании пакета по url адресу с сайта разработчика: `onInstallerBeforePackageDownload`
4. `onInstallerBeforeInstaller`
5. для плагинов `extension`: `onBeforeExtensionBoot` -> `onAfterExtensionBoot`
6. `onExtensionBeforeInstall`
7. установка расширения
8. `onExtensionAfterInstall`
9. `onInstallerAfterInstaller`

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

1. `onInstallerBeforePackageDownload`, если пакет нужно скачать
2. `onExtensionBeforeUpdate`
3. обновление
4. `onExtensionAfterUpdate`

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

1. `onExtensionBeforeUninstall`
2. удаление
3. `onExtensionAfterUninstall`

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

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

| Уровень | Где возникает | Примеры событий | Когда туда идти |
| --- | --- | --- | --- |
| Приложение | Общий цикл `CMSApplication`, `SiteApplication`, `AdministratorApplication`, `ApiApplication` | `onBeforeExecute`, `onAfterInitialise`, `onAfterRoute`, `onAfterDispatch`, `onBeforeRender`, `onAfterRender`, `onBeforeRespond`, `onAfterRespond` | Когда задача относится ко всему запросу: окружение, маршрут, документ, заголовки, финальное тело ответа |
| Загрузка расширений | Когда Joomla создаёт плагин, компонент или модуль через механизм загрузки расширений | `onBeforeExtensionBoot`, `onAfterExtensionBoot` | Когда нужно отследить или повлиять на момент создания расширения, а не на его бизнес-логику |
| Компонент и представление | Внутри MVC-компонента и HTML-представления | `onBeforeDisplay`, `onAfterDisplay`, а также собственные события компонента | Когда данные уже выбраны компонентом, но страница ещё не собрана целиком |
| Контент | В `com_content`, модулях материалов и вспомогательной обработке текста. Эти же события могут вызываться и в других компонента, следующих "Joomla way" в разработке. | `onContentPrepare`, `onContentAfterTitle`, `onContentBeforeDisplay`, `onContentAfterDisplay` | Когда нужно менять текст, вставки вокруг материала или результат вывода статьи |
| Формы и данные формы | При построении формы и перед валидацией отправленных данных | `onContentPrepareData`, `onContentPrepareForm`, `onContentNormaliseRequestData`, `onContentBeforeValidateData` | Когда нужно добавить поля, изменить XML-форму или подготовить сырые данные до проверки |
| Модель сохранения | В `AdminModel` и наследниках перед записью, после записи, при удалении и смене состояния | `onContentBeforeSave`, `onContentAfterSave`, `onContentBeforeDelete`, `onContentAfterDelete`, `onContentBeforeChangeState`, `onContentChangeState` | Когда нужно применить бизнес-правило компонента или отменить действие на уровне модели |
| Таблица | В `Table` и `Nested` прямо перед низкоуровневой операцией с записью | `onTableBeforeBind`, `onTableAfterBind`, `onTableCheck`, `onTableBeforeStore`, `onTableAfterStore`, `onTableBeforeDelete`, `onTableAfterDelete` | Когда нужна самая близкая к базе точка и уже есть объект таблицы |
| Модули | При сборке списка модулей, выводе отдельного модуля и позиции | `onPrepareModuleList`, `onAfterModuleList`, `onAfterCleanModuleList`, `onRenderModule`, `onAfterRenderModule`, `onAfterRenderModules` | Когда нужно подменить список модулей, атрибуты модуля или HTML всей позиции |
| Пользователи и вход | В аутентификации, авторизации входа, сохранении и удалении пользователя | `onUserAuthenticate`, `onUserAuthorisation`, `onUserLogin`, `onUserAfterLogin`, `onUserBeforeSave`, `onUserAfterSave` | Когда задача относится к подлинности, разрешению входа, пользовательской сессии или данным пользователя |
| Установка и обновление | В `com_installer`, `InstallerHelper` и `Installer` | `onInstallerBeforePackageDownload`, `onInstallerBeforeInstallation`, `onExtensionBeforeInstall`, `onExtensionAfterInstall`, `onExtensionBeforeUpdate`, `onExtensionAfterUpdate` | Когда нужно вмешаться в скачивание, установку, обновление или удаление расширения |
| Административное меню | В отдельных экранах и модулях административной части | `onPreprocessMenuItems`, `onBeforeRenderMenuItems`, `onAfterGetMenuTypeOptions` | Когда задача касается дерева меню, списка пунктов меню или выбора типа пункта меню |
| API-сериализация | Внутри API-представления и сериализатора | `onApiGetFields`, `onGetApiAttributes`, `onGetApiRelation` | Когда роут 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')`, происходит следующее:

1. из таблицы `#__extensions` выбираются только включённые плагины;
2. выборка сортируется по полю `ordering`;
3. плагин загружается и создаётся;
4. его обработчики регистрируются в диспетчере событий.

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

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

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

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

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

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

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

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

```
<?php

namespace 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`.

Пример:

```
<?php

namespace 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`

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

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

- `onContentAfterTitle`, `onContentBeforeDisplay`, `onContentAfterDisplay` собирают строки;
- `onContentBeforeSave`, `onUserLogin`, `onUserLogout` собирают логические значения;
- наличие хотя бы одного `false` часто останавливает дальнейшее действие ядра.

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

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

- произвольная замена аргументов запрещена;
- менять нужно объект внутри аргумента или использовать предусмотренный `update*()`;
- старые трюки с прямой подменой аргументов надо считать устаревшими.

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

Если нужно:

| Задача | Нужная точка |
| --- | --- |
| Подправить окружение до основного цикла | `onBeforeExecute` |
| Реагировать на уже разобранный адрес | `onAfterRoute` |
| Добавить стили, сценарии, метаданные | `onAfterInitialiseDocument`, `onBeforeCompileHead`, `onBeforeRender` |
| Переписать готовый HTML целиком | `onAfterRender` |
| Изменить форму редактирования | `onContentPrepareForm` |
| Изменить данные формы до проверки | `onContentNormaliseRequestData`, `onContentBeforeValidateData` |
| Проверить или отменить сохранение | `onContentBeforeSave`, `onUserBeforeSave` |
| Вмешаться на самом низком уровне записи в таблицу | `onTableBeforeBind`, `onTableCheck`, `onTableBeforeStore` |
| Добавить вставки вокруг текста материала | `onContentAfterTitle`, `onContentBeforeDisplay`, `onContentAfterDisplay` |
| Изменить список модулей позиции | `onPrepareModuleList`, `onAfterModuleList`, `onAfterCleanModuleList` |

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

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

| Событие | Где вызывается | Когда | Аргументы | Что реально менять | Зачем применять |
| --- | --- | --- | --- | --- | --- |
| `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()`.

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

1. `onContentPrepare`
2. `onContentAfterTitle`
3. `onContentBeforeDisplay`
4. вывод основного содержимого материала
5. `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()` конкретной модели | `context`, `data`, служебный `subject` | `data` через `updateData()` или по ссылке совместимости | Предварительно заполнить или поправить исходные данные формы |
| `onContentPrepareForm` | `FormBehaviorTrait::preprocessForm()` | `subject` = `Form`, `data` | Сам объект формы | Добавить, убрать, скрыть, переименовать поля и вкладки |
| `onContentNormaliseRequestData` | `FormController::normalizeRequestData()` | `context`, `data`, `subject` = `Form` | Объект `data` | Нормализация сырых данных формы до проверки |
| `onContentBeforeValidateData` | `FormModel::validate()` | `subject` = `Form`, `data` | `data` через `updateData()` | Последняя правка перед фильтрацией и правилами |

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

1. строится объект формы;
2. вызывается `onContentNormaliseRequestData`;
3. вызывается `onContentBeforeValidateData`;
4. форма фильтрует и проверяет данные;
5. затем уже вызывается сохранение модели.

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

- `onContentNormaliseRequestData` — для приведения сырых данных к нужному виду;
- `onContentBeforeValidateData` — для правки данных прямо перед правилами формы;
- `onContentPrepareForm` — для изменения самой формы, а не значений.

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

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

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

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

1. `onTableBeforeLoad`
2. `Table::load()`
3. `onTableAfterLoad`
4. `onTableBeforeBind`
5. `Table::bind()`
6. `onTableAfterBind`
7. `prepareTable()`
8. `Table::check()`, внутри базовой реализации — `onTableCheck`
9. `onContentBeforeSave`
10. `Table::store()`, внутри него — `onTableBeforeStore` и `onTableAfterStore`
11. `onContentCleanCache`
12. `onContentAfterSave`

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

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

То есть:

- для работы с ещё не связанными данными лучше перехватывать форму и валидацию;
- для работы с итоговой строкой записи лучше брать `onContentBeforeSave` или `onTableBeforeStore`.

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

В `AdminModel::delete()`:

1. `onTableBeforeLoad`
2. `Table::load()`
3. `onTableAfterLoad`
4. `onContentBeforeDelete`
5. `onTableBeforeDelete`
6. `Table::delete()`
7. `onTableAfterDelete`
8. `onContentAfterDelete`
9. `onContentCleanCache`

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

В `AdminModel::publish()`:

1. `onContentBeforeChangeState`
2. `onTableBeforePublish`
3. `Table::publish()`
4. `onTableAfterPublish`
5. `onContentChangeState`
6. `onContentCleanCache`

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

| Событие | Аргументы | Что можно менять | Как ядро использует результат |
| --- | --- | --- | --- |
| `onContentBeforeSave` | `context`, `subject` = таблица, `isNew`, `data` | Объект таблицы; можно вернуть `false` через `result` | При любом `false` сохранение отменяется |
| `onContentAfterSave` | Те же аргументы | Обычно только побочные действия | Результат не отменяет уже выполненное сохранение |
| `onContentBeforeDelete` | `context`, `subject` = таблица | Объект таблицы; можно вернуть `false` | Удаление отменяется |
| `onContentAfterDelete` | `context`, `subject` = таблица | Обычно только побочные действия | Это уже постфактум |
| `onContentBeforeChangeState` | `context`, `subject` = список первичных ключей, `value` | Можно вернуть `false` | Изменение состояния отменяется |
| `onContentChangeState` | Те же аргументы | Обычно побочные действия | Состояние уже изменено |
| `onBeforeBatch` | Зависит от команды партии | Таблицу и результат | Тонкая настройка пакетных операций в списке элементов в админке |
| `onContentCleanCache` | `defaultgroup`, `cachebase`, `result` | Обычно только чтение результата очистки | Удобно для реакций после сброса кеша |

## События таблиц (классы 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()` | `src`, `ignore`, `subject` | Наиболее ранняя правка полей таблицы |
| `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()` | `newTags`, `replaceTags`, `removeTags`, `subject` | Специальная обработка пакетного назначения меток |

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

- меняйте поля у объекта `subject`;
- не рассчитывайте на широкую подмену служебных аргументов;
- если логика относится к конкретному бизнес-правилу компонента, часто лучше использовать событие модели, а не таблицы.

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

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

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

1. `onPrepareModuleList`
2. если событие не дало свой список, Joomla строит список сама
3. `onAfterModuleList`
4. Joomla чистит список от дублей и недопустимых элементов
5. `onAfterCleanModuleList`

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

1. `onRenderModule`
2. отрисовка модуля
3. `onAfterRenderModule`

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

1. все модули позиции отрисованы
2. `onAfterRenderModules`

| Событие | Аргументы | Что можно менять | Для чего нужно |
| --- | --- | --- | --- |
| `onPrepareModuleList` | `modules` | Полностью заменить список модулей через `updateModules()` | Свой источник модулей |
| `onAfterModuleList` | `modules` | Править уже собранный список | Фильтрация и перестановка |
| `onAfterCleanModuleList` | `modules` | Править очищенный список | Финальная чистка |
| `onRenderModule` | `subject` = модуль, `attributes` | Параметры и сам объект модуля | Подмена стиля, атрибутов и содержимого |
| `onAfterRenderModule` | `subject` = уже отрисованный модуль, `attributes` | Обычно только содержимое модуля | Постобработка одного модуля |
| `onAfterRenderModules` | `content`, `attributes` | Общую строку позиции через `updateContent()` | Обёртки, агрегаты, массовая правка HTML |

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

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

- `libraries/src/Authentication/Authentication.php`;
- `libraries/src/Application/CMSApplication.php`;
- `libraries/src/User/User.php`.

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

1. `onUserAuthenticate`
2. `onUserAuthorisation`
3. при отказе `onUserAuthorisationFailure`
4. `onUserLogin`
5. при успехе `onUserAfterLogin`
6. при неудаче `onUserLoginFailure`

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

1. `onUserLogout`
2. при успехе `onUserAfterLogout`
3. при неудаче `onUserLogoutFailure`

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

В `User::save()`:

1. `onUserBeforeSave`
2. запись в таблицу пользователя
3. `onUserAfterSave`

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

В `User::delete()`:

1. `onUserBeforeDelete`
2. удаление из таблицы
3. `onUserAfterDelete`

| Событие | Аргументы | Что можно менять | Для чего нужно |
| --- | --- | --- | --- |
| `onUserAuthenticate` | `credentials`, `options`, `subject` = `AuthenticationResponse` | Объект ответа аутентификации | Подтвердить или отклонить подлинность пользователя |
| `onUserAuthorisation` | `subject` = `AuthenticationResponse`, `options` | Обычно добавляется результат | Решить, можно ли пустить уже проверенного пользователя |
| `onUserLogin` | `subject` = массив ответа аутентификации, `options` | Возврат `false` через `result` отменяет вход | Поднять пользовательскую сессию в своём хранилище |
| `onUserAfterLogin` | `subject`, `options` | Обычно побочные действия | Журналы, внешние сервисы, одноразовые метки |
| `onUserBeforeSave` | `subject` = старые данные пользователя, `isNew`, `data` = новые данные | Можно вернуть `false` | Бизнес-правила перед сохранением |
| `onUserAfterSave` | `subject` = сохранённые данные, `isNew`, `savingResult`, `errorMessage` | Обычно побочные действия | Синхронизация после сохранения |
| `onUserBeforeDelete` | `subject` = массив пользователя | Обычно проверка и исключение | Запрет удаления |
| `onUserAfterDelete` | `subject`, `deletingResult`, `errorMessage` | Обычно побочные действия | Уборка следов пользователя |

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

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

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

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

| Событие | Где вызывается | Аргументы | Что можно менять |
| --- | --- | --- | --- |
| `onInstallerBeforePackageDownload` | `InstallerHelper::downloadPackage()` | `url`, `headers` | Адрес и заголовки через `updateUrl()` и `updateHeaders()` |
| `onInstallerBeforeInstallation` | `com_installer` | Контекст установки и `result` | Можно прервать установку |
| `onExtensionBeforeInstall` | `Installer::install()` | объект установщика, пакет | Обычно проверки перед установкой |
| `onExtensionAfterInstall` | `Installer::install()` | объект установщика, `eid`, `result` | Реакция после установки |
| `onExtensionBeforeUpdate` | `Installer::update()` | объект установщика, пакет | Проверки перед обновлением |
| `onExtensionAfterUpdate` | `Installer::update()` | объект установщика, `eid`, `result` | Реакция после обновления |
| `onExtensionBeforeUninstall` | `Installer::uninstall()` | объект установщика, идентификатор | Проверки перед удалением |
| `onExtensionAfterUninstall` | `Installer::uninstall()` | объект установщика, идентификатор, `result` | Уборка после удаления |

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

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

- события имеют типизированные классы;
- аргументы названы явно;
- для изменения данных всё чаще есть специальные методы;
- основная точка расширения — не «магия триггера», а точное понимание стадии жизненного цикла.

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

## Об авторе

![Толкачев Сергей Юрьевич](https://web-tolk.ru/images/uslugi/sergey-tolkachyov-apr-2023.webp)

### Толкачев Сергей Юрьевич

Joomla-разработчик. [Контрибьютер ядра Joomla](https://github.com/joomla/joomla-cms/pulls?q=is%3Apr+author%3Asergeytolkachyov+). Один из ведущих Telegram-канала русскоязычного Joomla-сообщества [JoomlaFeed](https://t.me/joomlafeed), один из модераторов [чата русскоязычного Joomla-сообщества](https://t.me/joomlaru). Мои расширения в официальном маркетплейсе расширений Joomla - [Joomla Extensions Directory](https://extensions.joomla.org/profile/profile/details/528051/). Имею публикации в [официальном журнале международного Joomla-сообщества - Joomla Community Magazine](https://magazine.joomla.org/authors/sergeytolkachyov) и на [официальном сайте русскоязычного Joomla-сообщества](https://joomlaportal.ru/users/sergey-tolkachyov).

Муж. Отец 3 детей.

Россия, Саратов.

## JSON-LD Schema

```json
{
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "@id": "https://web-tolk.ru/#/schema/BreadcrumbList/17",
    "itemListElement": [
        {
            "@type": "ListItem",
            "position": 1,
            "item": {
                "@id": "https://web-tolk.ru/",
                "name": "Главная"
            }
        },
        {
            "@type": "ListItem",
            "position": 2,
            "item": {
                "@id": "https://web-tolk.ru/blog",
                "name": "Блог"
            }
        },
        {
            "@type": "ListItem",
            "position": 3,
            "item": {
                "name": "Общая информация о принципе действия Joomla 6. Жизненный цикл Приложений Joomla."
            }
        }
    ]
}
```

```json
{
    "@context": "https://schema.org",
    "@graph": [
        {
            "@type": "Organization",
            "@id": "https://web-tolk.ru/#/schema/Organization/base",
            "name": "WebTolk",
            "url": "https://web-tolk.ru/",
            "logo": {
                "@type": "ImageObject",
                "@id": "https://web-tolk.ru/#/schema/ImageObject/logo",
                "url": "images/webtolk-1080p.jpg",
                "contentUrl": "images/webtolk-1080p.jpg",
                "width": 1920,
                "height": 1080
            },
            "image": {
                "@id": "https://web-tolk.ru/#/schema/ImageObject/logo"
            },
            "sameAs": [
                "https://github.com/WebTolk",
                "https://github.com/sergeytolkachyov",
                "https://vk.com/web_tolk",
                "https://vk.com/webtolkru",
                "https://tenchat.ru/sergeytolkachyov",
                "https://t.me/sergeytolkachyov",
                "https://t.me/webtolkru"
            ]
        },
        {
            "@type": "WebSite",
            "@id": "https://web-tolk.ru/#/schema/WebSite/base",
            "url": "https://web-tolk.ru/",
            "name": "WebTolk",
            "publisher": {
                "@id": "https://web-tolk.ru/#/schema/Organization/base"
            }
        },
        {
            "@type": "WebPage",
            "@id": "https://web-tolk.ru/#/schema/WebPage/base",
            "url": "https://web-tolk.ru/blog/obshchaya-informatsiya-o-printsipe-dejstviya-joomla-6-zhiznennyj-tsikl-prilozhenij-joomla",
            "name": "Общая информация о принципе действия Joomla 6. Жизненный цикл Приложений Joomla. - WebTolk",
            "description": "Подробно разбираем жизненный цикл приложений Joomla 6: Site, Administrator, API, Console и Daemon, порядок событий, маршрутизацию, dispatch, render и плагины.",
            "isPartOf": {
                "@id": "https://web-tolk.ru/#/schema/WebSite/base"
            },
            "about": {
                "@id": "https://web-tolk.ru/#/schema/Organization/base"
            },
            "inLanguage": "ru-RU",
            "breadcrumb": {
                "@id": "https://web-tolk.ru/#/schema/BreadcrumbList/17"
            }
        },
        {
            "@type": "Article",
            "image": "https://web-tolk.ru/images/blog/obshchaya-informatsiya-o-printsipe-dejstviya-joomla-6-zhiznennyj-tsikl-prilozhenij-joomla/header-1080.webp",
            "headline": "Общая информация о принципе действия Joomla 6. Жизненный цикл Приложений Joomla.",
            "description": "Подробно разбираем жизненный цикл приложений Joomla 6: Site, Administrator, API, Console и Daemon, порядок событий, маршрутизацию, dispatch, render и плагины.",
            "author": {
                "@type": "person",
                "name": "Сергей Толкачев",
                "url": "https://web-tolk.ru",
                "email": "info@web-tolk.ru"
            },
            "datePublished": "2026-06-28T00:00:00+00:00",
            "dateModified": "2026-06-28T00:00:00+00:00",
            "about": "Joomla 6, CMSApplication, SiteApplication, AdministratorApplication, ApiApplication, ConsoleApplication, DaemonApplication, Joomla plugin events",
            "@id": "https://web-tolk.ru/#/schema/com_content/article/180",
            "isPartOf": {
                "@id": "https://web-tolk.ru/#/schema/WebPage/base"
            }
        }
    ]
}
```
