Sandlight CMS III: PHP Abstract Factory

abfacNote: This is the third in a series for developing a CMS for Sandlight Productions. Drop by the Sandlight site to see the progress so far, and be sure to check your country’s flag count!

Now that the CMS has a filter for different devices, it now needs a pattern to take care of those devices and different types of content that they will display. Previous posts on this blog used the Bridge pattern and the Factory Method pattern. However, another pattern might be more useful for all of the different things that a Content Management System (CMS) might do. A review of the series of posts on this blog for how to select a design pattern, shows the different criteria to consider. The CMS has to create different elements of a Web page for different devices and that fact must be the focal point of the consideration. The section head for Creational Patterns in Learning PHP Design Patterns, lists the Abstract Factory pattern as a creational one, but that pattern was not discussed in the book nor on this blog. It would appear to be just what this CMS needs.

The Abstract Factory Design Pattern

The Abstract Factory pattern has features for families of factories and products instead of individual factories as does the Factory Method pattern. In comparing the relatively simple Factory Method pattern with the Abstract Factory, the Abstract Factory has multiple abstract product interfaces with multiple concrete factories for the families of the products.

Figure 1 shows the the Abstract Factory class diagram, and when you look at it, try to focus on the fact that the pattern has two types of interfaces: Factory and Product. So, if you understand the Factory Method pattern, you have a starting point for appreciating the Abstract Factory:

Figure 1: Abstract Factory class diagram

Figure 1: Abstract Factory class diagram

Unlike the Factory Method pattern, the Abstract Factory includes a Client class as an integral part of the pattern. Further, the Client holds an association between both the AbstractFactory (AbsFactory) and the two AbstractProduct classes. So while it shares some of the basic Factory Method characteristics, it is clearly a different pattern than the Factory Method.

Since the Abstract Factory may appear to be daunting, the color-coded the product instantiations (dashed lines) in the file diagram (appearing in the Play window) help show where each concrete factory method calls. Experiment with different combinations of factories (devices) and products (page parts) and look at the diagram so that you can see the path. Phone instantiations are in green, Tablet in red, and Desktop in blue. Experiment with the different single products first, and then click the bottom button to see what different “pages” each device factory displays.

Implementing the Abstract Factory in PHP

To see how this implementation of the Abstract Factory design pattern works, click the Play button. You will see both the interactive Abstract Pattern tester and the of the file diagram of this implementation of the Abstract Factory for the evolving Sandlight CMS. Click the Download button to see all of the files in the diagram. For this particular post, downloading all of the files is more important than usual because there are lots of them, and rather than having listings for all in this write-up, I’ve just selected representative ones.
PlayDownload

In addition to the Abstract Factory file diagram (viewed when you click the Play button), the following quick overview of the participants’ roles and how the CMS implements the Abstract Factory explain how this implementation works:

Client client: Only uses interfaces declared by IAbFactory (interface) and IProducts (abstract classes IHeaderProduct, IImageProduct and ITextProduct.) This means that the Client can only use the classes and methods implemented from those two interface types—factory or products. In other words, it should not directly implement a product (a page element) by directly using a product independent of the factory and product interfaces. Figure 2 illustrates this point:

Figure 2: Client works through Abstract Factory implementations

Figure 2: Client works through Abstract Factory implementations

In going over the other participants below, keep in mind that the Client can only implement concrete implementations of the IAbFactory to request products (page parts for different devices.)

IAbFactory interface: Establishes the methods for the concrete factories.

  • PhoneFactory implements operations to create phone products
  • TabletFactory implements operations to create tablet products
  • DesktopFactory implements operations to create desktop products

IHeaderProduct: abstract class Establishes the method for concrete header products and adds protected property for returning completed product.

  • PhoneHeader defines phone object to be created by the PhoneFactory and
    implements the IHeaderProduct interface
  • TabletHeader defines tablet object to be created by the Tabletactory and
    implements the IHeaderProduct interface
  • DesktopHeader defines desktop object to be created by the DesktopFactory and
    implements the IHeaderProduct interface

IImageProduct: abstract class Establishes the method for concrete image and/or video products and adds protected property for returning completed product.

  • PhoneImage defines phone object to be created by the PhoneFactory and
    implements the IImageProduct interface
  • TabletImage defines tablet object to be created by the Tabletactory and
    implements the IImageProduct interface
  • DesktopImage defines desktop object to be created by the DesktopFactory and
    implements the IImageProduct interface

