composite Компоновщик (англ. Composite pattern) — структурний шаблон проєктування, що поєднує об’єкти в деревоподібну структуру для подання ієрархії від частки до цілого. Компонувальник дозволяє клієнтам звертатися до окремих об’єктів та до груп об’єктів однаково.

Дуже простий шаблон. Будемо створювати HTML документ для надсилання нашому менеджеру. У нас є один інтерфейс для всіх компонентів документа HTML. Це дає нам можливість звертатися до всіх компонентів одним і тим самим способом. Для наочності реалізуємо інтерфейс двома абстрактними класами:

  • Composite - батьківський компонент, який має спадкоємці;
  • Leaf - кінцевий елемент. Спадкоємців мати не може.

У тесті(CompositeTest) наведено приклад побудови HTML документа.

  • Abstraction
    • Composite.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Abstraction;
      
      use DesignPatterns\Structural\Composite\Helper\NewLineHelper;
      
      /**
       * Class Composite
       * @package DesignPatterns\Structural\Composite\Abstraction
       */
      abstract class Composite implements CompositeInterface
      {
          use NewLineHelper;
      
          /**
           * @var CompositeInterface[]
           */
          protected $elements = [];
      
          /**
           * @param CompositeInterface $element
           *
           * @return void
           */
          public function addElement(CompositeInterface $element)
          {
              $this->elements[] = $element;
          }
      
          /**
           * @return string
           */
          abstract public function render(): string;
      }
      
                    
    • CompositeInterface.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Abstraction;
      
      /**
       * Interface CompositeInterface
       * @package DesignPatterns\Structural\Composite\Abstraction
       */
      interface CompositeInterface
      {
          /**
           * @param CompositeInterface $element
           *
           * @return void
           */
          public function addElement(CompositeInterface $element);
      
          /**
           * @return string
           */
          public function render(): string;
      }
      
                    
    • Leaf.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Abstraction;
      
      use DesignPatterns\Structural\Composite\Helper\NewLineHelper;
      
      /**
       * Class Leaf
       * @package DesignPatterns\Structural\Composite\Abstraction
       */
      abstract class Leaf implements CompositeInterface
      {
          use NewLineHelper;
      
          /**
           * @var string
           */
          protected $content;
      
          /**
           * Leaf constructor.
           *
           * @param string $content
           */
          public function __construct(string $content)
          {
              $this->content = $content;
          }
      
          /**
           * @param CompositeInterface $element
           *
           * @return void
           */
          public function addElement(CompositeInterface $element)
          {
          }
      
          /**
           * @return string
           */
          abstract public function render(): string;
      }
      
                    
  • Helper
    • NewLineHelper.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Helper;
      
      /**
       * Class NewLineHelper
       * @package DesignPatterns\Structural\Composite\Helper
       */
      trait NewLineHelper
      {
          /**
           * @param string $str
           *
           * @return string
           */
          protected function newLineStringPrepare(string $str): string
          {
              return $str . PHP_EOL;
          }
      }
      
                    
  • Html
    • Body.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Html;
      
      use DesignPatterns\Structural\Composite\Abstraction\Composite;
      
      /**
       * Class Body
       * @package DesignPatterns\Structural\Composite\Html
       */
      class Body extends Composite
      {
          /**
           * @var string
           */
          protected $document;
      
          /**
           * @return string
           */
          public function render(): string
          {
              $this->document = $this->newLineStringPrepare('<body>');
      
              foreach ($this->elements as $element) {
                  $this->document .= $element->render();
              }
      
              $this->document .= $this->newLineStringPrepare('</body>');
      
              return $this->document;
          }
      }
      
                    
    • Div.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Html;
      
      use DesignPatterns\Structural\Composite\Abstraction\Composite;
      
      /**
       * Class Div
       * @package DesignPatterns\Structural\Composite\Html
       */
      class Div extends Composite
      {
          /**
           * @var string
           */
          protected $document;
      
          /**
           * @return string
           */
          public function render(): string
          {
              $this->document = $this->newLineStringPrepare('<div>');
      
              foreach ($this->elements as $element) {
                  $this->document .= $element->render();
              }
      
              $this->document .= $this->newLineStringPrepare('</div>');
      
              return $this->document;
          }
      }
      
                    
    • H4.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Html;
      
      use DesignPatterns\Structural\Composite\Abstraction\Leaf;
      
      /**
       * Class H4
       * @package DesignPatterns\Structural\Composite\Html
       */
      class H4 extends Leaf
      {
          /**
           * @return string
           */
          public function render(): string
          {
              return $this->newLineStringPrepare('<h4>' . $this->content . '</h4>');
          }
      }
      
                    
    • Header.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Html;
      
      use DesignPatterns\Structural\Composite\Abstraction\Composite;
      use DesignPatterns\Structural\Composite\Helper\NewLineHelper;
      
      /**
       * Class Header
       * @package DesignPatterns\Structural\Composite\Html
       */
      class Header extends Composite
      {
          use NewLineHelper;
      
          /**
           * @var string
           */
          protected $document;
      
          /**
           * @return string
           */
          public function render(): string
          {
              $this->document = $this->newLineStringPrepare('<header>');
      
              foreach ($this->elements as $element) {
                  $this->document .= $element->render();
              }
      
              $this->document .= $this->newLineStringPrepare('</header>');
      
              return $this->document;
          }
      }
      
                    
    • Html.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Html;
      
      use DesignPatterns\Structural\Composite\Abstraction\Composite;
      
      /**
       * Class Html
       * @package DesignPatterns\Structural\Composite\Html
       */
      class Html extends Composite
      {
          /**
           * @var string
           */
          protected $document;
      
          /**
           * @return string
           */
          public function render(): string
          {
              $this->document = $this->newLineStringPrepare('<html>');
      
              foreach ($this->elements as $element) {
                  $this->document .= $element->render();
              }
      
              $this->document .= $this->newLineStringPrepare('</html>');
      
              return $this->document;
          }
      }
      
                    
    • Paragraph.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Html;
      
      use DesignPatterns\Structural\Composite\Abstraction\Leaf;
      
      /**
       * Class Paragraph
       * @package DesignPatterns\Structural\Composite\Html
       */
      class Paragraph extends Leaf
      {
          /**
           * @return string
           */
          public function render(): string
          {
              return $this->newLineStringPrepare('<p>' . $this->content . '</p>');
          }
      }
      
                    
    • Title.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Composite\Html;
      
      use DesignPatterns\Structural\Composite\Abstraction\Leaf;
      
      /**
       * Class Title
       * @package DesignPatterns\Structural\Composite\Html
       */
      class Title extends Leaf
      {
          /**
           * @return string
           */
          public function render(): string
          {
              return $this->newLineStringPrepare('<title>' . $this->content . '</title>');
          }
      }
      
                    

    Test case:

    CompositeTest.php
      
    <?php
    
    namespace DesignPatterns\Tests\Structural\Composite;
    
    
    use DesignPatterns\Structural\Composite\Html\Body;
    use DesignPatterns\Structural\Composite\Html\Div;
    use DesignPatterns\Structural\Composite\Html\H4;
    use DesignPatterns\Structural\Composite\Html\Header;
    use DesignPatterns\Structural\Composite\Html\Html;
    use DesignPatterns\Structural\Composite\Html\Paragraph;
    use DesignPatterns\Structural\Composite\Html\Title;
    use PHPUnit_Framework_TestCase;
    
    class CompositeTest extends PHPUnit_Framework_TestCase
    {
        public function testRender()
        {
            $result = '<html>' . PHP_EOL
                . '<header>' . PHP_EOL
                . '<title>Custom Page</title>' . PHP_EOL
                . '</header>' . PHP_EOL
                . '<body>' . PHP_EOL
                . '<div>' . PHP_EOL
                . '<h4>Custom header</h4>' . PHP_EOL
                . '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
                Vivamus convallis velit massa, faucibus egestas mauris vestibulum id. Vivamus ut justo.</p>' . PHP_EOL
                . '</div>' . PHP_EOL
                . '</body>' . PHP_EOL
                . '</html>' . PHP_EOL;
    
    
            $htmlDocument = new Html();
    
            $header = new Header();
            $title = new Title('Custom Page');
            $header->addElement($title);
            $htmlDocument->addElement($header);
    
            $body = new Body();
            $div = new Div();
            $h4 = new H4('Custom header');
            $paragraph = new Paragraph(
                'Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
                Vivamus convallis velit massa, faucibus egestas mauris vestibulum id. Vivamus ut justo.'
            );
            $div->addElement($h4);
            $div->addElement($paragraph);
            $body->addElement($div);
            $htmlDocument->addElement($body);
    
            $this->assertEquals($result, $htmlDocument->render());
        }
    }