Builder Design Pattern

Builder pattern is a creational design pattern which separates the contruction process of complex objects from its representation so that same contruction process can create different representation.

Let’s demonstrate this with an example of Pizza.

class Pizza
{
    private int $size;
    private bool $pepperoni;
    private bool $mushrooms;
    private bool $extraCheese;
    private bool $onions;
    private bool $bacon;
 
    public function __construct(
        int $size, 
        bool $pepperoni, 
        bool $mushrooms, 
        bool $extraCheese,
        bool $onions,
        bool $bacon
    ) 
    {
        $this->size = $size;
        $this->pepperoni = $pepperoni;
        $this->mushrooms = $mushrooms;
        $this->extraCheese = $extraCheese;
        $this->onions = $onions;
        $this->bacon = $bacon;
    }
 
    // getter methods here
}

Pizza class looks like this. Let’s say we want to have a 12 inch pizza with customised toppings(we want to include extraCheese and bacon). so to instantiate this pizza class in client will be as following

$pizza1 = new Pizza(12, false, false, true, false, true);

This way, the Problem is that constructor params are not named here which is hard to maintain. It will get more messy in the long run if requirements come to extend. Now we can decide to have setters in the Pizza class and create object as following

$pizza1 = new Pizza(12);
$pizza1->setExtraCheese(true);
$pizza1->setBacon(true);
 
 

Now the problem is, this way the object is not immutable. Each use of a setter changing object states.

That’s why we can use Builder pattern to solve the problem. Let’s create PizzaBuilder

class PizzaBuilder
{
    public $size;
    public $pepperoni = false;
    public $mushrooms = false;
    public $extraCheese = false;
    public $onions = false;
    public $bacon = false;
 
    public function __construct(int $size) 
    {
        $this->size = $size;
    }
 
    public function setPepperoni(bool $pepperoni) : PizzaBuilder
    {
        $this->pepperoni = $pepperoni;
        return $this;
    }
 
    public function setMushrooms(bool $mushrooms) : PizzaBuilder
    {
        $this->mushrooms = $mushrooms;
        return $this;
    }
 
    public function setExtraCheese(bool $extraCheese) : PizzaBuilder
    {
        $this->extraCheese = $extraCheese;
        return $this;
    }
 
    public function setOnions(bool $onions) : PizzaBuilder
    {
        $this->onions = $onions;
        return $this;
    }
 
    public function setBacon(bool $bacon) : PizzaBuilder
    {
        $this->bacon = $bacon;
        return $this;
    }
 
 
    public function build() : PizzaBuilder
    {
        return $this;
    }
 
}

and refactor Pizza class to use PizzaBuilder in the constructor

class Pizza
{
    private int $size;
    private bool $pepperoni;
    private bool $mushrooms;
    private bool $extraCheese;
    private bool $onions;
    private bool $bacon;
 
    public function __construct(PizzaBuilder $pizzaBuilder) 
    {
        $this->size = $pizzaBuilder->size;
        $this->pepperoni = $pizzaBuilder->pepperoni;
        $this->mushrooms = $pizzaBuilder->mushrooms;
        $this->extraCheese = $pizzaBuilder->extraCheese;
        $this->onions = $pizzaBuilder->onions;
        $this->bacon = $pizzaBuilder->bacon;
    }
 
    public function getSize() : int
    {
        return $this->size;
    }
 
    public function getPepperoni() : bool
    {
        return $this->pepperoni;
    }
 
    public function getMushrooms() : bool
    {
        return $this->mushrooms;
    }
 
    public function getExtraCheese() : bool
    {
        return $this->extraCheese;
    }
 
    public function getOnions() : bool
    {
        return $this->onions; 
    }
 
    public function getBacon() : bool
    {
        return $this->bacon;
    }
 
}

Now we can create pizza class object with builder as following

$pizzaBuilderObject = (new PizzaBuilder(12))
    ->setPepperoni(false)
    ->setMushrooms(false)
    ->setExtraCheese(true)
    ->setOnions(false)
    ->setBacon(true)
    ->build();
 
$pizza = new Pizza($pizzaBuilderObject);

or just specify what we want.

$pizzaBuilderObject = (new PizzaBuilder(12))
    ->setExtraCheese(true)
    ->setBacon(true)
    ->build();
 
$pizza = new Pizza($pizzaBuilderObject);

Test cases:

<?php declare(strict_types=1);
namespace App\BuilderPattern\Tests;
 
require_once './vendor/autoload.php'; 
use PHPUnit\Framework\TestCase;
use App\BuilderPattern\PizzaBuilder;
use App\BuilderPattern\Pizza;
 
class BuilderPatternTest extends TestCase
{
    private $pizza;
 
    public function setUp()
    {
        $pizzaBuilderObject = (new PizzaBuilder(12))
            ->setExtraCheese(true)
            ->setBacon(true)
            ->build();
        $this->pizza = new Pizza($pizzaBuilderObject);
    }
 
    public function testIsObjectInstanceOfPizzaClass()
    {        
        $this->assertInstanceOf(Pizza::class, $this->pizza);
    }
 
    public function testPizzaHasExtraCheese()
    {        
        $this->assertEquals($this->pizza->getExtraCheese(), true);
    }
}
 
?>

Execute tests: ./vendor/bin/phpunit tests/BuilderPatternTest.php PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.. 2 / 2 (100%)

Time: 32 ms, Memory: 4.00 MB

OK (2 tests, 2 assertions)

Written on March 28, 2021