ITextProduct: abstract class Establishes the method for concrete text products and adds protected property for returning completed product.

  • PhoneText defines phone object to be created by the PhoneFactory and
    implements the ITextProduct interface
  • TabletText defines tablet object to be created by the Tabletactory and
    implements the ITextProduct interface
  • DesktopText defines desktop object to be created by the DesktopFactory and
    implements the ITextProduct interface

Comparing the above outline with the Abstract Factory file diagram (seen when you click the Play button) shows that the Abstract Factory is bound to the idea of a factory implementing a product. The specific classes (products) requested are never directly referenced by the Client; rather it is through a factory. The CMS application requires factories for the different device categories; Phone, Tablet and Desktop. Each factory should be able to build the necessary parts (products) for each device. In this case (and for this example) the products are a Header, Graphic and Text. Each factory can build its own version of the products; so requesting a header, for example, is through a concrete factory, and depending on which concrete factory the clients requests, it builds the appropriate product.

The Client

The class diagram in Figure 1 shows that the Client holds references to both the factory and product interfaces; thus, making it appear as though the Client makes requests directly to the product. It doesn’t. Instead, the reference to the product is a choice of which product the Client would like the factory to return as illustrated in Figure 2. In order to provide a better idea of what the Client does, this introduction to the Abstract Factory has two different client classes. The first client only implements a single product from a selected concrete factory. The following shows the bare bones class:

class Client
{
    static $factoryClass;
    //client request
    public static function request()
    {
        $factory = $_GET['device'];
	$method=$_GET['part'];
	self::$factoryClass = new $factory();
	echo self::$factoryClass->{$method}();
    } 
}

The ‘device’ superglobal passes the name of the concrete factory, and the ‘part’ superglobal is the specific method implemented by the factory to reference one of the three product interfaces . (See the HTML listing below.) The Client request() method assigns the static variable $factoryClass the appropriate concrete factory class, and the $factoryClass calls the product through the name of the method passed from the HTML document. ({$method}()). For example, TabletFactory is one of the factory class names and createGraphic one of the methods of the IAbFactory interface; so when those two values are passed through superglobals, the resulting call would be tabletFactory->createGraphic() where tabletFactory is an instant name of the concrete TableFactory class.

A second client, ClientPage shows how a dynamic page is built with a single device factory. With a device chosen, the ClientPage first instantiates an instance of the appropriate concrete factory, and then it makes calls to all of the different methods established in the abstract factory interface (IAbFactory). Because all of the factories use the same interface, it’s easy to establish a program that can create dynamic pages for different types of devices. The same pattern can also be incorporated to add content to structured HTML documents; especially those that are embedded in heredoc strings within a PHP program.

class ClientPage
{
    static $factoryClass,$factory;
    //client request
    public static function request()
    {
        self::$factory = $_GET['deviceNow'];
		self::buildPage();
    }
	static function buildPage()
	{
		self::$factoryClass = new self::$factory();
		$header=self::$factoryClass->createHeader();
		$graphic=self::$factoryClass->createGraphic();
		$text=self::$factoryClass->createText();
		echo "$graphic $header $text";
	}                      
}

As you seen in the buildPage() static method, once the device is selected and a concrete factory instantiated, the developer doesn’t have to worry about which kind of device details have to be handled, the program already is set up for it. That’s the beauty of the Abstract Factory design pattern.

The Factories

The Abstract Factory pattern has a single abstract factory (interface) and as many concrete implementations of the interface as needed. Like most interfaces, this one is pretty simple:

<?php
interface IAbFactory
{
    function createHeader();
    function createGraphic();
    function createText();
}
?>

The implementation of the methods is left up to the concrete factories. The PhoneFactory, for example, implements the concrete products that are designed to return phone-type products. In this case, those products are all concrete implementations of one of three types of abstract products—headers, graphics, and text.

<?php
class PhoneFactory implements IAbFactory
{
    function createHeader()
    {
        $header=new PhoneHeader();
        return $header->makeHeader();
    }
    function createGraphic()
    {
        $image=new PhoneImage();
        return $image->makeImage();
    }
    function createText()
    {
        $text=new PhoneText();
        return $text->makeText();
    }
}
?>

Since all of the device factories share the same interface, it’s easy to add more device factories. For example, if we wanted to include a “Phablet” factory for big phones like the iPhone 6+ and Galaxy Phone 3, we could just add another concrete factory that implements the same IAbFactory interface.

