Improve your factories (PHP)

Georgi Razsolkov • May 23, 2020

Factory is a creational design pattern which is used to retrieve instances of objects, based on some given parameter. Working on PHP projects, I find myself using the pattern very often, although a lot of factory classes I see across the projects that I've been working with are messed up.

For example:

Let's say we have factory that will create CarEngine based on the Car brand. Most of the time I see factories like this:


class CarEngineFactory implements ICarEngineFactory { public function make(string $carBrand): IEngine { switch ($carBrand) { case "mercedes": return new MercedesEngine(); case "bmw": return new BmwEngine(); default: throw new CarEngineNotAvailableException(); } } }

This looks kinda okay ?

Well, since we have only two cases - yes, but once we add more and more Car brands in our system this factory will become hard to use.

Problems:

  1. Parameters are just random strings, so developers will have hard time figuring out what to pass.
  2. The more car brands we add - the bigger switch case block will be.

So let me show you my take on it.

class CarEngineFactory implements ICarEngineFactory
{
    protected static $availableEngines = [
        Mercedes::class => MercedesEngine::class,
        Bmw::class => BmwEngine::class,
    ];

    public function make(ICar $car): IEngine
    {
        $carClass = get_class($car);

        if (!array_key_exists(get_class($car), self::$availableEngines)) {
            throw new CarEngineNotAvailableException();
        }

        $engineClass = self::$availableEngines[$carClass];

        return (new $engineClass());

    }

}

Now we have a static property which holds the corresponding Car class and Car Engine class. Instead of random strings we're passing the whole object. Now if we want to add another Car brand with corresponding Car Engine, we just need to describe it into the $availableEngines property and we're good to go.

If we have very big switch case block, then array_key_exists will be much faster, because it does a hash lookup and it runs almost in O(1) time complexity (almost, because of possible hash collisions).

This method also gives us more flexibility. The $availableEngines could be moved to some configuration file for example.

Of course this is just a basic factory implementation. The factories could be much more complex, but the main point that I wanted to show in this example is that we should not be passing random strings to our factories. It makes them hard to read and hard to use for other developers working on the same project.