Factory Pattern in PHP

The factory pattern is one of the most commonly used design patterns, something you’ll definitely be glad to have in your toolbox. It’s also pretty simple to get to grips with and start reaping the benefits.

The purpose of any factory class, is to create and return instances of other classes, just like a real world factory creates products. There are two types of factory design patterns. One is called the factory method and the other is called an abstract factory. There is another not official pattern called simple factory. All are described in detail below. Let’s get going.

Simple factory

Simple factories, which are sometimes called static factories, are the simplest form of a factory. Although it’s not an official design pattern, it does have it’s uses especially in small to mid sized applications.

Simple factories are classes which can return a single class (these are called product classes) instance, the class to initialise could be hard coded (if the factory only returns objects of one particular product) or determined at runtime (if there are multiple classes which could be initialised), this is commonly done with a switch statement.

Rules

  1. The factory class must have a static method, this is called a factory method.
  2. The factory method must return a class instance.
  3. Only one object should be created and returned at a time.

Example

/**
 * All animal should extend this abstract animal class
 */
abstract class AnimalAbstract 
{
    protected $species;

    public function getSpecies() {
        return $this->species;
    }
}

/**
 * used to represent a cat
 */
class Cat extends AnimalAbstract 
{
    protected $species = 'cat';
}

/**
 * used to represent a dog
 */
class Dog extends AnimalAbstract 
{
    protected $species = 'dog';
}

/**
 * The is the factory which creates animal objects
 */
class AnimalFactory 
{
    public static function factory($animal) 
    {
        switch ($animal) {
            case 'cat':
                $obj = new Cat();
                break;
            case 'dog':
                $obj = new Dog();
                break;
            default:
                throw new Exception("Animal factory could not create animal of species '" . $animal . "'", 1000);
        }
        return $obj;
    }
}

try {

    $cat = AnimalFactory::factory('cat'); // object(Cat)#1
    echo $cat->getSpecies(); // cat

    $dog = AnimalFactory::factory('dog'); // object(Dog)#1
    echo $dog->getSpecies(); // dog

    $hippo = AnimalFactory::factory('hippopotamus'); // This will throw an Exception

} catch(Exception $e) {
    echo $e->getMessage(); // AnimalFactory could not create animal of species 'hippopotamus'
}

Factory method

The factory method is close to identical to the simple factory. The only difference between them, is that instead of having a single factory, you have multiple factories. This is useful if you have a large amount of product classes and want to group them. An example of this would be, instead of creating an animal factory, which could return any type of animal in the world (which would obviously be massive). You would have multiple factories to group animal how you see fit, e.g. PetAnimalFactory, FarmAnimalFactory, SafariAnimalFactory etc.

Rules

  1. Each factory you create e.g. PetAnimalFactory and FarmAnimalFactory, must implement a factory interface, making them polymorphic.
  2. Each product class (here these are the animals) you create must also implement an animal interface, making them polymorphic too.
  3. Each factory must have a factory method which will return an animal instance.

Advantages

  1. Hides product classes from the client/controller, which helps to remove dependencies.
  2. All product classes are forced to be polymorphic and thus interchangable.

Drawbacks

  1. Each factory, becomes a dependency of the application, these could be stored in a dependency injection container to mitigate this.

Example

/**
 * All animal should extend this abstract animal class
 */
abstract class AnimalAbstract 
{
    protected $species;

    public function getSpecies() {
        return $this->species;
    }
}

/**
 * used to represent a cat
 */
class Cat extends AnimalAbstract 
{
    protected $species = 'cat';
}

/**
 * used to represent a dog
 */
class Dog extends AnimalAbstract 
{
    protected $species = 'dog';
}

/**
 * used to represent a pig
 */
class Pig extends AnimalAbstract 
{
    protected $species = 'pig';
}

/**
 * used to represent a chicken
 */
class Chicken extends AnimalAbstract 
{
    protected $species = 'chicken';
}

/**
 * used to represent a zebra
 */
class Zebra extends AnimalAbstract 
{
    protected $species = 'zebra';
}

/**
 * used to represent a giraffe
 */
class Giraffe extends AnimalAbstract 
{
    protected $species = 'giraffe';
}

/**
 * used to represent a all factories should implement this interface
 */
interface AnimalFactoryInterface 
{
    public static function factory($animal);
}

/**
 * this should be used to create all animals which are pets
 */
class PetAnimalFactory implements AnimalFactoryInterface
{
    public static function factory($animal) 
    {
        switch ($animal) {
            case 'cat':
                $obj = new Cat();
                break;
            case 'dog':
                $obj = new Dog();
                break;
            default:
                throw new Exception("Pet animal factory could not create animal of species '" . $animal . "'", 1000);
        }
        return $obj;
    }
}

/**
 * this should be used to create all animals which are farm animals
 */
class FarmAnimalFactory implements AnimalFactoryInterface 
{
    public static function factory($animal) 
    {
        switch ($animal) {
            case 'pig':
                $obj = new Pig();
                break;
            case 'chicken':
                $obj = new Chicken();
                break;
            default:
                throw new Exception("Farm animal factory could not create animal of species '" . $animal . "'", 1000);
        }
        return $obj;
    }
}

/**
 * this should be used to create all animals which are safari animals
 */
