Joomla 4 MVC Anatomy: Файлы сайта компонента

  1. Структура файлов сайта
  2. DisplayContoller.php
    1. Уведомление об авторских правах
    2. Пространство имен и проверка определений
    3. use Statements
    4. Класс контроллера
  3. src/View/Countries/HtmlView.php
    1. Переменные класса
    2. Функция display
  4. Model/CountriesModel.php
    1. constructor
    2. populateState
    3. getStoreId
    4. getListQuery
  5. tmpl/countries/default.php
  6. tmpl/countries/default.xml
  7. forms/filter_countries.xml
  8. language/en-GB/com_countrybase.ini
  9. src/Service/Router.php
  10. Анатомия MVC Joomla 4


Структура файлов сайта.

В части компонента "Сайт" (Site) меньше файлов, чем в части "Администратор" (Administrator), поэтому это кажется хорошим местом для начала. Будут рассмотрены только те части каждого файла, которые нуждаются в объяснении. Лучше всего, если вы откроете каждый файл, о котором идет речь, посмотрите на общее содержание, а затем найдете те части, которые нужно объяснить. Структура файлов в алфавитном порядке выглядит следующим образом:

Site
 |- forms
    |- filter_countries.xml
 |- language
    |- en-GB
       |- com_countrybase.ini
 |- src
    |- Controller
       |- DisplayController
    |- Model
       |- CountriesModel.php
    |- Service
       |- Router.php
    |- View
       |- Countries
          |- HtmlView.php
 |- tmpl
    |- countries
       |- default.php
       |- default.xml

DisplayContoller.php

<?php
/**
 * @package     Countrybase.Site
 * @subpackage  com_countrybase
 *
 * @copyright   (C) 2022 Clifford E Ford
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace J4xdemos\Component\Countrybase\Site\Controller;

defined('_JEXEC') or die;

use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;

/**
 * Контроллер компонента Countrybase
 *
 * @since  4.0.0
 */
class DisplayController extends BaseController
{
	/**
	 * Вид по умолчанию.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $default_view = 'countries';

	protected $app;
}

Части этого файла можно пояснить следующим образом:

Уведомление об авторских правах.

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

<?php
/**
 * @package     Countrybase.Site
 * @subpackage  com_countrybase
 *
 * @copyright   (C) 2022 Clifford E Ford
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

Если вы часто создаете новые файлы и копируете/вставляете этот раздел, не забудьте обновить название компонента и уведомление об авторских правах.

Пространство имен и проверка определений.

После предупреждения об авторских правах каждый php-файл должен содержать строку defined('_JEXEC') or die; за исключением того, что файлы с пространством имен должны объявлять пространство имен (namespace) перед любым другим php-кодом, то есть перед проверкой defined. Файлы php с пространством имен - это файлы, содержащие компонентные php-классы в папке src или ее подпапках.

namespace J4xdemos\Component\Countrybase\Site\Controller;

defined('_JEXEC') or die;

Данная проверка предотвращает выполнение php-файла при прямом вызове через его url. Константа _JEXEC определяется при запуске приложения Joomla 4 через корневой или администраторский файл index.php. Это важное средство обеспечения безопасности.

В сочетании с пространством имен, объявленным в файле манифеста countrybase.xml, Joomla будет искать любой класс, объявленный в текущем файле в root/components/com_countrybase/src/Controller - в данном случае добавляя имя этого файла, DisplayController.php

use Statements

Правила use обычно следуют за проверкой defined и часто перечисляются в алфавитном порядке. Декларации use определяют местоположение классов, используемых данным php-файлом. Иногда операторы use появляются по ошибке, будучи объявленными, но не используемыми. Это не причиняет вреда, но это следует исправить. Здесь есть две неиспользуемые декларации:

use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;

Класс контроллера

Контроллер отображения практически ничего не делает, поскольку вся работа выполняется в родительском классе. Единственное, что он делает, это устанавливает представление по умолчанию, в данном случае страны. Это заставит представление компонента по умолчанию использовать файлы Countries/HtmlView.php и tmpl/countries/default.php для отображения данных о странах.

/**
 * Контроллер компонента Countrybase
 *
 * @since  4.0.0
 */
class DisplayController extends BaseController
{
	/**
	 * Вид по умолчанию.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $default_view = 'countries';

	protected $app;
}

Для разметки кода используется стандартная разметка Joomla для php (https://developer.joomla.org/coding-standards/basic-guidelines.html). Блоки документации предназначены для автоматического документирования кода. Значения параметра since, показанные здесь, предназначены для кода Joomla 4.

Не забудьте установить $default_view для этого контроллера. Без него DisplayController будет использовать представление по умолчанию, определенное в конфигурационном файле компонента или, если такового не существует, в имени компонента.

src/View/Countries/HtmlView.php

Контроллер установил представление по умолчанию для countries, поэтому следующим шагом будет загрузка соответствующего кода HtmlView.php. Части этого файла требуют некоторых пояснений.

Переменные класса.

class HtmlView extends BaseHtmlView
{
	/**
	 * Состояние Модели
	 *
	 * @var  \Joomla\CMS\Object\CMSObject
	 */
	protected $state = null;
	...
	protected $items = null;
	...
	protected $pagination = null;
	...
	public $filterForm;
	...
	public $activeFilters;

Переменные класса используются для хранения информации об отображаемой странице:

