Backend DevelopmentDesign Patterns

Composite Design Pattern In PHP With Real World Example

Composite Design Pattern In PHP With Real World Example
The composite design pattern is one of the important structural patterns which allows dealing with a tree of objects as if it were a single object.

 
 
 
 
When we have a tree of objects and we want to interact with this tree in a recursive way, the composite pattern is the most suitable. As we know the tree data structure consists of sub-trees of nodes which are nodes that contain other child nodes and the leaf nodes which have no children and represent the end of the tree.
 
At first let’s identify the components of the composite pattern. There are two main components of this pattern:
  • The composite: This object represent nodes that have other children nodes also called (subtree).
  • The leaf node: which represent nodes that have no children or called terminal nodes.

If we think about the composite pattern as a recursive function, in recursive functions we must have a stop point, otherwise the function will execute infinitely. This stop point in the context of the composite pattern is the leaf node, so the leaf node mark the termination point in the composite pattern.

Now let’s think about building the composite structure. We will need an interface with a single method for example called “execute” which the composite and the leaf classes must implement as we will see in the below example.

In this example i will use the composite pattern to render an html recursive menu. This menu consist of a list of items. Each item may contain other child items or it just a leaf node, so we will have these components:
  • IMenu interface.
  • MenuItem (leaf)
  • MenuGroup (composite): contains children of MenuItem or MenuGroup.
<?php

/**
 * Interface IMenu
 */
interface IMenu
{
    public function render();
}


/**
 * Class MenuItem (Leaf)
 */
class MenuItem implements IMenu
{
    private $text;
    private $url;
    
    public function __construct($text, $url)
    {
        $this->text = $text;
        $this->url = $url;
    }
    
    public function render()
    {
        return '<a href="'.$this->url.'">'.$this->text.'</a>';
    }
}


/**
 * Class MenuGroup (Composite)
 */
class MenuGroup implements IMenu
{
    private $items = [];
    
    private $text;
    private $url;
    
    public function __construct($text = null, $url = null)
    {
        $this->text = $text;
        $this->url = $url;
    }
    
    public function addChildren(IMenu $item)
    {
        array_push($this->items, $item);
    }
    
    public function render()
    {	
        $html = "";
        
        if($this->text && $this->url) {
            $html .= '<a href="'.$this->url.'">'.$this->text.'</a>';
        }
    
        $html .= "<ul class='menu'>";
        
        foreach($this->items as $item) {
            $html .= '<li>' . $item->render() . '</li>';
        }
        
        $html .= "</ul>";
        
        return $html;
    }
    
    public function getItems()
    {
        return $this->items;
    }
}

In this code both the MenuItem and MenuGroup classes implement the Menu interface with different implementations for the render() method and this is the core goal of the composite pattern. The render() method in the MenuItem class just outputs an html anchor tag with a text and link properties that we inject through the constructor.

The MenuGroup class also implement the render() method and above that it contains the addChildren() method which when called it will add an item of type IMenu, which can be a MenuItem or another MenuGroup. Finally in the render() method we iterates over the items array and call each item corresponding render() method.

Basic Usage:

$menu = new MenuGroup();
$menu->addChildren(new MenuItem("Home", "#"));
$menu->addChildren(new MenuItem("About", "#"));
$menu->addChildren(new MenuItem("Contact US", "#"));
echo $menu->render();

Usage With Nested Menu Items:

$menu = new MenuGroup(); 
$menu->addChildren(new MenuItem("Home", "#")); 
$menu->addChildren(new MenuItem("About", "#")); 

$subMenu = new MenuGroup("Services", "#"); 
$subMenu->addChildren(new MenuItem("Web Development", "#")); 
$subMenu->addChildren(new MenuItem("Mobile App Development", "#")); 
$subMenu->addChildren(new MenuItem("Artificial Intelligence", "#")); 

$menu->addChildren($subMenu); 

$subMenu2 = new MenuGroup("Products", "#"); 
$subMenu2->addChildren(new MenuItem("WebSites", "#")); 

$subMenu3 = new MenuGroup("Mobile Apps", "#"); 
$subMenu3->addChildren(new MenuItem("Android", "#")); 
$subMenu3->addChildren(new MenuItem("IOS", "#")); 

$subMenu2->addChildren($subMenu3); 

$menu->addChildren($subMenu2); 

$menu->addChildren(new MenuItem("Contact", "#")); 

echo $menu->render();

Using an Array of Items:

$items = [
        new MenuItem("Home", "#"),
        new MenuItem("About", "#"),
        [
            new MenuGroup("Services", "#"),
            'children' => [
                new MenuItem("Web Development", "#"),
                new MenuItem("Mobile App Development", "#")
            ]
        ],
        [
            new MenuGroup("Products", "#"),
            'children' => [
                new MenuItem("WebSites", "#"),
                [
                    new MenuGroup("Mobile Apps", "#"),
                    'children' => [
                        new MenuItem("Android", "#"),
                        new MenuItem("IOS", "#")
                    ]
                ]
            ]
        ],
        new MenuItem("Contact us", "#")
    ];


/**
 * recursive function to build the menu array
 */
function buildMenu(MenuGroup $menu, $items)
{
    foreach ($items as $item) {
        if(!is_array($item)) {
            $menu->addChildren($item);
        } else {
            $submenu = buildMenu($item[0], $item['children']);

            $menu->addChildren($submenu);
        }
    }

    return $menu;
}

$menu = buildMenu(new MenuGroup(), $items);

echo $menu->render();

 

Styling The Menu

I created a simple php page with a simple style and included the menu script as shown below:
 
menu.php
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <title>Navbar Rendered With Composite Pattern</title>
    <style>
        ul.menu {
            list-style: none;
        }

        ul.menu li {
            display: inline;
            margin: 5px;
            position: relative;
        }

        ul.menu li a {
            color: #fff;
            text-decoration: none;
            font-family: Arial, Helvetica, serif;
            background: gray;
            padding: 10px;
        }

        ul.menu li ul.menu {
            display: none;
            position: absolute;
            width: 250px;
            top: 35px;
            left: -45px;
        }

        ul.menu li:hover > ul {
            display: inline;
        }

        ul.menu li ul.menu li {
            margin-top: 6px;
            margin-bottom: 22px;
            display: block;
        }

        ul.menu li {

        }
    </style>
</head>

<body>
    <?php require_once("composite_pattern.php") ?>
</body>

</html>

 

 

5 2 votes
Article Rating

What's your reaction?

Excited
1
Happy
0
Not Sure
1
Confused
0

You may also like

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments