Get it All
Together

The application that I’m in the process of building is designed around the idea of large metropolitan areas being grouped together in their own subdomains. So for example, I would like all people in the Rochester, NY metro area to go to rochester.example.com and all the Buffalo traffic to go to buffalo.example.com. So, I had to figure out how, exactly, I was going to go about doing this and I started looking around. After some trials and tribulations, I’m going to commit my process to the blog for the benefit of those who are searching for similar solutions.

I began my quest by reading this post at the CakePHP Bakery. A very good article and well documented, but it has a few problems for my application. Primarily, this example creates completely separate instances of the application for each domain, with different databases in each instance. That’s not at all what I had in mind for my application, which I expect to be cross-pollinating.

Apache and Your Domain Host

It is important to note at this point that hosted services such as BlueHost or 1and1.com will not be able to accomodate this kind of setup. Or at least, it will require a lot of work that is beyond the scope of this article. But I wanted to quickly cover the needed parameters for Apache and your domain host, assuming you’re using a VPS of some kind. You’ll need to setup any additional requirements with your web host as needed.

You can setup subdomains in one of two ways: either setting each domain up on your host separately, or else using a wild card to cover all the bases. I used this second option, which with GoDaddy.com, requires the use of an ANAME record. Note that if you use a wildcard, this will necessitate having some sort of “catch all” clause somewhere in your code to accomodate erroneous subdomains.

As for Apache, you’ll need to setup your VirtualHost entries for each anticipated subdomain. I personally use two VirtualHost files: one for my domain and a default file for stuff that comes in that I didn’t intend, which points to a generic “Whoopsies!” kind of message. The VirtualHost entries for your subdomains can just point to your Cake directory like so:
[code lang=”xml”]
<VirtualHost *:80>
ServerName domain1.example.com
DocumentRoot /var/www/path/to/cake
</VirtualHost>

<VirtualHost *:80>
ServerName domain2.example.com
DocumentRoot /var/www/path/to/cake
</VirtualHost>
[/code]
. . . and so on. Remember to reload Apache once this is done to get it to work.

CakePHP Part the First: Bootstrap.php

Based on the suggestion from the Bakery article I initially read, I setup my bootstrap.php file for subdomains. The bootstrap runs before everything else and is a great way to define some basic variables. Since many different controllers and methods will be available on a single domain, it’s important to at least verify that the correct operation is happening on the correct subdomain. Thus I defined a CLIENT_NAME per the original Bakery example, though with a few modifications, here:
[code lang=”php”]
if ( isset($_SERVER[‘SERVER_NAME’]) ) {
// define values that should NOT be affected by this test:
$subdomain = substr($_SERVER["HTTP_HOST"], 0, strpos($_SERVER["HTTP_HOST"], "."));
$donot = array(
‘productionserver’,
‘developmentserver’,
‘cake’
‘www’);
if (!in_array($subdomain, $donot)) {
define(‘CLIENT_NAME’, $subdomain);
} else {
define(‘CLIENT_NAME’, ‘home’);
}
}
[/code]
As you can see, I’ve used an array to define some known values that I’d like the system to define as “home.” If a person comes to productionserver.com, I obviously want them to be routed as coming to the home page. I also want to avoid misinterpreting my development server’s name as a metro, so I’ve specified a couple possible dev domain names as “home,” as well. Even though it is my habit to avoid using the ‘www’ on a domain name, for security’s sake, I’ve also defined this as “home” as well. The default option if none of these values is identified assumes that the segment of the URL passed is going to be a valid metro area and specify that as the current CLIENT_NAME.

This provides me the verification that I need whenever a new request is processed for a subdomain. But I have a controller to handle metro requests which I’d like to use in place of the generic pageController I use for the home page. For this, I need to use the Routes configuration:

CakePHP Part the Second: Routes Configuration

routes.php is a file you use to define what controllers get used with what URL strings. This is a hugely flexible and powerful piece of code which I have found you should use sparingly and cautiously. But there’s no question that Route configuration can be your best friend when building a functional application.

In our case, we need to snag the subdomain name once more and if it is indeed a subdomain, call the metroController with the host name as the first parameter. If this code looks familiar, that’s because it should! It’s practically the same code as the bootstrap.php code:
[code lang=”php”]
$subdomain = substr($_SERVER["HTTP_HOST"], 0, strpos($_SERVER["HTTP_HOST"], "."));
// define values that should NOT be affected by this test:
$donot = array(
‘potholepatrol’,
‘holisticnetworking’,
‘cake’);
if (!in_array($subdomain, $donot)) {
Router::connect(‘/’, array(‘controller’ => ‘metros’, ‘action’ => ‘view’, $subdomain));
} else {
Router::connect(‘/’, array(‘controller’ => ‘pages’, ‘action’ => ‘home’));
}
[/code]
Why not just see if the ‘CLIENT_NAME’ is defined? Well, because these are two separate checks on the same thing, and we don’t want to allow an error in one to affect the other.

This concludes my setup example for using CakePHP with multiple domains. I hope other developers find it interesting and useful. Please add any comments you have if you think there’s a better way!