- Your leaf nodes and composites must implement the same component interface.
- Your composite classes must have methods to both add and remove leaf nodes.
- To treat a group of objects the same way as a single instance of the object.
Last active
March 25, 2020 08:55
-
-
Save Kcko/f57073d7713e9442e6859b01512da5db 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 | |
// dedicnost vs kompozice | |
// dedicnost | |
class PersonForm extends Nette\Application\UI\Form | |
{ | |
public function __construct() | |
{ | |
parent::__construct(); | |
$this->addText('name', 'Jméno'); | |
$this->addSubmit('submit', 'Odeslat'); | |
} | |
} | |
class PersonWithAddressForm extends PersonForm | |
{ | |
public function __construct() | |
{ | |
parent::__construct(); | |
$this->addText('city', 'Město'); | |
$this->addText('street', 'Ulice'); | |
} | |
} | |
// kompozice | |
class PersonContainer extends Nette\Forms\Container | |
{ | |
public function __construct() | |
{ | |
parent::__construct(); | |
$this->addText('name', 'Jméno'); | |
$this->addText('surname', 'Příjmení'); | |
} | |
} | |
class AddressContainer extends Nette\Forms\Container | |
{ | |
public function __construct() | |
{ | |
parent::__construct(); | |
$this->addText('city', 'Město'); | |
$this->addText('street', 'Ulice'); | |
} | |
} | |
// USAGE | |
class MyPresenter extends BasePresenter | |
{ | |
protected function createComponentPersonWithAddressForm() | |
{ | |
$form = new Nette\Application\UI\Form; | |
$form['user'] = new PersonContainer(); | |
$form['user']['address'] = new AddressContainer(); | |
$form['user']['correspondence'] = new AddressContainer(); | |
$form['user']['billing'] = new AddressContainer(); | |
$form->addSubmit('submit', 'Odeslat'); | |
$form->onSuccess[] = function () {}; | |
return $form; | |
} | |
} | |
// nebo tovarna | |
class MyPresenter extends BasePresenter | |
{ | |
public function __construct() protected function createComponentPersonWithAddressForm() | |
{ { | |
parent::__construct(); $form = new Nette\Application\UI\Form; | |
$form['user'] = new PersonContainer(); | |
$form['user']['address'] = new AddressContainer(); | |
$form['user']['correspondence'] = new AddressContainer(); | |
$form['user']['billing'] = new AddressContainer(); | |
$this['user'] = new PersonContainer(); $form->addSubmit('submit', 'Odeslat'); | |
$this['user']['address'] = new AddressContainer(); $form->onSuccess[] = function () {}; | |
$this['user']['correspondence'] = new AddressContainer(); | |
$this['user']['billing'] = new AddressContainer(); | |
$this->addSubmit('submit', 'Odeslat'); return $form; | |
} } | |
} |
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 base Component class declares an interface for all concrete components, | |
* both simple and complex. | |
* | |
* In our example, we'll be focusing on the rendering behavior of DOM elements. | |
*/ | |
abstract class FormElement | |
{ | |
/** | |
* We can anticipate that all DOM elements require these 3 fields. | |
*/ | |
protected $name; | |
protected $title; | |
protected $data; | |
public function __construct(string $name, string $title) | |
{ | |
$this->name = $name; | |
$this->title = $title; | |
} | |
public function getName(): string | |
{ | |
return $this->name; | |
} | |
public function setData($data): void | |
{ | |
$this->data = $data; | |
} | |
public function getData(): array | |
{ | |
return $this->data; | |
} | |
/** | |
* Each concrete DOM element must provide its rendering implementation, but | |
* we can safely assume that all of them are returning strings. | |
*/ | |
abstract public function render(): string; | |
} | |
/** | |
* This is a Leaf component. Like all the Leaves, it can't have any children. | |
*/ | |
class Input extends FormElement | |
{ | |
private $type; | |
public function __construct(string $name, string $title, string $type) | |
{ | |
parent::__construct($name, $title); | |
$this->type = $type; | |
} | |
/** | |
* Since Leaf components don't have any children that may handle the bulk of | |
* the work for them, usually it is the Leaves who do the most of the heavy- | |
* lifting within the Composite pattern. | |
*/ | |
public function render(): string | |
{ | |
return "<label for=\"{$this->name}\">{$this->title}</label>\n" . | |
"<input name=\"{$this->name}\" type=\"{$this->type}\" value=\"{$this->data}\">\n"; | |
} | |
} | |
/** | |
* The base Composite class implements the infrastructure for managing child | |
* objects, reused by all Concrete Composites. | |
*/ | |
abstract class FieldComposite extends FormElement | |
{ | |
/** | |
* @var FormElement[] | |
*/ | |
protected $fields = []; | |
/** | |
* The methods for adding/removing sub-objects. | |
*/ | |
public function add(FormElement $field): void | |
{ | |
$name = $field->getName(); | |
$this->fields[$name] = $field; | |
} | |
public function remove(FormElement $component): void | |
{ | |
$this->fields = array_filter($this->fields, function ($child) use ($component) { | |
return $child != $component; | |
}); | |
} | |
/** | |
* Whereas a Leaf's method just does the job, the Composite's method almost | |
* always has to take its sub-objects into account. | |
* | |
* In this case, the composite can accept structured data. | |
* | |
* @param array $data | |
*/ | |
public function setData($data): void | |
{ | |
foreach ($this->fields as $name => $field) { | |
if (isset($data[$name])) { | |
$field->setData($data[$name]); | |
} | |
} | |
} | |
/** | |
* The same logic applies to the getter. It returns the structured data of | |
* the composite itself (if any) and all the children data. | |
*/ | |
public function getData(): array | |
{ | |
$data = []; | |
foreach ($this->fields as $name => $field) { | |
$data[$name] = $field->getData(); | |
} | |
return $data; | |
} | |
/** | |
* The base implementation of the Composite's rendering simply combines | |
* results of all children. Concrete Composites will be able to reuse this | |
* implementation in their real rendering implementations. | |
*/ | |
public function render(): string | |
{ | |
$output = ""; | |
foreach ($this->fields as $name => $field) { | |
$output .= $field->render(); | |
} | |
return $output; | |
} | |
} | |
/** | |
* The fieldset element is a Concrete Composite. | |
*/ | |
class Fieldset extends FieldComposite | |
{ | |
public function render(): string | |
{ | |
// Note how the combined rendering result of children is incorporated | |
// into the fieldset tag. | |
$output = parent::render(); | |
return "<fieldset><legend>{$this->title}</legend>\n$output</fieldset>\n"; | |
} | |
} | |
/** | |
* And so is the form element. | |
*/ | |
class Form extends FieldComposite | |
{ | |
protected $url; | |
public function __construct(string $name, string $title, string $url) | |
{ | |
parent::__construct($name, $title); | |
$this->url = $url; | |
} | |
public function render(): string | |
{ | |
$output = parent::render(); | |
return "<form action=\"{$this->url}\">\n<h3>{$this->title}</h3>\n$output</form>\n"; | |
} | |
} | |
/** | |
* The client code gets a convenient interface for building complex tree | |
* structures. | |
*/ | |
function getProductForm(): FormElement | |
{ | |
$form = new Form('product', "Add product", "/product/add"); | |
$form->add(new Input('name', "Name", 'text')); | |
$form->add(new Input('description', "Description", 'text')); | |
$picture = new Fieldset('photo', "Product photo"); | |
$picture->add(new Input('caption', "Caption", 'text')); | |
$picture->add(new Input('image', "Image", 'file')); | |
$form->add($picture); | |
return $form; | |
} | |
/** | |
* The form structure can be filled with data from various sources. The Client | |
* doesn't have to traverse through all form fields to assign data to various | |
* fields since the form itself can handle that. | |
*/ | |
function loadProductData(FormElement $form) | |
{ | |
$data = [ | |
'name' => 'Apple MacBook', | |
'description' => 'A decent laptop.', | |
'photo' => [ | |
'caption' => 'Front photo.', | |
'image' => 'photo1.png', | |
], | |
]; | |
$form->setData($data); | |
} | |
/** | |
* The client code can work with form elements using the abstract interface. | |
* This way, it doesn't matter whether the client works with a simple component | |
* or a complex composite tree. | |
*/ | |
function renderProduct(FormElement $form) | |
{ | |
// .. | |
echo $form->render(); | |
// .. | |
} | |
$form = getProductForm(); | |
loadProductData($form); | |
renderProduct($form); | |
/* | |
<form action="/product/add"> | |
<h3>Add product</h3> | |
<label for="name">Name</label> | |
<input name="name" type="text" value="Apple MacBook"> | |
<label for="description">Description</label> | |
<input name="description" type="text" value="A decent laptop."> | |
<fieldset><legend>Product photo</legend> | |
<label for="caption">Caption</label> | |
<input name="caption" type="text" value="Front photo."> | |
<label for="image">Image</label> | |
<input name="image" type="file" value="photo1.png"> | |
</fieldset> | |
</form> | |
*/ |
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 | |
abstract class Database { | |
protected $connection; | |
function __construct(Connection $connection) { | |
$this->connection = $connection; | |
} | |
abstract function getTables(); | |
function getVals($query) { | |
$this->connection->query($query); | |
// ... | |
} | |
} | |
class DatabaseMysql extends Database { | |
function getTables() { | |
return $this->getVals("SHOW TABLES"); | |
} | |
} | |
interface Connection { | |
function query($query); | |
} | |
class ConnectionMysql implements Connection { | |
function query($query) { | |
return mysql_query($query); | |
} | |
} | |
$connection = new ConnectionMysql; | |
$database = new DatabaseMysql($connection); | |
$connection->query(""); | |
$database->getTables(); | |
$database->getVals(""); |
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 | |
abstract class Component | |
{ | |
/** | |
* @var Component | |
*/ | |
protected $parent; | |
public function setParent(Component $parent) | |
{ | |
$this->parent = $parent; | |
} | |
public function getParent(): Component | |
{ | |
return $this->parent; | |
} | |
public function add(Component $component): void { } | |
public function remove(Component $component): void { } | |
public function isComposite(): bool | |
{ | |
return false; | |
} | |
abstract public function operation(): string; | |
} | |
class Leaf extends Component | |
{ | |
public function operation(): string | |
{ | |
return "Leaf"; | |
} | |
} | |
class Composite extends Component | |
{ | |
/** | |
* @var \SplObjectStorage | |
*/ | |
protected $children; | |
public function __construct() | |
{ | |
$this->children = new \SplObjectStorage; | |
} | |
public function add(Component $component): void | |
{ | |
$this->children->attach($component); | |
$component->setParent($this); | |
} | |
public function remove(Component $component): void | |
{ | |
$this->children->detach($component); | |
$component->setParent(null); | |
} | |
public function isComposite(): bool | |
{ | |
return true; | |
} | |
public function operation(): string | |
{ | |
$results = []; | |
foreach ($this->children as $child) { | |
$results[] = $child->operation(); | |
} | |
return "Branch(" . implode("+", $results) . ")"; | |
} | |
} | |
// USAGE | |
function clientCode(Component $component) | |
{ | |
echo "RESULT: " . $component->operation();. | |
} | |
/** | |
* This way the client code can support the simple leaf components... | |
*/ | |
$simple = new Leaf; | |
echo "Client: I've got a simple component:\n"; | |
clientCode($simple); | |
echo "\n\n"; | |
/** | |
* ...as well as the complex composites. | |
*/ | |
$tree = new Composite; | |
$branch1 = new Composite; | |
$branch1->add(new Leaf); | |
$branch1->add(new Leaf); | |
$branch2 = new Composite; | |
$branch2->add(new Leaf); | |
$tree->add($branch1); | |
$tree->add($branch2); | |
echo "Client: Now I've got a composite tree:\n"; | |
clientCode($tree); | |
echo "\n\n"; | |
/** | |
* Thanks to the fact that the child-management operations are declared in the | |
* base Component class, the client code can work with any component, simple or | |
* complex, without depending on their concrete classes. | |
*/ | |
function clientCode2(Component $component1, Component $component2) | |
{ | |
// ... | |
if ($component1->isComposite()) { | |
$component1->add($component2); | |
} | |
echo "RESULT: " . $component1->operation(); | |
// ... | |
} | |
echo "Client: I don't need to check the components classes even when managing the tree:\n"; | |
clientCode2($tree, $simple); | |
/* | |
Client: I get a simple component: | |
RESULT: Leaf | |
Client: Now I get a composite tree: | |
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)) | |
Client: I don't need to check the components classes even when managing the tree:: | |
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf) | |
*/ | |
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 | |
interface Renderable | |
{ | |
public function render(): string; | |
} | |
/** | |
* The composite node MUST extend the component contract. This is mandatory for building | |
* a tree of components. | |
*/ | |
class Form implements Renderable | |
{ | |
/** | |
* @var Renderable[] | |
*/ | |
private array $elements; | |
/** | |
* runs through all elements and calls render() on them, then returns the complete representation | |
* of the form. | |
* | |
* from the outside, one will not see this and the form will act like a single object instance | |
*/ | |
public function render(): string | |
{ | |
$formCode = '<form>'; | |
foreach ($this->elements as $element) { | |
$formCode .= $element->render(); | |
} | |
$formCode .= '</form>'; | |
return $formCode; | |
} | |
public function addElement(Renderable $element) | |
{ | |
$this->elements[] = $element; | |
} | |
} | |
class InputElement implements Renderable | |
{ | |
public function render(): string | |
{ | |
return '<input type="text" />'; | |
} | |
} | |
class TextElement implements Renderable | |
{ | |
private string $text; | |
public function __construct(string $text) | |
{ | |
$this->text = $text; | |
} | |
public function render(): string | |
{ | |
return $this->text; | |
} | |
} | |
// USAGE | |
function testRender() | |
{ | |
$form = new Form(); | |
$form->addElement(new TextElement('Email:')); | |
$form->addElement(new InputElement()); | |
$embed = new Form(); | |
$embed->addElement(new TextElement('Password:')); | |
$embed->addElement(new InputElement()); | |
$form->addElement($embed); | |
// This is just an example, in a real world scenario it is important to remember that web browsers do not | |
// currently support nested forms | |
$this->assertSame( | |
'<form>Email:<input type="text" /><form>Password:<input type="text" /></form></form>', | |
$form->render() | |
); | |
} |
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 | |
abstract class ComponentInterface | |
{ | |
public $id; | |
public $name; | |
abstract public function getFile($offset = 0); | |
abstract public function addFile(ComponentInterface $file); | |
abstract public function removeFile(ComponentInterface $file); | |
public function __construct($id, $name) | |
{ | |
$this->id = $id; | |
$this->name = $name; | |
} | |
} | |
class DirectoryComponent extends ComponentInterface | |
{ | |
private $files = []; | |
public function getFile($offset = 0) | |
{ | |
$content = str_repeat("-", $offset) . ' ' . $this->name . "/\n"; | |
foreach ($this->files as $file) { | |
$content .= $file->getFile(++$offset); | |
} | |
return $content; | |
} | |
public function addFile(ComponentInterface $file) | |
{ | |
$this->files[$file->id] = $file; | |
return $this; | |
} | |
public function removeFile(ComponentInterface $file) | |
{ | |
unset($this->files[$file->id]); | |
} | |
} | |
class FileComponent extends ComponentInterface | |
{ | |
public function getFile($offset = 0) | |
{ | |
return str_repeat("-", $offset-1) . '> ' . $this->name . "\n"; | |
} | |
public function addFile(ComponentInterface $file) | |
{ | |
return false; | |
} | |
public function removeFile(ComponentInterface $file) | |
{ | |
return false; | |
} | |
} | |
$root = new DirectoryComponent(1, "/"); | |
$etc = new DirectoryComponent(2, "etc"); | |
$var = new DirectoryComponent(3, "var"); | |
$root->addFile($etc)->addFile($var); | |
$etc->addFile( | |
new FileComponent(2, "php.ini", "simon") | |
); | |
$var->addFile( | |
new FileComponent(3, "nginx.log", "simon") | |
); | |
$var->addFile( | |
new FileComponent(3, "hadoop.log", "simon") | |
); | |
echo $root->getFile(); | |
/* | |
/ | |
– etc | |
-> php.ini | |
– var | |
-> nginx.log | |
-> hadoop.log | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment