Builder Pattern in PHP

The builder pattern is another creational pattern, meaning its purpose is the creation of objects. Similar to the purposes of Singleton, Multiton and Factory patterns.

However the builder pattern will not only create objects, but prepare them for your application too. The builder pattern actually uses the factory method pattern behind the scenes, but I’ll get onto that soon.

When to use a builder

The builder pattern is most useful in large(ish) applications (like most patterns). The goal is to create an object, pre-set some of its attributes, inject dependencies and other general object setup. So by the time the application (client/controller) gets the object, it’s ready to be used in its glory.

If you need a class which requires any kind of setup, creating a builder which will create the object for you, is almost always the better option.

When not to use a builder

If all you need to do initialise a class but it doesn’t require any setup, you may as well initialise the class yourself using a dependency injection container or a factory.

Builders for n00bs

If you were ever a junior developer, which we all were once upon a time. You will have almost definitely created a class, where you pass a bunch of attributes through to the constructor, which in turn sets up the object. This has the same end result as using a builder class but does have its drawbacks. Here are a couple of them.

  1. It can get messy quick if you start adding more than 2 or 3 arguments to you constructor. PHP Mess Detector will be on your back for sure.
  2. If you need to create multiple builders for a particular class, you’ll be stuck with having to implement some disgusting switch in your constructor.
  3. Testing can be harder as __construct methods cannot be stubbed.

Saying all that, if you are absolutely sure you will only ever need 1 or 2 arguments of config and that you won’t need multiple builders. Building an object in the constructor can be tidier. Just remember to never do anything but set values inside a constructor.

Rules when using a builder

  1. Each builder you create should have multiple methods, each method should only set one attribute, prepare one piece of config or inject one dependency.
  2. Each builder you create for a class, should be polymorphic and implement the same interface.
  3. Each builder should have a getResult() method, which will return the fully prepared object.
  4. Instead of directing calling a builders prepare methods, the builder should be created and passed to a director object.
  5. Each director object should have a build method which calls each method of the builders setup. This is possible as long as all builders implement the same interface, making them polymorphic.

Drawbacks

Clutching at straws, I would say using a builder makes your code marginally harder to debug, as it adds one more layer to your application.

Example

Please note that to keep this example slightly simpler, I have sacrificed encapsulation, by not creating setter and getter methods. Normally you would create these, but here I would rather focus on the builder class.

/**
 * An extremely basic class for creating people objects
 */
class Person
{
    public $employed;

    public $gender;

    const GENDER_MALE   = "Male";

    const GENDER_FEMALE = "Female";

}

/**
 * All people builder should implement this interface
 */
interface PersonBuilderInterface
{
    public function setGender();
    public function setEmployed();
    public function getResult();
}

/**
 * builder to create an employed male
 */
class EmployedMaleBuilder implements PersonBuilderInterface
{
    private $person;

    public function __construct()
    {
        $this->person = new Person();
    }

    public function setGender()
    {
        $this->person->gender = Person::GENDER_MALE;
    }

    public function setEmployed()
    {
        $this->person->employed = true;
    }

    public function getResult()
    {
        return $this->person;
    }
}

/**
 * builder to create an unemployed male
 */
class UnemployedMaleBuilder implements PersonBuilderInterface
{
    private $person;

    public function __construct()
    {
        $this->person = new Person();
    }


    public function setGender()
    {
        $this->person->gender = Person::GENDER_MALE;
    }

    public function setEmployed()
    {
        $this->person->employed = false;
    }

    public function getResult()
    {
        return $this->person;
    }
}

/**
 * builder to create an employed female
 */
class EmployedFemaleBuilder implements PersonBuilderInterface
{
    private $person;

    public function __construct()
    {
        $this->person = new Person();
    }

    public function setGender()
    {
        $this->person->gender = Person::GENDER_FEMALE;
    }

    public function setEmployed()
    {
        $this->person->employed = true;
    }

    public function getResult()
    {
        return $this->person;
    }
}

/**
 * builder to create an unemployed female
 */
class UnemployedFemaleBuilder implements PersonBuilderInterface
{
    private $person;

    public function __construct()
    {
        $this->person = new Person();
    }

    public function setGender()
    {
        $this->person->gender = Person::GENDER_FEMALE;
    }

    public function setEmployed()
    {
        $this->person->employed = false;
    }

    public function getResult()
    {
        return $this->person;
    }
}

/**
 * The director class is part of the builder patter, the build method should be passed a builder.
 * The build method should than call all of the builder methods and return a Person object
 */
class PersonDirector
{
    public function build(PersonBuilderInterface $builder)
    {
        $builder->setGender();
        $builder->setEmployed();

        return $builder->getResult();
    }
}

$director                = new PersonDirector();
$employedMaleBuilder     = new EmployedMaleBuilder();
$unemployedMaleBuilder   = new UnemployedMaleBuilder();
$employedFemaleBuilder   = new EmployedFemaleBuilder();
$unemployedFemaleBuilder = new UnemployedFemaleBuilder();

/**
 * object(Person)#3 (2) {
 * (
 *   ["employed"] => bool(true)
 *   ["gender"] => string(4) "Male"
 * )
 */
$employedMale     = $director->build($employedMaleBuilder);

/**
 * object(Person)#5 (2) {
 * (
 *   ["employed"] => bool(false)
 *   ["gender"] => string(4) "Male"
 * )
 */
$unemployedMale   = $director->build($unemployedMaleBuilder);

/**
 * object(Person)#7 (2) {
 * (
 *   ["employed"] => bool(true)
 *   ["gender"] => string(4) "Female"
 * )
 */
$employedFemale   = $director->build($employedFemaleBuilder);

/**
 * object(Person)#11 (2) {
 * (
 *   ["employed"] => bool(false)
 *   ["gender"] => string(4) "Female"
 * )
 */
$unemployedFemale = $director->build($unemployedFemaleBuilder);

The code above is all pretty simple stuff. There is just one thing I don’t like, the new keyword. Creating objects by using the new keyword, should be kept to an absolute minimum, ideally objects should only be created inside service locators and factories.

46 Love This