Factory method pattern using PHP 5.3 and late static bindings
Factory method design pattern introduced by GoF (Gang of Four) which I’ve talked about in a previous blog, has the base idea of a method that generates objects. Most implementations are “poor”, because they use a lot of hard-coding for the factory (just like me in the previous blog).
PHP 5.3 offers the possibility of a very interesting implementation using “Late static bindings“.
The classes from which the objects will be generated are:
1// abstract base class that will be inherited
2abstract class Drink {
3
4 // ingredients
5 protected $ingredients;
6
7 // public method for producing the drink
8 abstract public function MakeDrink();
9}
10
11// a child class for tea
12class Tea_Drink extends Drink {
13
14 // ingredients for tea
15 protected $ingredients = array('tea', 'sugar', 'mink', 'water');
16
17 // make tea
18 public function MakeDrink() {
19
20 // make tea
21 }
22}
23
24// another class for Bloody Mary
25class BloodyMary_Drink extends Drink {
26
27 // ingredients for Bloody Mary
28 protected $ingredients = array('votka', 'salt', 'tomato juice');
29
30 // make Bloody Mary
31 public function MakeDrink() {
32
33 // make BloodyMary
34
35 }
36}
The idea is to have an abstract factory class to extend as simple as possible when creating each new factory class.
PHP 5
In PHP 5 the class will look something like this:
1// abstract Factory class
2abstract class absFactory {
3
4 // name of the base class
5 static protected $base_class = '';
6
7 // factory method
8 public static function getInstance($type) {
9
10 // name of the resulting class
11 $class_name = $type . '_' . self::$base_class;
12
13 // check if class exists
14 // here you can add an autoloader
15 if(!class_exists($class_name)) {
16 throw new Exception( 'Class ' . $class_name . ' not loaded!');
17 }
18
19 // check to see if the class inherits the base class
20 if(!is_subclass_of($class_name, self::$base_class)) {
21 throw new Exception(
22 'Class ' . $class_name . ' is not a child of ' . self::$base_class
23 );
24 }
25
26 // new object
27 return new $class_name;
28
29 }
30
31}
Because the getInstance() method is static the property will be static too.
If we try:
1class DrinkFactory extends absFactory {
2
3 static protected $base_class = 'Drink';
4}
5
6try {
7
8 $obj = DrinkFactory::getInstance('Tea');
9
10} catch (Exception $e) {
11
12 echo $e->getMessage();
13}
The output will be:
1Class Tea_ not loaded!
Because of the “self”, we can’t just call the method using the child class because the value of $base_class will be “” and not “Drink”, we must overwrite the getInstance() method. Which is quite “complicated”.
A working version in PHP 5 will be:
1class DrinkFactory extends absFactory {
2
3 public static function getInstance($type) {
4
5 self::$base_class = 'Drink';
6
7 // factory method of the base factory class
8 parent::getInstance($type);
9
10 }
11
12}
13
14try {
15
16 $obj = DrinkFactory::getInstance('Tea');
17
18} catch (Exception $e) {
19
20 echo $e->getMessage();
21}
But is not exactly “elegant”.
PHP 5.3
Here we have “Late static bindings”, which is basically introducing the work “static”.
The base factory class will look something like this:
1// abstract Factory class
2abstract class absFactory {
3
4 // name of the base class
5 static protected $base_class = '';
6
7 // factory method
8 public static function getInstance($type) {
9
10 // name of the resulting class
11 $class_name = $type . '_' . static::$base_class;
12
13 // check if class exists
14 // here you can add an autoloader
15 if(!class_exists($class_name)) {
16 throw new Exception( 'Class ' . $class_name . ' not loaded!');
17 }
18
19 // check to see if the class inherits the base class
20 if(!is_subclass_of($class_name, static::$base_class)) {
21 throw new Exception(
22 'Class ' . $class_name . ' is not a child of ' . static::$base_class
23 );
24 }
25
26 // new object
27 return new $class_name;
28
29 }
30
31}
A change so small allows us to create a much “nicer” factory class:
1class DrinkFactory extends absFactory {
2
3 static protected $base_class = 'Drink';
4
5}
6
7try {
8
9 $obj = DrinkFactory::getInstance('Tea');
10
11} catch (Exception $e) {
12
13 echo $e->getMessage();
14}
Basically in this version only the relevant property in this context is overwritten.