Создание модулей с учётом новой структуры Joomla 4

от автора

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)

Старая файловая структура модуля Joomla 3
Старая файловая структура модуля Joomla 3

Для создания модуля было необходимо как минимум 3 файла:

  • mod_wtyandexmapitems.xml — описание модуля для установщика расширений Joomla (системное имя, дата, версия, сайт разработчика и т.д.), параметры конфигурации, сервер обновлений и т.д.

  • mod_wtyandexmapitems.php — «точка входа» в модуль. С этого файла начинается работа Вашего кода.

  • tmpl/default.php — макет вывода для модуля. Здесь находится HTML-вёрстка Вашего модуля. При необходимости, можно скопировать и переименовать этот файл, изменить вывод HTML по своему вкусу и выбрать в настройках свой новый макет вывода.
    Этот же способ позволяет выполнять любой свой PHP-код в нужном месте и в нужное время.

Именно такую структуру мы видим в одном из простейших модулей Joomla — mod_custom — «HTML-код».

Файловая структура модуля типа HTML-код в Joomla 3.
Файловая структура модуля типа HTML-код в Joomla 3.

Хелпер (helper) модуля Joomla

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

  • отображает список новых статей на сайте

  • показывает карусель товаров из компонента интернет-магазина

  • выводит популярные комментарии или фотографии из фото-галереи

  • подтягивает данные модуля по ajax (ajax-корзина товаров, к примеру)

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

Стало (Joomla 4)

Новая файловая структура модуля для Joomla 4
Новая файловая структура модуля для Joomla 4

Для создания модуля в Joomla 4 нужны следующие файлы (с меньшим количеством можно поэкспериментировать спортивного интереса ради):

Файл mod_wtyandexmapitems.xml

Этот файл содержит описание модуля для установщика расширений Joomla (системное имя, дата, версия, сайт разработчика и т.д.), параметры конфигурации, сервер обновлений, а также задаёт Namespace модуля и директории для автозагрузки классов.

Namespace Joomla\Module\Wtyandexmapitems начинается в modules/mod_wtyandexmapitems/src
Namespace Joomla\Module\Wtyandexmapitems начинается в modules/mod_wtyandexmapitems/src
<?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 для пользовательских полей модуля.

Собственные типы полей в Joomla 4
Собственные типы полей в Joomla 4

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

<field type="moduleinfo" addfieldprefix="Joomla\Module\Wtyandexmapitems\Site\Fields" name="moduleinfo"/>
Вариант использования пользовательского типа поля в Joomla 4
Вариант использования пользовательского типа поля в Joomla 4

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

Вариант использования пользовательского типа поля в Joomla 3
Вариант использования пользовательского типа поля в Joomla 3

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

Получение данных для поля из API стороннего сервиса в Joomla 3
Получение данных для поля из API стороннего сервиса в Joomla 3

Гибридный вариант модуля

Если у Вас совсем нет времени, а завести «со шморгалкой» старый модуль на Joomla 4 всё-таки надо, поддерживается (пока что) как старый, так и гибридный вариант структуры модуля.

  1. Пока что можно обойтись без сервис-провайдера. Вообще. Тогда нужен файл «точки входа» mod_wtyandexmapitems.php и соответствующая строка в xml-манифесте модуля.

  2. Пока что можно обойтись без папки src. И подключать хелпер по старинке через JLoader::register('ModWtyandexmapitemsHelper', __DIR__ . '/helper.php'). Соответственно файл helper.php должен лежать рядом с «точкой входа» в модуль.

  3. Можно переместить хелпер в папку 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

  1. Статья Как правильно подключать JavaScript и CSS в Joomla 4

  2. Статья на хабре Использование 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()

Заключение

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

Полезные ресурсы

Ресурсы сообщества:

Telegram:


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


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *