Design patterns are common solutions to commonly-encountered problems in software development. Of these the most widely used are creational patterns – methods of creating objects, most notably the factory pattern. The factory pattern is used for the centralised instantiation of related class objects.
Let’s illustrate this with an example using the idea of shapes. We want to have an abstract base Shape class and then concrete classes derived from the base class of specific (or concrete) shapes.
abstract class Shape
{
abstract function Draw();
}
class Square extends Shape
{
function Draw()
{
echo "Square";
}
}
class Circle extends Shape
{
function Draw()
{
echo "Circle";
}
}
We would, without any of this factory nonsense, instantiate the classes directly:
$square = new Square();
$circle = new Circle();
Using the standard factory method rather than instantiating directly we would create classes to handle the instantiation for us. First we define an abstract ShapeFactory class and then specific concrete factories to create individual shapes. So adding to our previous code:
abstract class ShapeFactory
{
abstract function Create();
}
class SquareFactory extends ShapeFactory
{
function Create()
{
return new Square();
}
}
class CircleFactory extends ShapeFactory
{
function Create()
{
return new Circle();
}
}
So now, rather than creating instances directly we first create a concrete factory instance and then use that to create the concrete shape:
$squareFactory = new SquareFactory();
$circleFactory = new CircleFactory();
$square = $squareFactory->Create();
$circle = $circleFactory->Create();
This is all very well and good (and design patterns in general are a very powerful and useful tool) but it fails to take advantage of PHP’s powers of runtime adaptability, the ability to change and update code behaviour and functionality during execution (at runtime).
For example having these factories provides us with a standard way to create shapes but how would we add new shapes easily. With the factory pattern we would still need code modification, new shapes require a new factory and for this factory to be explicitly used to create objects.
With the wonder that is PHP however we can be more flexible, more dynamic.
Consider the possibility we want to be able to create any type of shape from a factory.
As an example you could have a Shape Maker class which took a text string for the type and returned an appropriate object (this is known as a paramerized factory):
class ShapeMaker
{
public function Create($type)
{
if ($type == "circle")
return new Circle();
else if ($type == "square")
return new Square();
else
return null;
}
}
This would allow the creation of shapes as follows:
$maker = new ShapeMaker();
$square = $maker->Create("square");
$circle = $maker->Create("circle");
But we would really like to go further than this; we want to create a dynamic factory, one in which shapes are simply registered with a type and a associated class name, and then created by passing the type.
For this to work we would need the following:
- A list (array) of registered types and their class names
- A method to register new types
- A method to create an object of the given type
- A fall-back; what to do if a creation request is made for a type that doesn’t exist
To make this all even easier to use in our example the class will be an abstract class using static members and methods (though of course the same idea holds true for a non-abstract class using non-static members, it would just need to be instantiated first).
So, using the same shape code but replacing the factory code with the following:
abstract class ShapeFactory
{
protected static $types = array();
public static function Register($type, $class)
{
self::$types[$type]=$class;
}
public static function IsRegistered($type)
{
if (isset(self::$types[$type]))
return true;
else
return false;
}
public static function Create($type)
{
if (isset(self::$types[$type]) &&
class_exists(self::$types[$type]))
return new self::$types[$type];
else
return null;
}
}
ShapeFactory::Register("square","Square");
ShapeFactory::Register("circle","Circle");
We create a ShapeFactory class with the ability to register, check the existence of, and create shape classes. All that remains to do is to register the Square and Circle classes with type names (the lower case versions). Once this is done they can be created with:
$square = ShapeFactory::Create("square");
$circle = ShapeFactory::Create("circle");
Which creates objects of Square and Circle using the dynamic type identifiers.
So what is the advantage of this?
Well imagine now in our code we wish to add a new shape. This shape (Triangle) is contained in a file triangle.php which may or may not be included at runtime with an include_once. We may want to limit the inclusion of triangle to an add-on pack or for just specific users, so we have logic to decide if triangle should be included.
All triangle.php needs now to contain is:
class Triangle extends Shape
{
function Draw()
{
echo "Triangle";
}
}
ShapeFactory::Register("triangle","Triangle");
And then if it is included the triangle type becomes automatically available from the ShapeFactory. If ShapeFactory had been extended (very easy) to return a list of possible shapes which was used to populate a UI component then triangle would now appear, could be selected, and used as a type identifier to create a concrete shape of the correct type (triangle) at runtime.
The idea of the dynamic factory can be taken much further with list provision or description fields for example and is with certain purplepixie.org products such as FreeDESK.
Another layer of abstraction could also easily be added; all the functionality of the ShapeFactory would be generic to any dynamic factory, just the list of types and objects being unique (if this was a non-static class these could be just different instances of a general DynamicFactory class). In the static example we create a DynamicFactory and then extend from it product-specific dynamic factories to be used to house the lists of specific types:
<?php
abstract class Shape
{
abstract function Draw();
}
class Square extends Shape
{
function Draw()
{
echo "Square";
}
}
class Circle extends Shape
{
function Draw()
{
echo "Circle";
}
}
abstract class DynamicFactory
{
protected static $types = array();
public static function Register($type, $class)
{
self::$types[$type]=$class;
}
public static function IsRegistered($type)
{
if (isset(self::$types[$type]))
return true;
else
return false;
}
public static function Create($type)
{
if (isset(self::$types[$type]) &&
class_exists(self::$types[$type]))
return new self::$types[$type];
else
return null;
}
}
class ShapeFactory extends DynamicFactory { }
ShapeFactory::Register("square","Square");
ShapeFactory::Register("circle","Circle");
$a_square = ShapeFactory::Create("square");
$a_circle = ShapeFactory::Create("circle");
$a_square->Draw();
echo "\n";
$a_circle->Draw();
echo "\n";
?>
Some links to source files for download:
classic.php : Shows the classic factory pattern
dynamic.php : Shows the first dynamic iteration along with the maker class
dynamic2.php : Shows the finalised version