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());
        }
    }