Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP Builder pattern without inner classes

I've been reading through Effective Java by Joshua Bloch. I also develop in PHP and I wanted to implement the builder pattern outlined in item 2, but PHP doesn't have inner classes. Is there any way to achieve this pattern in PHP, keeping the constructor for the product private?

like image 805
Jackson Avatar asked Jun 09 '12 14:06

Jackson


2 Answers

Since PHP does not support inner classes, there must be a public method on the product class that creates an instance of it. Consider the following PHP classes:

<?php
class NutritionalFactsBuilder {
    private $sodium;
    private $fat;
    private $carbo;

    /**
     * It is preferred to call NutritionalFacts::createBuilder
     * to calling this constructor directly.
     */
    function __construct($s) {
        $this->sodium = $s;
    }

    function fat($f) {
        $this->fat = $f;
        return $this;
    }

    function carbo($c) {
        $this->carbo = $c;
        return $this;
    }

    function getSodium() {
        return $this->sodium;
    }

    function getFat() {
        return $this->fat;
    }

    function getCarbo() {
        return $this->carbo;
    }

    function build() {
        return new NutritionalFacts($this);
    }
}

class NutritionalFacts {
    private $sodium;
    private $fat;
    private $carbo;

    static function createBuilder($s) {
        return new NutritionalFactsBuilder($s);
    }

    /**
     * It is preferred to call NutritionalFacts::createBuilder
     * to calling this constructor directly.
     */
    function __construct(NutritionalFactsBuilder $b) {
        $this->sodium = $b->getSodium();
        $this->fat = $b->getFat();
        $this->carbo = $b->getCarbo();
    }
}

echo '<pre>';
var_dump(NutritionalFacts::createBuilder(10)->fat(23)->carbo(1)->build());
echo '</pre>';
?>

Note that in the above example the constructor of NutritionalFacts is public. Due to the constraints of the language, however, having a public constructor is not at all bad. Since one must call the constructor with a NutritionalFactsBuilder, there are only a limited number of ways to instantiate NutritionalFacts. Let's compare them:

// NutritionalFacts Instantiation #0
$nfb = new NutritionalFactsBuilder(10);
$nfb = $nfb->fat(23)->carbo(1);
$nf0 = new NutritionalFacts($nfb);

// NutritionalFacts Instantiation #1
$nfb = new NutritionalFactsBuilder(10);
$nf1 = $nfb->fat(23)->carbo(1)->build();

// NutritionalFacts Instantiation #2
$nf2 = NutritionalFacts::createBuilder(10)->fat(23)->carbo(1)->build();

// NutritionalFacts Instantiation #3
// $nf3 = (new NutritionalFactsBuilder(10))->fat(23)->carbo(1)->build();

To leverage function chaining to its fullest extent, "NutritionalFacts Instantiation #2" is the preferred usage.

"NutritionalFacts Instantiation #3" shows another nuance of PHP syntax; one cannot chain a method on a newly instantiated object. Update: In PHP 5.4.0, there is now support for the syntax in "NutritionalFacts Instantiation #3." I haven't tested it yet though.


Making the Constructor Private

You could make the constructor private, but I wouldn't recommend it. If the constructor were made private, a public, static factory method would be necessary, as in the following code snippet. Looking at the below code, we might as well make the constructor public instead of introducing indirection just to make the constructor private.

class NutritionalFacts {
    private $sodium;
    private $fat;
    private $carbo;

    static function createBuilder($s) {
        return new NutritionalFactsBuilder($s);
    }

    static function createNutritionalFacts($builder) {
        return new NutritionalFacts($builder);
    }

    private function __construct($b) {
        $this->sodium = $b->getSodium();
        $this->fat = $b->getFat();
        $this->carbo = $b->getCarbo();
    }
}
like image 185
creemama Avatar answered Oct 04 '22 14:10

creemama


Immutability is good and definitely something to strive for, this applies to PHP as it does to any other language no matter what. Immutability gives you certainty that you do not have to fear that the instance suddenly mutates without you knowing.

That being said, there is an easy way to implement the builder pattern to build immutable objects even without inner classes (although available now with PHP 7).

The first important building block is a common base class for the actual immutable class and the builder. This allows them to access each others properties. Something that is also known as friend classes or solvable through extended access modifiers in other languages, something PHP does not have. Note that the clone ability is restricted, it makes no sense to clone immutable objects but more about the protected modifier later.

abstract class NutritionalFactData {

    protected $sodium = 0;
    protected $fat = 0;
    protected $carbo = 0;

    protected function __clone() {}

}

The immutable class is straight forward with stupid example getters and the default constructor. Note the final modifier for the class itself and that it is not aware of the builder class at all.

final class NutritionalFacts extends NutritionalFactData {

    public function getSodium() {
        return $this->sodium;
    }

    public function getFat() {
        return $this->fat;
    }

    public function getCarbo() {
        return $this->carbo;
    }

}

Now the actual builder implementation. Note how we operate directly on an instance of the immutable class and that we simply clone it when the build method is called. This ensures that later calls to the setters of the builder will not alter the instances that were previously built and ensures that no receiver of such an instance has to take care of the cloning on their own.

final class NutritionalFactBuilder extends NutritionalFactData {

    private $nutritional_facts;

    public function __construct() {
        $this->nutritional_facts = new NutritionalFacts;
    }

    public function build() {
        return clone $this->nutritional_facts;
    }

    public function setSodium($sodium) {
        $this->nutritional_facts->sodium = $sodium;
        return $this;
    }

    public function setFat($fat) {
        $this->nutritional_facts->fat = $fat;
        return $this;
    }

    public function setCarbo($carbo) {
        $this->nutritional_facts->carbo = $carbo;
        return $this;
    }

}

For completeness a usage example:

var_dump(
    (new NutritionalFactBuilder)
        ->setSodium(21)
        ->setFat(42)
        ->build()
);

Here is the runnable example.

I think it is obvious that we can now implement as many builder implementations as we like. Not really needed for this example but we can think of other constructs where many more properties are involved. Like the car example given on (the very bad) builder pattern article of Wikipedia. We might want to have pre-configured builders for known car categories.

abstract class CarParts {}

final class Car extends CarParts {}

abstract class CarBuilder extends CarParts {
    abstract public function build(): Car;
}

final class CompactCarBuilder extends CarBuilder {}

final class SportsCarBuilder extends CarBuilder {}

final class RaceCarBuilder extends CarBuilder {}
like image 32
Fleshgrinder Avatar answered Oct 04 '22 15:10

Fleshgrinder