Joomla 4 «под капотом» претерпела немало изменений относительно предыдущих версий. Её кодовую базу сообщество разработчиков регулярно подтягивают до современных реалий, вводя актуальные технологии в ядро CMS. Так, например, если раньше загрузка классов была вариациями на тему include, то в Joomla 4 появился лоадер, приведённый к PSR-4. Ядро CMS переводится на концепцию сервис-провайдеров, внедрены DI-контейнеры, переработанная система событий для плагинов позволила увеличить производительность при генерации страниц более чем в два раза. Эти изменения влекут за собой изменения в структуре компонентов, модулей и плагинов.
В данной статье пойдёт речь о том, как создать модуль для Joomla 4 с новой структурой файлов и классов. К слову сказать, legacy ещё работает и многие расширения, созданные по канонам Joomla 3 (а не работавшие на Joomla 3, но написанные по канонам Joomla 1.5) ещё долго будут работать на Joomla 4.
Отступление
Я предполагаю, что часть читателей имеет опыт работы с Joomla, но не имеет опыта создания модулей, поэтому постараюсь описать создание модуля как можно подробнее. Статья имеет сугубо прикладной характер, без погружения в теорию ООП и его реализацию в Joomla. Основная цель — подсказать что «делать руками», когда поставлена определённая задача.
Рассказывать о создании модуля я буду на примере своего модуля WT Yandex map items — модуля вывода материалов Joomla на Яндекс.карты по координатам из пользовательских полей, который создавался под проект на Joomla 4, поэтому названия файлов, классов и namespace будут содержать название именно этого модуля. При создании своего модуля, естественно, нужно изменить их на свои.
Файловая структура модуля Joomla 3 vs Joomla 4 и распределение функционала
Было (Joomla 3)

Для создания модуля было необходимо как минимум 3 файла:
-
mod_wtyandexmapitems.xml — описание модуля для установщика расширений Joomla (системное имя, дата, версия, сайт разработчика и т.д.), параметры конфигурации, сервер обновлений и т.д.
-
mod_wtyandexmapitems.php — «точка входа» в модуль. С этого файла начинается работа Вашего кода.
-
tmpl/default.php — макет вывода для модуля. Здесь находится HTML-вёрстка Вашего модуля. При необходимости, можно скопировать и переименовать этот файл, изменить вывод HTML по своему вкусу и выбрать в настройках свой новый макет вывода.
Этот же способ позволяет выполнять любой свой PHP-код в нужном месте и в нужное время.
Именно такую структуру мы видим в одном из простейших модулей Joomla — mod_custom — «HTML-код».

Хелпер (helper) модуля Joomla
Если наш модуль делает что-то более сложное, чем просто вывод значений из настроек модуля, например:
-
отображает список новых статей на сайте
-
показывает карусель товаров из компонента интернет-магазина
-
выводит популярные комментарии или фотографии из фото-галереи
-
подтягивает данные модуля по ajax (ajax-корзина товаров, к примеру)
то все функции, выполняющие эту работу, помещаются в хелпер модуля. В Joomla 3 он помещался в файл helper.php, находящийся рядом с основным php-файлом. Подключение хелпера было в «точке входа» с помощью JLoader::register('ModWtyandexmapitemsHelper', __DIR__ . '/helper.php');
Стало (Joomla 4)

Для создания модуля в Joomla 4 нужны следующие файлы (с меньшим количеством можно поэкспериментировать спортивного интереса ради):
Файл mod_wtyandexmapitems.xml
Этот файл содержит описание модуля для установщика расширений Joomla (системное имя, дата, версия, сайт разработчика и т.д.), параметры конфигурации, сервер обновлений, а также задаёт Namespace модуля и директории для автозагрузки классов.

<?xml version="1.0" encoding="utf-8"?> <extension type="module" client="site" method="upgrade"> <name>MOD_WTYANDEXMAPITEMS</name> <author>Sergey Tolkachyov</author> <creationDate>13/09/2022</creationDate> <copyright>(C) 2022 Sergey Tolkachyov.</copyright> <license>GNU General Public License version 2 or later</license> <authorEmail>info@web-tolk.ru</authorEmail> <authorUrl>https://web-tolk.ru</authorUrl> <version>1.0.0</version> <description>MOD_WTYANDEXMAPITEMS_DESC</description> <namespace path="src">Joomla\Module\Wtyandexmapitems</namespace> <files> <folder module="mod_wtyandexmapitems">src</folder> <folder>language</folder> <folder>services</folder> <folder>tmpl</folder> </files> <languages> <language tag="en-GB">language/en-GB/mod_wtyandexmapitems.ini</language> <language tag="en-GB">language/en-GB/mod_wtyandexmapitems.sys.ini</language> <language tag="ru-RU">language/ru-RU/mod_wtyandexmapitems.ini</language> <language tag="ru-RU">language/ru-RU/mod_wtyandexmapitems.sys.ini</language> </languages> </extension>
Также обратите внимание, что для корректной установки и работы модуля нужно указывать атрибут module="mod_wtyandexmapitems" в xml-манифесте. Если в Joomla 3 этот атрибут указывался для файла «точки входа» (<filename module="mod_wtyandexmapitems">mod_wtyandexmapitems.php</filename>), то сейчас он указывается для папки src модуля — <folder module="mod_wtyandexmapitems">src</folder> .
Ещё одно нововведение связано с языковыми файлами: теперь в именах файлов не обязательно дублировать префикс языка — «ru-RU.mod_wtyandexmapitems.ini». Достаточно того, что файл лежит в папке «ru-RU».
Файл services/provider.php
Файл — сервис-провайдер Вашего модуля. Он сообщает Joomla, что Ваш модуль существует и регистрирует namespace модуля в глобальном пространстве имён.
<?php /** * @package WT Yandex Map items * * @copyright (C) 2022 Sergey Tolkachyov * @link https://web-tolk.ru * @license GNU General Public License version 2 or later */ defined('_JEXEC') or die; use Joomla\CMS\Extension\Service\Provider\HelperFactory; use Joomla\CMS\Extension\Service\Provider\Module; use Joomla\CMS\Extension\Service\Provider\ModuleDispatcherFactory; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; /** * The WT Yandex map items module service provider. * * @since 1.0.0 */ return new class implements ServiceProviderInterface { /** * Registers the service provider with a DI container. * * @param Container $container The DI container. * * @return void * * @since 4.0.0 */ public function register(Container $container) { // Основной namespace модуля $container->registerServiceProvider(new ModuleDispatcherFactory('\\Joomla\\Module\\Wtyandexmapitems')); // Namespace модуля для хелпера $container->registerServiceProvider(new HelperFactory('\\Joomla\\Module\\Wtyandexmapitems\\Site\\Helper')); // Namespace модуля для своих типов полей $container->registerServiceProvider(new HelperFactory('\\Joomla\\Module\\Wtyandexmapitems\\Site\\Fields')); $container->registerServiceProvider(new Module); } };
Некоторые модули для Joomla могут быть довольно сложными, использовать дополнительные PHP-библиотеки и SDK, поэтому в папке src модуля могут быть самые разные Namespace, которые можно зарегистрировать в сервис-провайдере, дабы они были доступны глобально. Однако, я предпочитаю библиотеки оформлять отдельными расширениями Joomla и устанавливать их в папку libraries в корне сайта, а обращаться к ним уже по namespace. Это удобно для тех случаев, когда не одно Ваше расширение использует данную библиотеку, а несколько. В таком случае для библиотеки потребуется системный плагин, регистрирующий её namespace в глобальном пространстве имён.
Берём на заметку, что в Namespace указывается «клиент» модуля — «Site» или «Administrator».
Файл src/Dispatcher/Dispatcher.php
Этот файл используется для того, чтобы передать данные из хелпера модуля в макет (layout).
<?php /** * @package WT Yandex Map items * * @copyright (C) 2022 Sergey Tolkachyov * @link https://web-tolk.ru * @license GNU General Public License version 2 or later */ namespace Joomla\Module\Wtyandexmapitems\Site\Dispatcher; \defined('JPATH_PLATFORM') or die; use Joomla\CMS\Application\CMSApplicationInterface; use Joomla\CMS\Dispatcher\AbstractModuleDispatcher; use Joomla\CMS\Extension\ModuleInterface; use Joomla\Input\Input; use Joomla\Module\Wtyandexmapitems\Site\Helper\WtyandexmapitemsHelper; use Joomla\Registry\Registry; /** * Dispatcher class for mod_wtyandexmapitems * * @since 1.0.0 */ class Dispatcher extends AbstractModuleDispatcher { /** * The module extension. Used to fetch the module helper. * * @var ModuleInterface|null * @since 1.0.0 */ private $moduleExtension; public function __construct(\stdClass $module, CMSApplicationInterface $app, Input $input) { parent::__construct($module, $app, $input); $this->moduleExtension = $this->app->bootModule('mod_wtyandexmapitems', 'site'); } /** * Returns the layout data. * * @return array * * @since 1.0.0 */ protected function getLayoutData() { $data = parent::getLayoutData(); // Вариант использования хелпера через Namespace $data['placemarks'] = (new WtyandexmapitemsHelper)->getPlacemarks($data['params'], $this->getApplication()); // ИЛИ // Вариант использования хелпера через $this->moduleExtension, // который мы загрузили в конструкторе класса $helper = $this->moduleExtension->getHelper('WtyandexmapitemsHelper'); // ИЛИ // Вариант использования хелпера напрямую из этого метода, // не загружая модуль в $this->moduleExtension. // Тогда строка $this->moduleExtension в __construct() не нужна. $helper = $this->app->bootModule('mod_wtyandexmapitems', 'Site')->getHelper('WtyandexmapitemsHelper'); $data['placemarks'] = $helper->getPlacemarks($data['params'], $this->getApplication()); return $data; } }
Особый интерес для нас представляет функция getLayoutData(), так как именно в ней мы обращаемся к методам нашего хелпера модуля и помещаем полученные данные в массив $data. Ключ массива $data может быть любым и может быть не единственным. Можно провести параллель с Model в MVC, когда мы из разных мест собираем данные и передаём их для отображения.
В хелпере модуля можно собрать один результирующий массив данных и в диспетчере передать на рендер только его (обратившись только к одному методу хелпера — точке входа в хелпер). А можно обращаться к разным методам хелпера в диспетчере, присваивая данные разным элементам массива $data. Подход к реализации Вы выбираете сами, распределяя функционал между этими двумя файлами.
Файл src/Helper/WtyandexmapitemsHelper.php
Хелпер модуля. Имя файла = имя модуля без суффикса «mod_» + Helper (с заглавной буквы).
Namespace хелпера — Joomla\Module\Wtyandexmapitems\Site\Helper. Вместо «Site» может быть «Administrator», если у Вас модуль для панели администратора, например для дашбордов Joomla 4. Имя класса совпадает с именем файла. Внутри — нужные Вам функции.
<?php /** * @package WT Yandex Map items * * @copyright (C) 2022 Sergey Tolkachyov * @link https://web-tolk.ru * @license GNU General Public License version 2 or later */ namespace Joomla\Module\Wtyandexmapitems\Site\Helper; use Joomla\CMS\Access\Access; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Helper\ModuleHelper; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\Layout\FileLayout; use Joomla\CMS\Router\Route; use Joomla\CMS\Uri\Uri; use Joomla\Component\Content\Site\Helper\RouteHelper; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Registry\Registry; \defined('_JEXEC') or die; /** * Helper for mod_wtyandexmapitems * * @since 1.0 */ class WtyandexmapitemsHelper { public function getPlacemarks($params, $app):array { /** * Этот метод мы вызывали в файле * src/Dispatcher/Dispatcher.php * в строке * $data['placemarks'] = (new WtyandexmapitemsHelper)->getPlacemarks($data['params'], $this->getApplication()); */ } }
В данном случае в методе getPlacemarks() я получаю с помощью нескольких методов список материалов Joomla 4, их пользовательские поля, выбираю (сообразно настройкам модуля) поле, в котором хранятся координаты, а затем собираю массив со структурой, необходимой для Яндекс карт.
Работа с Ajax в модулях Joomla 4
Если Вашему модулю есть что отдать по ajax на фронт, то для этого нужно в хелпере модуля создать метод getAjax().В нашем случае на Яндекс.карты будет загружаться более 100 меток с текстами и картинками. Поэтому целесообразнее получать эти данные по ajax.
Согласно документации Joomla по использованию ajax Вы можете в запросе указывать конкретный метод хелпера. В таком случае имя метода должно заканчиваться на «Ajax»: например method=mySuperAwesomeMethodToTrigger вызовет метод mySuperAwesomeMethodToTriggerAjax модуля.
Пример ajax-запроса, реализованного нативными средствами Joomla (статья-мануал на Хабре).
Joomla.request({ url: window.location.origin + "/index.php?option=com_ajax&module=wtyandexmapitems&format=raw", onSuccess: function (response, xhr){ if (response !== ""){ let placemarks = JSON.parse(response); console.log(placemarks); objectManager.add(placemarks); myMap' . $module->id . '.geoObjects.add(objectManager); } } }); }
Файл tmpl/default.php — макет вывода в Joomla
Здесь по-прежнему находится HTML-вёрстка Вашего модуля. Его по-прежнему можно скопировать в ту же папку или в папку с Вашим шаблоном (сделать переопределение), переименовать и изменить вывод HTML, не отказывая себе в самых страшных извращениях и при этом не опасаясь того, что Ваши изменения будут затёрты при обновлении движка.
В файлах макетов вывода, как правило, находится цикл вывода данных foreach.
В Joomla 3 нередко можно было встретить следующую конструкцию
// Файл "точка входа" модуля mod_menu.php // $list - массив с пунктами меню, которые передаются в макет вывода $list = ModMenuHelper::getList($params);
// Файл tmpl/default.php - макет вывода модуля mod_menu // $list - массив с пунктами меню, которые передаются в макет вывода foreach ($list as $i => &$item){ // здесь работа по отображению HTML меню, с учетом настроек модуля и данных в массиве }
В Joomla 4 в принципе осталось то же самое, за небольшими изменениями:
-
данные мы получаем из хелпера и передаём не в «точке входа» (которой теперь нет по определению), а в файле
src/Dispatcher/Dispatcher.php. Те самые$data['placemarks']в макете вывода становятся просто$placemarks. -
«рядом» с Вашими переменными передаются следующие:
-
$module— объект модуля. Оттуда Вы можете взять id модуля ($module->id), заголовок модуля, его позицию и т.д. -
$app— объект приложения. Это значит, что Вам не нужно самостоятельно вызыватьJoomla\CMS\Factory::getApplication(). Он уже есть для Вашего удобства. -
$input— также в макете модуля теперь сразу доступен объект Input (через него мы получаем GET, POST параметры, SERVER и т.д.), который раньше приходилось вызывать самостоятельно. -
$params— параметры модуля. Получаем их как раньше:$params->get('param_name' , 'default_value_if_value_is_empty'). Эти параметры мы собираем с помощью различных типов полей Joomla в xml-манифесте модуля. -
$template— параметры настроек стиля текущего шаблона. У шаблонов Joomla есть templateDetails.xml, в которых можно задавать различные параметры шаблона: логотипы, шрифты, пользовательские скрипты в <head> и <body> и всё, что душе угодно. Теперь в модуле Вы имеете возможность без лишних шевелений получить доступ к этим параметрам. Однако, стоит помнить, что многие студийные шаблоны (JoomShaper Helix и иже с ними) не используют стандартное место хранение параметров, поэтому там может оказаться пусто.
-
Свои типы полей Joomla для модуля
Как и в Joomla 3, в Joomla 4, если Вам не хватает стандартных типов полей, у Вас есть возможность создавать свои типы полей. Это могут быть нестандартные выборки из базы данных, получение значений списка из сторонних сервисов по API и т.д.
Возможность создавать свои пользовательские типы полей открывает широкие возможности Joomla. Наглядный пример:
Joomla 3
В Joomla 3 Вам надо было указать свой тип поля и назначить атрибут addfieldpath родительскому <fieldset> или напрямую <field>. Например
<field addfieldpath="modules/mod_wtyandexmapitems/fields" type="moduleinfo" name="moduleinfo"/>
Php-файл поля находится в папке с модулем modules/mod_wtyandexmapitems/fields.
Joomla 4
В Joomla 4 атрибут addfieldpath не работает. Вместо него используется атрибут addfieldprefix, в котором нужно указать namespace для пользовательских полей модуля.

Поля мы складываем в src/Fields. У файлов полей должен быть namespace namespace Joomla\Module\Wtyandexmapitems\Site\Fields. Я использую собственный тип поля, расширяющий тип поля spacer (пробел), для вывода своего логотипа, версии модуля, ссылки на сайт и иногда дополнительной информации.
<field type="moduleinfo" addfieldprefix="Joomla\Module\Wtyandexmapitems\Site\Fields" name="moduleinfo"/>

А вот пример использования в Joomla 3. Плагин для двухсторонней интеграции Joomla с CRM Битрикс 24 в настройках показывает информацию об аккаунте, из-под которого создан вебхук на стороне Битрикс 24. Если информация отображается, значит плагин настроен верно.

А здесь в настройках плагина отображается список стадий лида (или сделки), получаемый по API из CRM Битрикс 24. Это так же реализовано с помощью пользовательских типов полей (пример из версии для Joomla 3).

Гибридный вариант модуля
Если у Вас совсем нет времени, а завести «со шморгалкой» старый модуль на Joomla 4 всё-таки надо, поддерживается (пока что) как старый, так и гибридный вариант структуры модуля.
-
Пока что можно обойтись без сервис-провайдера. Вообще. Тогда нужен файл «точки входа»
mod_wtyandexmapitems.phpи соответствующая строка в xml-манифесте модуля. -
Пока что можно обойтись без папки src. И подключать хелпер по старинке через
JLoader::register('ModWtyandexmapitemsHelper', __DIR__ . '/helper.php'). Соответственно файл helper.php должен лежать рядом с «точкой входа» в модуль. -
Можно переместить хелпер в папку
src, переименовать файл, назначить ему namespace (и в xml-манифесте модуля тоже) и использовать в «точке входа» простоuse Joomla\Module\Wtyandexmapitems\Site\Helper\WtyandexmapitemsHelper— namespace хелпера. На момент написания статьи большая часть даже стандартных модулей Joomla переделана именно так, с частичным сохранением старой структуры. Полностью новым канонам пока что соответствует лишь модуль панели управленияmod_quickicon— иконок быстрого доступа.
Полезные дополнения
Об использовании \Joomla\CMS\Factory
Из статьи Распространенные ошибки при написании плагинов Joomla 4
Вы должны использовать ТОЛЬКО ДВА метода \Joomla\CMS\Factory в Joomla 4:
-
getContainer()— возвращает контейнер внедрения зависимостей Joomla (DI Container, иногда сокращенно DIC). -
getApplication()— возвращает текущий объект приложения Joomla, обрабатывающий запрос.
Всё. Больше ничего другого использовать не нужно! Всё остальное предоставляется либо через DI-контейнер, либо через сам объект приложения.
Чтобы получить документ приложения используйте \Joomla\CMS\Factory::getApplication()->getDocument().
Правильное подключение CSS и JS в Joomla 4
-
Статья на хабре Использование WebAssetsManager Joomla 4 и добавление собственных пресетов с помощью плагина. Мы помним, что все CSS и JS файлы должны лежать в папке
media. Подробнее в статьях.
Замена для популярных, но устаревших методов
Многие из этих методов работали ещё со времен Joomla 1.5 (с 2008 года!).
-
JRequest::getUri()заменяем на $uri =Joomla\CMS\Uri::getInstance()и читаем документацию к нему. -
методы
JRequest::getCmdи аналогичные перекочевали вJoomla\Input\Inputили (что проще)$app->getInput(). Пока что поддерживается устаревший синтаксис$app->input, но в Joomla 5 (выйдет осенью 2023 года) он может быть удалён (план выпуска релизов и принципы удаления устаревшего кода в Joomla). -
$app->isAdmin()и$app->isSite()стали$app->isClient('Site')и$app->isClient('Administrator'). -
Подключение к базе данных: вместо
JFactory::getDbo()(илиJoomla\CMS\Factory::getDbo) используем$app->getContainer()->get('DatabaseDriver')(‘DatabaseDriver’ регистрозависимый). -
Получение объекта пользователя: вместо
JFactory::getUser()(илиJoomla\CMS\Factory::getUser()) используем$app->getIdentity()
Заключение
Буду рад, если статья окажется полезной для разработчиков. Также буду признателен замечаниям и исправлениям, если таковые имеются у читателей.
Полезные ресурсы
Ресурсы сообщества:
-
https://vc.ru/s/1146097-joomla — Сообщество Joomla на VC.
Telegram:
-
Вакансии и предложения работы по Joomla: фуллтайм, частичная занятость и разовые подработки. Размещение вакансий здесь.
ссылка на оригинал статьи https://habr.com/ru/post/684534/
Добавить комментарий