- От автора
- Назначение компонента и его схема данных (Data Schema)
- Структура файлов манифеста и компонентов
- Файлы языковых констант
- Файлы фронтенда сайта
- Control Flow
- И наконец
От автора
Будучи опытным разработчиком на Joomla 3.x мне нужно было узнать о Joomla 4.x, и реально было действительно трудно начать работу. Несмотря на опыт, мои знания о внутреннем устройстве Joomla ограничены. Я начал работать с Joomla на стадии версии 1.6, и многие новые функции прошли мимо меня. Таким образом, это руководство может быть примером того, как слепой ведет слепого. Мне потребовалось около 10 дней, чтобы заставить хоть что-то работать, прочитав код, запустив отладчик и прочитав ограниченное количество доступной документации по Joomla 4. Это руководство написано для Joomla 4.x на этапе Alpha 10 и было обновлено для этапа Beta 4. Даже в этом случае оно может слишком скоро устареть.
Текст руководства был подготовлен как статья, написанная с помощью Joomla 4.0 Alpha 10, преобразованная в MediaWiki, а затем в форматы Github Markdown с помощью Pandoc. И вот что в итоге вышло.
Назначение компонента и его схема данных (Data Schema)
Последние несколько лет я гуляю с семьей, иногда один раз в неделю, иногда два раза, но только в хорошую погоду. Я вел список - всего около 50 прогулок, и каждая отличалась от других. Все это было частью попытки поддерживать форму в старости. Поэтому для самообучения я решил разработать компонент на CMS Joomla, который имеет два представления: список прогулок и детали отдельных прогулок. Чтобы не усложнять, я не хочу никаких излишеств: ни ввода данных на стороне сайта, ни оценок, ни счетчиков посещений, ни категорий, ни других плюшек Joomla. Для целей тестирования часть данных была введена непосредственно в базу данных с помощью phpMyAdmin. Компоненту необходимы две таблицы базы данных: список прогулок и список индивидуальных посещений. Я решил назвать компонент com_mywalks
и таблицы #__mywalks
и #__mywalks_dates
.
Весь код для этого учебного компонента можно получить по этой ссылке.
Возможно, вам будет полезно установить компонент или распаковать его без установки и посмотреть рабочие файлы.
Таблица mywalks
На момент написания скрипт установки в папке admin/sql
не вызывается. Итак, если вы устанавливаете рабочую версию кода этого руководства, сначала запустите следующие сценарии вручную. Это ошибка в Joomla или в учебном коде? Скрипт удаления работает - таблицы успешно пропадают.
Файлы sql в рабочем примере zip-файла включают образцы данных.
CREATE TABLE IF NOT EXISTS `#__mywalks` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` varchar(64) NOT NULL,
`description` text NOT NULL,
`distance` decimal(10,0) NOT NULL,
`toilets` tinyint(1) NOT NULL DEFAULT '0',
`cafe` tinyint(1) NOT NULL DEFAULT '0',
`hills` int(11) NOT NULL DEFAULT '0',
`bogs` int(11) NOT NULL DEFAULT '0',
`picture` varchar(128) DEFAULT NULL,
`width` int(11) DEFAULT NULL,
`height` int(11) DEFAULT NULL,
`alt` varchar(64) DEFAULT NULL,
`state` TINYINT NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
Таблица mywalks_dates
CREATE TABLE IF NOT EXISTS `#__mywalk_dates` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`walk_id` int(11) NOT NULL,
`date` date NOT NULL,
`weather` varchar(256) DEFAULT NULL,
`state` TINYINT NOT NULL DEFAULT '1',
KEY `idx_walk` (`walk_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
Если бы это был реальный компонент, было бы очевидно, что до окончания разработки схемы еще далеко! Посмотрите на сайт WalkHighlands, чтобы узнать, как далеко. Однако этого достаточно для учебных целей.
Структура файлов манифеста и компонентов
Zip-файл компонента, используемый для установки, должен содержать файл манифеста с именем mywalks.xml
(без уведомления com_
) вместе с папками администратора и сайта, примерно так:
com_mywalks.zip
admin
site
mywalks.xml
При установке файл манифеста копируется в папку site_root/administrator/components/com_mywalks
, где он нужен для удаления. Его не должно быть в исходном коде! Записи также делаются в site_root/administrator/cache/autoload_psr4.php
[Это нововведения в Joomla 4].
Файл манифеста
Обратите внимание, что метод настроен на обновление (upgrade
), поэтому компонент можно устанавливать повторно, например, при обновлении кода. Однако операторы sql не будут выполняться второй раз. Если инструкции install sql не выполняются по какой-либо причине, попробуйте выполнить их вручную, скопировав их из исходного кода в phpMyAdmin.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_mywalks</name>
<!-- Следующие элементы являются необязательными и не содержат ограничений форматирования. -->
<creationDate>August 2019</creationDate>
<author>Clifford E Ford</author>
<authorEmail>cliff@ford.myzen.co.uk</authorEmail>
<authorUrl>http://www.fford.me.uk/</authorUrl>
<copyright>Copyright (C) 2019 Clifford E Ford, All rights reserved.</copyright>
<license>GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html</license>
<!-- Строка версии записывается в таблице компонентов. -->
<version>0.2.0</version>
<!-- Описание является необязательным и по умолчанию используется name -->
<description>COM_MYWALKS_XML_DESCRIPTION</description>
<namespace path="src">J4xdemos\Component\Mywalks</namespace>
<install> <!-- Запускается при установке -->
<sql>
<file driver="mysql" charset="utf8">sql/install.mysql.sql</file>
</sql>
</install>
<uninstall> <!-- Запускается при удалении -->
<sql>
<file driver="mysql" charset="utf8">sql/uninstall.mysql.sql</file>
</sql>
</uninstall>
<!-- Раздел копирования основных файлов фронтенда сайта -->
<!-- Обратите внимание на атрибут folder: этот атрибут описывает папку,
из которой нужно скопировать в пакете для установки, поэтому
файлы, скопированные в этом разделе, копируются из /site/ в пакете. -->
<files folder="site">
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="site">
<language tag="en-GB">language/en-GB/com_mywalks.ini</language>
</languages>
<administration>
<files folder="admin">
<file>access.xml</file>
<file>config.xml</file>
<folder>forms</folder>
<folder>services</folder>
<folder>sql</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_mywalks.ini</language>
<language tag="en-GB">language/en-GB/com_mywalks.sys.ini</language>
</languages>
<menu img="class:default" link="option=com_mywalks">com_mywalks</menu>
</administration>
</extension>
Пространство имен
Обратите внимание на тег namespace
(пространства имен) в файле манифеста. Первым элементом должно быть название компании. У меня его нет, поэтому я использовал J4xdemos
. Пространство имен используется в расширении, чтобы отличать его код от кода в других расширениях, которые могут иметь идентичные имена классов. Пространство имен используется для регистрации поставщика услуг - см. Ниже.
Второй элемент - это тип расширения:
- Компонент (Component),
- Модуль (Module),
- Плагин (Plugin),
- Шаблон (Template).
Третий элемент - это имя расширения без добавления com_
, mod_
и т. Д., В данном случае Mywalks
.
Атрибут пространства имен path="src"
указывает, что все файлы, содержащие код пространства имен, будут найдены в каталоге src
.
Файлы языковых констант
Если вы не знакомы с расширениями Joomla, языковая папка исходного сайта содержит один файл: en-GB.com_mywalks.ini
, который содержит переведенные значения фиксированных строк, используемых для перевода с английского на другие языки. Структура папок проста:
site - папка, содержащая файлы сайта
language - папка, содержащая файл языкового перевода сайта
en-GB - папка с английскими переводами
com_mywalks.ini - файл языковых констант
И com_mywalks.ini
имеет такое содержание:
COM_MYWALKS_LIST_DESCRIPTION="Description"
COM_MYWALKS_LIST_DISTANCE="Distance in Km"
COM_MYWALKS_LIST_LAST_VISIT="Last Visit"
COM_MYWALKS_LIST_NVISITS="nVisits"
COM_MYWALKS_LIST_PAGE_HEADING="List of Walks"
COM_MYWALKS_LIST_TABLE_CAPTION="List of Walks"
COM_MYWALKS_LIST_TITLE="Title"
COM_MYWALKS_ERROR_WALK_NOT_FOUND="Walk not found!"
COM_MYWALKS_WALK_DATE="Visit date"
COM_MYWALKS_WALK_REPORTS="Walk Reports"
COM_MYWALKS_WALK_WEATHER="Weather Report"
Для каждой строки первая часть является ключом, а вторая часть - ее значением, английским переводом. Любой фиксированный текст, требующий перевода в интерфейсе сайта компонента должен быть в этом файле. Например, заголовки столбцов списка обходов должны быть ключами в исходном коде и переведены здесь. Также обратите внимание, что основным языком Joomla является британский английский. Для других языков требуются отдельные файлы перевода. По соглашению ключи следует отсортировать в алфавитном порядке!
Языковые файлы администратора: Смотри в следующей статье!
Файлы фронтенда сайта
Вы можете заметить, что некоторые имена папок и файлов Joomla 4.x начинаются с заглавных букв, а другие начинаются с строчных букв. J4 также имеет отличную от J3 структуру. Возможно, в продакшен версии Joomla 4 все имена будут приведены к единому виду.
Кроме того, имейте в виду, что код, необходимый для отображения представлений сайта, также включает некоторые функции из кода администратора, описанные ниже.
tmpl файлы (Файлы вида)
Файлы tmpl содержат код, отображающий виды страниц. Их должно быть проще всего объяснить и понять. Структура файла tmpl в исходном коде выглядит так:
site
tmpl
mywalk
default.php
mywalks
default-items.php
default.php
default.xml
Вид отображения одной прогулки - tmpl/mywalk/default.php
:
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @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;
?>
<div class="page-header">
<h1><?php echo $this->item->title; ?></h1>
</div>
<p><?php echo $this->item->description; ?>!</p>
<h2><?php echo Text::_('COM_MYWALKS_WALK_REPORTS'); ?></h2>
<div class="table-responsive">
<table class="table table-striped">
<caption><?php echo Text::_('COM_MYWALKS_WALK_REPORTS'); ?></caption>
<thead>
<tr>
<th scope="col"><?php echo Text::_('COM_MYWALKS_WALK_DATE'); ?></th>
<th scope="col"><?php echo Text::_('COM_MYWALKS_WALK_WEATHER'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->reports as $id => $report) : ?>
<tr>
<td><?php echo $report->date; ?></td>
<td><?php echo $report->weather; ?></td>
</tr>
<?php endforeach; ?><?php //endif; ?>
</tbody>
</table>
</div>
Для новичков в Joomla: каждый php-файл начинается с DocBlock, используемого в автоматизированной документации; в файлах с пространством имен следующий оператор - это пространство имен, которое не используется в файлах tmpl; первый исполняемый оператор должен быть всегда определён ('_ JEXEC'
) или дальше скрипт не работает; что гарантирует, что файл загружен Joomla, а не вызывается напрямую через веб-адрес.
Остальные строки выводят название прогулки, описание и список посещений, извлеченных из базы данных. Оператор use Joomla\CMS\Language\Text
загружает класс, который преобразует строковые ключи в строковые значения. Оператор use Joomla\CMS\HTML\HTMLHelper;
закомментирован, потому что в этом файле не используется ни одно из множества украшений HTML. Посмотрите ради интереса на файл, чтобы узнать, что он делает: site_root/libraries/src/HTML/HTMLHelper.php
.
Вид списка прогулок - tmpl/mywalks/default.php
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @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;
HTMLHelper::_('behavior.core');
?>
<h1><?php echo Text::_('COM_MYWALKS_LIST_PAGE_HEADING'); ?></h1>
<div class="com-contact-categories categories-list">
<?php
echo $this->loadTemplate('items');
?>
</div>
Обратите внимание, что операторы use
загружают дополнительные файлы php, используя их пространства имен. Joomla\CMS\HTML\HTMLHelper
добавляет файлы, используемые при отображении страницы, например файлы Javascript, необходимые для сортировки таблиц. Joomla\CMS\Language\Text
добавляет файл, используемый для преобразования фиксированных строковых ключей в их английские значения. Joomla\CMS\Layout\LayoutHelper
был скопирован сюда, когда во время разработки использовалось копирование и вставка из другого места. Он оставлен, но закомментирован, чтобы проиллюстрировать, что у меня может быть много случаев случайного кода, который ничего не делает, кроме использования ресурсов сервера.
Этот файл выводит заголовок страницы, а затем загружает другой файл, default-items.php
, который отображает список прогулок. $this->loadTemplate('items')
использует код библиотеки, чтобы найти файл default_items.php
в том же каталоге, в котором он был вызван.
Список items - tmpl/mywalks/default_items.php
Обратите внимание на создание ярлыка путем преобразования заголовка в буквенно-числовые символы в нижнем регистре только с заменой пробелов знаками минус.
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @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\Router\Route;
use J4xdemos\Component\Mywalks\Site\Helper\RouteHelper as MywalksHelperRoute;
?>
<div class="table-responsive">
<table class="table table-striped">
<caption><?php echo Text::_('COM_MYWALKS_LIST_TABLE_CAPTION'); ?></caption>
<thead>
<tr>
<th scope="col"><?php echo Text::_('COM_MYWALKS_LIST_TITLE'); ?></th>
<th scope="col"><?php echo Text::_('COM_MYWALKS_LIST_DESCRIPTION'); ?></th>
<th scope="col"><?php echo Text::_('COM_MYWALKS_LIST_DISTANCE'); ?></th>
<th scope="col"><?php echo Text::_('COM_MYWALKS_LIST_LAST_VISIT'); ?></th>
<th scope="col"><?php echo Text::_('COM_MYWALKS_LIST_NVISITS'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($this->items as $id => $item) :
$slug = preg_replace('/[^a-z\d]/i', '-', $item->title);
$slug = strtolower(str_replace(' ', '-', $slug));
?>
<tr>
<td><a href="/<?php echo Route::_(MywalksHelperRoute::getWalkRoute($item->id, $slug)); ?>">
<?php echo $item->title; ?></a></td>
<td><?php echo $item->description; ?></td>
<td><?php echo $item->distance; ?></td>
<td><?php echo $item->last_visit //$item->lastvisit; ?></td>
<td><?php echo $item->nvisits; ?></td>
</tr>
<?php endforeach; ?><?php //endif; ?>
</tbody>
</table>
</div>
Также обратите внимание на статический вызов Route
, который используется для создания URL-адреса для ссылки на отдельное описание прогулки. И обратите внимание на связанный с ним вызов use
, который сообщает загрузчику, где найти требуемый класс и функцию. Подробнее о маршрутизации позже.
Это отрывок из функции getWalkRoute
:
public static function getWalkRoute($id, $slug, $language = 0, $layout = null)
{
// Создание URL ссылки
$link = 'index.php?option=com_mywalks&view=mywalk&id=' . $id . '&slug=' . $slug;
if ($language && $language !== '*' && Multilanguage::isEnabled())
{
$link .= '&lang=' . $language;
}
if ($layout)
{
$link .= '&layout=' . $layout;
}
return $link;
}
Получение данных - файлы HtmlView
Предполагается, что файлы tmpl имеют дело исключительно с html. Любые данные, необходимые для создания html, такие как список прогулок, должны храниться в переменных в файлах HtmlView, где они становятся доступными в объекте $this
.
Файл HtmlView.php
для просмотра одиночной прогулки
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Site\View\Mywalk;
defined('_JEXEC') or die;
//use Joomla\CMS\HTML\HTMLHelper;
//use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
/**
* HTML Mywalk View class для компонента Mywalks
*
* @since 1.5
*/
class HtmlView extends BaseHtmlView
{
/**
* Состояние модели item
*
* @var \Joomla\Registry\Registry
* @since 1.6
*/
protected $state;
/**
* Детали объекта item
*
* @var \JObject
* @since 1.6
*/
protected $item;
protected $reports;
/**
* Выполнить и отобразить шаблон по сценарию.
*
* @param string $tpl Имя анализируемого файла шаблона; автоматически просматривает пути к шаблонам.
*
* @return mixed Строка в случае успеха, в противном случае - объект ошибки.
*/
public function display($tpl = null)
{
$state = $this->get('State');
$item = $this->get('Item');
$reports = $this->get('Reports');
$this->state = &$state;
$this->item = &$item;
$this->reports = &$reports;
// Проверка на ошибки.
if (count($errors = $this->get('Errors')))
{
throw new GenericDataException(implode("\n", $errors), 500);
}
return parent::display($tpl);
}
}
Функция display
очень проста. Он извлекает из модели данные о состоянии, одиночной прогулке и отчетах об этой прогулке. Если какой-либо из шагов извлечения данных возвращает ошибку, он генерирует исключение, что обычно приводит к появлению какой-либо страницы с сообщением об ошибке. В противном случае управление передается через Joomla в файл tmpl для создания вывода html. Файлы HtmlView могут быть довольно сложными.
Файл HtmlView для списка прогулок
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Site\View\Mywalks;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
//use Joomla\CMS\HTML\HTMLHelper;
//use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
//use Joomla\CMS\Router\Route;
/**
* Список прогулок View class
*
* @since 1.6
*/
class HtmlView extends BaseHtmlView
{
/**
* Состояние модели элемента item
*
* @var \Joomla\Registry\Registry
* @since 1.6.0
*/
protected $state;
/**
* Детали элемента item
*
* @var \JObject
* @since 1.6.0
*/
protected $items;
/**
* Объект разбивки на страницы
*
* @var \JPagination
* @since 1.6.0
*/
protected $pagination;
/**
* Параметры страницы
*
* @var \Joomla\Registry\Registry|null
* @since 4.0.0
*/
protected $params = null;
/**
* Метод отображения представления.
*
* @param string $tpl Имя анализируемого файла шаблона; автоматически просматривает пути к шаблонам.
*
* @return mixed \Исключение при неудаче, недействительно при успехе.
*
* @since 1.6
*/
public function display($tpl = null)
{
$app = Factory::getApplication();
$params = $app->getParams();
// Get some data from the models
$state = $this->get('State');
$items = $this->get('Items');
$pagination = $this->get('Pagination');
// Флаг указывает на то, что не следует добавлять limitstart=0 в URL
$pagination->hideEmptyLimitstart = true;
// Проверка на ошибки.
if (count($errors = $this->get('Errors')))
{
throw new GenericDataException(implode("\n", $errors), 500);
}
$this->state = &$state;
$this->items = &$items;
$this->params = &$params;
$this->pagination = &$pagination;
return parent::display($tpl);
}
}
Готовы к моделям?
Получение данных - файлы модели
Для модели одиночной прогулки нам нужен файл модели, который реализует populateState
, getItem
и getVisits
. Для списка прогулок нам нужны populateState
, getListQuery
, getItems
и некоторые другие для сортировки по столбцам и разбивки на страницы длинных списков, ни один из которых не реализован в этом руководстве.
Файл модели: MywalkModel
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\ItemModel;
/**
* Mywalk Component Mywalk Model
*
* @since 1.5
*/
class MywalkModel extends ItemModel
{
/**
* Строка контекста модели.
*
* @var string
*/
protected $_context = 'com_mywalks.mywalk';
/**
* Метод автоматического заполнения состояния модели.
*
* Примечание. Вызов getState в этом методе приведет к рекурсии.
*
* @since 1.6
*
* @return void
*/
protected function populateState()
{
$app = Factory::getApplication();
// Состояние загрузки из запроса.
$pk = $app->input->getInt('id');
$this->setState('mywalk.id', $pk);
$offset = $app->input->getUInt('limitstart');
$this->setState('list.offset', $offset);
// Загрузка параметров.
$params = $app->getParams();
$this->setState('params', $params);
}
/**
* Метод получения данных о прогулке.
*
* @param integer $pk Идентификатор прогулки.
*
* @return object|boolean Объект данных пункта меню в случае успеха, false
*/
public function getItem($pk = null)
{
$pk = (!empty($pk)) ? $pk : (int) $this->getState('mywalk.id');
try
{
$db = $this->getDbo();
$query = $db->getQuery(true)
->select(
$this->getState(
'item.select', 'a.*'
)
);
$query->from('#__mywalks AS a')
->where('a.id = ' . (int) $pk);
$db->setQuery($query);
$data = $db->loadObject();
if (empty($data))
{
throw new \Exception(Text::_('COM_MYWALKS_ERROR_WALK_NOT_FOUND'), 404);
}
}
catch (\Exception $e)
{
if ($e->getCode() == 404)
{
// Чтобы перенаправление отработало, необходимо пройти через обработчик ошибок.
throw new \Exception($e->getMessage(), 404);
}
else
{
$this->setError($e);
$this->_item[$pk] = false;
}
}
return $data;
}
/**
* Метод получения данных о посещениях с прогулкой.
*
* @param integer $pk The id of the walk.
*
* @return object|boolean Объект данных пункта меню в случае успеха, false
*/
public function getReports($pk = null)
{
$pk = (!empty($pk)) ? $pk : (int) $this->getState('mywalk.id');
try
{
$db = $this->getDbo();
$query = $db->getQuery(true)
->select('b.*');
$query->from('#__mywalk_dates AS b')
->where('b.walk_id = ' . (int) $pk);
$query->order('`date` DESC');
$db->setQuery($query);
$data = $db->loadObjectList();
// Совершенно нормально прогуляться без данных о посещениях - обработка вид.
}
catch (\Exception $e)
{
if ($e->getCode() == 404)
{
// Чтобы перенаправление работало, необходимо пройти через обработчик ошибок.
throw new \Exception($e->getMessage(), 404);
}
else
{
$this->setError($e);
$this->_item[$pk] = false;
}
}
return $data;
}
}
Файл модели: MywalksModel
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\ListModel;
/**
* Эта модель поддерживает получение списков статей.
*
* @since 1.6
*/
class MywalksModel extends ListModel
{
/**
* Constructor.
*
* @param array $config Необязательный ассоциативный массив настроек конфигурации.
*
* @see \JController
* @since 1.6
*/
public function __construct($config = array())
{
if (empty($config['filter_fields']))
{
$config['filter_fields'] = array(
'id', 'a.id',
'title', 'a.title',
);
}
parent::__construct($config);
}
/**
* Метод автоматического заполнения состояния модели.
*
* Этот метод следует вызывать только один раз для каждого экземпляра и
* предназначен для вызова при первом вызове метода getState(),
* если не установлен флаг конфигурации модели для игнорирования запроса.
*
* Примечание. Вызов getState в этом методе приведет к рекурсии.
*
* @param string $ordering Необязательное поле для сортировки.
* @param string $direction Необязательное направление сортировки (asc | desc).
*
* @return void
*
* @since 3.0.1
*/
protected function populateState($ordering = 'ordering', $direction = 'ASC')
{
$app = Factory::getApplication();
// Информация о состоянии списка
$value = $app->input->get('limit', $app->get('list_limit', 0), 'uint');
$this->setState('list.limit', $value);
$value = $app->input->get('limitstart', 0, 'uint');
$this->setState('list.start', $value);
$orderCol = $app->input->get('filter_order', 'a.id');
if (!in_array($orderCol, $this->filter_fields))
{
$orderCol = 'a.id';
}
$this->setState('list.ordering', $orderCol);
$listOrder = $app->input->get('filter_order_Dir', 'ASC');
if (!in_array(strtoupper($listOrder), array('ASC', 'DESC', '')))
{
$listOrder = 'ASC';
}
$this->setState('list.direction', $listOrder);
$params = $app->getParams();
$this->setState('params', $params);
//$this->setState('layout', $app->input->getString('layout'));
}
/**
* Метод получения идентификатора на основе состояния конфигурации модели.
*
* Это необходимо, потому что модель используется компонентом
* и разными модулями, которым могут потребоваться
* разные наборы данных или разные требования к порядку.
*
* @param string $id Префикс для идентификатора id.
*
* @return string Идентификатор id.
*
* @since 1.6
*/
protected function getStoreId($id = '')
{
// Получение id.
return parent::getStoreId($id);
}
/**
* Получить главный запрос для получения списка прогулок в зависимости от состояния модели.
*
* @return \JDatabaseQuery
*
* @since 1.6
*/
protected function getListQuery()
{
// Получить текущего пользователя для проверки авторизации
$user = Factory::getUser();
// Создать новый объект запроса.
$db = $this->getDbo();
$query = $db->getQuery(true);
// Выберать необходимые поля из таблицы.
$query->select(
$this->getState(
'list.select',
'a.*,
(SELECT MAX(`date`) from #__mywalk_dates WHERE walk_id = a.id) AS last_visit,
(SELECT count(`date`) from #__mywalk_dates WHERE walk_id = a.id) AS nvisits
')
);
$query->from('#__mywalks AS a');
$params = $this->getState('params');
// Добавить данные о порядке списка.
$query->order($this->getState('list.ordering', 'a.id') . ' ' . $this->getState('list.direction', 'ASC'));
return $query;
}
/**
* Метод получения списка прогулок.
*
* Переопределение для вставки преобразования поля attribs в объект \JParameter.
*
* @return mixed Массив объектов в случае успеха, false в случае неудачи.
*
* @since 1.6
*/
public function getItems()
{
$items = parent::getItems();
return $items;
}
/**
* Метод получения начального количества элементов для набора данных.
*
* @return integer Начальное количество элементов, доступных в наборе данных.
*
* @since 3.0.1
*/
public function getStart()
{
return $this->getState('list.start');
}
}
Control Flow
Запуск компонента - Контроллер
Стоит помнить, что URL-адрес страницы со списком прогулок, не относящийся к SEF, - это index.php?option=com_mywalks&task=display&view=mywalks
. Часть task
часто не учитывается, и в этом случае устанавливается task
по умолчанию для вида. Если часть вида не указана, компонент должен установить вид по умолчанию.
Каждый запрос страницы начинается с последовательности инициализации. После этого точки входа в компонент находятся через их файлы контроллеров. Вид компонентов по умолчанию отображается, поэтому неудивительно, что контроллером по умолчанию является DisplayController
. Этот контроллер не делает ничего, кроме вызова своего родительского контроллера. Однако для начальной обработки можно использовать контроллеры. Например, если форма отправляется, обычно проверяют токен формы и отменяют дальнейшие действия, если он недействителен.
DisplayController
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Site\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
/**
* Mywalks Component Controller
*
* @since 1.5
*/
class DisplayController extends BaseController
{
/**
* Method to display a view.
*
* @param boolean $cachable Если true, вывод представления будет кэширован.
* @param array $urlparams Массив безопасных параметров URL и их типов переменных, допустимые значения см. {@link \JFilterInput::clean()}.
*
* @return static Этот объект поддерживает chaining.
*
* @since 1.5
*/
public function display($cachable = false, $urlparams = array())
{
return parent::display();
}
}
Основные файлы администратора
Хотя мы все еще разрабатываем код для отображения сайта, необходим некоторый код администратора. Файл services/provider.php
используется для загрузки компонента, либо для отображения его собственных представлений сайта, либо для использования модулем меню для создания пунктов меню.
The services provider file: administrator/components/com_mywalks/services/provider.php
Обратите особое внимание на строки, начинающиеся с $container->registerServiceProvider
, поскольку именно здесь ваш код регистрируется в контейнере для использования позже.
<?php
/**
* @package Mywalks.Administrator
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
//use Joomla\CMS\Categories\CategoryFactoryInterface;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
//use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Extension\Service\Provider\CategoryFactory;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
use Joomla\CMS\HTML\Registry;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use J4xdemos\Component\Mywalks\Administrator\Extension\MywalksComponent;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
/**
* The mywalks service provider.
*
* @since 4.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)
{
$container->registerServiceProvider(new CategoryFactory('\\J4xdemos\\Component\\Mywalks'));
$container->registerServiceProvider(new MVCFactory('\\J4xdemos\\Component\\Mywalks'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\J4xdemos\\Component\\Mywalks'));
$container->registerServiceProvider(new RouterFactory('\\J4xdemos\\Component\\Mywalks'));
$container->set(
ComponentInterface::class,
function (Container $container)
{
$component = new MywalksComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setRegistry($container->get(Registry::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
// $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
return $component;
}
);
}
};
The component boot file: administrator/components/com_mywalks/src/Extension/MywalksComponent.php
<?php
/**
* @package Joomla.Administrator
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Administrator\Extension;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Component\Router\RouterServiceInterface;
use Joomla\CMS\Component\Router\RouterServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use Psr\Container\ContainerInterface;
/**
* Component class for com_mywalks
*
* @since 4.0.0
*/
class MywalksComponent extends MVCComponent implements
BootableExtensionInterface, RouterServiceInterface
{
use RouterServiceTrait;
use HTMLRegistryAwareTrait;
/**
* Загрузка расширения. Это функция для настройки среды расширения,
* например, для регистрации новых загрузчиков классов и т.д.
*
* При необходимости можно выполнить некоторую первоначальную
* настройку из служб контейнера, например. регистрация HTML-сервисов.
*
* @param ContainerInterface $container The container
*
* @return void
*
* @since 4.0.0
*/
public function boot(ContainerInterface $container)
{
//$this->getRegistry()->register('mywalksadministrator', new AdministratorService);
}
}
На данный момент обращение вызова к регистрации Administrator Service закомментирован. Это приводит к ошибке времени выполнения при вызове компонента Mywalks из интерфейса администратора. См. часть 2.
The Component Router
На этом этапе работает компонент com_mywalks
. Для перехода к списку прогулок нужен один пункт меню. Есть загвоздка: в списке прогулок ссылки на отдельные прогулки примерно такие:
/site-root/my-walks.html?view=mywalk&id=1
(где корень сайта мой или не может быть деревом вложенных папок). Пришло время сделать собственный роутер SEF? И сделайте перерыв, чтобы прочитать Поддержка URL-адресов SEF в вашем компоненте. У меня есть другой пакет Joomla, который использует URL-адреса SEF в форме [domain]/XXX/YY/page-title.html
, где XXX
- это код филиала организации, а YY
- код языка. Некоторые ветки используют несколько языков. Нестандартно! Да, но именно об этом и просил заказчик.
Для компонента mywalks я хочу использовать отдельные URL-адреса прогулки, например:
/site-root/mywalks/walk-n/walk-title.html
Где n
- индивидуальный идентификатор прогулки, а название прогулки автоматически генерируется из фактического названия. На самом деле ни walk-title
, ни .html
не нужны. Первое - за дружелюбие, второе - за то, что я старомоден.
Нет пунктов меню для индивидуальных прогулок. Они никому не нужны, и их невозможно создать. Требуется настраиваемый маршрутизатор, состоящий из двух файлов: Router.php
и MywalksNomenuRules.php
.
The Router File: component/com_mywalks/src/Service/Router.php
<?php
/**
* @package Mywalks.Site
* @subpackage com_mywalks
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Site\Service;
defined('_JEXEC') or die;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Categories\CategoryFactoryInterface;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Component\Router\Rules\MenuRules;
//use Joomla\CMS\Component\Router\Rules\NomenuRules;
use J4xdemos\Component\Mywalks\Site\Service\MywalksNomenuRules as NomenuRules;
use Joomla\CMS\Component\Router\Rules\StandardRules;
use Joomla\CMS\Menu\AbstractMenu;
use Joomla\Database\DatabaseInterface;
/**
* Routing class of com_mywalks
*
* @since 3.3
*/
class Router extends RouterView
{
protected $noIDs = false;
/**
* The category factory
*
* @var CategoryFactoryInterface
*
* @since 4.0.0
*/
private $categoryFactory;
/**
* The db
*
* @var DatabaseInterface
*
* @since 4.0.0
*/
private $db;
/**
* Mywalks Component router constructor
*
* @param SiteApplication $app Объект приложения
* @param AbstractMenu $menu Объект меню для работы
* @param CategoryFactoryInterface $categoryFactory Объект категории
* @param DatabaseInterface $db Объект базы данных
*/
public function __construct(SiteApplication $app, AbstractMenu $menu,
CategoryFactoryInterface $categoryFactory, DatabaseInterface $db)
{
$this->categoryFactory = $categoryFactory;
$this->db = $db;
$params = ComponentHelper::getParams('com_mywalks');
$this->noIDs = (bool) $params->get('sef_ids');
$mywalks = new RouterViewConfiguration('mywalks');
$mywalks->setKey('id');
$this->registerView($mywalks);
$mywalk = new RouterViewConfiguration('mywalk');
$mywalk->setKey('id');
$this->registerView($mywalk);
parent::__construct($app, $menu);
$this->attachRule(new MenuRules($this));
$this->attachRule(new StandardRules($this));
$this->attachRule(new NomenuRules($this));
}
}
Обратите внимание на строки, которые определяют и используют настраиваемые правила:
use Joomla\Component\Mywalks\Site\Service\MywalksNomenuRules as NomenuRules;
...
$this->attachRule(new NomenuRules($this));
Правила включают функцию build
для создания ссылок на отдельные прогулки и функцию синтаксического анализа для преобразования входящего URL-адреса SEF во внутренний маршрут Joomla. Не нужно беспокоиться о ссылке в пункте меню, так как это регулируется правилами MenuRules.
The Router Rules file: components/my_walks/src/Service/MywalksNomenuRules.php
<?php
/**
* Joomla! Content Management System
*
* @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace J4xdemos\Component\Mywalks\Site\Service;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\Rules\RulesInterface;
/**
* Правило обработки URL без пункта меню
*
* @since 3.4
*/
class MywalksNomenuRules implements RulesInterface
{
/**
* Маршрутизатор, которому принадлежит это правило
*
* @var RouterView
* @since 3.4
*/
protected $router;
/**
* Class constructor.
*
* @param RouterView $router Маршрутизатор, которому принадлежит это правило
*
* @since 3.4
*/
public function __construct(RouterView $router)
{
$this->router = $router;
}
/**
* Dummymethod для выполнения требований интерфейса
*
* @param array &$query Массив запросов для обработки
*
* @return void
*
* @since 3.4
* @codeCoverageIgnore
*/
public function preprocess(&$query)
{
$test = 'Test';
}
/**
* Parse a menu-less URL
*
* @param array &$segments Сегменты URL для анализа
* @param array &$vars Части, полученные в результате сегментации
*
* @return void
*
* @since 3.4
*/
public function parse(&$segments, &$vars)
{
//with this url: http://localhost/j4x/my-walks/mywalk-n/walk-title.html
// segments: [[0] => mywalk-n, [1] => walk-title]
// vars: [[option] => com_mywalks, [view] => mywalks, [id] => 0]
$vars['view'] = 'mywalk';
$vars['id'] = substr($segments[0], strpos($segments[0], '-') + 1);
array_shift($segments);
array_shift($segments);
return;
}
/**
* Создание URL-адреса без меню
*
* @param array &$query Части, которые нужно преобразовать
* @param array &$segments Сегменты URL для сборки
*
* @return void
*
* @since 3.4
*/
public function build(&$query, &$segments)
{
// content of $query ($segments is empty or [[0] => mywalk-3])
// when called by the menu: [[option] => com_mywalks, [Itemid] => 126]
// when called by the component: [[option] => com_mywalks, [view] => mywalk, [id] => 1, [Itemid] => 126]
// when called from a module: [[option] => com_mywalks, [view] => mywalks, [format] => html, [Itemid] => 126]
// when called from breadcrumbs: [[option] => com_mywalks, [view] => mywalks, [Itemid] => 126]
// the url should look like this: /site-root/mywalks/walk-n/walk-title.html
// if the view is not mywalk - the single walk view
if (!isset($query['view']) || (isset($query['view']) && $query['view'] !== 'mywalk') || isset($query['format']))
{
return;
}
$segments[] = $query['view'] . '-' . $query['id'];
// последняя часть URL-адреса может отсутствовать
if (isset($query['slug'])) {
$segments[] = $query['slug'];
unset($query['slug']);
}
unset($query['view']);
unset($query['id']);
}
}
Когда есть пункт меню для страницы списка mywalks, функция сборки MywalksNomenuRules
будет вызываться для каждой внутренней ссылки на странице: в модулях, меню и даже статьях с контентом. Так что следите за сообщениями об ошибках во время выполнения.
И наконец
То есть он - рабочий компонент, но работает пока только на стороне фронтенда сайта!
Читать дальше >> «Учебное пособие по компонентам Joomla 4: Mywalks, Часть 2 - Код админки»
Перевод с аглицкого:
https://docs.joomla.org/Part_1:_The_Site_code
Заберите ссылку на статью к себе, чтобы потом легко её найти!
Раз уж досюда дочитали, то может может есть желание рассказать об этом месте своим друзьям, знакомым и просто мимо проходящим?
Не надо себя сдерживать! ;)