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:

http://www.example.com/index.php?option=com_yourcomponent&view=article&id=1&catid=20&Itemid=50

Your goal is to transform this into SEF URL like this:

 http://www.example.com/example-menu-item/example-category/example-article

You have two tasks: Signalling the system that certain pieces of text are URLs and need to be transformed, and explaining the system how to transform URLs.

JRoute::_

It is difficult for Joomla system 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,

JRoute::_('index.php?view=article&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, and the Itemid defaults to the current menu item's ID.

In the JRoute::_ you pass a query string. If SEF URLs are enabled on the site, then its job is to generate the 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 it's on

  3. If there are still query parameters which don't match those of the menu item, then it will call the component router's build() function, passing an array of these query parameters. This function returns the segments of the URL which it should append.

Joomla Router

The router is a single file containing a class with three functions that convert system URLs to and from SEF URLs. This file needs to be placed at site part of the component:

/components/com_componentname/router.php

The class is called [componentname]Router

For example for com_content, ContentRouter, and it must implement:

Joomla\CMS\Component\Router\RouterInterface

The aim of the router is to direct the request to the right component:

  • Language
  • Formatting options, etc to apply to the web page
  • Parameters such as id, format (eg html/json), view, etc

For example, Joomla parses an SEF URL of the form:

http://www.domainname.net/en/menu1/submenu-a/segment1/segment2

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 a path:

en/menu1/submenu-a/segment1/segment2

Now, the router applies varies rules to try 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.

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:

menu1/submenu-a/segment1/segment2

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 ones - one which matches "menu1" and another which matches "menu1/submenu-a", in which case it will take the one with the longest match.

Now that it's found the menu item, it knows the component, component parameters, and also the formatting and other options which are associated with this menu item. You are now left with

segment1/segment2

At this point, the router turns to the component to parse the remaining segments. To parse the remaining segments, it calls the parse() function of the component router, and passes in an array of the remaining segments. This function 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.

Three Functions of Router

1. build(&$query)

It transforms an array of URL parameters into an array of segments that will form the SEF URL. It converts system URLs to SEF URLs.

2. parse(&$segments)

It transforms an array of segments back into an array of URL parameters. It converts SEF URLs to system URLs.

The two functions must cooperate in such a way that the original URL can be reconstructed. Any URL format needs to contain some kind of information that identifies the data you want to show. Generally, in the system URLs, this information is stored in the id URL parameter (for example, id=1). You can also use SEF URLs to contain a textual description of the data they point to. In Joomla, this is done by giving your users a way to enter an alias to be used in the URL.

3. preprocess($query)

It is a preparation method for URLs. This method is executed on each URL, regardless of SEF mode switched on or not.

<?php

defined('_JEXEC') or die;

class HelloworldRouter implements JComponentRouterInterface
{

public function build(&$query)
{
$segments = array();
if (isset($query['id']))
{
$db = JFactory::getDbo();
$qry = $db->getQuery(true);
$qry->select('alias');
$qry->from('#__helloworld');
$qry->where('id = ' . $db->quote($query['id']));
$db->setQuery($qry);
$alias = $db->loadResult();
$segments[] = $alias;
unset($query['id']);
}
unset($query['view']);
return $segments;
}

public function parse(&$segments)
{
$vars = array();

$db = JFactory::getDbo();
$qry = $db->getQuery(true);
$qry->select('id');
$qry->from('#__helloworld');
$qry->where('alias = ' . $db->quote($segments[0]));
$db->setQuery($qry);
$id = $db->loadResult();

if(!empty($id))
{
$vars['id'] = $id;
$vars['view'] = 'helloworld';
}

return $vars;
}

public function preprocess($query)
{
return $query;
}
}

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 JInput'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. 

Component Router

In general, there can be three views:

  1. List of categories (view=categories)
  2. Single category (view=category)
  3. Single article (view=article)

URL Structures

When viewing an article:

http://www.example.com/[menualias]/[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:

http://www.example.com/[menualias]/[category]

he Link to the category would look like this:

index.php?view=category&id=' . $row->catslug

When viewing the categories overview:

http://www.example.com/[menualias]

BuildRoute Function

JRouter passes a $query array to the [componentname]BuildRoute function. This function adds the relevant parts of the array to the $segments array in the right order and returns the properly ordered array. The content of the $query array needs to be unset, otherwise JRouter will add it to the URL in the form of a query string.

For example, $query array is something like this:

$query = array('view' => 'article', 'id' => 1, 'catid' => 20)

The function is like:

function [componentname]BuildRoute(&$query)
{
$segments = array();
if (isset($query['view']))
{
$segments[] = $query['view'];
unset($query['view']);
}
if (isset($query['id']))
{
$segments[] = $query['id'];
unset($query['id']);
};
return $segments;
}

ParseRoute Function

The function is like this:

function [componentname]ParseRoute($segments)
{
$vars = array();
switch($segments[0])
{
case 'categories':
$vars['view'] = 'categories';
break;
case 'category':
$vars['view'] = 'category';
$id = explode(':', $segments[1]);
$vars['id'] = (int) $id[0];
break;
case 'article':
$vars['view'] = 'article';
$id = explode(':', $segments[1]);
$vars['id'] = (int) $id[0];
break;
}
return $vars;
}

from the BuildRoute function, you know that the view is first and the id is second in the array. By reading $segments[0], you can access the name of the view. Then, you set the right view and identifier (if applicable) and finally return the $vars array to JRouter.

New Joomla Router

Since Joomla 3.8, there is a new way of working on routers using the \Joomla\CMS\Component\Router\RouterView base class. This 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($app = null, $menu = null)
{
$category = new RouterViewConfiguration('category');
$category->setKey('id')->setNestable();
$this->registerView($category);
$article = new RouterViewConfiguration('article');
$article->setKey('id')->setParent($category, 'catid');
$this->registerView($article);
}

Here, you have registered a category view that has a routing key of it's id, which can be nested (can have multiple levels). You have also registered an article view which also has a routing key of id, which has a parent of the category view.

The next step is to register the rules. There are three rules provided by 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.

public function __construct(CMSApplication $app = null, AbstractMenu $menu = null)
{
$category = new RouterViewConfiguration('category');
$category->setKey('id')->setNestable();
$this->registerView($category);
$article = new RouterViewConfiguration('article');
$article->setKey('id')->setParent($category, 'catid');
$this->registerView($article);

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

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

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