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: 

  1. Tell the system that certain pieces of text are URLs and need to be transformed.
  2. 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.

  1. The option defaults to the name of the component currently being executed.
  2. 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:

  1. Checking if the language is multilingual, and if so adding the appropriate language segment.
  2. 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.
  3. 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:

  1. List of categories (view=categories)
  2. Single category (view=category)
  3. 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.

  1. get[Viewname]Segment($id, $query)
  2. 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.