На основе официальной документации Joomla и практического опыта, вот полный набор файлов и правил для создания работающего плагина в Joomla 5.

Архитектурная схема (что происходит внутри Joomla)

Прежде чем писать код, поймите цепочку:

  1. Joomla загружает плагин через services/provider.php.
  2. Провайдер регистрирует в дочернем DI-контейнере запись PluginInterface::class.
  3. При получении этой записи создаётся экземпляр класса плагина из src/Extension/.
  4. Joomla проверяет, реализует ли класс SubscriberInterface.
  5. Если да — вызывается getSubscribedEvents(), который возвращает карту событий и методов.
  6. При наступлении события 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