2.2. Builder

2.2.1. Intent

According to the Gang of Four, the Builder pattern is a way to “separate the construction of a complex object from its representation so that the same construction process can create different representations” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 97).

The construction process is controlled by a Director object and uses a step by step approach.

2.2.2. When to use it?

The Builder pattern should be used to create complex objects with lots of things to do to instantiate them. It permits to maintain creation algorithms decoupled from the rest of the code so that you can add objects’ representations without affecting the rest.

The Builder pattern focuses on creating complex objects in a step by step manner while the Abstract Factory pattern puts its emphasis on products’ families.

2.2.3. Diagram

Created using PhpStorm and yFiles.

Class diagram of the Builder pattern

2.2.4. Implementation

TrafficMap.php

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?php

namespace Phpatterns\Creational\Builder\TrafficMap;

/**
 * Class TrafficMap
 * A map used to emphasize traffic congestion.
 */
class TrafficMap
{
    /** @var Point */
    private $origin;

    /** @var Point */
    private $destination;

    /** @var int */
    private $height;

    /** @var int */
    private $width;

    /** @var int */
    private $waterColor;

    /** @var int */
    private $groundColor;

    /** @var int */
    private $lowTrafficColor;

    /** @var int */
    private $mediumTrafficColor;

    /** @var int */
    private $highTrafficColor;

    /**
     * @param Point $origin
     * @param Point $destination
     */
    public function __construct(Point $origin = null, Point $destination = null)
    {
        $this->origin = $origin;
        $this->destination = $destination;

        //default values
        $this->height = 100;
        $this->width = 100;
        $this->waterColor = Color::CYAN;
        $this->groundColor = Color::BROWN;
        $this->lowTrafficColor = Color::YELLOW;
        $this->mediumTrafficColor = Color::ORANGE;
        $this->highTrafficColor = Color::RED;
    }

    /**
     * @param Point $origin
     * @return $this
     */
    public function setOrigin(Point $origin)
    {
        $this->origin = $origin;
        return $this;
    }

    /**
     * @param Point $destination
     * @return $this
     */
    public function setDestination(Point $destination)
    {
        $this->destination = $destination;
        return $this;
    }

    /**
     * @param int $height
     */
    public function setHeight($height)
    {
        $this->height = $height;
    }

    /**
     * @param int $width
     */
    public function setWidth($width)
    {
        $this->width = $width;
    }

    /**
     * @param int $color
     */
    public function setWaterColor($color)
    {
        $this->waterColor = $color;
    }

    /**
     * @param int $color
     */
    public function setGroundColor($color)
    {
        $this->groundColor = $color;
    }

    /**
     * @param int $color
     */
    public function setLowTrafficColor($color)
    {
        $this->lowTrafficColor = $color;
    }

    /**
     * @param int $color
     */
    public function setMediumTrafficColor($color)
    {
        $this->mediumTrafficColor = $color;
    }

    /**
     * @param int $color
     */
    public function setHighTrafficColor($color)
    {
        $this->highTrafficColor = $color;
    }
}

Point.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

namespace Phpatterns\Creational\Builder\TrafficMap;

/**
 * Class Point
 * Represents a point in a Map
 */
class Point
{
    /** @var float */
    private $latitude;

    /** @var float */
    private $longitude;

    /**
     * @param float $latitude
     * @param float $longitude
     */
    public function __construct($latitude, $longitude)
    {
        $this->latitude = $latitude;
        $this->longitude = $longitude;
    }

    /**
     * @return float
     */
    public function getLatitude()
    {
        return $this->latitude;
    }

    /**
     * @return float
     */
    public function getLongitude()
    {
        return $this->longitude;
    }
}

Color.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace Phpatterns\Creational\Builder\TrafficMap;