  • $state - состояние модели, часто устанавливается при вводе формы или из строки запроса.
  • $items - список данных о стране, полученный из базы данных.
  • $pagination - объект, используемый для отображения механизма постраничной навигации, если страниц больше, чем лимит списка, обычно 20 элементов.
  • $filterForm - обычно не встречается на страницах сайта, но используется в com_countrybase для фильтрации по названию страны или опубликованному состоянию.
  • $activeFilters - используется для отслеживания того, какие фильтры используются.

Функция display.

Это довольно стандартное представление для сайта, за исключением частей filterForm и activeFilters:

	public function display($tpl = null)
	{
		$this->state      = $this->get('State');
		$this->items      = $this->get('Items');
		$this->pagination = $this->get('Pagination');
		$this->filterForm    = $this->get('FilterForm');
		$this->activeFilters = $this->get('ActiveFilters');

		// Флаг указывает не добавлять limitstart=0 к URL
		$this->pagination->hideEmptyLimitstart = true;

		// Проверка на наличие ошибок.
		if (count($errors = $this->get('Errors')))
		{
			throw new GenericDataException(implode("\n", $errors), 500);
		}

		parent::display($tpl);
	}

Команды вида $this->get('Xxxx') заставляют Joomla искать в CountriesModel.php функцию с именем getXxxx() и возвращать любые данные, выполненные этим кодом, для хранения и использования в представлении. Часто функция находится в родительском CountriesModel. Например, функция getItems отсутствует в CountriesModel.php, но присутствует в ListModel, которую она расширяет.

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

Model/CountriesModel.php

В Model есть небольшое количество функций, которые обычно требуют самостоятельного выполнения. Другие наследуются от родительской ListModel.

constructor

Нормальной практикой является включение в конструктор любых полей фильтра, которые требуется использовать. Без них может показаться, что фильтры не имеют никакого эффекта. О них часто забывают, когда позднее потребуется добавить фильтр. Каждое поле задается как имя поля и псевдоним имени таблицы, обычно a, b, c и т.д., но может быть любым по собственному выбору, согласующимся с кодом в других местах.

	public function __construct($config = array())
	{
		if (empty($config['filter_fields']))
		{
			$config['filter_fields'] = array(
					'id', 'a.id',
					'title', 'a.title',
					'iso_2', 'a.iso_2',
					'iso_3', 'a.iso_3',
					'country_code', 'a.country_code',
					'region_code', 'a.region_code',
					'state', 'a.state',
					'subregion_code', 'a.subregion_code',
					'phone_prefix', 'a.phone_prefix',
					'currency_code', 'a.currency_code',
			);
		}

		parent::__construct($config);
	}

populateState

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

	protected function populateState($ordering = 'title', $direction = 'ASC')
	{
		// Список сведений о состоянии.
		parent::populateState($ordering, $direction);
	}

getStoreId

Эта функция создает хэш для хранения запроса для использования в других местах.

