You are here:Home»KB»Web Design»CMS»Joomla»My Joomla Modern Router Notes
Saturday, 14 November 2020 13:32

My Joomla Modern Router Notes

Written by

During my quest to understand the new Joomla Modern Router present in Joomla 3.8+ and Joomla 4.0 I found some good information and figured out the rest. These are the notes from that battle.

I will be using my reference component QWPeople which is based on com_contact from Joomla 3.9.22, my version has all of the class renamed to the latest standard so is a better place to learn from.

List of different routers available in Joomla (dont worry I will go through these later, it just makes it easier to write this document)

  • Original Joomla 3.x (dont use if making a new component)
  • Modern Router (Class Based)
    • RouterViewBase
    • RouterView
    • RouterView + Legacy Rule

Notes:

  • Variables can end up in the $query from $_GET requests, $_POST requests or just being put in there by Joomla itself.
  • These is a typical example of variables pass in the query used for routing
    option=com_qwpeople
    task=mytask or view=myview
    language=en-GB
    Itemid=101
    view=category
    layout=blog
    id=9
    lang=en

Modern Router

The router entry file is still present as [component]/router.php i.e. com_qwpeople/router.php

So how does this new router work. What is a class based router. Well I am not 100% but hopefully my notes will fill in the blanks that this Joomla doc creates: J3.x:Supporting SEF URLs in your component - Joomla! Documentation. Please read this Joomla Document before reading my article as it will make more sense. One other thing the component name part of the router class name should be in PascalCase and not all lowercase like it says, it does not affect operation but PascalCase follows the Joomla syntax rules.

The basic idea of the new router is to do most of the heavy lifting for the normal developer but allow for expansion when needed unlike the old router where you only had one parse() and build() and everything had to be done within this. The new router has definitely expanded on this. The router is broken down into 2 parts (as far as I am concerned)

The following instructions are utilising the RouterViews class.

Registering the Routes

Look at the following code:

// Categories route
$categories = new RouterViewConfiguration('categories');
$categories->setKey('id');
$this->registerView($categories);

// Category route
$category = new RouterViewConfiguration('category');
$category->setKey('id')->setParent($categories, 'catid')->setNestable();
$this->registerView($category);

// Contact route
$contact = new RouterViewConfiguration('contact');
$contact->setKey('id')->setParent($category, 'catid');
$this->registerView($contact);

// Featured route
$this->registerView(new RouterViewConfiguration('featured'));

What is happening here is that you are building a route (ultimately to your content) by using Joomla component views.

We will deal with featured first because this is the simple one. featured is not a category, is not in a category and therefore is a standalone page so it is very simple to route to this all that needs to be done is have the view set to featured which has already been set by the menu item so really there is no component routing required.

Now if we look at the contact view route because this is at the bottom of the tree, if we wanted the categories or category view they we we would just start earlier up the tree and ignore the rest.

  • Categories route
    • The routing key of categories is `id`
    • I don't think it is really necessary to have this route section but I think it just made it easier for the developers to implement a categories view. (I could be wrong)
  • Category route
    • The routing key of category is `id` 
    • It has a parent route of categories, whose routing key `id` is the same as category `catid` and these have now been associated together.
    • The category has a `id` which will match a categories with the same `catid`
    • setNestable() = that this routing section could be made of many segments because it is nestable
  • Contact route
    • The routing key of contact is `id`
    • It has a parent route of category, whose routing key `id` is the same as contact `catid` and these have now been associated together.
    • The contact has a `catid` which will match with a category with the same `id`
    • Now the connection is made to the category we can move up to the category route

Notes:

  • You should note that these rules work in both directions, building and parsing SEF URLs. For now I am concentrating on parsing the URLs as this is harder.
  • You can start a request any where in the view tree `Categories route/Category route/Contact route`, you do not have to start at either end.

Routing Rules

The best bit of the new router is the ability to use rules and Joomla comes with some premade ones that perform very common tasks, so again have a look at this code:

// Router rules
$this->attachRule(new MenuRules($this));		
if ($params->get('sef_advanced', 0))
{
    // Modern routing
    $this->attachRule(new StandardRules($this));
    $this->attachRule(new NomenuRules($this));
}
else
{
    // Legacy routing
    JLoader::register('QwpeopleRouterRulesLegacy', __DIR__ . '/helpers/legacyrouter.php');
    $this->attachRule(new QwpeopleRouterRulesLegacy($this));
}

Joomla Inbuilt Rules

This is a very brief explanation of the Joomla premade rules which should help:

  • MenuRules - Looks to see if the URL matches a known menu item, and ensures in multilingual sites that a language tag is present. This also strips the sgements that make up the path to the menu before passing them on but also adds the ItemId into the Query.
  • StandardRules - Uses your view configuration to build up a menu path. Including utilising the RouterViewConfiguration configurations, utilises segment and ID functions in the router for the particular view it is working on such as getCategoriesSegment() or getCategoriesId().
  • NomenuRules - Provides a fallback when there is no good match found for building or parsing the URL.

You will find all of these rules at /libraries/src/Component/Router/.

Custom Rules

