Zend Framework gets hostname routing

Saturday, July 19, 2008

Introducing note: The examples in this article don't reflect the usage of the final hostname routing in 1.6 anymore.

There were many needs for it, in different mailing lists, forums and blogs, but it was never officially requested. There were also many approaches to accomplish it, but they were all kinda hacky. As I neeed it in an earlier project yet, were I had do do the hacky way as well, and now a new project came up, were I even need a more complex implementation, I decided to put a feature-rich hostname routing implementation into the ZF core itself. I did most of the implementation stuff myself, created unit tests and documenation, and SpotSec did some finetuning on it afterwards.

Well, it will be included in ZF 1.6 RC1, which will come very soon. Between the final release will be an RC2, which gives you enough time to try it out and send in issues, if there are any. I really guess, this is a feature which was hardly required by many developers. For those of you who are interested yet, you can check it out from trunk. Here is the documentation part:


Hostname routing
You can also use the hostname for route matching. For simple matching there is a static hostname option:
<?php
$route = new Zend_Controller_Router_Route(
    array(
        'host' => 'blog.mysite.com',
        'path' => 'archive'
    ),
    array(
        'module'     => 'blog',
        'controller' => 'archive',
        'action'     => 'index'
    )
);
$router->addRoute('archive', $route);
If you want to match parameters in the hostname, there is a regex option. In the following example, the subdomain is used as username parameter for the action controller. When assembling the route, you simply give the username as parameter, as you would with the other path parameters:
<?php
$route = new Zend_Controller_Router_Route(
    array(
        'host' => array(
            'regex'   => '([a-z]+).mysite.com',
            'reverse' => '%s.mysite.com'
            'params'  => array(
                1 => 'username'
            )
        ),
        'path' => ''
    ),
    array(
        'module'     => 'users',
        'controller' => 'profile',
        'action'     => 'index'
    )
);
$router->addRoute('profile', $route);

