Archive for March 4th, 2008

04
Mar

Dynamically Loading Symfony Applications Via Subdomains

The concept of “Symfony applications” is sometimes confusing for those new to Symfony, but they can be a very powerful way to break up the functionality of a project. The classic example for this separation is the “front end” and “back end” or “back office” applications. The idea here being that the front-end will be what the normal user sees and he back-end being what the administrator of the site uses to maintain the site through an admin interface. Since these two applications are in the same Symfony project, they can share certain common elements (such as database configurations and schemas), but they can have their own modules and templates. In essence, each application can provide a different “view” of the same data.

If you only have one application, you’re probably content with the way Symfony automatically sets up your first application to use the default index.php controller, but what happens when we add applications? You’ll notice that Symfony adds controllers for each application in the web directory, so you might end up with a set of files like this:

web/index.php
web/frontend_dev.php
web/backend.php
web/backend_dev.php

… assuming you have a “frontend” and “backend” application. Also, note that since we created the “frontend” application first, Symfony automatically used the index.php filename for what would have been called frontend.php.

So, this means that when we view our site at www.mysite.com/ the frontend application is loaded and when we view www.mysite.com/backend.php we get the backend application. This is fine for most uses, but what if we could streamline this a bit so that applications were loaded based on a sub-domain?

That’s exactly what we’ll do.

Setting It Up

First, we’ll rename our index.php file to frontend.php (this will be useful later on) and create a new, blank index.php controller in its stead. Our controllers should now be as follows:

web/index.php (blank!)
web/frontend.php
web/frontend_dev.php
web/backend.php
web/backend_dev.php

Open up the newly created index.php file. We’re going to add a little bit of our own logic here so that Symfony dynamically loads the correct application based on what’s set in the sub-domain:

<?php
// define the standard symfony environment constants
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);

// get the domain's parts
list($tld, $domain, $subdomain, $subdomain2) = array_reverse(explode('.', $_SERVER['HTTP_HOST']));

// determine which subdomain we're looking at
$app = ($subdomain == 'staging') ? $subdomain2 : $subdomain;
$app = (empty($app) || $app == 'www' ) ? 'frontend' : $app;

// determine which app to load based on subdomain
if (!is_dir(SF_ROOT_DIR.'/apps/'.$app))
{
    define('SF_APP','frontend');
}
else
{
    define('SF_APP',$app);
}

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.
'config'.DIRECTORY_SEPARATOR.'config.php'); // Should be on one line, with the above text
sfContext::getInstance()->getController()->dispatch();

Note: I generally use a “staging” sub-domain for testing on the production server before pushing to the actual production site, so you’ll notice that I check for this sub-domain separately. This is so I can use something like backend.staging.mysite.com and still load the correct back-end application. Feel free to simply remove that step if you want your staging sub-domain to point to an actual Symfony application for some reason.

This controller file should look familiar to you as it’s just a bit of a reworking of a default controller. We simply added some logic to detect a subdomain, check if it’s a valid application and then, if it is, we have Symfony load it dynamically. You’ll notice that instead of using frontend.mysite.com for the front-end we simply check for www or an empty sub-domain field and set the application to “frontend” by hand. If your default application name is something different, you’ll have to change this.

Some Notes About This Method

Firstly, you should know that this method will only work if you have the correct sub-domain addresses as DNS records pointing to your server (or if you have wildcard DNS set up so that anything.yoursite.com is sent to your server).

Also, for clean URL rewriting on each sub-domain, don’t forget to set no_script_name to “on” in each application’s settings.yml file for the “prod” environment:

prod:
  .settings:
    no_script_name:           on

… this allows our sub-domains to use URLs in the form backend.mysite.com/module/action instead of backend.mysite.com/index.php/module/action. This is usually disabled by default for all but the first application you create (in our case, frontend).

Also, remember that file we renamed to frontend.php? Well, we did that for a reason. In previous articles, I’ve stated why I like setting up local development environments, so I won’t go into detail here, but you’ll notice with this method we can still access the frontend application directly by typing something like mysite.dev/frontend.php which may be useful for testing purposes. You could even create an index_dev.php that loads the dev environment for our custom front controller if you needed to. Note though, that I’m specifically not including support for a “dev” sub-domain (or anything that loads the dev environment) since the dev controllers should not live on your production server!

So What?

You might be wondering what all the fuss is about. Why go through the trouble of setting up these dynamic sub-domains linked to Symfony applications? Well, as with many things, necessity is the mother of invention: for a project I’m working on right now, I needed to add an iPhone/iPod Touch specific site to an existing Symfony project. A separate Symfony application makes perfect sense since it will simply be a different view for the same data. It would have been easy enough to just use the default mysite.com/iphone.php controller, but I wanted it to be a separate sub-domain because it just feels better separating it out that way, and you get nice clean URLs and an easy to remember iPhone-specifc domain name to boot.

In another article, I’ll be publishing some tips for incorporating this method into a site that automatically detects an iPhone or iPod Touch and reacts accordingly.