class SafariAnimalFactory implements AnimalFactoryInterface
{
    public static function factory($animal) 
    {
        switch ($animal) {
            case 'zebra':
                $obj = new Zebra();
                break;
            case 'giraffe':
                $obj = new Giraffe();
                break;
            default:
                throw new Exception("Safari animal factory could not create animal of species '" . $animal . "'", 1000);
        }
        return $obj;
    }
}

try {

    $cat = PetAnimalFactory::factory('cat'); // object(Cat)#1
    echo $cat->getSpecies(); // cat

    $pig = FarmAnimalFactory::factory('pig'); // object(Pig)#1
    echo $pig->getSpecies(); // pig

    $giraffe = SafariAnimalFactory::factory('giraffe'); // object(Giraffe)#1
    echo $giraffe->getSpecies(); // giraffe

    $petChicken = PetAnimalFactory::factory('chicken'); // This will throw an Exception

} catch(Exception $e) {
    echo $e->getMessage(); // PetAnimalFactory could not create animal of species 'chicken'
}

Abstract factory

The goal here, is to create a factory (the abstract factory), which creates product factories (in the example below these are output types). The product factories then create the actual products (below these are the pretty and ugly output type classes). Each product factory class has multiple methods for creating different class instances.

Rules

  1. Each product factory must extend the same abstract class or implement the same interface.
  2. Each product factory must have multiple methods, one for each class it wants to initialise.
  3. Each product factory class you create must have the same methods. The same methods in each factory must return polymorphic class instances and implement the same interface . If this is still unclear, the example should clear things up.

Advantages

  1. Hides product classes from the client/controller, which helps to remove dependencies.
  2. All product classes are made to be polymorphic, thus interchangable.
  3. It promote consistency among products, all products should be grouped by the product factories

Drawbacks

  1. When adding a new product class, the abstract factory would need updating, this violates the Interface Segregation principle. You’ll be hard pushed to find someone who actually gives a damn.

Example

/**
 * This is the abstract factory, each factory should extend this (WebOutputTypeFactory & DataOutputTypeFactory)
 */
abstract class AbstractOutputTypeFactory
{
    abstract public function prettyOutput();

    abstract public function uglyOutput();
}

/**
 * All class which represent pretty output, should extend this class
 */
abstract class AbstractPrettyOutput
{
    abstract public function getPrettyOutput();
}

/**
 * All class which represent ugly output, should extend this class
 */
abstract class AbstractUglyOutput
{
    abstract public function getUglyOutput();
}

/**
 * This is a factory which can create all types of web output objects
 */
class WebOutputTypeFactory extends AbstractOutputTypeFactory
{
    public function prettyOutput()
    {
        return new WebPrettyOutput();
    }

    public function uglyOutput()
    {
        return new WebUglyOutput();
    }
}

/**
 * This is a factory which can create all types of data output objects
 */
class DataOutputTypeFactory extends AbstractOutputTypeFactory
{
    public function prettyOutput()
    {
        return new DataPrettyOutput();
    }

    public function uglyOutput()
    {
        return new DataUglyOutput();
    }
}

/**
 * This class will represent pretty web output (e.g. HTML)
 */
class WebPrettyOutput extends AbstractPrettyOutput
{
    public function getPrettyOutput()
    {
        return '<h1>Imagine you had some really pretty web output here</h1>';
    }
}

/**
 * This class will represent ugly web output (e.g. XML)
 */
class WebUglyOutput extends AbstractUglyOutput
{
    public function getUglyOutput()
    {
        return 'Imagine you had some really ugly web output here';
    }
}

/**
 * This class will represent pretty data output (e.g. JSON)
 */
class DataPrettyOutput extends AbstractPrettyOutput
{
    public function getPrettyOutput()
    {
        return "{ 'text': 'Imagine you had some really pretty data output here' }";
    }
}

/**
 * This class will represent ugly data output(e.g. CSV)
 */
class DataUglyOutput extends AbstractUglyOutput
{
    public function getUglyOutput()
    {
        return 'Imagine, you, had, some, really, ugly, CSV, output, here';
    }
}

// this is used to create web outputs
$webFactory = new WebOutputTypeFactory();

$webPretty = $webFactory->prettyOutput();
echo $webPretty->getPrettyOutput(); // 

Imagine you had some really pretty web output here

$webUgly = $webFactory->uglyOutput(); echo $webUgly->getUglyOutput(); // Imagine you had some really ugly web output here // this is used to create data outputs $dataFactory = new DataOutputTypeFactory(); $dataPretty = $dataFactory->prettyOutput(); echo $dataPretty->getPrettyOutput(); // { 'text': 'Imagine you had some really pretty data output here' } $dataUgly = $dataFactory->uglyOutput(); echo $dataUgly->getUglyOutput(); // Imagine, you, had, some, really, ugly, CSV, output, here

Conclusion

The factory pattern is a powerful design pattern in all of its forms. Some people may tell you the simple factory shouldn’t be used, generally having poor reasons not to do so. At the end of the day we just want to get things done and unless you’re working with someone extremely anal, nobody should have a problem.

The abstract factory pattern is by far the hardest to understand out of the three. It’s not always obvious when a good time to use it would be. Don’t force this, there is a huge amount of abstraction in place which is only required when building complex systems. As long as you know what an abstract factory is, sooner or later you’ll find a genuine use case.

Thanks for reading

Until next time!

16 Love This

Leave a Reply

Your email address will not be published.