A Flexible CMS
The previous post on the PHP Bridge Design pattern shows how a Bridge pattern has two connected but independent interfaces to make design flexibility for different online devices. This post explores how that same flexibility extends to making a Content Management System (CMS). Most of the Bridge participants in the design are unchanged or only slightly changed.
The major change in the Bridge design pattern actually makes it more in line with the original intention of the Bridge. The RefinedAbstraction participant (RefinedPage) no longer includes concrete content for the page. Instead, it provides the parameters for a client to add the content. This change adds flexibility and gives the developer more options than the original StandardPage class.
Two UIs and Multiple Clients
In order to make a decent CMS, you need to have at least two UIs:
- An Administrative UI for previewing and adding new content
- A User UI for viewing but not changing content
In creating the Administrative UI (HTML5/PHP/JavaScript), I had to use two PHP clients. One client is to preview the new data entered by the admin and the other client is to store the new data (after previewing and possibly editing it). Figure I provides a general overview of the UIs and the Clients that will use the Bridge pattern for a CMS:

Figure 1: User Interfaces and Clients
The Administrative UI (BridgeCMSAdmin.html) uses the BridgeCMSAdminClient class for displaying different content and the StoreDataClient class for storing the information in a JSON file. An important condition to remember is that when using JSON files, you need to make their permissions available for reading and writing. (See the Memento II post and the special mini-post on setting permissions on Raspberry Pi systems.) Thus, the need for two clients; one for previewing new material and another for storing it in a JSON file. A lot of files are involved in this CMS; so take a look at the two different UIs and download the files for everything:
To use the Administrator Module, follow these steps in the listed order:
- Type in Header data, select a graphic from the drop down menu, and then type in text for the body.
- Click a Desktop, Tablet or Phone radio button and then click Preview Page
- When you have everything the way you want it, First click Transfer to Storage and next click Store Data
- Now click the Play button and see the page you created.
In the admin UI, I used a drop down menu with only three selections for the graphic file since only three were set up. However, it would not be difficult to upload graphics and their file names. (See the post on uploading graphics using the Template Method.)
The UIs and their Clients
The main feature in creating a CMS is the Administrative UI. It calls two different clients for dealing with previews and persistent data storage. Unless you’re planning on a fairly long body text entry, the JSON file works fine. Look at the code below, and you can see that one of the issues is that the data that is entered for the preview must be transferred to a different form. It transferring the data is a simple task with a little JavaScript. The following script is all it takes:
function transferData(formNow) { formNow.header.value = bridgeWork.header.value; formNow.graphic.value = bridgeWork.graphic.value; formNow.bodytext.value = bridgeWork.bodytext.value; } |
Stored in an external JS file, it was used only when the data was going to be stored; however, before storing it, it had to be transferred from the bridgeWork form to the dataStore form.
Store New Data |
Then using build-in PHP JSON json_encode() method, the data were placed into an array and stored in the JSON file. This was done using the StoreDataClient class:
class StoreDataClient
{
private static $dataStorage=array();
private static $jsonFile="content.json";
//Client stores data
public static function store()
{
if (isset($_POST['jsonstore']))
{
self::setStore();
}
file_put_contents(self::$jsonFile,json_encode(self::$dataStorage,JSON_UNESCAPED_UNICODE));
}
private static function setStore()
{
//Pushes data from HTML to array
array_push(self::$dataStorage,$_POST['header'],$_POST['graphic'],$_POST['bodytext']);
}
}
StoreDataClient::store();
?>
|
Just in case you’re wondering why a single PHP client class was not used for both preview and storage, it’s simple:
OOP Principle: Each class should have only a single responsibility.
We don’t want to cram classes; so each responsibility has its own class. (Click below to see the other client and the rest of the CMS.)
The other responsibility for the Administrative UI is to preview the added data before sending it to be stored.
error_reporting(E_ALL | E_STRICT);
ini_set("display_errors", 1);
function __autoload($class_name)
{
include $class_name . '.php';
}
class BridgeCMSAdminClient
{
private static $pageDevice;
private static $device;
private static $header;
private static $graphic;
private static $body;
//Client request
public static function request()
{
if (isset($_POST['deliver']))
{
self::setPage();
self::$device=$_POST['device'];
}
self::$pageDevice=new RefinedPage();
//The data is not stored but used in the key Implementor method doDevice();
self::$pageDevice->doDevice(self::$header,self::$graphic,self::$body,new self::$device());
}
private static function setPage()
{
self::$header=$_POST['header'];
self::$graphic=$_POST['graphic'];
self::$body=$_POST['bodytext'];
}
}
BridgeCMSAdminClient::request();
?>
|
As you can see, it is almost identical to the client used in the first post on the PHP Bridge pattern. (Remember, code re-use is an essential goal of both OOP and Design Patterns.) You will see the same code re-use in the Bridge portion of the program. Figure 2 provides an overview of the Bridge portion:

Figure 2: Bridge used in CMS
While the code is not identical to the PHP used in the initial Bridge example, it’s pretty close, and as you can see, the design is the same.
The Bridge (Again)
Some small but key changes have been made in the Abstractor classes, while the Implementor interface/classes are virtually the same. (There’s that re-use again). First, take a look at the two classes that make up the Abstractor side of the equation:
//Abstractor Interface (Abstract class) abstract class IPage { //Low-level abstract function doDevice($headerNow, $graphicNow, $bodyNow, IDevice $deviceNow); //High-level //deviceSelected is the concrete implementation of IDevice (Implementor) protected function buildPage() { echo $this->deviceSelected->buildDevice($this->header,$this->graphic,$this->body); } //Properties protected $header, $graphic, $body, $deviceSelected; } ?> //Refined Abstraction class RefinedPage extends IPage { //Parameters added for content from client public function doDevice($headerNow, $graphicNow, $bodyNow,IDevice $deviceNow) { $this->deviceSelected=$deviceNow; $this->header=$headerNow; $this->graphic=$graphicNow; $this->body=$bodyNow; $this->buildPage(); } } ?> |

Figure 3: The Concrete Implementators inject content into the Web pages
With the interface and classes that make up the Implementator, you see very little change compared to the original. As you can see in Figure 3, each concrete implementation receives content through the parameters, passes those values into the big heredoec string and returns it all to the client. The client in this case is actually an aggregate part of the bridge between the the Abstractor and Implementor participants. If you look at the IPage buildPage() method, you can see that the method prints (using the echo statement) the Web page embedded in the returned heredoc string. Taken as a whole, the Bridge is the composite object that displays the Web page from any one of the three devices. The following are all of the objects in the Implementator side of the Bridge:
//Interface interface IDevice { function buildDevice($head,$image,$text); } ?> //Desktop class Desktop implements IDevice { private $page; private $header; private $graphic; private $bodyText; public $pageNow; function buildDevice($head,$image,$text) { $this->header=$head; $this->graphic=$image; $this->bodyText=$text; //Begin heredoc string $this->pageNow=<< |