Using a custom rule to allows the use of a legacy router:

  • which is a file containg a class called  class QwupdateserverRouterRulesLegacy implements RulesInterface
  • This is a old style router build()parse() and preprocess() functions wrapped in a class that implements the Joomla\CMS\Component\Router\Rules\RulesInterface interface.
  • So the legacy router can just be moved into a custom rule.
  • For those of you that are not upto date with these terms you will find it easier just to look at the file in QWPeople component and all shall be revealed.

Making your own custom rule

It is as simple as:

  • Copy the RulesInterface class file and use as a template
  • Rename the class name to something like
    class QwpeopleRouterRulesLegacy implements RulesInterface
    {
    } 
  • Insert your code in the build()parse() and preprocess() functions as required
  • Register the rule in your router. You will probably need to use JLoader to register the rule class you have just made. After it is registered, you should then attach the rule. The process is outlined below but I would recommend putting your custom rules after the main Joomla ones unless you have a need to change the order and which case you probably know why.
    // Legacy routing
    JLoader::register('QwpeopleRouterRulesLegacy', __DIR__ . '/helpers/legacyrouter.php');
    $this->attachRule(new QwpeopleRouterRulesLegacy($this));

Remember you can make as many rules as you want to handle different aspects of your router.

A little trick for custom rules you can use

You can just use return statement in the beginning of a function if you want this code only to work on a particular view or thing. This trick allows you to separate complex routing code into different rules if required.

public function parse(&$segments, &$vars)
{
    // Skip this functions code if not 'categories' view
    if(!isset($vars['view']) || $vars['view'] !== 'categories') { return; }
}

But how do the rules work

  • All rules extend the class Joomla\CMS\Component\Router\Rules\RulesInterface which means they must all have at least the functions parse(), build() and preprocess() even if they don't have any code in them.
    • parse() - If called it will process the segments to see if it can extract any information and then add that to the $query
    • build() - If called it will aid in building the SEF link from the query variables. when this function adds a SEF segment to $segment it will unset the related $query variables so Joomla does not get upset.
    • preprocess() - This function is run irrespective of whether Joomla is in SEF mode.
  • Each registered rule is cycled through in order they are registered so in my example just using modern routing.
    The next rule is only run if there are segments that have not been processed or the view has not been set.
    • MenuRules
      • This grabs the menu item ID, language settings and of course any other parameters configured in the menu item which it then loads into the $query.
      • This rule also removes the segments from the SEF leading upto and including the menu item.
      • This rule uses all of the getXxxSegment() functions in the router.php, I would also assume it uses all of the getXxxId() functions.
      • For every menu URL to be built from the joomla internal links (i.e. inde_.php?view=categories) each getXxxSegment() for a registered view is cycled through; looking for a match, this is my best guess.
      • Does not set any thing in to the $vars (The variables that result from the segments) but does set variables in the $query, like all of the parameters from the menu item.
    • StandardRules
      • Parses the remaining segments in the SEF using the `get ID` functions for the matching views in the in the router.php.
      • By now all the require variables should be in the $query and Joomla will know the correct view to load and in which case no further rule processing will happen and the page will be loaded.
      • This rule uses all of the getXxxSegment() functions in the router.php, I would also assume it uses all of the getXxxId() functions.
      • For every URL to be built from the joomla internal links (i.e. inde_.php?view=categories) each getXxxSegment() for a registered view is cycled through; looking for a match, this is my best guess.
      • sets the option='com_qwpeople' and view='catgories' into the $vars (The variables that result from the segments)
    • NoMenuRules
      • This is only triggered if no matching menu item has been found. A minimal cleanup of assets happens.
      • This does not run any getXxxSegment() functions in the router.php
    • Custom Rules
      • These would get activated now if we had one configured. We would have a custom rule here if we needed to do some complex work on the SEF to complete the routing like I have in QWUpdateServer.

Notes

  • if you add a blank custom rule and then use a debugger you can easily see what each of the rules set by adding a breakpoint in the relevant functions.

What are the different classes for

So of the keen people amongst you might of noticed the different class types that extended by the router. I will outline what they do.

  • class QwpeopleRouter extends RouterBase
    • This class is very much like the original router in Joomla 3.x which allows you to use 1 of each of the following functions parse()build() and preprocess() only.
    • parse()build() and preprocess() will be present in the router class QwpeopleRouter.
    • It does not use rules
    • The developer will need to write all of the routing logic instead of being able to use what Joomla provides for you. However this might be beneficial in some large projects.
    • You could add your legacy router code in the corresponding functions quite easily with maybe a few changes depending which version of Joomla 3.x they were written for.
  • class QwpeopleRouter extends RouterView
    • This allows rules to be registered and used.
    • parse()build() and preprocess() will be present in each of the rules files.
    • Most of the hardwork of routing has been done by Joomla in the pre-written Rules (MenuRules/StandardRules/NoMenuRules).
    • You can write and add your own custom rules.
    • By using a custom rule you can add your legacy router code in.
    • Rules can be turned on and off programmatically (see my trick above), this is especially useful if you only want a custom rule to run on a particular rule.

Conclusion

This is an easy one, use your router in the RouterView mode (by extending your component router with this class) and use custom rules to add any required extra routing logic.

If you have used all of the modern class names like in QWPeople, this router should also be ready for Joomla 4.x.

Links

Read 1519 times Last modified on Sunday, 15 November 2020 12:27