Skip to content

Instantly share code, notes, and snippets.

@Kcko
Last active March 25, 2020 08:55
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/f57073d7713e9442e6859b01512da5db to your computer and use it in GitHub Desktop.
Save Kcko/f57073d7713e9442e6859b01512da5db to your computer and use it in GitHub Desktop.
<?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;
} }
}
<?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>
*/
<?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("");
<?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)
*/
<?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()
);
}
<?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
*/

  • 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment