PHP Factory Method Design Pattern : Decoupling Your Products

Let the Factory Build Your Products

One of the easiest design patterns to both understand and create is the Factory Method. However, the Gang of Four provide more than a single basic structure for the pattern and do not specify exactly where the Client makes its request. As a result, one may not recognize one implementation or another. Generally, I assume that requests from the Client class are made directly through the Creator class. However, some implementations suggest that the Client holds a reference to both the Creator and Product (GoF, 110). For this example I chose to use a parameterized version. In this version of the Factory Method pattern, the factory method is allowed to create multiple kinds of products. As noted by GoF,

The factory method takes a parameter that identifies the kind of object to create. All objects the factory method creates will share the Product interface.(pp. 110-111).

This approach calls for an abstract class that can handle a wide variety of concrete products. At the same time, though, we want it to be useful as well. For this example, the Product class has an abstract method to generate the concrete product (getData), but it also holds the properties with the connection values for the MySQL server. So this allows the developer to change the contents of the concrete products without having to even think about the connection values. (This assumes that the same connection information is used by all of the products.) Figure 1 shows the class diagram for this kind of Factory Method implementation.

Figure 1: Parameterized Factory Method Class Diagram

The Client’s role is implied to hold references to the Creator class (factory) and the Product class. The Product reference is through a parameter in the Creator instance. Because the the Creator interface holds a Product as its parameter in the form of a type hint, the request from the Client is to the abstract Product class and not the concrete product declared in the ConcreteCreator parameter. As a result, it’s easy to add more concrete products without having to re-write the whole program. You can just add the new concrete product class with the appropriate interfaces and call them with the Client referencing the concrete product as a parameter of the ConcreteCreator instance. For instance, the following lines shows such a request:

$this->graphicGetter=new ConcreteCreator();
print($this->graphicGetter->factoryMethod(new GraphicProduct()));

The same instance could be called again for a TextProduct. Therein lies the beauty of programming to the interface instead of the implementation. You can test the program and download the files by clicking the buttons below.

Note: When you look at the code in the downloaded files or in the listings, I used the MySQL settings from my localhost on my computer. Obviously, you will want to substitute those settings for your own; so be sure to get your own MySQL user, password, databases and tables set up before trying to run the files. Also, the project stemmed from a Dreamweaver PHP group whose names were substituted by known and unknown blues, boogie and jazz artists. Make changes in those elements to suit your tastes.

To better understand the actual program, click below to go to the steps in building the application:

Simple Table and Data Entry Scripts

To get started, we’ll put together the table we’ll be using and a data entry script. The table has four fields—name, expertise, location and graphic URL. All can be handled by nvarchar MySQL data types, and so all fields have the same type.


        ini_set("display_errors","2");
        ERROR_REPORTING(E_ALL);
        $server="localhost";
        $user="bill";
        $pass="billz";
        $currentDB="phpwork";
        $tableMaster="phpGroup";
 
        print phpversion() . "
"
;   $hookup=new mysqli($server, $user, $pass, $currentDB);   if (mysqli_connect_error()) { die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error()); }   $drop = "DROP TABLE IF EXISTS $tableMaster";   if($hookup->query($drop) === true) { printf("Old table $tableMaster has been dropped.
"
); }   $sql = "CREATE TABLE $tableMaster (gname nvarchar(30), gexpert nvarchar(40) , gplace nvarchar(40), gpix nvarchar(40))";   if($hookup->query($sql) === true) { printf("Table $tableMaster has been created successfully.
"
); }   $hookup->close(); ?>

You can build the table with a MySQL tool instead of the code as long as the field characteristics and types are the same.

Once your table is ready, you’ll want to enter some data so that you can periodically test the participants (classes and interfaces) in the program. The following HTML5 and PHP scripts do that:





Data Entry


PHP Dreamweaver Group

Enter Data

Name

Expertise

Location

 

//Save as DataEntry.php
        ini_set("display_errors","2");
        ERROR_REPORTING(E_ALL);
        $server="localhost";
        $user="bill";
        $pass="billz";
        $currentDB="phpwork";
        $tableMaster="phpGroup";
 
        $hookup=new mysqli($server, $user, $pass, $currentDB);
 
        if (mysqli_connect_error()) 
        {
             die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
        }
 
        $uname=$_POST['uname'];
        $expert=$_POST['expert'];
        $place=$_POST['place'];
        $pix=$_POST['pix'];
 
