Using sub-domains as account keys

In a recent project I wanted to use the sub-domain as the account key of a web application (i.e. noginn.myapp.com, where “noginn” is the account key). In Ruby on Rails this is achieved using the Account Location plugin, and I wanted to do something similar using the Zend Framework.

I have three modules in my application:

  • Default - The marketing website, accessible from www.myapp.com or myapp.com
  • Admin - The overall administration of the application, accessible from admin.myapp.com
  • Account - The user account, accessible from all other sub-domains

Initially I wrote a custom router, however, as of Zend Framework 1.6.0 this functionality is available out of the box with Zend_Controller_Router_Route_Hostname.

It took me a little while to get to grips with the hostname router as the manual is not currently up to date, although the unit tests were useful in giving examples of it’s usage.

Setting up the routers

The routing is achieved by chaining a standard Zend_Controller_Router_Route with the path :controller/:action/* to each of the hostname routes. Note that the order of the routes is important so the account route regex does not match www. and admin. as well.

Here is my code:

$router = $controller->getRouter();
$router->removeDefaultRoutes();

$pathRoute = new Zend_Controller_Router_Route(
    ':controller/:action/*',
    array(
        'controller' => 'index',
        'action' => 'index'
    )
);

$accountRoute = new Zend_Controller_Router_Route_Hostname(
    ':account.myapp.com',
    array(
        'module' => 'account',
    ),
    array(
        'account' => '([a-z0-9]+)',
    )
);
$router->addRoute('account', $accountRoute->chain($pathRoute));

$adminRoute = new Zend_Controller_Router_Route_Hostname(
    'admin.myapp.com',
    array(
        'module' => 'admin',
    )
);
$router->addRoute('admin', $adminRoute->chain($pathRoute));

$defaultRoute = new Zend_Controller_Router_Route_Hostname(
    'www.myapp.com',
    array(
        'module' => 'default',
    )
);
$router->addRoute('default', $defaultRoute->chain($pathRoute));

Front controller plugin

To automatically retrieve the Account object I wrote a very simple preDispatch() plugin:

class Noginn_Controller_Plugin_AccountKey extends Zend_Controller_Plugin_Abstract
{
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $module = $request->getModuleName();

        // Only retrieve account if it's the account module
        if ($module == 'account') {
            $accounts = new Accounts();
            $account = $accounts->findBySubdomain($request->getParam('account'));

            if (!$account) {
                $request->setControllerName('error');
                $request->setActionName('error');
                return;
            }

            Zend_Registry::set('account', $account);
        }
    }
}

This uses the account parameter that is the sub-domain in the account module.

Usage

Linking or redirecting to accounts/modules in the controllers is just as simple.

To link to “http://noginn.myapp.com/profile/edit” in the view:

$this->url(array('controller' => 'profile', 'action' => 'edit', 'account' => 'noginn'), 'account);

To redirect to “http://admin.myapp.com/auth/login” in the controller:

$this->_helper->redirector->gotoRoute(array('controller' => 'auth', 'action' => 'login'), 'admin');

Within my controller I retrieve the registered Account to be used with Zend_Registry::get('account').

In closing

The beauty of this method over my original custom router is that it can be easily extended for additional routes.

If you have any ideas of how this approach could be improved, or any other comments really then please get in touch.

9 Comments

1. Giuliano posted on 13/09/2008 at 1:20 AM

Hi,
I get the following error:

Catchable fatal error: Argument 1 passed to Zend_Controller_Router_Route_Chain::setRequest() must be an instance of Zend_Controller_Request_Abstract, null given, called in […]\Zend\Controller\Router\Rewrite.php on line 92 and defined in […]\Zend\Controller\Router\Route\Chain.php on line 107

The code I’m using is:

require ‘Zend/Loader.php’;
Zend_Loader::registerAutoload();
$controller = Zend_Controller_Front::getInstance();

[your router setup code]

Am I doing anything wrong?
Thanks in advance.

Giuliano

2. Tom posted on 13/09/2008 at 8:46 AM

Giuliano,

Try setting the request object to be used before you create your routes:

$request = new Zend_Controller_Request_Http();
$controller->setRequest($request);

Hope that helps,

Tom

3. Giuliano posted on 13/09/2008 at 10:31 AM

Thanks Tom,
the new request fixed the problem :)

Giuliano

4. Ben Scholzen posted on 20/09/2008 at 9:48 AM

That error is afaik fixed in the 1.6.1 tag, or at least the 1.6 branch.

5. Kevin Stone posted on 23/09/2008 at 7:15 PM

Great article.

Would you be able to post some code snippets of this using Zend_Config_Ini?

Particularly how to setup the hostname routing through an .ini file and chaining it to routes layed out in an ini file?

Is this even possible?

6. Mute posted on 10/10/2008 at 11:40 PM

I have been trying out the hostname route, I use it slightly differently to achieve a similar goal:

$route = new Zend_Controller_Router_Route_Hostname(
‘:username.domain.com’,
array(
‘module’ => ‘account’
),
array(
‘username’ => ‘(?!.*www|admin)[a-zA-Z-_0-9]+’
)
);
$route = new Zend_Controller_Router_Route_Hostname(
‘admin.domain.com’,
array(
‘module’ => ‘admin’
)
);
$route = new Zend_Controller_Router_Route_Hostname(
‘www.domain.com’,
array(
‘module’ => ‘default’
)
);

7. Ben posted on 23/10/2008 at 6:46 AM

Hi, thanks much for the straightforward writeup about this niche but important feature. I’m having trouble with the URL-assembling side of it though. When I do this:

$this->url(array(’controller’ => ‘profile’, ‘action’ => ‘edit’, ‘account’ => ‘noginn’), ‘account’);

I get:

http://noginn.myapp.com/profile/edit/account/noginn

rather than just:

http://noginn.myapp.com/profile/edit

Looking through the ZF code, I can’t find any way to get the pathRoute to omit the /account/noginn part unless I remove the /* from the end of $pathRoute’s routing string. But then I lose the ability to append any _other_ parameters to the URL. What’s your trick? Does this problem sound familiar? (I see this behavior with ZF 1.6.1, 1.6.2, and 1.7.0PR. 1.6.0 crashes, at least the version accessible via the SVN tag.)

Thanks in advance for any suggestions you might have!

8. Ben posted on 23/10/2008 at 6:51 AM

@Kevin Stone: It appears that in 1.6.x, route chaining cannot be configured via Zend_Config (and therefore .ini files) but fortunately that seems to be coming in 1.7.0.

9. Sean Brant posted on 28/10/2008 at 12:12 AM

Just came across this. I needed to do almost the same exact thing. However I prefer to do my routing via a ini config file. So if anyone else is interested here is the code.

routes.account.type = “Zend_Controller_Router_Route_Hostname”
routes.account.route = :account.myapp.com
routes.account.defaults.module = account
routes.account.defaults.controller = index
routes.account.defaults.action = index

routes.admin.type = “Zend_Controller_Router_Route_Hostname”
routes.admin.route = admin. myapp.com
routes.admin.defaults.module = admin
routes.admin.defaults.controller = index
routes.admin.defaults.action = index

routes.default.type = “Zend_Controller_Router_Route_Hostname”
routes.default.route = http://www. myapp.com
routes.default.defaults.module = default
routes.default.defaults.controller = index
routes.default.defaults.action = index

Have your say

(Never published)
(Optional)

About the author

Tom Graham is a web developer living in Leicester, UK. This blog is his vent for all things web design and development. Read more about Tom.

Categories

Pages

Attending

phpnw08 PHP Conference 22/11/2008