class Color
{
    const BLACK       = 0x000000;
    const BLUE        = 0x0000FF;
    const BROWN       = 0x996633;
    const CYAN        = 0x00FFFF;
    const DARK_GRAY   = 0x444444;
    const GRAY        = 0x888888;
    const GREEN       = 0x00FF00;
    const LIGHT_GRAY  = 0xCCCCCC;
    const MAGENTA     = 0xFF00FF;
    const ORANGE      = 0xFF6600;
    const RED         = 0xFF0000;
    const WHITE       = 0xFFFFFF;
    const YELLOW      = 0xFFFF00;
}

TrafficMapBuilderInterface.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php

namespace Phpatterns\Creational\Builder;

interface TrafficMapBuilderInterface
{
    /**
     * @return TrafficMap\TrafficMap
     */
    public function getMap();

    /**
     * @return $this
     */
    public function setOrigin();

    /**
     * @return $this
     */
    public function setDestination();

    /**
     * @return $this
     */
    public function setHeight();

    /**
     * @return $this
     */
    public function setWidth();

    /**
     * @return $this
     */
    public function setWaterColor();

    /**
     * @return $this
     */
    public function setGroundColor();

    /**
     * @return $this
     */
    public function setLowTrafficColor();

    /**
     * @return $this
     */
    public function setMediumTrafficColor();

    /**
     * @return $this
     */
    public function setHighTrafficColor();
}

ParisToBerlinTrafficMapBuilder.php

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
<?php

namespace Phpatterns\Creational\Builder\ConcreteBuilder;

use Phpatterns\Creational\Builder;
use Phpatterns\Creational\Builder\TrafficMap;

class ParisToBerlinTrafficMapBuilder implements Builder\TrafficMapBuilderInterface
{
    /** @var TrafficMap\TrafficMap */
    protected $map;

    public function __construct()
    {
        $this->map = new TrafficMap\TrafficMap();
    }

    /**
     * @return TrafficMap\TrafficMap
     */
    public function getMap()
    {
        return $this->map;
    }

    /**
     * @return $this
     */
    public function setOrigin()
    {
        $this->map->setOrigin(
            new TrafficMap\Point(48.856614, 2.3522219000000177) // Paris
        );
        return $this;
    }

    /**
     * @return $this
     */
    public function setDestination()
    {
        $this->map->setOrigin(
            new TrafficMap\Point(52.52000659999999, 13.404953999999975) // Berlin
        );
        return $this;
    }

    /**
     * @return $this
     */
    public function setHeight()
    {
        $this->map->setHeight(100);
        return $this;
    }

    /**
     * @return $this
     */
    public function setWidth()
    {
        $this->map->setWidth(200);
        return $this;
    }

    /**
     * @return $this
     */
    public function setWaterColor()
    {
        $this->map->setWaterColor(TrafficMap\Color::BLUE);
        return $this;
    }

    /**
     * @return $this
     */
    public function setGroundColor()
    {
        $this->map->setGroundColor(TrafficMap\Color::DARK_GRAY);
        return $this;
    }

    /**
     * @return $this
     */
    public function setLowTrafficColor()
    {
        $this->map->setLowTrafficColor(TrafficMap\Color::LIGHT_GRAY);
        return $this;
    }

    /**
     * @return $this
     */
    public function setMediumTrafficColor()
    {
        $this->map->setMediumTrafficColor(TrafficMap\Color::MAGENTA);
        return $this;
    }

    /**
     * @return $this
     */
    public function setHighTrafficColor()
    {
        $this->map->setHighTrafficColor(TrafficMap\Color::ORANGE);
        return $this;
    }
}

SanFranciscoToSantaCruzTrafficMapBuilder.php

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
<?php

namespace Phpatterns\Creational\Builder\ConcreteBuilder;

use Phpatterns\Creational\Builder;
use Phpatterns\Creational\Builder\TrafficMap;

class SanFranciscoToSantaCruzTrafficMapBuilder implements Builder\TrafficMapBuilderInterface
{
    /** @var TrafficMap\TrafficMap */
    protected $map;

    public function __construct()
    {
        $this->map = new TrafficMap\TrafficMap();
    }

    /**
     * @return TrafficMap\TrafficMap
     */
    public function getMap()
    {
        return $this->map;
    }