Comments to this article

  • Avatar of Jurriën Stutterheim Reply Jurriën Stutterheim Saturday, July 19, 2008 1:51 PM

    Awesome :D Nice work boys!
    Actually, the real reason why I comment this is because I want to test out Figlet :x

  • Avatar of Chris Abernethy Reply Chris Abernethy Saturday, July 19, 2008 5:47 PM

    This seems pretty cool, but can you give some examples of situations where this functionality would be useful?

  • Avatar of Ben 'DASPRiD' Scholzen Reply Ben 'DASPRiD' Scholzen Sunday, July 20, 2008 12:15 AM

    Well, two good examples are given in the documentation itself, no?

  • Avatar of SpotSec Reply SpotSec Sunday, July 20, 2008 12:15 AM

    Whoo!

  • Avatar of robinsk Reply robinsk Sunday, July 20, 2008 12:22 AM

    Wey! It was great to have this implemented so quickly :)

    I'm thinking of trying to use this to support multiple application instances (on sub domains) without duplicating the directory structure for each. I'll let you know how it works out :)

  • Avatar of Chris Abernethy Reply Chris Abernethy Sunday, July 20, 2008 1:26 AM

    I'm seeing your two examples of 'how' in this article, but what I'm really looking for is 'why'. You're describing functionality that exists to serve a purpose that I have not yet encountered, and I'm interested in learning more. Do you have a link to the full documentation? I'd love to check it out!

  • Avatar of Ben 'DASPRiD' Scholzen Reply Ben 'DASPRiD' Scholzen Sunday, July 20, 2008 1:53 AM

    This is in fact the full documentation. I was too lazy (as I always am), to write some more ;)

  • Avatar of Federico Reply Federico Sunday, July 20, 2008 3:13 AM

    I think this implementation has a flaw, the routes should be loaded based on the current hostname and, optionally, module.

    If hostname A has 30 routes, why do you need to load another 60 for host B and C? It doesn't make sense.

    Nice captcha BTW ;)

  • Avatar of Federico Reply Federico Sunday, July 20, 2008 3:41 AM

    In order to make the design more scalable, the router should get an instance of the request object and load a set of routes based on the hostname. Here's a better solution:

    application
    |-- bootstrap.php
    |-- config
    | `-- routes
    | |-- blog.mysite.com
    | |-- forum.mysite.com
    | `--eshop.mysite.com

    The router maps the hostname to a directory and loads the routes from there. Otherwise, every time I visit blog.mysite.com the router has to iterate through all the routes and perform regular expression until it finds a positive match.

  • Avatar of SpotSec Reply SpotSec Sunday, July 20, 2008 5:33 AM

    The problem with that design and the current router is that it isn't exactly easy to lazy load routes for the Url and Redirector Helpers that rely on route's being loaded to generate urls.
    eg. $this->url(array(), 'someRouteFromAnotherSubDomain') no workie.

    It wouldn't be too hard to implement though as some modules are segregated enough from another to not have to refer to routes from the other. This is the same style of routing that symfony does. It has it's pro's and con's.

  • Avatar of Federico Reply Federico Sunday, July 20, 2008 3:50 PM

    Yes, it seems to me that they copied the idea from Symfony instead of looking for a new and better solution.

    There are many ways you can implement a faster and more scalable routing process without the need of loading unnecessary objects or running an excessive amount of regular expressions. You can even have a map as a config file telling the router where to find the routes, this solves all the problems.

    I've looked at the trunk/ and I honestly think that the code added to the router is more of a hack than a solution, and the router is starting to smell. The router at the moment is not very effective when used in high-traffic ecommerce and/or social networking sites.

    This routing process is sometimes referred as *highway to hell*.

  • Avatar of Ben 'DASPRiD' Scholzen Reply Ben 'DASPRiD' Scholzen Sunday, July 20, 2008 4:45 PM

    At the current stage, this was was the only solution to implement hostname support for me, as no BC breaks are allowed until 2.0. With 2.0 then, I plan to make an entire rewrite of the router system, which will allow to only load necessary routes. Until then, you have to live with this solution.

    And no, it wasn't copied from any other framework.

  • Avatar of Ben 'DASPRiD' Scholzen Reply Ben 'DASPRiD' Scholzen Sunday, July 20, 2008 5:01 PM

    Ah and by the way, currently this makes the performance better than worse. Yet it really has to iterate over all routes and evaluates every path pattern (may it be static, normal route or regex).

    When you now use the static hostname in 1.6, it will simply skip all hostnames, which don't match the hostname in the route, and the path won't get evaluated. Remember here, that the static routes are simple string comparisions, and only the extended hostname matching uses regex.

  • Avatar of Federico Reply Federico Sunday, July 20, 2008 10:07 PM

    Hi Ben,

    You don't have to break backward compatibility to add this. I know that it skips the hostnames that don't match, but if you are using INI files, then the config object has to process all the routes and the router load all the objects into memory.

    What I'm suggesting is making this optional, for example:

    $router = $frontController->getRouter();
    $router->setRoutesDirectory('../application/config/routes');
    $router->setRoutesFilename('routes.php');
    $router->enableHostnameMapping();

    It will only load a set of routes from: /application/config/routes/blog.mysite.com/routes.php

    What I meant is that Zend's router follows the same logic as Symfony and CodeIgniter. Django, on the other side, offers a different solution, it allows you to include other routes based on a given pattern.

  • Avatar of Tom Graham Reply Tom Graham Wednesday, September 3, 2008 2:28 PM

    Great work on the hostname router.

    I've written a blog post about how to use the router to use sub-domains as account keys. I wrote it after the changes to the router had been made so usage is quite different to that in this post.

    Check it out: http://www.noginn.com/2008/09/03/using-subdomains-as-account-keys/

    Thanks for the great work DASPRiD

  • Avatar of Ben 'DASPRiD' Scholzen Reply Ben 'DASPRiD' Scholzen Thursday, September 4, 2008 3:22 AM

    Very good article Tom. I think that this will be a good start-up for users of the new hostname route.

Leave a comment

Please note that your email address will not be shown, it is only used to fetch your avatar image from gravatar.com and for notifications.

                                  
  __ _  ___  _   _  ___ _ __ __ _ 
 / _` |/ _ \| | | |/ _ \ '__/ _` |
| (_| | (_) | |_| |  __/ | | (_| |
 \__, |\___/ \__,_|\___|_|  \__,_|
 |___/