Composite Pattern

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


Purpose

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

Examples

A form class instance handles all its form elements like a single instance of the form, when render() is called, it subsequently runs through all its child elements and calls render() on them

UML


Code

Renderable


namespace DesignPatterns\Structural\Composite;

interface Renderable
{
    public function render(): string;
}

Form


namespace DesignPatterns\Structural\Composite;

/**
* 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 = '
'; foreach ($this->elements as $element) { $formCode .= $element->render(); } return $formCode . '
'; } public function addElement(Renderable $element) { $this->elements[] = $element; } }

InputElement


namespace DesignPatterns\Structural\Composite;

class InputElement implements Renderable
{
    public function render(): string
    {
        return '';
    }
}

TextElement


namespace DesignPatterns\Structural\Composite;

class TextElement implements Renderable
{
    public function __construct(private string $text)
    {
    }

    public function render(): string
    {
        return $this->text;
    }
}

Tests


public function testRender()
{
    // Start building form (1st)
    $form = new Form();
    $form->addElement(new TextElement('Email:'));
    $form->addElement(new InputElement());

    // Build another form (2nd)
    $embed = new Form();
    $embed->addElement(new TextElement('Password:'));
    $embed->addElement(new InputElement());

    // Attach 2nd form to 1st form
    $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(
        '
Email:Password:
', $form->render() ); }