    /**
     * @return $this
     */
    public function setOrigin()
    {
        $this->map->setOrigin(
            new TrafficMap\Point(37.7749295, -122.41941550000001) // San Francisco
        );
        return $this;
    }

    /**
     * @return $this
     */
    public function setDestination()
    {
        $this->map->setOrigin(
            new TrafficMap\Point(36.9741171, -122.03079630000002) // Santa Cruz
        );
        return $this;
    }

    /**
     * @return $this
     */
    public function setHeight()
    {
        $this->map->setHeight(250);
        return $this;
    }

    /**
     * @return $this
     */
    public function setWidth()
    {
        $this->map->setWidth(150);
        return $this;
    }

    /**
     * @return $this
     */
    public function setWaterColor()
    {
        $this->map->setWaterColor(TrafficMap\Color::CYAN);
        return $this;
    }

    /**
     * @return $this
     */
    public function setGroundColor()
    {
        $this->map->setGroundColor(TrafficMap\Color::BLACK);
        return $this;
    }

    /**
     * @return $this
     */
    public function setLowTrafficColor()
    {
        //don't do anything and keep the default value from TrafficMap\TrafficMap's constructor
        return $this;
    }

    /**
     * @return $this
     */
    public function setMediumTrafficColor()
    {
        //don't do anything and keep the default value from TrafficMap\TrafficMap's constructor
        return $this;
    }

    /**
     * @return $this
     */
    public function setHighTrafficColor()
    {
        //don't do anything and keep the default value from TrafficMap\TrafficMap's constructor
        return $this;
    }
}

TrafficMapDirector.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

namespace Phpatterns\Creational\Builder;

use Phpatterns\Creational\Builder\TrafficMap;

class TrafficMapDirector
{
    /**
     * The director constructs a TrafficMap using the TrafficMapBuilderInterface
     * (he knows nothing about the concrete details of the creation)
     * @param TrafficMapBuilderInterface $builder
     * @return TrafficMap\TrafficMap
     */
    public function buildMap(TrafficMapBuilderInterface $builder)
    {
        return $builder
            ->setOrigin()
            ->setDestination()
            ->setHeight()
            ->setWidth()
            ->setWaterColor()
            ->setGroundColor()
            ->setLowTrafficColor()
            ->setMediumTrafficColor()
            ->setHighTrafficColor()
            ->getMap();
    }
}

2.2.5. Tests

TrafficMapDirectorTest.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

namespace Test\Phpatterns\Creational\Builder;

use Phpatterns\Creational\Builder;
use Phpatterns\Creational\Builder\ConcreteBuilder;
use Phpatterns\Creational\Builder\TrafficMap;

class TrafficMapDirectorTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var Builder\TrafficMapDirector
     */
    private $trafficMapDirector;

    protected function setUp()
    {
        $this->trafficMapDirector = new Builder\TrafficMapDirector();
    }

    /**
     * Testing if concrete builders implement TrafficMapBuilderInterface
     * @param Builder\TrafficMapBuilderInterface $builder
     * @dataProvider buildersProvider
     */
    public function testConcreteBuildersImplementBuilderInterface(Builder\TrafficMapBuilderInterface $builder)
    {
        $this->assertInstanceOf(Builder\TrafficMapBuilderInterface::class, $builder);
    }

    /**
     * Testing creation mechanism
     * @param Builder\TrafficMapBuilderInterface $builder
     * @dataProvider buildersProvider
     */
    public function testCreationMechanism(Builder\TrafficMapBuilderInterface $builder)
    {
        $this->assertInstanceOf(
            TrafficMap\TrafficMap::class,
            $this->trafficMapDirector->buildMap($builder)
        );
    }

    public function buildersProvider()
    {
        return [
            [new ConcreteBuilder\ParisToBerlinTrafficMapBuilder()],
            [new ConcreteBuilder\SanFranciscoToSantaCruzTrafficMapBuilder()]
        ];
    }
}