Создание модулей с учётом новой структуры 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()

Заключение

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

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

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

  • форум русской поддержки Joomla.

  • интернет-портал Joomla-сообщества.

  • https://vc.ru/s/1146097-joomla - Сообщество Joomla на VC.

Telegram:

  • Чат сообщества «Joomla! по-русски».

  • Joomla для профессионалов, разработчики Joomla.

  • Новости о Joomla! и веб-разработке по-русски.

  • Вакансии и предложения работы по Joomla: фуллтайм, частичная занятость и разовые подработки. Размещение вакансий здесь.

  • Англоязычный чат сообщества.

  • Новости Joomla! по-английски

Источник: https://habr.com/ru/post/684534/


Интересные статьи

Интересные статьи

Всем привет.В этой части настроим анимацию движения во все стороны для персонажа.
В системе аналитики при наличии нескольких источников данных зачастую возникает проблема с актуальностью, полнотой и изменением справочников данных.Обычно проблема решается при помощи создания справоч...
Приветствую вас, хабровчане! Как и обещал в конце прошлой статьи, я обратил свой взгляд на игровой движок. Правда мне больше приглянулся Godot. Почему? Тема для отдельной статьи размышления, а...
Создание анимаций это здорово. Они являются важной частью iOS Human Interface Guidelines. Анимации помогают привлечь внимание пользователя к важным вещам или попросту делают приложение не таким ...
29 марта 2019 года — Майкл Тротт, главный научный сотрудник — Вступление — Мир обсуждает грядущие перемены — Краткий обзор соответствующих ингредиентов языка Wolfram Language — Вернемся к...