Creational Design Patterns in PHP
Provided by Leumas Naypoka / www.apphp.com
Contents
Introduction
In this tutorial we'll discuss about the creational patterns, that aim to separate a system from how its objects are created, composed, and represented. They increase the system's flexibility in terms of the what, who, how, and when of object creation. Let's check this idea in details.
What are creational design patterns?
Creational Design Patterns are design patterns that deal with object creation and
inheritance mechanism, trying to create objects in a manner suitable to the situation.
These patterns are composed of two dominant ideas. One is encapsulating knowledge about which
concrete classes the system uses. Another is hiding how instances of these concrete classes are created and
combined. Creational design patterns are further categorized into Object-creational patterns and Class-creational
patterns, where Object-creational patterns deal with Object creation and Class-creational patterns deal with
Class-instantiation.
Usage and structure
As modern software engineering depends more on object composition than class inheritance, emphasis shifts away
from hard-coding behaviors toward defining a smaller set of basic behaviors that can be composed into more
complex ones. Hard-coding behaviors are inflexible because they require overriding or re-implementing the whole
thing in order to change parts of the design. Additionally, hard-coding does not promote reuse and is hard to
keep track of errors. For these reasons, creational patterns are more useful than hard-coding behaviors.
Creational patterns make design become more flexible. They provide different ways (patterns) to remove explicit
references in the concrete classes from the code that needs to instantiate them. In other words, they create
independency for objects and classes.
Below is a simple class structure that most creational patterns have in common.
Note that different creational patterns require additional and different participated classes.
Members:
- Creator: Declares object interface. Returns object.
- ConcreteCreator: Implements object's interface.
Pattern Examples
Below you may find examples of all common creational design patterns.
1. Registry
Lets start with this template. He's a little distracting from the overall series, because is not really creational, but in the future we will need to know how it's working. So the Registry - is a hash, which access data through the static methods. The main purpose of the Registry as a safe replacement of global variables.
<?php
/**
* Registry
*/
class Products
{
/**
* @var mixed[]
*/
protected static $data = array();
/**
* Add value to the Registry
* @param string $key
* @param mixed $value
* @return void
*/
public static function set($key, $value)
{
self::$data[$key] = $value;
}
/**
* Returns value from Registry by key
* @param string $key
* @return mixed
*/
public static function get($key)
{
return isset(self::$data[$key]) ? self::$data[$key] : null;
}
/**
* Removes value from Registry by key
* @param string $key
* @return void
*/
final public static function removeProduct($key)
{
if(array_key_exists($key, self::$data)){
unset(self::$data[$key]);
}
}
}
// REGISTRY USAGE
// --------------------
// Set new computer
Products::set('new_computer', 'Apple');
// Get new computer
print_r(Products::get('new_computer'));
// Output: Apple
?>
2. Object Pool
This pattern is a special type of the registry. Object Pool - is a hash, which can include initialized objects and pop them out when needed.
<?php
/**
* Object Pool
*/
class Factory
{
/**
* @var Product[]
*/
protected static $products = array();
/**
* Adds product to the pool
* @param Product $product
* @return void
*/
public static function pushProduct(Product $product)
{
self::$products[$product->getId()] = $product;
}
/**
* Returns product from the pool
* @param int|string $id - product ID
* @return Product $product
*/
public static function getProduct($id)
{
return isset(self::$products[$id]) ? self::$products[$id] : null;
}
/**
* Removes product from the pool
* @param int|string $id - product ID
* @return void
*/
public static function removeProduct($id)
{
if(array_key_exists($id, self::$products)){
unset(self::$products[$id]);
}
}
}
class Product
{
/**
* @var int|string
*/
protected $id;
public function __construct($id) {
$this->id = $id;
}
/**
* @return int|string
*/
public function getId()
{
return $this->id;
}
}
// OBJECT POOL USAGE
// --------------------
Factory::pushProduct(new Product('first'));
Factory::pushProduct(new Product('second'));
print_r(Factory::getProduct('first')->getId());
// Output: first
print_r(Factory::getProduct('second')->getId());
// Output: second
?>
3. Singleton
Probably one of the most popular patterns. Typically, it is the first pattent you learn on programming courses.
And yet when you're looking for a job are very fond to ask in interview.
Singleton principle is very simple. In order to ensure the existence of only one instance of Product,
we closed all the magical methods to create an instance of the class, cloning and serialization.
The only possible way to get the object - to use a static method Product::getInstance()
.
The first time he creates an instance of the class itself and put it in a static property
Product::$instance
. In the next calls, as part of a script, the method will return the same to us,
previously created, an instance of the class. You may read the full tutorial
about this pattern.
Below is a simple example:
<?php
/**
* Singleton
*/
final class Product
{
/**
* @var self
*/
private static $instance = null;
/**
* @var mixed
*/
public $prop;
/**
* Returns an instance of itself
* @return self
*/
public static function getInstance()
{
if(self::$instance != null){
self::$instance = new self();
}
return self::$instance;
}
/**
* Construct is closed
*/
private function __construct() {}
/**
* Cloning is prohibited
*/
private function __clone() {}
/**
* Serialization is prohibited
*/
private function __sleep() {}
/**
* Deserialization is prohibited
*/
private function __wakeup() {}
}
// USING OF SINGLETON
// --------------------
$firstProduct = Product::getInstance();
$secondProduct = Product::getInstance();
$firstProduct->prop = 1;
$secondProduct->prop = 2;
print_r($firstProduct->prop);
// Output: 2
print_r($secondProduct->prop);
// Output: 2
?>
4. Multiton (Singleton Pool)
Perhaps you may want to use different singletons in your project. Then, you have to separate the logic of the template with the implementation. Let's try to put together Singleton and Object Pool and see what we get:
<?php
/**
* The common interface of Object Pool
*/
abstract class FactoryAbstract
{
/**
* @var array
*/
protected static $instances = array();
/**
* Returns an instance of class then was called
* @return static
*/
public static function getInstance()
{
$className = static::getClassName();
if(!(self::$instances[$className] instanceof $className)){
self::$instances[$className] = new $className();
}
return self::$instances[$className];
}
/**
* Removes an instance of class
* @return void
*/
public static function removeInstance()
{
$className = static::getClassName();
if(array_key_exists($className, self::$instances)){
unset(self::$instances[$className]);
}
}
/**
* Returns an instance of class
* @return string
*/
final protected static function getClassName()
{
return get_called_class();
}
/**
* Construct is closed
*/
protected function __construct() {}
/**
* Cloning is prohibited
*/
final protected function __clone() {}
/**
* Serialization is prohibited
*/
final protected function __sleep() {}
/**
* Deserialization is prohibited
*/
final protected function __wakeup() {}
}
/**
* Interface of Object Pool
*/
abstract class Factory extends FactoryAbstract
{
/**
* It returns an instance of the class from which it was called
* @return static
*/
final public static function getInstance()
{
return parent::getInstance();
}
/**
* Removes an instance of class
* @return void
*/
final public static function removeInstance()
{
parent::removeInstance();
}
}
// MULTITON USAGE
// --------------------
/**
* First Singleton
*/
class FirstProduct extends Factory
{
public $a = array();
}
/**
* Second Singleton
*/
class SecondProduct extends FirstProduct
{
}
// Fill properties of singletons
FirstProduct::getInstance()->a[] = 1;
SecondProduct::getInstance()->a[] = 2;
FirstProduct::getInstance()->a[] = 3;
SecondProduct::getInstance()->a[] = 4;
print_r(FirstProduct::getInstance()->a);
// Output: array(1, 3)
print_r(SecondProduct::getInstance()->a);
// Output: array(2, 4)
?>
We devided the general logic in two abstract classes. Now let's take a more complicate example. Let's try to create multiple Singletons for each class, that differs by a unique identifier.
5. Factory Method
Now lets go back to basics. For example, we know that there are factories that produce some kind of a products. We do not care about how they make these products, but we know that any factory has only one universal way to get it:
<?php
/**
* Factory
*/
interface Factory
{
/**
* Returns a product
* @return Product
*/
public function getProduct();
}
/**
* Product
*/
interface Product
{
/**
* Returns name of the product
* @return string
*/
public function getName();
}
/**
* First Factory
*/
class FirstFactory implements Factory
{
/**
* Returns a product
* @return Product
*/
public function getProduct()
{
return new FirstProduct();
}
}
/**
* Second Factory
*/
class SecondFactory implements Factory
{
/**
* Returns a product
* @return Product
*/
public function getProduct()
{
return new SecondProduct();
}
}
/**
* First Product
*/
class FirstProduct implements Product
{
/**
* Returns a name of product
* @return string
*/
public function getName()
{
return 'The first product';
}
}
/**
* Second Product
*/
class SecondProduct implements Product
{
/**
* Returns a name of product
* @return string
*/
public function getName()
{
return 'Second product';
}
}
// USING OF FACTORY METHOD
// --------------------
$factory = new FirstFactory();
$firstProduct = $factory->getProduct();
$factory = new SecondFactory();
$secondProduct = $factory->getProduct();
// The first product
print_r($firstProduct->getName());
// The second product
print_r($secondProduct->getName());
?>
getProduct()
is a factory method.
6. Abstract Factory
Sometimes we have several factories of the same type and we want to encapsulate the logic of choice, which factories to use for a given task. This is where we come to the aid of this template.
<?php
/**
* Some configuration file
*/
class Config
{
public static $factory = 1;
}
/**
* some product
*/
interface Product
{
/**
* Returns a name of product
* @return string
*/
public function getName();
}
/**
* Abstract Factory
*/
abstract class AbstractFactory
{
/**
* Returns factory
* @return AbstractFactory - child object
* @throws Exception
*/
public static function getFactory()
{
switch(Config::$factory){
case 1:
return new FirstFactory();
case 2:
return new SecondFactory();
}
throw new Exception('Bad configuration');
}
/**
* Returns product
* @return Product
*/
abstract public function getProduct();
}
/**
* FIRST FAMILY
*/
class FirstFactory extends AbstractFactory
{
/**
* Returns product
* @return Product
*/
public function getProduct()
{
return new FirstProduct();
}
}
/**
* Product from the first factory
*/
class FirstProduct implements Product
{
/**
* Returns a name of product
* @return string
*/
public function getName()
{
return 'The product from the first factory';
}
}
/**
* SECOND FAMILY
*/
class SecondFactory extends AbstractFactory
{
/**
* Returns product
* @return Product
*/
public function getProduct()
{
return new SecondProduct();
}
}
/**
* Product from the second factory
*/
class SecondProduct implements Product
{
/**
* Returns a name of product
* @return string
*/
public function getName()
{
return 'The product from second factory';
}
}
// USING OF ABSTRACT FACTORY
// --------------------
$firstProduct = AbstractFactory::getFactory()->getProduct();
Config::$factory = 2;
$secondProduct = AbstractFactory::getFactory()->getProduct();
// The first product from the first factory
print_r($firstProduct->getName());
// The second product from second factory
print_r($secondProduct->getName());
7. Lazy Initialization
Lets think that you have a factory, but you do not know what part of its functionality you need now, and what - no. In such cases, the necessary operations are performed only when they are needed and only once:
<?php
/**
* The product
*/
interface Product
{
/**
* Returns a name of product
* @return string
*/
public function getName();
}
class Factory
{
/**
* @var Product
*/
protected $firstProduct;
/**
* @var Product
*/
protected $secondProduct;
/**
* Returns product
* @return Product
*/
public function getFirstProduct()
{
if(!$this->firstProduct){
$this->firstProduct = new FirstProduct();
}
return $this->firstProduct;
}
/**
* Returns product
* @return Product
*/
public function getSecondProduct()
{
if (!$this->secondProduct) {
$this->secondProduct = new SecondProduct();
}
return $this->secondProduct;
}
}
/**
* First Product
*/
class FirstProduct implements Product
{
/**
* Returns a name of product
* @return string
*/
public function getName()
{
return 'The first product';
}
}
/**
* Second Product
*/
class SecondProduct implements Product
{
/**
* Returns a name of product
* @return string
*/
public function getName()
{
return 'Second product';
}
}
// USING OF LAZY INITIALIZATION
// --------------------
$factory = new Factory();
print_r($factory->getFirstProduct()->getName());
// The first product
print_r($factory->getSecondProduct()->getName());
// Second product
print_r($factory->getFirstProduct()->getName());
// The first product
8. Prototype
Some objects must be created many times. It makes sense to save on their initialization, particularly if initialization requires many time or resources. The prototype is a pre-initialized and saved object. If necessary, it can be cloned.
<?php
/**
* Product
*/
interface Product
{
}
/**
* Factory
*/
class Factory
{
/**
* @var Product
*/
private $product;
/**
* @param Product $product
*/
public function __construct(Product $product)
{
$this->product = $product;
}
/**
* Returns new product by cloning
* @return Product
*/
public function getProduct()
{
return clone $this->product;
}
}
/**
* Product
*/
class SomeProduct implements Product
{
public $name;
}
// USING OF PROTOTYPE
// --------------------
$prototypeFactory = new Factory(new SomeProduct());
$firstProduct = $prototypeFactory->getProduct();
$firstProduct->name = 'The first product';
$secondProduct = $prototypeFactory->getProduct();
$secondProduct->name = 'The second product';
print_r($firstProduct->name);
// The first product
print_r($secondProduct->name);
// The second product
9. Builder
The last template is Builder. It is useful when we want to encapsulate the creation of a complex object. We just tell the factory to what builder to entrust the creation of the product.
<?php
/**
* Some Product
*/
class Product
{
/**
* @var string
*/
private $name;
/**
* @param string $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* @return string
*/
public function getName() {
return $this->name;
}
}
/**
* Some Factory
*/
class Factory
{
/**
* @var Builder
*/
private $builder;
/**
* @param Builder $builder
*/
public function __construct(Builder $builder)
{
$this->builder = $builder;
$this->builder->buildProduct();
}
/**
* Returns create dproduct
* @return Product
*/
public function getProduct()
{
return $this->builder->getProduct();
}
}
/**
* Some Builder
*/
abstract class Builder
{
/**
* @var Product
*/
protected $product;
/**
* Returns created product
* @return Product
*/
final public function getProduct()
{
return $this->product;
}
/**
* Creates product
* @return void
*/
public function buildProduct()
{
$this->product = new Product();
}
}
/**
* First Builder
*/
class FirstBuilder extends Builder
{
/**
* Creates product
* @return void
*/
public function buildProduct()
{
parent::buildProduct();
$this->product->setName('The product of the first builder');
}
}
/**
* Second Builder
*/
class SecondBuilder extends Builder
{
/**
* Creates product
* @return void
*/
public function buildProduct()
{
parent::buildProduct();
$this->product->setName('The product of second builder');
}
}
// USING OF BUILDER
// --------------------
$firstDirector = new Factory(new FirstBuilder());
$secondDirector = new Factory(new SecondBuilder());
print_r($firstDirector->getProduct()->getName());
// The product of the first builder
print_r($secondDirector->getProduct()->getName());
// The product of the second builder