2.3. Composite

2.3.1. Purpose

To treat a group of objects the same way as a single instance of theobject.

2.3.2. Examples

  • a form class instance handles all its form elements like a singleinstance of the form, when render() is called, it subsequentlyruns through all its child elements and calls render() on them
  • Zend_Config: a tree of configuration options, each one is aZend_Config object itself

2.3.3. UML Diagram

Alt Composite UML Diagram

2.3.4. Code

You can also find this code on GitHub

RenderableInterface.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Composite;
  4.  
  5. interface RenderableInterface
  6. {
  7. public function render(): string;
  8. }

Form.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Composite;
  4.  
  5. /**
  6. * The composite node MUST extend the component contract. This is mandatory for building
  7. * a tree of components.
  8. */
  9. class Form implements RenderableInterface
  10. {
  11. /**
  12. * @var RenderableInterface[]
  13. */
  14. private $elements;
  15.  
  16. /**
  17. * runs through all elements and calls render() on them, then returns the complete representation
  18. * of the form.
  19. *
  20. * from the outside, one will not see this and the form will act like a single object instance
  21. *
  22. * @return string
  23. */
  24. public function render(): string
  25. {
  26. $formCode = '<form>';
  27.  
  28. foreach ($this->elements as $element) {
  29. $formCode .= $element->render();
  30. }
  31.  
  32. $formCode .= '</form>';
  33.  
  34. return $formCode;
  35. }
  36.  
  37. /**
  38. * @param RenderableInterface $element
  39. */
  40. public function addElement(RenderableInterface $element)
  41. {
  42. $this->elements[] = $element;
  43. }
  44. }

InputElement.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Composite;
  4.  
  5. class InputElement implements RenderableInterface
  6. {
  7. public function render(): string
  8. {
  9. return '<input type="text" />';
  10. }
  11. }

TextElement.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Composite;
  4.  
  5. class TextElement implements RenderableInterface
  6. {
  7. /**
  8. * @var string
  9. */
  10. private $text;
  11.  
  12. public function __construct(string $text)
  13. {
  14. $this->text = $text;
  15. }
  16.  
  17. public function render(): string
  18. {
  19. return $this->text;
  20. }
  21. }

2.3.5. Test

Tests/CompositeTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Composite\Tests;
  4.  
  5. use DesignPatterns\Structural\Composite;
  6. use PHPUnit\Framework\TestCase;
  7.  
  8. class CompositeTest extends TestCase
  9. {
  10. public function testRender()
  11. {
  12. $form = new Composite\Form();
  13. $form->addElement(new Composite\TextElement('Email:'));
  14. $form->addElement(new Composite\InputElement());
  15. $embed = new Composite\Form();
  16. $embed->addElement(new Composite\TextElement('Password:'));
  17. $embed->addElement(new Composite\InputElement());
  18. $form->addElement($embed);
  19.  
  20. // This is just an example, in a real world scenario it is important to remember that web browsers do not
  21. // currently support nested forms
  22.  
  23. $this->assertEquals(
  24. '<form>Email:<input type="text" /><form>Password:<input type="text" /></form></form>',
  25. $form->render()
  26. );
  27. }
  28. }