	protected function getStoreId($id = '')
	{
		// Сборка id хранилища.
		$id .= ':' . $this->getState('filter.search');
		$id .= ':' . $this->getState('filter.published');

		return parent::getStoreId($id);
	}

getListQuery

Здесь создается запрос для извлечения данных из базы данных. это требует некоторого понимания того, как строятся запросы.

	protected function getListQuery()
	{
		// Создание нового объекта запроса.
		$db    = $this->getDbo();
		$query = $db->getQuery(true);

		// Выбор необходимых полей из таблицы.
		$query->select(
			$this->getState(
				'list.select',
				[
					$db->quoteName('a.title'),
					$db->quoteName('a.iso_2'),
					$db->quoteName('a.iso_3'),
					$db->quoteName('a.country_code'),
					$db->quoteName('a.region_code'),
					$db->quoteName('a.subregion_code'),
					$db->quoteName('a.phone_prefix'),
					$db->quoteName('a.currency_code'),
					$db->quoteName('a.state'),
					$db->quoteName('b.title') . ' AS currency_title',
					$db->quoteName('b.symbol'),
					$db->quoteName('b.dollar_exchange_rate'),
				]
			)
		)
		->from($db->quoteName('#__countrybase_countries', 'a'))
		->leftjoin($db->quoteName('#__countrybase_currencies', 'b') . 'ON a.currency_code = b.currency_code');

		// Фильтр по поиску в заголовке.
		$search = $this->getState('filter.search');

		if (!empty($search))
		{
			$search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%'));
			$query->where('(a.title LIKE ' . $search . ')');
		}

		// Фильтр по состоянию публикации
		$published = (string) $this->getState('filter.published');

		if ($published !== '*')
		{
			if (is_numeric($published))
			{
				$state = (int) $published;
				$query->where($db->quoteName('a.state') . ' = :state')
				->bind(':state', $state, ParameterType::INTEGER);
			}
		}

		// Добавление пункта упорядочивания списка.
		$orderCol  = $this->state->get('list.ordering', 'a.title');
		$orderDirn = $this->state->get('list.direction', 'ASC');

		$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));
		return $query;
	}

Пояснение

  • getQuery(true) получает новый пустой объект запроса.
  • $query->select() добавляет оператор SELECT. Операторов select может быть несколько - Joomla объединяет их.
  • Псевдонимы таблиц a и b показывают, что некоторые столбцы берутся из разных таблиц.
  • ->from() определяет, какая таблица является таблицей a. Это может быть в отдельном операторе: $query->from();
  • ->leftjoin() определяет таблицу b и то, как она должна быть присоединена к таблице a.
  • $query->where() использует все заданные фильтры, один для поиска, другой для состояния.
  • return $query здесь нет родительского вызова, всё в запросе должно быть установлено здесь.

tmpl/countries/default.php

Это часть кода, в которой создается html-содержимое. В самом крайнем случае он может содержать только <h1>Hello World</h1>. Для списка стран необходима таблица с заголовком и одной строкой для данных о каждой стране. Поскольку существует 250 стран, необходим механизм пагинации для отображения подмножества стран по нескольку за раз. Для этого нужна форма. И в этом случае пригодится стандартная панель инструментов поиска Joomla. Вот она:

