Background: Building a web app (as an introduction to CakePHP) which allows users to manage a lounge. A lounge is composed of a blog, contacts, calendar, etc. Each lounge is associated with a subdomain (so jcotton.lounger.local would take you to my lounge). The root of the site, used for creating new lounges, registering users, etc is hosted on lounger.local. I am using Cake 2.0.
Questions:
I wanted to be able to separate actions and views associated with the root site (lounger.local) from individual lounges (subdomains of lounger.local). After a good deal of research I settled on the following soln. I setup a prefix route "lounge" and added the the following code in routes.php. Actions (and views) associated with a lounge all contain the prefix lounge (ex: lounge_index()). How would you handle this?
if(preg_match('/^([^.]+)\.lounger\.local$/',env("HTTP_HOST"),$matches)){
$prefix = "lounge";
Router::connect('/', array('controller' => 'loungememberships','action' => 'index', 'prefix' => $prefix, $prefix => true));
/* Not currently using plugins
Router::connect("/:plugin/:controller", array('action' => 'index', 'prefix' => $prefix, $prefix => true));
Router::connect("/:plugin/:controller/:action/*", array('prefix' => $prefix, $prefix => true));
*/
Router::connect("/:controller", array('action' => 'index', 'prefix' => $prefix, $prefix => true));
Router::connect("/:controller/:action/*", array('prefix' => $prefix, $prefix => true));
unset($prefix);
}
Each time a user performs an action within a lounge such as posting a comment within the blog, adding a contact, etc, it is necessary to lookup the lounge_id (based on the subdomain); this is necessary to verify the user is authorized to perform that action and to associate the corresponding data with the correct lounge. I have implemented this via the beforeFilter function in AppController. Each time a request is received with a subdomain a search is performed and the lounge_id is written to a session variable. Each controller then loads CakeSession and reads the corresponding lounge_id. Is this better than calling ClassRegistry::Init('Lounge') and doing the lookup in each controller? Is there a better soln?
Thanks in advance for the help
The way I approached this was with a custom route, and some trickery with route configuration similar to your example.
First, I have a "Master domain" that is redirected to and used as the main domain for the multi-tenancy site. I also store a default action i want them to take. I store these in configuration variables:
Configure::write('Domain.Master', 'mastersite.local');
Configure::write('Domain.DefaultRoute', array('controller' => 'sites', 'action' => 'add'));
Next, I created a DomainRoute
route class in /Lib/Route/DomainRoute.php
:
<?php
App::uses('CakeRoute', 'Routing/Route');
App::uses('CakeResponse', 'Network');
App::uses('Cause', 'Model');
/**
* Domain Route class will ensure a domain has been setup before allowing
* users to continue on routes for that domain. Instead, it redirects them
* to a default route if the domain name is not in the system, allowing
* creation of accounts, or whatever.
*
* @package default
* @author Graham Weldon (http://grahamweldon.com)
*/
class DomainRoute extends CakeRoute {
/**
* A CakeResponse object
*
* @var CakeResponse
*/
public $response = null;
/**
* Flag for disabling exit() when this route parses a url.
*
* @var boolean
*/
public $stop = true;
/**
* Parses a string url into an array. Parsed urls will result in an automatic
* redirection
*
* @param string $url The url to parse
* @return boolean False on failure
*/
public function parse($url) {
$params = parent::parse($url);
if ($params === false) {
return false;
}
$domain = env('HTTP_HOST');
$masterDomain = Configure::read('Domain.Master');
if ($domain !== $masterDomain) {
$defaultRoute = Configure::read('Domain.DefaultRoute');
$Cause = new Cause();
if (!($Cause->domainExists($domain)) && $params != $defaultRoute) {
if (!$this->response) {
$this->response = new CakeResponse();
}
$status = 307;
$redirect = $defaultRoute;
$this->response->header(array('Location' => Router::url($redirect, true)));
$this->response->statusCode($status);
$this->response->send();
$this->_stop();
}
$params['domain'] = $domain;
}
return $params;
}
/**
* Stop execution of the current script. Wraps exit() making
* testing easier.
*
* @param integer|string $status see http://php.net/exit for values
* @return void
*/
protected function _stop($code = 0) {
if ($this->stop) {
exit($code);
}
}
}
This custom route class is used within the /Config/routes.php
file to setup multi-tenancy.
if (env('HTTP_HOST') === Configure::read('Domain.Master')) {
// Master domain shows the home page.
$rootRoute = array('controller' => 'pages', 'action' => 'display', 'home');
} else {
// Subdomains show the cause view page.
$rootRoute = array('controller' => 'causes', 'action' => 'view', env('HTTP_HOST'));
}
Router::connect('/', $rootRoute, array('routeClass' => 'DomainRoute'));
On inspection of the custom router, you will see that I am pulling the current domain being accessed and adding that to the $params
array.
While this does not directly achieve what you are after, minor modifications will get you on the right track with your requirements. There is not a great deal of information about Custom Routes, but here is the CakePHP documentation link for custom route classes.
I hope that helps!