Last active
March 29, 2020 16:37
-
-
Save Kcko/9146d8159eb8b28bfc0fb2df92bbb6b3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Director | |
{ | |
public function build(Builder $builder): Vehicle | |
{ | |
$builder->createVehicle(); | |
$builder->addDoors(); | |
$builder->addEngine(); | |
$builder->addWheel(); | |
return $builder->getVehicle(); | |
} | |
} | |
interface Builder | |
{ | |
public function createVehicle(); | |
public function addWheel(); | |
public function addEngine(); | |
public function addDoors(); | |
public function getVehicle(): Vehicle; | |
} | |
class TruckBuilder implements Builder | |
{ | |
private Truck $truck; | |
public function addDoors() | |
{ | |
$this->truck->setPart('rightDoor', new Door()); | |
$this->truck->setPart('leftDoor', new Door()); | |
} | |
public function addEngine() | |
{ | |
$this->truck->setPart('truckEngine', new Engine()); | |
} | |
public function addWheel() | |
{ | |
$this->truck->setPart('wheel1', new Wheel()); | |
$this->truck->setPart('wheel2', new Wheel()); | |
$this->truck->setPart('wheel3', new Wheel()); | |
$this->truck->setPart('wheel4', new Wheel()); | |
$this->truck->setPart('wheel5', new Wheel()); | |
$this->truck->setPart('wheel6', new Wheel()); | |
} | |
public function createVehicle() | |
{ | |
$this->truck = new Truck(); | |
} | |
public function getVehicle(): Vehicle | |
{ | |
return $this->truck; | |
} | |
} | |
class CarBuilder implements Builder | |
{ | |
private Car $car; | |
public function addDoors() | |
{ | |
$this->car->setPart('rightDoor', new Door()); | |
$this->car->setPart('leftDoor', new Door()); | |
$this->car->setPart('trunkLid', new Door()); | |
} | |
public function addEngine() | |
{ | |
$this->car->setPart('engine', new Engine()); | |
} | |
public function addWheel() | |
{ | |
$this->car->setPart('wheelLF', new Wheel()); | |
$this->car->setPart('wheelRF', new Wheel()); | |
$this->car->setPart('wheelLR', new Wheel()); | |
$this->car->setPart('wheelRR', new Wheel()); | |
} | |
public function createVehicle() | |
{ | |
$this->car = new Car(); | |
} | |
public function getVehicle(): Vehicle | |
{ | |
return $this->car; | |
} | |
} | |
abstract class Vehicle | |
{ | |
/** | |
* @var object[] | |
*/ | |
private array $data = []; | |
public function setPart(string $key, object $value) | |
{ | |
$this->data[$key] = $value; | |
} | |
} | |
class Truck extends Vehicle | |
{ | |
} | |
class Engine | |
{ | |
} | |
class Wheel | |
{ | |
} | |
class Door | |
{ | |
} | |
// USAGE | |
$truckBuilder = new TruckBuilder(); | |
$newVehicle = (new Director())->build($truckBuilder); | |
$carBuilder = new CarBuilder(); | |
$newVehicle = (new Director())->build($carBuilder); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* The Builder interface declares a set of methods to assemble an SQL query. | |
* | |
* All of the construction steps are returning the current builder object to | |
* allow chaining: $builder->select(...)->where(...) | |
*/ | |
interface SQLQueryBuilder | |
{ | |
public function select(string $table, array $fields): SQLQueryBuilder; | |
public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder; | |
public function limit(int $start, int $offset): SQLQueryBuilder; | |
// +100 other SQL syntax methods... | |
public function getSQL(): string; | |
} | |
/** | |
* Each Concrete Builder corresponds to a specific SQL dialect and may implement | |
* the builder steps a little bit differently from the others. | |
* | |
* This Concrete Builder can build SQL queries compatible with MySQL. | |
*/ | |
class MysqlQueryBuilder implements SQLQueryBuilder | |
{ | |
protected $query; | |
protected function reset(): void | |
{ | |
$this->query = new \stdClass; | |
} | |
/** | |
* Build a base SELECT query. | |
*/ | |
public function select(string $table, array $fields): SQLQueryBuilder | |
{ | |
$this->reset(); | |
$this->query->base = "SELECT " . implode(", ", $fields) . " FROM " . $table; | |
$this->query->type = 'select'; | |
return $this; | |
} | |
/** | |
* Add a WHERE condition. | |
*/ | |
public function where(string $field, string $value, string $operator = '='): SQLQueryBuilder | |
{ | |
if (!in_array($this->query->type, ['select', 'update', 'delete'])) { | |
throw new \Exception("WHERE can only be added to SELECT, UPDATE OR DELETE"); | |
} | |
$this->query->where[] = "$field $operator '$value'"; | |
return $this; | |
} | |
/** | |
* Add a LIMIT constraint. | |
*/ | |
public function limit(int $start, int $offset): SQLQueryBuilder | |
{ | |
if (!in_array($this->query->type, ['select'])) { | |
throw new \Exception("LIMIT can only be added to SELECT"); | |
} | |
$this->query->limit = " LIMIT " . $start . ", " . $offset; | |
return $this; | |
} | |
/** | |
* Get the final query string. | |
*/ | |
public function getSQL(): string | |
{ | |
$query = $this->query; | |
$sql = $query->base; | |
if (!empty($query->where)) { | |
$sql .= " WHERE " . implode(' AND ', $query->where); | |
} | |
if (isset($query->limit)) { | |
$sql .= $query->limit; | |
} | |
$sql .= ";"; | |
return $sql; | |
} | |
} | |
/** | |
* This Concrete Builder is compatible with PostgreSQL. While Postgres is very | |
* similar to Mysql, it still has several differences. To reuse the common code, | |
* we extend it from the MySQL builder, while overriding some of the building | |
* steps. | |
*/ | |
class PostgresQueryBuilder extends MysqlQueryBuilder | |
{ | |
/** | |
* Among other things, PostgreSQL has slightly different LIMIT syntax. | |
*/ | |
public function limit(int $start, int $offset): SQLQueryBuilder | |
{ | |
parent::limit($start, $offset); | |
$this->query->limit = " LIMIT " . $start . " OFFSET " . $offset; | |
return $this; | |
} | |
// + tons of other overrides... | |
} | |
/** | |
* Note that the client code uses the builder object directly. A designated | |
* Director class is not necessary in this case, because the client code needs | |
* different queries almost every time, so the sequence of the construction | |
* steps cannot be easily reused. | |
* | |
* Since all our query builders create products of the same type (which is a | |
* string), we can interact with all builders using their common interface. | |
* Later, if we implement a new Builder class, we will be able to pass its | |
* instance to the existing client code without breaking it thanks to the | |
* SQLQueryBuilder interface. | |
*/ | |
function clientCode(SQLQueryBuilder $queryBuilder) | |
{ | |
// ... | |
$query = $queryBuilder | |
->select("users", ["name", "email", "password"]) | |
->where("age", 18, ">") | |
->where("age", 30, "<") | |
->limit(10, 20) | |
->getSQL(); | |
echo $query; | |
// ... | |
} | |
/** | |
* The application selects the proper query builder type depending on a current | |
* configuration or the environment settings. | |
*/ | |
// if ($_ENV['database_type'] == 'postgres') { | |
// $builder = new PostgresQueryBuilder(); } else { | |
// $builder = new MysqlQueryBuilder; } | |
// | |
// clientCode($builder); | |
echo "Testing MySQL query builder:\n"; | |
clientCode(new MysqlQueryBuilder); | |
echo "\n\n"; | |
echo "Testing PostgresSQL query builder:\n"; | |
clientCode(new PostgresQueryBuilder); | |
Output.txt: Execution result | |
Testing MySQL query builder: | |
SELECT name, email, password FROM users WHERE age > '18' AND age < '30' LIMIT 10, 20; | |
Testing PostgresSQL query builder: | |
SELECT name, email, password FROM users WHERE age > '18' AND age < '30' LIMIT 10 OFFSET 20; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Director { | |
public function buildDevice (DeviceBuilder $builder) { | |
$builder->createDevice(); | |
$builder->setHardware(); | |
$builder->setSoftware(); | |
$builder->setupSLA(); | |
return $builder->getDevice(); | |
} | |
} | |
interface DeviceBuilder { | |
public function setHardware (); | |
public function setSoftware (); | |
public function setupSLA (); | |
public function createDevice (); | |
public function getDevice (); | |
} | |
class InteractiveMirror { | |
public $hardware = []; | |
public $software = ''; | |
public $sla = ''; | |
} | |
class InteractiveMirrorBuilder implements DeviceBuilder { | |
private $device; | |
public function setHardware () { | |
$components = ['display', 'glass', 'PC', 'frame']; | |
foreach ($components as $component) { | |
$this->device->hardware[] = $component; | |
} | |
} | |
public function setSoftware () { | |
$this->device->software = 'Mirror software'; | |
} | |
public function setupSLA () { | |
$this->device->sla = 'Mirror SLA V2'; | |
} | |
public function createDevice () { | |
$this->device = new InteractiveMirror(); | |
} | |
public function getDevice () { | |
return $this->device; | |
} | |
} | |
$mirrorBuilder = new InteractiveMirrorBuilder(); | |
$director = new Director(); | |
$mirror = $director->buildDevice($mirrorBuilder); | |
var_dump($mirror); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* 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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment