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.
Update
Since writing this post I have found a few problems with the routes described, please take a look at my new post - Revisiting hostname routing with the Zend Framework.












13 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
10. gonzalo posted on 26/11/2008 at 12:31 AM
i have been trying to make work the cinfiguration of the hostname with the 1.0 realase, via chain routing but i coudl`t with ini files. If someon can bring me an example or post it, of how to do that please contact me.
11. Daniel posted on 03/12/2008 at 6:01 AM
I’ve thried the solution..but it didn’t work yet
my apache setting:
RewriteEngine On
RewriteCond %{REQUEST_URI} !^.*(\.css|\.js|\.gif|\.png|\.jpg|\.jpeg|\.swf|\.ico|/independent/.*|/static/.*|/more/.*)$
RewriteRule ^(/.*)$ /index.php
below is my setting im Boostrap.php:
$pathRoute = new Zend_Controller_Router_Route(
‘:controller/:action/*’,
array(
‘controller’ => ‘member’,
‘action’ => ‘index’
)
);
$accountRoute = new Zend_Controller_Router_Route_Hostname(
‘:account.coral.com’,
array(
‘account’ => ‘([a-z0-9]+)’,
)
);
$router->addRoute(’account’, $accountRoute->chain($pathRoute));
now, any access to “xxx.coral.com” will turned to “www.coral.com”, the router seemed not work at all…
anyone know why?
thx so much ,guys
13. Maxence posted on 19/02/2009 at 4:31 PM
Thanks for this article. The ZF doc is not clear on this point.
14. Michael posted on 20/05/2009 at 3:40 PM
Me too,
I’d love to know the answer on how to assemble URL as Ben said.
For example: account.mysite.com/a/b