Using Rule Classes to implement simple URL aliases system in Yii2

This article shows, how to use rule classes with yii\web\UrlManager to implement URL aliases-based routing system quickly and easily. It shows a bit different approach to this topic (static routing).

Introduction

There are two usage scenarios for URL aliases in this article:

  1. You allow only one alias per an item, for example per blog entry. And when user changes it, you always update it, so previous URL aliases are no longer working. Strange and mostly unusable, but many people design this like that. In this case you can store alias directly in blogs table as separate column named alias, seo, short, etc.
  2. You support multiple aliases per one item. When user edits alias, it always goes “on top” of all aliases per particular item. It is active, so this one will be used in all auto-generated URLs, but others (“old”) remain working. In this case you need a separate table, where you will be storing all aliases and bind them to correct items, via 1-to-n relation.

There are three “schools” on how URL aliases should be implemented in a web application:

  1. Dynamic routing. Aliases are generated (createUrl) and parsed (parseUrl) on-the-fly by UrlManager:
    1. In this scenario you store in database an alias and an ID of item, it routes to. And when user access URL with alias you always route him or her to destination URL, generated on-the-fly using an id of destination item.
    2. This scenario makes smaller, compact database, but gives you less flexibility, because you can only bind alias to particular item, having its id.
    3. It is also URL-independent, therefore this kind of routing will work on any hosting without any change.
  2. Static routing based on aliases. In this scenario you store an alias in one column and full-featured URL in second one:
    1. Your database becomes bigger as you store entire URLs and you have a lot of data redundancy in place.
    2. And you get the solution, that isn’t URL-independent any more, since you store full URL on one side.
    3. Therefore you need a special migration or some kind of update console command to update these URLs before publishing your website to another host.
    4. But, in the same time, you have a far more flexibility, because you can bind particular alias to any URL, such as contact page etc.
  3. Static routing based on URLs. In this scenario you store full-blown URLs on both sides (in both columns). This gives you even more data redundancy and a hell more flexibility, because you can route anything on anything.

This example covers second scenario / school, but can be easily extended toward third option.

The solution

First of all, read about creating rule classes, if you are not familiar with this topic.

Then create a UrlRule class similar to this one:

<?php

namespace commoncomponents;

use yiiwebUrlRuleInterface;
use yiihelpersUrl;

use commonmodelsAlias;
use commonmodelsMenu;

class PageRule implements UrlRuleInterface
{
    public function parseRequest($manager, $request)
    {
        $alias = $request->pathInfo;
        if ($alias = Alias::findOne(['alias' => $alias])) {
            $url = Menu::parseRoute($alias->url);
            return [array_shift($url), $url];
        }
        return false;
    }

    public function createUrl($manager, $route, $params)
    {
        if ($route === 'site/index') {
            return '';
        } else {
            if ($alias = Alias::findOne(['url' => $route . (empty($params) ? '' : '&'.http_build_query($params))])) {
                if ($alias->alias) {
                    return $alias->alias;
                }
            }
        }
        return false;
    }
}

Finally, add this class to your urlManager, as shown in here.

The Alias model is nothing more that Gii-auto-generated model for a table consisting of two columns, where first (alias) holds alias (or full-blown input URL in third scenario) and second one (url) stores output URL, to which user should be redirected, when he or she is using given URL alias.

And Menu::parseRoute() method is nothing more than…

Leave a Reply