<?php
/**
 * @package     Countrybase.Site
 * @subpackage  com_countrybase
 *
 * @copyright   (C) 2022 Clifford E Ford
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

\defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Router\Route;

$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn  = $this->escape($this->state->get('list.direction'));

?>
<h1><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES'); ?></h1>

<form action="<?php echo Route::_('index.php?option=com_countrybase'); ?>" method="post" name="adminForm" id="adminForm">

<?php echo LayoutHelper::render('joomla.searchtools.default', array('view' => $this)); ?>

<div class="table-responsive">
	<table class="table table-striped">
	<caption><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES_TABLE_CAPTION'); ?></caption>
	<thead>
	<tr>
		<th scope="col">
			<?php echo HTMLHelper::_('searchtools.sort', 'COM_COUNTRYBASE_COUNTRIES_COUNTRY', 'a.title', $listDirn, $listOrder); ?>
		</th>
		<th scope="col"><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES_ISO_2'); ?></th>
		<th scope="col"><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES_ISO_3'); ?></th>
		<th scope="col"><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES_CURRENCY_TITLE'); ?></th>
		<th scope="col"><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES_CURRENCY_SYMBOL'); ?></th>
		<th scope="col"><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES_CURRENCY_CODE'); ?></th>
		<th scope="col"><?php echo Text::_('COM_COUNTRYBASE_COUNTRIES_XRATE'); ?></th>
	</tr>
	</thead>
	<tbody>
	<?php foreach ($this->items as $id => $item) : ?>
	<tr>
		<td><?php echo $item->title; ?></td>
		<td><?php echo $item->iso_2; ?></td>
		<td><?php echo $item->iso_3; ?></td>
		<td><?php echo $item->currency_title; ?></td>
		<td><?php echo $item->symbol; ?></td>
		<td><?php echo $item->currency_code; ?></td>
		<td><?php echo $item->dollar_exchange_rate; ?></td>
	</tr>
	<?php endforeach; ?>
	</tbody>
	</table>
</div>

<?php echo $this->pagination->getListFooter(); ?>

<input type="hidden" name="task" value="">
<input type="hidden" name="boxchecked" value="0">
<?php echo HTMLHelper::_('form.token'); ?>

</form>

Обратите внимание:

  • $listOrder и $listDirection используются для упорядочивания по заголовку столбца. Лишь заголовок (title) настроен для этого.
  • action формы обычно настраивается так, чтобы ссылаться на себя.
  • LayoutHelper::render('joomla.searchtools.default',...) создает строку поиска того типа, который можно увидеть на страницах списков администратора. Ей нужна форма фильтра!
  • $this->pagination->getListFooter() извлекает html-код для виджета пагинации.
  • task это скрытое поле (hidden) заполняется javascript при отправке формы.
  • boxchecked это скрытое поле (hidden) используется, когда один или несколько флажков в строке выбраны для пакетной операции. Здесь оно не очень нужно!
  • HTMLHelper::_('form.token'); получает код для токена формы, используемого в качестве средства защиты при отправке формы, предполагающей ввод данных. Здесь не очень нужен!

tmpl/countries/default.xml

Этот файл используется для создания пункта меню. Он имеет то же имя, что и php-файл, поэтому в данном случае default.xml.

<?xml version="1.0" encoding="UTF-8"?>
<metadata>
	<layout title="COM_COUNTRYBASE_VIEW_DEFAULT_MENU_LABEL" 
		option="COM_COUNTRYBASE_VIEW_DEFAULT_OPTION">
		<help
			url="components/com_countrybase/help/en-GB/countrybase.html"
		/>
		<message>
			<![CDATA[COM_COUNTRYBASE_VIEW_DEFAULT_MENU_DESC]]>
		</message>
	</layout>

	<!-- Добавление полей в объект параметров для макета. -->
	<fields name="params">

		<!-- Параметры -->
		<fieldset name="options">
		</fieldset>

	</fields>
</metadata>

Примечания:

  • help url указывает на файл справки в папке администратора. Это позволяет создавать собственные внутренние файлы справки, вызываемые из кнопки Help формы редактирования меню после выбора типа меню Countrybase Default View.
  • params позволяет использовать параметры, например, показывать или нет определенную колонку в списке стран. Пока никаких параметров не указано.
  • Ключевые фразы переводов должны находиться в файле administrator/language/en-GB/countrybase.sys.ini.

forms/filter_countries.xml

Этот файл необходим для строки поиска. Без него Joomla выдаст фатальную ошибку. Имя файла должно быть точно таким, как показано здесь: имя представления (view name), которому предшествует filter_. Его содержание простое, здесь только определения для поля поиска и любых других фильтров, которые вы можете использовать.

<?xml version="1.0" encoding="utf-8"?>
<form>

	<fields name="filter">

		<field
			name="search"
			type="text"
			label="COM_COUNTRYBASE_COUNTRIES_FILTER_SEARCH_LABEL"
			description="COM_COUNTRYBASE_COUNTRIES_FILTER_SEARCH_DESC"
			hint="JSEARCH_FILTER"
		/>

		<field
			name="published"
			type="status"
			label="JOPTION_SELECT_PUBLISHED"
			onchange="this.form.submit();"
			>
			<option value="">JOPTION_SELECT_PUBLISHED</option>
		</field>

	</fields>

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

language/en-GB/com_countrybase.ini

Joomla всегда загружает ключи английского языка перед любым другим языком. Это гарантирует, что ключи не появятся в выводе, если язык переведен не полностью. Поскольку языковые ключи представляют собой длинные слова на псевдоанглийском языке, считается, что лучше иметь смесь английского и другого языка, чем смесь ключей и другого языка. Если используется другой язык, Joomla перезаписывает английские строки строками другого языка.

Обратите внимание, что общепринятой практикой является перечисление строк в алфавитном порядке ключей:

; Joomla! Project
; (C) 2005 Open Source Matters, Inc. <https://www.joomla.org>
; License GNU General Public License version 2 or later; see LICENSE.txt
; Note : All ini files need to be saved as UTF-8

COM_COUNTRYBASE_COUNTRIES_COUNTRY="Country"
COM_COUNTRYBASE_COUNTRIES_CURRENCY_CODE="Code"
COM_COUNTRYBASE_COUNTRIES_CURRENCY_SYMBOL="Symbol"
COM_COUNTRYBASE_COUNTRIES_CURRENCY_TITLE="Currency"
COM_COUNTRYBASE_COUNTRIES_FILTER_COUNTRY_ASC="Country ASC"
COM_COUNTRYBASE_COUNTRIES_FILTER_COUNTRY_DESC="Country DESC"
COM_COUNTRYBASE_COUNTRIES_FILTER_CURRENCY_CODE_ASC="Currency code ASC"
COM_COUNTRYBASE_COUNTRIES_FILTER_CURRENCY_CODE_DESC="Currency code DESC"
COM_COUNTRYBASE_COUNTRIES_FILTER_SEARCH_DESC="Search in Country Name"
COM_COUNTRYBASE_COUNTRIES_FILTER_SEARCH_LABEL="Search"
COM_COUNTRYBASE_COUNTRIES_ISO_2="ISO2"
COM_COUNTRYBASE_COUNTRIES_ISO_3="ISO3"
COM_COUNTRYBASE_COUNTRIES_TABLE_CAPTION="Table of Country Currencies"
COM_COUNTRYBASE_COUNTRIES_XRATE="Exchange Rate"
COM_COUNTRYBASE_COUNTRIES="Countries"

src/Service/Router.php

Router необходим для SEO-урлов. Без него ссылка в меню может выглядеть как option=com_countrybase&view=countries. С ним ссылка будет отображаться как country-base.html или любое другое имя, которое будет выбрано для псевдонима заголовка ссылки.

	public function __construct(SiteApplication $app, AbstractMenu $menu,
			CategoryFactoryInterface $categoryFactory, DatabaseInterface $db)
	{

		$countries = new RouterViewConfiguration('countries');
		$countries->setKey('id');
		$this->registerView($countries);

		parent::__construct($app, $menu);

		$this->attachRule(new MenuRules($this));
		$this->attachRule(new StandardRules($this));
		$this->attachRule(new NomenuRules($this));
	}

Если представлений больше, например, таблица валют, вы определите каждое представление здесь перед оператором parent::__construct().

Анатомия MVC Joomla 4.

  1. «Joomla 4 MVC Anatomy: Начало работы над созданием компонента»
  2. «Joomla 4 MVC Anatomy: Файловая структура компонента»
  3. «Joomla 4 MVC Anatomy: Файл манифеста компонента (Manifest File)»
  4. «Joomla 4 MVC Anatomy: Файлы сайта компонента»
  5. «Joomla 4 MVC Anatomy: Загрузочные файлы админки»
  6. «Joomla 4 MVC Anatomy: Файлы для правки админки»

Перевод с английского официальной документации CMS Joomla 4:
https://docs.joomla.org/J4.x:MVC_Anatomy:_Site_Files

Заберите ссылку на статью к себе, чтобы потом легко её найти!
Раз уж досюда дочитали, то может может есть желание рассказать об этом месте своим друзьям, знакомым и просто мимо проходящим?
Не надо себя сдерживать! ;)

Старт! Горячий старт на просторы интернета
Старт! Горячий старт на просторы интернета
Старт! Меню