Да, да :) Мы снова о нём - о роутере Joomla. Его ругают, его не любят, но с ним приходится жить. Но похоже на то, что наши "мучения" скоро закончатся. 20 февраля 2016 года произошло знаменательное событие - в ветку Joomla 3.6.x был влит Pull Request базовых классов для нового роутера Joomla.
Немного истории
Всё началось почти два года назад - в апреле 2014 года Hannes Papenberg открыл компанию по сбору средств на разработку нового роутера Joomla, которая завершилась успехом - средства были собраны. Hannes приступил к работе и первый Pull Request был готов уже в июне 2014 года. После этого была ещё серия Pull Request, которые постепенно подготавливали основу, но так и не были влиты по тем или иным причинам.
В итоге Hannes объединил их в один большой Pull Request, который был влит в ветку Joomla 3.6.x (к слову, был влит не полностью, о чём я уже сообщил виновнику кривого коммита).
Но на этом история не заканчивается - Hannes в своей компании обещал плагины, которые поддерживали бы различные варианты роутинга. Вот что он пишет по этому поводу:
I know that you are waiting for the new features that I described in this campaign. Please don't be alarmed that these are not present in that branch yet. What has been done so far was the ground work to get these new features in and I will soon provide the necessary code for this. Compare it with a car: What I've been working on is to get the engine running and the breaks and steering working. Now we can add the new features, like the color of the car, heated seats, navigation and the stereo.
Я знаю, что вы ждёте новые возможности, которые я описал в этой компании. Пожалуйста, не волнуйтесь о том, что их пока что нет в текущей ветке. Что было сделано на данный момент - это основа для внедрения новых возможностей, и я скоро предоставлю необходимый для этого код. Сравните это с машиной: то, над чем я работал, это возможность завести двигатель, заставить работать тормоза и рулевое управление. Теперь мы можем добавлять новые фишки, такие как цвет машины, сиденья с подогревом, навигация и стерео.
Каким будет роутер?
Вот это вопрос на миллион. Что там внутри? Я решил провести небольшой первичный анализ кода Pull Request, чтобы понять, каким будет новый роутер Joomla. Хочу сразу предупредить, что я могу ошибаться, так как никакой документации пока ещё нет.
Основные моменты
Хочу выделить несколько основных моментов:
- новый роутер будет включаться на уровне компонентов. В настройки каждого стандартного компонента добавлена соответствующая опция
sef_advanced
; - роутинг будет строится на основе правил, и на данный момент их несколько: Standard (если
sef_advanced
включен), Legacy (еслиsef_advanced
выключен), Menu и Nomenu; - регистрирация конфигурации представлений: каждое представление (view) компонента должно быть зарегистрировано в роутере как объект класса
JComponentRouterViewconfiguration
.
Базовые классы
Все базовые классы роутера компонента находятся в /libraries/cms/component/router.
JComponentRouterInterface
Интерфейс роутера компонента, который содержит три метода:
preprocess($query)
- используется для валидации и завершения URL параметров. Например, может быть использован для добавленияItemid
илиlanguage
параметров. Метод вызывается для каждого URL независимо от того, включен ли SEF или нет;build(&$query)
- используется для трансформации URL из query параметров в человекочитаемый формат. Метод вызывается только при включенном SEF;parse(&$segments)
- используется для трансформации URL из человекочитаемого формата обратно в query параметры. Метод вызывается только при включенном SEF.
JComponentRouterBase
Базовый абстрактный класс роутера компонента. Реализует JComponentRouterInterface
. Его основаная задача - это установка объектов приложения и меню.
JComponentRouterLegacy
Базовый класс для устаревшей версии роутера. Реализует JComponentRouterInterface
. Это класс по умолчанию для компонентов с устаревшим роутером (который включал методы *BuildRoute
и *ParseRoute
) или у которых нет своего роутера.
JComponentRouterView
Класс роутера компонента в котором за основу взято представление. Расширяет JComponentRouterBase.
Это по сути реализация нового роутера стандартных компонентов Joomla. Его основные задачи:
- регистрация представлений в виде объекта класса
JComponentRouterViewconfiguration
(методregisterView
); - добавление/удаление одного или сразу нескольких правил, реализующих интерфейс
JComponentRouterRulesInterface
(методыattachRule
,detachRule
,attachRules
,detachRules
); - получение полного пути - массива списка всех представлений, включая ID элементов контента, через обращение к методам объекта
JComponentRouterViewconfiguration
и своим методамget<Viewname>Segment
(методgetPath
); - обход правил в методах
preprocess
,build
иparse
.
JComponentRouterViewconfiguration
Конфигурационный класс представления для роутинга компонента, в котором за основу взято представление.
Содержит в себе имя представления (например article, category) , ключ (например id, catid), родительский объект JComponentRouterViewconfiguration
(если есть, например для материала это может быть объект категории), признак вложенности, список поддерживаемых макетов в виде массива (default, blog, form и т.п.), а также полный путь в виде массива от этого представления до корневого.
JComponentRouterRulesInterface
Интерфейс для правил находится в подпапке /rules и содержит в себе такие же методы, как и интерфейс роутера компонента JComponentRouterInterface
, но с другой сигнатурой. Эти методы вызывает класс роутера компонента, когда делает обход всех добавленных в него правил.
Правила
Также в подпапке /rules находятся различные правила роутинга, которые реализуют JComponentRouterRulesInterface
.
JComponentRouterRulesMenu
- это правило на основе меню, строит массив меню и выполняет поиск Itemid. Это то, что обычно разработчики делали в хелпере компонентов.
JComponentRouterRulesNomenu
- это правило предполагает отсутствие активного пункта меню.
JComponentRouterRulesStandard
- это правило стандартной обработки роутинга. Использует методы get<Viewname>Id
ротуера компонента для получения ID элементов контента.
Изменения в компонентах
Понятно, что простое изучение классов особо ничего не даст без реальных примеров. Так как документации не существует, будем ориентироваться на стандартные компоненты Joomla. Благо Pull Request включает в себя изменённые роутеры компонентов com_contact, com_content, com_newsfeed и com_users.
Рассмотрим в качестве примера роутер комопнента com_content.
Главное, что отличает новый класс роутера - он расширяет класс JComponentRouterView
. В конструкторе регистрируются все представления компонента и добавляются правила: всегда правило Menu, а также, если в настройках компонента включена опция sef_advanced
, добавляется правило Standard. Если опция выключена, то добавляется правило Legacy. Все текущие роутеры были переделаны в Legacy правила и перемещены в /helpers/legacyrouter.php компонентов.
/**
* Content Component router constructor
*
* @param JApplicationCms $app The application object
* @param JMenu $menu The menu object to work with
*/
public function __construct($app = null, $menu = null)
{
$categories = new JComponentRouterViewconfiguration('categories');
$categories->setKey('id');
$this->registerView($categories);
$category = new JComponentRouterViewconfiguration('category');
$category->setKey('id')->setParent($categories, 'catid')->setNestable()->addLayout('blog');
$this->registerView($category);
$article = new JComponentRouterViewconfiguration('article');
$article->setKey('id')->setParent($category, 'catid');
$this->registerView($article);
$this->registerView(new JComponentRouterViewconfiguration('archive'));
$this->registerView(new JComponentRouterViewconfiguration('featured'));
$this->registerView(new JComponentRouterViewconfiguration('form'));
parent::__construct($app, $menu);
$this->attachRule(new JComponentRouterRulesMenu($this));
$params = JComponentHelper::getParams('com_content');
if ($params->get('sef_advanced', 0))
{
$this->attachRule(new JComponentRouterRulesStandard($this));
}
else
{
require_once JPATH_SITE . '/components/com_content/helpers/legacyrouter.php';
$this->attachRule(new ContentRouterRulesLegacy($this));
}
}
Реализуются методы get<Viewname>Segment
для получения сегментов:
/**
* Method to get the segment(s) for a category
*
* @param string $id ID of the category to retrieve the segments for
* @param array $query The request that is build right now
*
* @return array|string The segments of this item
*/
public function getCategorySegment($id, $query)
{
$category = JCategories::getInstance($this->getName())->get($id);
if ($category)
{
return array_reverse($category->getPath());
}
return array();
}
/**
* Method to get the segment(s) for an article
*
* @param string $id ID of the article to retrieve the segments for
* @param array $query The request that is build right now
*
* @return array|string The segments of this item
*/
public function getArticleSegment($id, $query)
{
return array($id);
}
Также реализуются методы get<Viename>Id
для получения ID:
/**
* Method to get the id for a category
*
* @param string $segment Segment to retrieve the ID for
* @param array $query The request that is parsed right now
*
* @return mixed The id of this item or false
*/
public function getCategoryId($segment, $query)
{
if (isset($query['id']))
{
$category = JCategories::getInstance($this->getName())->get($query['id']);
foreach ($category->getChildren() as $child)
{
if ($child->id == (int) $segment)
{
return $child->id;
}
}
}
return false;
}
/**
* Method to get the id for an article
*
* @param string $segment Segment of the article to retrieve the ID for
* @param array $query The request that is parsed right now
*
* @return mixed The id of this item or false
*/
public function getArticleId($segment, $query)
{
return (int) $segment;
}
И больше ничего. Всю остальную работу на себя берут родительский класс и правила.
Все route хелперы (которые мы обычно создавали в /helpers/route.php) тоже полегчали - поиск Itemid теперь тоже работа правила. По сути все методы - это просто обёртки для возврата ссылки типа index.php?option=com_content&view=article&id=' . $id
.
Что в итоге?
Очень похоже на то, что Hannes удалось создать неплохую инфраструктуру нового роутера. Можно сразу отметить, что разработчикам жить становится намного проще, если они будут придерживаться стандартного роутинга. В этом случае всю муторную работу будет брать на себя базовый класс JComponentRouterView
и правила.
Непосредственно реализация правил вроде бы тоже неплоха, но я пока не понимаю, как их можно будет реализовывать в виде плагинов. Объект правила создаётся и потом добавляется в роутер через attachRule()
. Но как создать объект плагина - инстанцировать напрямую? В общем не до конца понятен этот момент... А ведь изначально идея состояла именно в этом - правила в виде плагинов.
Ну и пока рано говорить о ликвидации дублей и ненавистных всем циферок (ID) в адресной строке. Но вероятнее всего, это вопрос реализации конкретного правила.
P.S.
Если вы хотите самостоятельно изучить исходный код, то либо используйте ветку Joomla 3.6.x (но там пока нет некоторых файлов), либо репозитроий Hannes (на момент написания статьи он был актуален).