//Create Query Statement
 
        $sql = "INSERT INTO $tableMaster (gname,gexpert,gplace,gpix) VALUES ('$uname','$expert','$place','$pix')";
        //%s is a string from parameter
        if($hookup->query($sql))
        {
                printf("User name: %s 
Expertise: %s
From: %s.
Has been added to table: %s"
,$uname,$expert,$place,$tableMaster); }   elseif ( ($result = $hookup->query($sql))===false ) { printf("Invalid query: %s
Whole query: %s
"
, $hookup->error, $sql); exit(); }   $hookup->close(); ?>

Like creating the table, you can enter the data using a tool such as PHPMyAdmin or Navicat instead of the programs provided.

The Creator Interface and Concrete Class

The main reasons for using the Factory Method are the following:

  • Class cannot anticipate the class of objects it must create. Put another way, the developer wants the freedom to add additional objects without having to start over from scratch.
  • Class wants its subclasses to specify the objects it creates. Rather than tightly coupling objects to a class, the developer would rather have the concrete implementations do the object specification.
  • Classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of what helper subclass is the delegate. (p. 108, GoF).

The Creator interface and concrete classes can either be general (as we have done using parameters for passing the desired concrete product) or they can specify operations and a specific concrete product. So, we’ll start with the Creator interface.


interface Creator
{     
        public function factoryMethod(Product $product);
}
?>

Originally, I had used an abstract class for the Creator, but realizing that all that I wanted to do with it was to provide a general factoryMethod() method with type hinting for a Product parameter, I opted for an interface. The type hinting opens up the possibilities for any concrete product subclassed from the Product class. That means if I want to expand the number of objects in the program, I can easily add them simply by subclassing them from the Product class and include the necessary interface required by the Product class.

Because I used a parameterized version of the Factory Method design pattern, the concrete creator had to implement the factoryMethod() method with the same interface, but it would accept any product; so it did not require much more than returning the key getData() method from any of the products.

//ConcreteCreator.php

class ConcreteCreator implements Creator
{
        public function factoryMethod(Product $product)
        {     
                        return($product->getData());
        }
}
?>

As you will see when we look at the Product, its abstract method, getData() leaves its implementation wide open. However, no matter how it is implemented, it is returned to the requesting object through the Creator.

The Product

The Product interface and concrete subclasses in this example were designed to show how different kinds of objects could be created and then returned to a requesting client. Both concrete classes make similar requests for data from a MySQL table. However, one object is wrapped up in text format and the other in a graphic one. Both use HTML5 formatting and the output is very different for each but maintain a common appearance by sharing a common CSS StyleSheet. The following listing shows the CSS code that needs to be saved as phpGroup.css and put into the same folder as the PHP files.

@charset "UTF-8";
/* CSS Document */
body {
        background-color:#7C7B79;
        color:#F8FFFF;
        font-family:Verdana, Geneva, sans-serif;
        font-size:12px;
        margin-left:20px;
}
h1 {
        font-family:"Arial Black", Gadget, sans-serif;
        color:#585858;
}
h2 {
        font-family:"Arial Black", Gadget, sans-serif;
        color:#9E2C2C;
}
 
a {
        color:#9E2C2C;
        text-decoration:none;
        background-color:#F8FFFF;
}
 
 
h3 {
        color:#000;
        font-size:11pt;
}
        #graphicDisplay {
        display:table;
        text-align:center;
}
#fig {
        display:table-cell;
        text-align:center;
        width:150px;
}
aside {
        display:table-cell;
        text-align:center;
        width:150px;
}

The abstract Product class does provide an important set of properties, though. All concrete products subclasses from the Product interface can share the same connection and table information. So, it makes it easy to use and change. The main method, though, is thoroughly abstract.


//Product.php
abstract class Product
{
        protected $server="localhost";
        protected $user="bill";
        protected $pass="billz";
        protected $currentDB="phpwork";
        protected $tableMaster="phpGroup";
 
        abstract public function getData();
}
?>

If you plan on using different tables, databases or users for the different concrete products, you can leave the variables unassigned and assign values in then individual Product implementations.

The two concrete products load HTML5 elements into a big variable along with the contents of the table and return the whole thing to the requesting class (the Client) through the ConcreteCreator which is typed (type hinted) as the Creator interface.


