Wern Ancheta

PHP Design Patterns: Composite Pattern

· Wern Ancheta

This is the eleventh post in a series of articles that will walk you through how to implement design patterns in PHP.

In this part, we’re going to take a look at how you can implement the Composite Pattern.

This pattern is used for treating a group of objects the same way you treat any single instance of the object. This is most commonly used for building UI, specifically tree structures which has varying levels of nesting. It’s also used for logging, so you can either log a single lines or multiple lines based on the current type of logger.

The basic idea is to have both the group and the individual object abide to the same interface or abstract class so that they’re treated the same way:

<?php 
// compositepattern/app/MenuInterface.php

namespace CompositePattern\App;

interface MenuInterface 
{
    public function render() : array;
}

Now create another class which will implement it. Here we’re creating a Menu class which allows the user to add another instance of a Menu to it (add method) and also allows for rendering all the menus that were added to it (the render method). Usually you will output something here, but we’re constructing an array instead so that we can easily test it out:

<?php 
// compositepattern/app/Menu.php

namespace CompositePattern\App;

class Menu implements MenuInterface
{

    private array $menu_items;
 
    public function __construct(public string $title)
    {
        $this->menu_items = [];
    }   
    

    public function add(MenuInterface $link)
    {
        $this->menu_items[] = $link;
    }

    
    public function render() : array
    {
        $menu = [
            'title' => $this->title,
            'items' => [],
        ];
        foreach ($this->menu_items as $link)
        {
            $menu['items'][] = $link->render();
        }
        return $menu;
    }

} 

Zoom in to this part right here. This is where the magic happens:

foreach ($this->menu_items as $link)
{
    $menu['items'][] = $link->render();
}

We don’t have to check whether the render method exists because we’ve implemented the MenuInterface. This is what allows us to nest in varying levels.

Lastly, we have the link. Think of this as the individual leaves on a stem. This represents the individual items in a menu. For its render method, we’re simply returning an array containing the link and text passed to its constructor when an object is initialized:

<?php 
// compositepattern/app/Link.php

namespace CompositePattern\App;

class Link implements MenuInterface
{
    
    public function __construct(public string $link, public string $text)
    {

    }


    public function render() : array
    {
        return [
            'link' => $this->link,
            'text' => $this->text,
        ];
    }
}

To test this out, let’s create a single menu and attach two submenus to it. Each submenu will then have a number of menus attached to it, and each one has a link. We use the add method to link them together. Finally, calling the render method on the main menu will return every menu that was passed to it:

<?php 
// compositepattern/CompositeTest.php

require_once __DIR__ . '/../vendor/autoload.php';

use PHPUnit\Framework\TestCase;

class CompositeTest extends TestCase
{
    public function test_composite() : void
    {
        
        $main_menu = new \CompositePattern\App\Menu('Main');

        $herbs_menu = new \CompositePattern\App\Menu('Herbs');
       
        $herbs_menu->add(new \CompositePattern\App\Link('/rosemary', 'Rosemary'));
        $herbs_menu->add(new \CompositePattern\App\Link('/vietnamese-coriander', 'Vietnamese Coriander'));
        $herbs_menu->add(new \CompositePattern\App\Link('/tarragon', 'Tarragon'));

        
        $leafygreens_menu = new \CompositePattern\App\Menu('Leafy Greens');

        $leafygreens_menu->add(new \CompositePattern\App\Link('/bokchoi', 'Bokchoi'));
        $leafygreens_menu->add(new \CompositePattern\App\Link('/arugula', 'Arugula'));
        $leafygreens_menu->add(new \CompositePattern\App\Link('/pechay', 'Pechay'));
        $leafygreens_menu->add(new \CompositePattern\App\Link('/lettuce', 'Lettuce'));

        $main_menu->add($herbs_menu);
        $main_menu->add($leafygreens_menu);

       
        $this->assertEquals($main_menu->render(), [
            
            'title' => 'Main',
            'items' => [
                [
                    'title' => 'Herbs',
                    'items' => [
                        [
                            'link' => '/rosemary',
                            'text' => 'Rosemary',
                        ],
                        [
                            'link' => '/vietnamese-coriander',
                            'text' => 'Vietnamese Coriander',
                        ],
                        [
                            'link' => '/tarragon',
                            'text' => 'Tarragon',
                        ],
                    ]
                ],
                [
                    'title' => 'Leafy Greens',
                    'items' => [
                        [
                            'link' => '/bokchoi',
                            'text' => 'Bokchoi',
                        ],
                        [
                            'link' => '/arugula',
                            'text' => 'Arugula',
                        ],
                        [
                            'link' => '/pechay',
                            'text' => 'Pechay',
                        ],
                        [
                            'link' => '/lettuce',
                            'text' => 'Lettuce',
                        ],
                    ]
                ]
            ]
        ]);
       
    }
}

To run this. Be sure you have the following on your composer.json file then run composer dump-autoload:

"autoload": {
    "psr-4": {
        "CompositePattern\\App\\": "compositepattern/app/"
    }
},

Also install phpunit (composer require phpunit/phpunit) if you haven’t done so already.

Then you can run the tests:

vendor/bin/phpunit compositepattern/CompositePattern.php

That’s all there is to it to the Composite pattern. Whenever you find yourself working on tree or tree-like structures on your code, try to see if this pattern fits.

You can find the source code used in this tutorial on this GitHub repo.