Skip to content

Instantly share code, notes, and snippets.

@Kcko
Last active March 29, 2020 16:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kcko/9146d8159eb8b28bfc0fb2df92bbb6b3 to your computer and use it in GitHub Desktop.
Save Kcko/9146d8159eb8b28bfc0fb2df92bbb6b3 to your computer and use it in GitHub Desktop.
<?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);
<?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;
<?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);
<?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