PHP Bridge Pattern CMS

bridgeCMSA 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:

  1. An Administrative UI for previewing and adding new content
  2. 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

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:
PlayPlayAdminDownload

To use the Administrator Module, follow these steps in the listed order:

  1. Type in Header data, select a graphic from the drop down menu, and then type in text for the body.
  2. Click a Desktop, Tablet or Phone radio button and then click Preview Page
  3. When you have everything the way you want it, First click Transfer to Storage and next click Store Data
  4. 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:

?View Code JAVASCRIPT
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.

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="cmsbridge.css">
    <script src="formXfer.js"> </script>
    <title>CMS Admin Bridge</title>
</head>
<body>
    <h2>Enter Update Data</h2>
    <form method="post" name="bridgeWork" action="BridgeCMSAdminClient.php">
        <input type="text" name="header">&nbsp;Header<br />
        <select name="graphic">
            <option>Select Travel Graphic</option>
            <option value="nephpAir.jpg">Air</option>
            <option value="nephpTrain.jpg">Train</option>
            <option value="nephpShip.jpg">Ship</option>
        </select>
        <br />
        Enter the text for the body below:<br />
        <textarea name="bodytext" cols="48" rows="12"></textarea><p />
        <h3>Preview New Data</h3>
        <input type="radio" name="device" value="Desktop">&nbsp;Desktop<br />
        <input type="radio" name="device" value="Tablet">&nbsp;Tablet<br />
        <input type="radio" name="device" value="Phone">&nbsp;Phone<p />
        <input type="submit" name="deliver" value="Preview Page">
    </form>
 
    <h3>Store New Data</h3>
    <form method="post" name="dataStore" action="StoreDataClient.php">
        <input type="hidden" name="header">
        <input type="hidden" name="graphic">
        <input type="hidden" name="bodytext">
        <button type="button" onclick="transferData(dataStore)">Transfer to Storage</button>
        <input type="submit" name="jsonstore" value="Store Data">
    </form>
 
</body>
</html>

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:

<?php
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.

<?php
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

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)
<?php
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
<?php
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

Figure 3: The Concrete Implementators inject content into the Web pages

The Abstractor has an aggregate relationship with the Implementor (IDevice) in the reference to the concrete implementation of IDevice. The Implementor participants have changed little from the initial post of the Bridge pattern. They have not needed to. As this project grows from a simple example to a more and more complex CMS, the objects still work well together and can be changed independently of one another. That’s the beauty of design patterns. They handle complex quite well.

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
<?php
interface IDevice
{  
    function buildDevice($head,$image,$text); 
}
?>
 
//Desktop
<?php
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=<<<DESKTOP
        <!DOCTYPE html>
        <html>
        <head>
        <meta charset="utf-8">
        <link rel="stylesheet" type="text/css" href="cmsbridge.css">
            <title>Desktop Bridge</title>
        </head>
        <body>
        <header><h2>$this->header</h2></header>
        <img src="desktoppix/$this->graphic" alt="Air" width="450" height="400">
        <br />$this->bodyText
        </body>
        </html>
DESKTOP;
    return $this->pageNow;
    }
}
?>
 
//Tablet
<?php
class Tablet 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=<<<TABLET
        <!DOCTYPE html>
        <html>
        <head>
        <meta charset="utf-8">
        <link rel="stylesheet" type="text/css" href="cmsbridge.css">
            <title>Tablet Bridge</title>
        </head>
        <body>
        <header><h2>$this->header</h2></header>
        <img src="tabletpix/$this->graphic" alt="Air" width="450" height="400">
        <p>$this->bodyText</p>
        </body>
        </html>
TABLET;
    return $this->pageNow;
    }
}
?>
 
//Phone
<?php
class Phone 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=<<<PHONE
        <html>
        <head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>jQuery Mobile: Theme Download</title>
	<link rel="stylesheet" href="themes/bridgeCMS.min.css" />
	<link rel="stylesheet" href="themes/jquery.mobile.icons.min.css" />
	<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.3/jquery.mobile.structure-1.4.3.min.css" />
	<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
	<script src="http://code.jquery.com/mobile/1.4.3/jquery.mobile-1.4.3.min.js"></script>
</head>
        <body>
        <div data-role="page" data-theme="a">
          <div data-role="header" data-position="inline" data-theme="a">
            <h2>$this->header</h2>
          </div>
          <div role="main" class="ui-content" data-theme="a"> <img src="phonepix/$this->graphic" alt="Air" width="300" height="267">
            <p>$this->bodyText</p>
          </div>
        </div>
        </body>
        </html>
PHONE;
    return $this->pageNow;
    }
}
?>

The object in the form of a Web page can be understood in the context of OOP PHP as an object. Instead of thinking of and treating Web pages as separate entities from other PHP objects, by treating them like any other object, you can program them with the flexibility of OOP PHP. They then become dynamic entities instead of static ones.

One Last Bridge to Cross

To complete the Bridge CMS project, we need one more (small) element. Building on this and the previous Bridge design pattern, the little page that has been dynamically created and stored in a JSON file, needs a Web site context. A CMS that changes one dynamic aspect of a Web site while leaving the static material in the page is far more useful than one that just generate a small site. So, in the next and final installment of the Bridge CMS, the Web context will be the last piece of the puzzle.

Share

Copyright © 2014 William Sanders. All Rights Reserved.

0 Responses to “PHP Bridge Pattern CMS”


  • No Comments

Leave a Reply