How to Create Router for Joomla Component
Joomla! has capability of creating and parsing URLs as SEF (Search Engine Friendly) URLs. The SEF URLs follow a certain fixed pattern. User can define a short descriptive text (known as alias) for each segment of the URL.
In Joomla, the local part of a SEF URL (the part after the domain name) is called a route. Creating and processing SEF URLs is referred to as routing, and the relevant code is called a router. Each component is responsible for handling its own SEF URLs. So, you have to create your own router to allow your component to use SEF URLs.
SEF Concept in Joomla!
Joomla system URLs look like this:
https://example.com/index.php?option=com_planets&view=planet&id=1&catid=20&Itemid=50
Your objective is to transform this into SEF URL like this:
https://example.com/example-menu-item/example-category/example-planet
You have two tasks:
- Tell the system that certain pieces of text are URLs and need to be transformed.
- Explain the system how to transform URLs.
Route::_()
It is difficult for Joomla to figure out which parts of the component's output are URLs. To support SEF URLs, you need to change URL-generating code. For example,
Route::_('index.php?view=planets&id=1&catid=20');
You can leave out the parameters option and Itemid.
- The option defaults to the name of the component currently being executed.
- The Itemid defaults to the current menu item's ID.
In the Route::_() method, you pass a query string. If SEF URLs are enabled on the site, then its job is to generate segments of the SEF URL, and it does this by:
- Checking if the language is multilingual, and if so adding the appropriate language segment.
- Adding the route of the menu item passed in the "Itemid=xxx" part of the query string, or if no Itemid passed, then it will take the current ("active") menu item.
- If there are still query parameters which don't match those of the menu item, then it will call the component router's method, passing an array of these query parameters. This method returns the segments of the URL which it should append.
Router Workflow
The router is a single file containing a class that converts system URLs to SEF URLs and vice-versa. This file needs to be placed at the site part of the component.
src/Service/Router.php
The aim of the router is to direct the request to the right component. For example, Joomla parses an SEF URL of the form:
https://example.com/en/menu-1/submenu-a/segment-1/segment-2
Step 1
First, the router removes the domain name part, as it refers to the whole site, and doesn't figure in the routing. So, you are left with the path:
en/menu-1/submenu-a/segment-1/segment-2
Now, the router applies various rules to parse the segments of the URL, parsing from the left hand side. As segments are matched, the router builds up the configuration parameters and removes those matched segments from the URL, before applying the next rule.
Step 2
If the site is multilingual, then it will run the rule to parse the language. In this case it will find 'en' and will set the language to en-GB, and remove it from the path, leaving:
menu-1/submenu-a/segment-1/segment-2
Step 3
Now, the router runs a rule to try to find a matching menu item. It will get all the menu items on the site, and compare the route of each menu item with the leftmost segments of the path.
In this example it might find two matching - one that matches "menu-1" and another that matches "menu-1/submenu-a", in which case it will take the one with the longest match. You, you are now left with
segment-1/segment-2
After finding the menu item, Joomla knows the component, component parameters, and also the formatting and other options which are associated with this menu item.
Step 4
At this point, the router turns to the component to parse the remaining segments. To parse the remaining segments, it calls the parse() method of the component router, and passes in an array of the remaining segments.
This method returns a list of the query parameters (such as id, view), and the router uses these to overwrite any equivalent parameters which were set against the menu item.
Finally, it sets the HTTP Request parameters to the ones it has found, so that when the component is run it can use $input->get() to access them.
Component Router
In general, there can be three views:
- List of categories (view=categories)
- Single category (view=category)
- Single article (view=planet)
When viewing an article:
https://example.com/[menu-alias]/[category]/[article]
The link to the article would look like this:
index.php?view=article&catid=' . $row->catslug . '&id='.$row->slug
When viewing a category:
https://example.com/[menu-alias]/[category]
The Link to the category would look like this:
index.php?view=category&id=' . $row->catslug
When viewing the categories overview:
https://example.com/[menu-alias]
Router Methods
src/Service/Router.php
The RouterView base class handles routing by allowing you to register views into your system. So, first you have to build up your component's router constructor like this:
public function __construct(SiteApplication $app, AbstractMenu $menu)
{
$planets = new RouterViewConfiguration('planets');
$this->registerView($planets);
$planet = new RouterViewConfiguration('planet');
$planet->setKey('id')->setParent($planets);
$this->registerView($planet);
parent::__construct($app, $menu);
$this->attachRule(new MenuRules($this));
$this->attachRule(new StandardRules($this));
$this->attachRule(new NomenuRules($this));
}
Here, you have registered a planet view that has a routing key of its id. You have also registered planets view which which is the parent of planet view.
The next step is to register the rules. There are three rules provided by the Joomla:
1. \Joomla\CMS\Component\Router\Rules\MenuRules
It looks to see if the URL matches a known menu item, and ensures in multilingual sites that a language tag is present.
2. \Joomla\CMS\Component\Router\Rules\StandardRules
It uses your view configuration to build up a menu path.
3. \Joomla\CMS\Component\Router\Rules\NomenuRules
It provides a fallback when there is no good match found for building or parsing the URL.
Now, you have to convert the ids to and from their alias. So for each view registered, you need to provide two methods for building and parsing URLs.
- get[Viewname]Segment($id, $query)
- get[Viewname]Id($segment, $query)
For current example, you will need four functions for two views registered: getCategorySegment, getCategoryId, getArticleSegment and getArticleId
Get alias from id
public function getPlanetSegment($id, $query)
{
$db = Factory::getDbo();
$dbquery = $db->getQuery(true);
$dbquery->select($dbquery->qn('alias'))
->from($db->qn('#__planets'))
->where('id = ' . $db->q($id));
$db->setQuery($dbquery);
$id .= ':' . $db->loadResult();
list($void, $segment) = explode(':', $id, 2);
return array($void => $segment);
}
Get id from alias
public function getPlanetId($segment, $query)
{
$db = Factory::getDbo();
$dbquery = $db->getQuery(true);
$dbquery->select($dbquery->qn('id'))
->from($dbquery->qn('#__planets'))
->where('alias = ' . $dbquery->q($segment));
$db->setQuery($dbquery);
if (!(int) $db->loadResult())
{
return false;
}
return (int) $db->loadResult();
}
Slug
A slug is used to minimise the amount of code you need to support SEF URLs. Slug consists of the numerical identifier (id), a colon (:), and the alias.
For example, consider a SEF URL for an article with
- id: 1
- title: Welcome to Earth
- automatically generated alias: welcome-to-earth
- slug: 1:welcome-to-earth
The two elements (id and alias) can be combined during the database query in the model like this:
$query = 'SELECT a.*, '.
'CASE WHEN CHAR_LENGTH(a.alias) THEN CONCAT_WS(":", a.id, a.alias) ELSE a.id END as slug,'
/*...*/;
The advantage of this method of creating a slug is that you can simply use the slug as a drop-in replacement for the id in most places. For example, you don't need to check for and remove the colon and the alias from the request data manually: if you use Input's int (integer) filter, it will do that automatically.
The building process of \Joomla\CMS\Router\Router is divided into two steps:
1. Create the application route
The application route is fully handled by \Joomla\CMS\Router\Router and the component developer doesn’t have to do anything to make it work.
2. Create the component route
To create the component route, \Joomla\CMS\Router\Router looks for the Router.php in the component site directory which is responsible for building the route for the component. It is not used on admin or back-end pages.