For studying purpose, I've tried to create a simple PHP implementation of Builder GoF (not the Joshua Bloch's one) inspired on this slide.
The main goal of this example is to encapsulate the construction of data files from diferents inputs (Directors) and outputs (Builders)
Lots of Builder example (including the wikipedia's one) define an explicit "getProduct/getResult" on the Builder Class.
In my case, I thought the creation of output file would be considered the literal product, so I have opted to not use a method to get the file.
So, I would like to know if the absence of this method would invalidate it as Builder GoF implementation.
<?php
namespace BuilderGoFEnglish;
abstract class Builder {
protected \SplFileObject $output;
public function __construct(string $outputFileName) {
$this->output = new \SplFileObject($outputFileName, 'w');
}
abstract function addHeader(array $header);
abstract function addLine(array $linha);
abstract function finish();
}
abstract class Director {
protected Builder $builder;
public function __construct(Builder $builder) {
$this->builder = $builder;
}
public abstract function build(string $inputFileName);
}
class XmlDirector extends Director {
public function build(string $inputFileName) {
$document = new \DOMDocument();
$document->preserveWhiteSpace = false;
$document->load($inputFileName);
$root = $document->firstChild;
$item1 = iterator_to_array($root->firstChild->childNodes);
$this->builder->addHeader(array_column($item1, 'tagName'));
foreach($root->childNodes as $child){
$item = iterator_to_array($child->childNodes);
$this->builder->addLine(array_column($item1, 'nodeValue'));
}
$this->builder->finish();
}
}
class JsonDirector extends Director {
public function build(string $inputFileName) {
$jsonArray = json_decode(file_get_contents($inputFileName));
$this->builder->addHeader(array_keys((array) $jsonArray[0]));
foreach ($jsonArray as $jsonObject) {
$this->builder->addLine((array) $jsonObject);
}
$this->builder->finish();
}
}
class HtmlBuilder extends Builder {
private \DOMDocument $document;
private \DOMElement $table;
public function __construct(string $output) {
parent::__construct($output);
$this->document = new \DOMDocument('1.0', 'utf-8');
$this->document->appendChild($this->document->createElement('html'));
$this->table = $this->document->createElement('table');
$this->table->setAttribute('border', 1);
$this->document->firstChild->appendChild($this->table);
}
private function createTableRow(array $linha, $tipo = 'td'){
$tr = $this->document->createElement('tr');
array_map(fn($v) =>
$tr->appendChild($this->document->createElement($tipo, $v)),
$linha);
$this->table->appendChild($tr);
}
public function addHeader(array $header) {
$this->createTableRow($header, 'th');
}
public function addLine(array $linha) {
$this->createTableRow($linha);
}
public function finish() {
$this->output->fwrite($this->document->saveHTML());
}
}
class CsvBuilder extends Builder {
private array $csvArray = [];
public function addHeader(array $header) {
$this->csvArray[] = $header;
}
public function addLine(array $linha) {
$this->csvArray[] = $linha;
}
public function finish() {
foreach ($this->csvArray as $linha) {
$this->output->fputcsv($linha);
}
}
}