На основе официальной документации Joomla и практического опыта, вот полный набор файлов и правил для создания работающего плагина в Joomla 5.
Архитектурная схема (что происходит внутри Joomla)
Прежде чем писать код, поймите цепочку:
- Joomla загружает плагин через
services/provider.php. - Провайдер регистрирует в дочернем DI-контейнере запись
PluginInterface::class. - При получении этой записи создаётся экземпляр класса плагина из
src/Extension/. - Joomla проверяет, реализует ли класс
SubscriberInterface. - Если да — вызывается
getSubscribedEvents(), который возвращает карту событий и методов. - При наступлении события Joomla вызывает соответствующий метод, передавая объект события (PSR-14).
Ключевой вывод: с Joomla 4 плагины обязательно должны иметь services/provider.php и реализовывать SubscriberInterface. Старый способ (файл plugin.php с методами, названными по имени события) устарел и будет удалён в Joomla 6 [citation:15].
Структура файлов
plugins/system/example/
├── example.xml (манифест)
├── services/
│ └── provider.php (регистрация в DI)
└── src/
└── Extension/
└── Example.php (класс плагина)
1. Манифест example.xml
Манифест сообщает Joomla, что это плагин группы system, и указывает namespace и файлы для установки [citation:3].
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>System - Example</name>
<author>Your Name</author>
<version>1.0.0</version>
<description>Пример системного плагина для Joomla 5.</description>
<namespace path="src">Vendor\Plugin\System\Example</namespace>
<files>
<folder plugin="example">services</folder>
<folder>src</folder>
</files>
</extension>
Важно: атрибут plugin="example" на теге <folder> должен совпадать с именем XML-файла и именем папки плагина. Иначе Joomla установит плагин, но не построит namespace корректно [citation:3].
2. services/provider.php
Регистрирует плагин в DI-контейнере. Это стандартный шаблон для Joomla 5 [citation:6][citation:9].
<?php
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Vendor\Plugin\System\Example\Extension\Example;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new Example(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('system', 'example')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
Класс плагина получает два аргумента: диспетчер событий и объект с параметрами плагина из базы данных (id, name, type, params).
3. src/Extension/Example.php
Основной класс плагина. Реализует SubscriberInterface и подписывается на нужное событие.
<?php
namespace Vendor\Plugin\System\Example\Extension;
\defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
class Example extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onAfterInitialise' => 'onAfterInitialise',
];
}
public function onAfterInitialise(): void
{
$app = $this->getApplication();
// Пример: логирование в файл
file_put_contents(
JPATH_ROOT . '/example-plugin.log',
date('c') . " Плагин сработал, клиент: " . $app->getName() . "\n",
FILE_APPEND
);
}
}
Выбор события
Событие определяет, когда сработает плагин. Наиболее частые для системных плагинов:
onAfterInitialise— сразу после инициализации приложения. Подходит для регистрации сервисов или ранней логики.onAfterRoute— после определения маршрута (известныoption,view).onBeforeCompileHead— перед выводом<head>. Подходит для подключения CSS/JS [citation:2].onContentPrepare— для контентных плагинов (модификация текста материала).
Полный список событий смотрите в документации Joomla.
Частые ошибки и их точные причины
1. Плагин установлен и активирован, но не срабатывает
Причина: отсутствует services/provider.php, или namespace в манифесте не совпадает с namespace в классе плагина.
Проверка: удалите administrator/cache/autoload_psr4.php и загрузите страницу. Затем проверьте, есть ли ваш namespace в этом файле:
grep -n "Vendor\\\\Plugin\\\\System\\\\Example" administrator/cache/autoload_psr4.php
Если строка есть — namespace зарегистрирован. Если нет — манифест или путь не совпадают [citation:3].
2. Ошибка «Call to protected method getContainer()»
Причина: в плагине нельзя вызывать $app->getContainer() — метод protected.
Решение: используйте Factory::getContainer() или передавайте зависимости через конструктор в provider.php.
3. Событие не наступает
Причина: плагин подписан на событие, которое не генерируется в текущем контексте (например, onAfterRoute не срабатывает в CLI или API).
Решение: проверьте, где именно должно срабатывать событие. Для отладки используйте onAfterInitialise — оно срабатывает почти всегда.
4. Изменения в плагине не подхватываются
Решение: очистите кэш Joomla (System → Maintenance → Clear Cache) и удалите administrator/cache/autoload_psr4.php. Joomla пересоберёт его при следующем запросе.
Быстрая диагностика
Если плагин не срабатывает, добавьте лог в provider.php:
public function register(Container $container): void
{
file_put_contents(JPATH_ROOT . '/provider-debug.log', date('c') . " PROVIDER register\n", FILE_APPEND);
// ...
}
И в метод плагина:
public function onAfterInitialise(): void
{
file_put_contents(JPATH_ROOT . '/plugin-debug.log', date('c') . " onAfterInitialise\n", FILE_APPEND);
}
Откройте страницу. Если provider-debug.log не появился — Joomla не загружает провайдер. Если провайдер есть, а plugin-debug.log нет — событие не наступает или класс не инстанцируется.
Чеклист перед запуском
- Папка плагина:
plugins/{group}/{element}/ - Манифест:
{element}.xmlсtype="plugin"иgroup="{group}" - В манифесте указан
<namespace path="src">Vendor\Plugin\{Group}\{Element}</namespace> - В манифесте указаны
<folder plugin="{element}">services</folder>и<folder>src</folder> services/provider.phpсуществует и регистрируетPluginInterface::class- Класс плагина в
src/Extension/с правильным namespace - Класс реализует
SubscriberInterface getSubscribedEvents()возвращает корректную карту событий- Кэш очищен (
administrator/cache/autoload_psr4.phpудалён) - Плагин активирован в
System → Plugins
При соблюдении этих пунктов плагин создаётся за одну итерацию.
Автор статьи — ИИ, в партнёрстве с Геннадием Едигом. Сайт: http://jedig.ru