Routing Subdomains to Modules in Zend Framework
Sunday, March 29th, 2009While working on a recent in-house project, we saw a need to take a step back to look at the big picture. We asked ourselves, “How can we fully leverage the flexibility of the Zend Framework in order to keep our related in house projects under one code-base?” This would allow for such things as a common login, easy access to common code, and site-wide variables.
The answer? Define each project as its own module that could access, for example, a common “Auth” session object and a common layout. So we started with the idea that we would have urls like http://mydomain.com/module/controller/action/. However, we preferred to have each project on its own subdomain. Thus we had a new question, “How do we route a subdomain to a module?”
The answer to that turned out to be incredibly simple. Only three things need to be in place:
- Properly configured name-based Apache hosts
- Properly written .htaccess
- Specific code within your Zend Framework bootstrap file
Apache Configuration
Assuming that you’re on a LAMP server, and Apache is configured for Virtual Hosts, your httpd.conf or httpd.include might look something like this:
Note that this has been simplified for the point of explanation here, and you should use the real public IP of your server not the localhost address
ServerName www.mydomain.com:80
UseCanonicalName Off
DocumentRoot /path/to/www/document-root
</VirtualHost>
<VirtualHost 127.0.0.1:80>
ServerName mydomain.com:80
UseCanonicalName Off
DocumentRoot /path/to/application/document-root
</VirtualHost>
This ensures that your www host has its own document root, isolated from your application. Of course, you could just as easily include your www to be a module within your application by leaving out that first VirtualHost declaration.
Notice that in the second VirtualHost definition the ServerName does not contain a subdomain; it simply states the root level of your domain. This will act as a sort of catch-all for all subdomains that have not been explicitly declared within their own VirtualHost definitions. Any subdomain that you do not want included as a module within your application should be explicitly defined via its own VirtualHost definition.
.htaccess and mod_rewrite
I’m of the opinion that examples from Zend Quickstart and other resources over-complicate the mod_rewrite rules necessary for putting Zend Framework into action. Here is the mod_rewrite rules that we use without a hitch:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php [QSA,L]
Note that that is just a sample of our .htaccess file that has numerous features to enhance security and performance. Something we’ll be writing about soon.
Magic in the Bootstrap
Firstly, the standard router in Zend Framework expects the URI to follow a format like /module/controller/action/, and references the $_SERVER['REQUEST_URI'] to find it. The problem is that our subdomain-per-module approach moves the module from the $_SERVER['REQUEST_URI'] to $_SERVER['SERVER_NAME']. How then would Zend properly route controllers and actions to the appropriate module? Take a look at the following code:
define(‘MODULE’,strtolower(array_shift (explode(‘.’,$_SERVER['SERVER_NAME']))));
// Declare existing modules
$modules = array(‘blog’=>APPLICATION_PATH.’modules/blog/controllers’,
’store’=>APPLICATION_PATH.’modules/store/controllers’);
// Redirect if user enters subdomain that has not been declared
if (!array_key_exists(MODULE,$modules))
{
header (‘Location: http://www.sliceoflime.com’);
exit;
}
// Prepare $Request object and set the URI
$Request = new Zend_Controller_Request_Http;
$Request->setRequestUri(‘/’.MODULE.$_SERVER['REQUEST_URI']);
// Prepare the front controller and dispatch
$frontController = Zend_Controller_Front::getInstance();
$frontController->setControllerDirectory($modules);
$frontController->setDefaultModule(’store’);
$frontController->setRequest($Request);
$frontController->dispatch();
That first line does three things at once: extracts the subdomain part of the url, converts it to lowercase, and creates a MODULE constant we can use later in our code. Now we need to head off the front controller at the pass so to speak. By default, if no request object was given to the front controller, it will create one itself, and use the default properties the request object generates. In such a case, our module would not be included in the $_SERVER['REQUEST_URI'] and therefore not properly routed. In this case we explicitly create a request object, and set its $_requestUri property via setRequestUri() by prepending the MODULE constant to the existing $_SERVER['REQUEST_URI'].
From there, we continue on as normal. We create the front controller object and set its controller directories by passing it an array that defines each of our modules and their respective controller directories. That second to the last line, where we call the setRequest method, is vitally important. This is where the front controller is set to use the request object we’ve created and modified.
I hope this will be useful to other developers out there. Please do leave your comments, we’d love to hear how this worked or did not work for you, and other issues relating to routing subdomains to modules in Zend Framework.

