Running multiple sites with CakePHP 3 using plugins

I had an interesting concept for a business that required running multiple sites from within the same vertical. I also wanted the ability for the core backend to span into other verticals. To do this I needed a highly scaleable and flexible core. The premise was the core code would just serve as light-weight CRM, with additional functionality being built as plugins (including the front-ends). Sites would then import these plugins and be configured via some JSON settings. This blog will focus on just running multiple sites off the CakePHP 3 framework, but if interest is expressed I’ll go into further detail.

Site Plugins

Let’s say you want to run two sites on the same install, but you might want some different functionality between the two. We’ll call these two sites Widgets and Gadgets. The first thing we’d do is create two plugins SiteWidgets and SiteGadget. Since these are plugins they can have their own independent MVC, but still core application functionality and other plugins. Now with native functionality you can’t point the Apache document root at these plugins without some hefty hacking. In retrospect I would’ve have done this a bit different and will likely refactor, but the premise remains.

In Short

  1. We add some code to webroot/index.php which modifies the REQUEST_URI global and sets it to the site plugins path.
  2. Cake proceeds as normal going next to config/bootstrap.php where it loads the actual site plugin.
  3. Next that plugins boostrap file at plugins/SiteWidgets/config/bootstrap.php is called where we load that sites specific plugins.
  4. Cake then loads in routes for the site plugin at plugins/SiteWidgets/config/routes.php.
  5. Finally plugins/SiteWidgets/src/controller/AppController.php is called where we instruct cake to use the proper layout.
  6. This inherits from src/controller/AppSitePluginController.php where we load in site specific settings which of course inherits from the main App Controller where some additional logic is performed.
cakephp3-multiple-sites

Diagram

Here’s the code and a deeper explanation.

Modifying Document Root

In our applications webroot/index.php we’ll want to add our initial logic for determining which site plugin was requested. I did the following just before the DispatcherFactory goes to work:

Site Logic

Initialize Request

We call initRequest with the NetworkRequest as a dependency using that to determine what the visitor requested. After Ignoring any debug kit requests as we want the plugin to continue to function normally it does a few things. The method performs a lookup (by calling $this->getDomain) in our sites table to get necessary information and then overwrites $_SERVER[‘REQUEST_URI’] to “fool” cake.

At this point cake proceeds normally and will now look in config/bootstrap.php and config/routes.php. Because we don’t want to constantly update boostrap.php We’ll now call the bootstrapSites method of our Sites object. Inside boostrap.php something like this will do:

We put this code after the DispatchFactory calls and before loading other application wide plugins such as Bake. The bootstrapSites method will divide requests between main application requests and our plugin sites. You can see this is where we actually load the site as a plugin. This avoids loading every single site in bootstrap.php allowing the application to be dynamic. At this point Cake has been told the plugin is the base site, but we still have a few more pieces to put in place.

App Controllers

In our app controller we will want to add a beforeFilter to determine whether a request to the controller is for a site plugin or the core application. We’ll also need to some things to ensure CSRF protection can still work.

You can also see I’ve set some public variables on the app controller such as isSite and isBackend to differentiate between the core app and a site plugin. You can ignore some of that stuff you don’t plan on using CSRF protection. We’ll need another app controller specifically for our site plugins that extends the main app controller above.

In here we are loading additional settings for the site and informing cake to use the plugins layout. Now let’s put this together inside our plugins app controller at plugins/SiteWidgets/Controller/AppController.php:

Configure plugins/SiteWidgets/config/bootstrap.php to bootstrap this sites plugins.

Configure  plugins/SiteWidgets/config/routes.php:

This took a bit of thinking through and hacking to get working, but ultimately it came out pretty clean and I never had to touch the core CakePHP3 code. Had I had to modify core internals I would have abandoned this. This solution is pretty impervious to cake upgrades, though I’ve had to upgrade a few things here and there as items were deprecated from 3.0 to 3.1. There is a lot here, some of which I didn’t explain as it didn’t relate specifically to running multiple sites as plugins. Feel free to ask questions and refer to the “In Short” section if you get loss as that is the basic thought process I went by when building this.