Creational Design Patterns in PHP

This tutorial describes and demonstrates the most popular 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($keyself::$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($idself::$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($classNameself::$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)

?>
So, to add a new class of Singleton we just need to inherit it from class Factory. In the example we created two such classes and checked that each of these classes has only one copy.

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());
?>
In this example, the method 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());
As you can see, we do not have to worry about what kind of factory to take. Abstract Factory itself verifies the configuration and returns a suitable object. Of course, it is not necessary to be guided by the abstract factory configuration file. The logic of selection can be any logic you want.

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


Comments


Please post only comments related to the original tutorial. Be polite and helpful, do not spam or offend others.
Create Your Free Account
Please remember that this information is essential to use our services correctly.
After creating the account you will be able to download all of our FREE products.
Fields marked with * are mandatory






Please send me information about updates, new products, specials and discounts from ApPHP!
We recommend that your password should be at least 6 characters long and should be different from your username/email. Please use only letters of the English alphabet to enter your name.

Your e-mail address must be valid. We use e-mail for communication purposes (order notifications, etc). Therefore, it is essential to provide a valid e-mail address to be able to use our services correctly.

All your private data is confidential. We will never sell, exchange or market it in any way. Please refer to Privacy Policy.

By clicking "Create Account", you are indicating that you have read and agree to the ApPHP Terms & Conditions.

Quick Registration with: Facebook / Google