The Products

Like the family of devices, the families of products all begin with an interface. Each family has three implementations. The interfaces are made up of abstract classes instead of interfaces. This allowed me to add a return property, $content, that can be used consistently throughout the implementations without a declaration in every implementation.

All of the three abstract classes for the products are similar. Each has a single abstract public function and a protected property, as can be seen in the interface:

<?php
abstract class IHeaderProduct
{
    abstract public function makeHeader();
    protected $content;
}
?>

Each of the three properties for each of the three interfaces has an implementation unique to it’s task. For example, implementations of the headers for the phone devices uses a smaller header than the others (h4 instead of h3 or h2).

<?php
class PhoneHeader extends IHeaderProduct
{
    public function makeHeader()
    {
        $this->content='<h4 style="color:#c00;font-family:Verdana, sans-serif;margin-left:5%;">Phone Header</h4>';
        return $this->content;
    }
}
?>

The returned product could be nothing more than an unformatted string that is set into a larger static framework or it could be a mini-formatted element as shown in this example. The other products (graphics and text) are similar and geared to the context provided by the device factory that implements the concrete product. Likewise, the concrete product could be used to pull information from a MySql database. A lot of different possibilities present themselves with this pattern.

The Abstract Factory Test Platform

Before discussing how the Abstract Factory fits into the CMS, it’s important to take a look at the HTML form with the origins of the selections. It will help understand how to better use the Abstract Factory as a tool:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="sandlight.css" />
    <title>Abstract Factory Pattern</title>
</head>
 
<body>
    <div>
        <p><img src="Graphics/AbFacFileDiagram.jpg" alt="file diagram"></p>
    </div>
    <h2>Abstract Factory Tester</h2>
    <h3>Select device and page part:</h3>
    <form action="Client.php" method="get" target="feedback">
        <div>
        <input type="radio" name="device" value="PhoneFactory">&nbsp;Phone<br/>
        <input type="radio" name="device" value="TabletFactory" checked="true">&nbsp;Tablet<br/>
        <input type="radio" name="device" value="DesktopFactory">&nbsp;Desktop<br/><br/>
        </div>
        <div>
        <input type="radio" name="part" value="createHeader">&nbsp;Header<br/>
        <input type="radio" name="part" value="createGraphic" checked="true">&nbsp;Graphic or Video<br/>
        <input type="radio" name="part" value="createText">&nbsp;Text<br/><br/>
        </div>
        <input type="submit" value="Send choices"> 
    </form>
    <br />
    <iframe name="feedback">feedback</iframe>
    <h3>Create page:</h3>
    <form action="ClientPage.php" method="get" target="feedback">
        <div>
        <input type="radio" name="deviceNow" value="PhoneFactory">&nbsp;Phone<br/>
        <input type="radio" name="deviceNow" value="TabletFactory" checked="true">&nbsp;Tablet<br/>
        <input type="radio" name="deviceNow" value="DesktopFactory">&nbsp;Desktop<br/><br/>
        </div>
         <input type="submit" value="Make Page"> 
    </form>
</body>
</html>

In the top form with two sets of radio buttons, the user experiments with a single factory and a single product. Therefore, each must be specified and passed to the Client class. In the bottom form, only a single group of buttons select a factory. The client, (ClientPage), calls all of the products from the selected factory. It doesn’t care what’s in the products; it just wants all of the products for the device that will use them.

The Abstract Factory in the CMS

The first two installments of this CMS series focused on getting the right device and creating responsive pages. The Abstract Factory allows the selected device (the one found by the Chain of Responsibility) to then create individual components for pages responding to different device categories. Now, though, the return value of the the Chain of Responsibility becomes the “factory chooser.” It informs the Abstract Factory implementation, which device factory to use to supply it with information.

In the next installment, now that the CMS has a way to identify the viewing device and to return an appropriate dynamic page, the basic page (static) and dynamic page (updated by administrative input) must be joined. Envisioned is a two-column page for the desktop and table implementations and a single-column extended implementation using Mobile jQuery for the phone implementation. This next stage uses both the Chain of Responsibility pattern for identifying the device and the Abstract Factory for building the pages.

Share

Copyright © 2015 William Sanders. All Rights Reserved.

0 Responses to “Sandlight CMS III: PHP Abstract Factory”


  • No Comments

Leave a Reply