//TextProduct.php
class TextProduct extends Product
{
        public function getData()
        {
                $hookup=new mysqli($this->server, $this->user, $this->pass, $this->currentDB);
 
                if (mysqli_connect_error()) 
                {
                die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
                }
 
                //Create Query Statement
                $sql ="SELECT gname, gexpert, gplace FROM $this->tableMaster";
 
                if ($result = $hookup->query($sql))
                {
                        $textPack = "";
                        $textPack .= "";
                        $textPack .= '';
                        $textPack .= "";
                        $textPack .= "";
                        $textPack .= "

Member Vital Statistics

"
; $textPack .= "

This information is from the Adobe Vaults

"
;   while($row=$result->fetch_assoc()) { $textPack .= $row['gname'] . " is a " . $row['gexpert'] . " expert from " . $row['gplace'] . "
"
; }   $textPack .= "

"; $textPack .= "Database returned $result->num_rows rows.

"; $textPack .= "

 Back to request page "; $textPack .= ""; $textPack .= ""; $result->close();   return $textPack; } $hookup->close(); } } ?>   ////   //GraphicProduct.php class GraphicProduct extends Product { private $image; private $row; private $graphicPack;   public function getData() { $hookup=new mysqli($this->server, $this->user, $this->pass, $this->currentDB);   if (mysqli_connect_error()) { die('Connect Error (' . mysqli_connect_errno() . ') ' . mysqli_connect_error()); }   //Create Query Statement $sql ="SELECT gpix FROM $this->tableMaster";   if ($result = $hookup->query($sql)) { $this->graphicPack = ""; $this->graphicPack .= ""; $this->graphicPack .= ''; $this->graphicPack .= ""; $this->graphicPack .= ""; $this->graphicPack .= "

Member Photos

"
; $this->graphicPack .= "

These photos are stored in MySQL Database

"
; $counter=0; $this->graphicPack .= '
'; while($this->row=$result->fetch_assoc()) { $counter++; if($counter===6) { $counter=1; $this->graphicPack .= "

"; } $this->setAside(); } $this->graphicPack .= '
'
; $this->graphicPack .= "

 Back to request page "; $this->graphicPack .= ""; $this->graphicPack .= ""; $result->close();   return $this->graphicPack; } }   private function setAside() { $this->image=$this->row['gpix']; $this->graphicPack .='

'
; } } ?>

In the TextProduct, I used a standard variable, but I opted for declared private variables in the GraphicProduct. I used a conditional statement inside the while loop in the the GraphicProduct getData() method and so had to provide global access for the class. Given the PHP requirement to use the $this statement with all inherited and declared variables I try to avoid it for clarity. (They just seem to clutter things up, but I have nothing against them otherwise. In fact I prefer them for encapsulation but not for explanations.)

Requesting a Product

The Client requests an object through the Creator. For this demonstration of a Factory Method design pattern, all I did was to send it to the screen with a print statement. First the Client class established public methods for calling either the text or graphic objects. Then within the appropriate method the script declared ConcreteCreator instances. Then, using the factoryMethod(), each instance calls the requested product to be output with the print statement.


ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Product.php');
include_once('TextProduct.php');
include_once('GraphicProduct.php');
include_once('Creator.php');
include_once('ConcreteCreator.php');
 
class Client
{
        private $textGetter;
        private $graphicGetter;
 
        public function displayText()
        {     
                $this->textGetter=new ConcreteCreator();
                print($this->textGetter->factoryMethod(new TextProduct()));
        }
 
        public function displayGraphics()
        {     
                $this->graphicGetter=new ConcreteCreator();
                print($this->graphicGetter->factoryMethod(new GraphicProduct()));
        }
}
?>

To call the specific Client methods, I used two little trigger scripts:

//TextTrigger.php

ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Client.php');
$client=new Client();
$client->displayText();
?>
 
//GraphicTrigger.php

ini_set("display_errors","2");
ERROR_REPORTING(E_ALL);
include_once('Client.php');
$client=new Client();
$client->displayGraphics();
?>

Finally, each of the trigger scripts was called from an HTML5 applications.





Request


PHP Dreamweaver Group

View Text

View Graphic

Created with Dreamweaver CS5 & HTML5

Copyright © 2010 - All Rights Reserved

0 Responses to “PHP Factory Method Design Pattern : Decoupling Your Products”


  • No Comments

Leave a Reply