Design Pattern: Composite Pattern in PHP

Composite pattern represents the hierarchy of a group of objects in a tree structure. The client can treat a single object and list/group of objects the same way, so it becomes easy for the client.

NOTES

In this article, we discuss the implementation of the Composite Pattern in PHP.

See the composite in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Composite Pattern-

Implementation

Follow the steps below the implement Composite pattern in PHP-

  • Create an interface for the item class.
  • Create item classes and implement the interface.
  • Create composite class(a class to handle a group of items).
  • In the composite class maintain a list of the item objects.
  • Implement the same item interface for the composite class. In the method implementations loop through the items and perform operations on the item objects.

Here is a simple example of Composite pattern-

<?php
// Composite pattern implemention in PHP

// Item interface
interface Item {
    function operation1(): void;
    function operation2(): void;
}

// First item class
class Item1 implements Item {
    public function operation1(): void {
        echo "Item 1: operation 1n";
    }

    public function operation2(): void {
        echo "Item 1: operation 2n";
    }
}

// Second item class
class Item2 implements Item {
    public function operation1(): void {
        echo "Item 2: operation 1n";
    }

    public function operation2(): void {
        echo "Item 2: operation 2n";
    }
}

// Composite class
class ItemGroup implements Item {
    private array $itemList = [];

    public function operation1(): void {
        foreach ($this->itemList as $item) {
            $item->operation1();
        }
    }

    public function operation2(): void {
        foreach ($this->itemList as $item) {
            $item->operation2();
        }
    }

    public function addItem(Item $item): void {
        array_push($this->itemList, $item);
    }
}


// Demo        
$item1 = new Item1();
$item2 = new Item2();
$anotherItem1 = new Item1();

$itemGroup = new ItemGroup();
$itemGroup->addItem($item1);
$itemGroup->addItem($item2);
$itemGroup->addItem($anotherItem1);

$itemGroup->operation1();
$itemGroup->operation2();

Output generated by the code above is –

Item 1: operation 1
Item 2: operation 1
Item 1: operation 1

Item 1: operation 2
Item 2: operation 2
Item 1: operation 2

Examples

Check the following Composite pattern examples-

Example #1: Transport List

Transport Interface [Item Interface]

  • Create a file “Transport.php”.
  • Define interface “Transport”.
  • Declare methods as per requirement, here we have -“start”, “stop”, and “operate”.
<?php
// Transport.php

namespace BigBoxCodeDesignPatternCompositeTransportList;


interface Transport {
    function start(): void;
    function operate(): void;
    function stop(): void;
}

Bike Class [Item Class]

  • Create a file “Bike.php”.
  • Create class “Bike”.
  • Implement “Transport” interface for the class.
<?php
// Bike.php

namespace BigBoxCodeDesignPatternCompositeTransportList;


class Bike implements Transport {
    public function start(): void {
        echo "Starting Bike...n";
    }

    public function operate(): void {
        echo "Riding Biken";
    }

    public function stop(): void {
        echo "Stopping Bike...n";
    }
}

Plane Class [Item Class]

  • Create a file “Plane.php”.
  • Define “Plane” class and implement “Transport” interface for the class.
<?php
// Plane.php

namespace BigBoxCodeDesignPatternCompositeTransportList;

class Plane implements Transport {
    public function start(): void {
        echo "Starting Plane...n";
    }

    public function operate(): void {
        echo "Flying Planen";
    }

    public function stop(): void {
        echo "Stopping Plane...n";
    }
}

Car Class [Item Class]

  • Create a file “Car.php”.
  • Define “Car” class and implement “Transport” interface.
<?php
// Car.php

namespace BigBoxCodeDesignPatternCompositeTransportList;


class Car implements Transport {
    public function start(): void {
        echo "Starting Car...n";
    }

    public function operate(): void {
        echo "Driving Carn";
    }

    public function stop(): void {
        echo "Stopping Car...n";
    }
}

Composite Class

  • Create a file “TransportGroup.php”.
  • Define class “TransportGroup”.
  • Declare private property “$transportList”, which is an array and will be used to store a list of “Transport” object.
  • Implement “Transport” interface for the class.
  • In the method implementation loop through the “$transportList” items and call the relevant method. That way the operation will be preformed over all the items of the transport list.
<?php
// TransportGroup.php

namespace BigBoxCodeDesignPatternCompositeTransportList;

class TransportGroup implements Transport {
    private array $transportList = [];

    public function start(): void {
        foreach ($this->transportList as $transport) {
            $transport->start();
        }
    }

    public function operate(): void {
        foreach ($this->transportList as $transport) {
            $transport->operate();
        }
    }

    public function stop(): void {
        foreach ($this->transportList as $transport) {
            $transport->stop();
        }
    }

    public function addTransport(Transport $transport): void {
        array_push($this->transportList, $transport);
    }

    public function removeTransport(Transport $transport): void {
        $elemIndex = array_search($transport, $this->transportList);

        if ($elemIndex !== false) {
            unset($this->transportList[$elemIndex]);
        }
    }
}

Demo

Client can create objects, and then add them to the group(or remove them from the group). Then, operation can be performed on the group like an individual object-

<?php
// demo.php

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

use BigBoxCodeDesignPatternCompositeTransportListBike;
use BigBoxCodeDesignPatternCompositeTransportListCar;
use BigBoxCodeDesignPatternCompositeTransportListPlane;
use BigBoxCodeDesignPatternCompositeTransportListTransportGroup;

$bike = new Bike();
$plane = new Plane();
$car = new Car();
$secondCar = new Car();

$transports = new TransportGroup();
$transports->addTransport($bike);
$transports->addTransport($plane);
$transports->addTransport($car);
$transports->addTransport($secondCar);

echo "-----------------Output with 4 transports------------------n";

$transports->start();
$transports->operate();
$transports->stop();

echo "n-----------------Output when plane is removed------------------n";

$transports->removeTransport($plane);

$transports->start();
$transports->operate();
$transports->stop();

Output

The output of the demo above will be like below.

-----------------Output with 4 transports------------------
Starting Bike...
Starting Plane...
Starting Car...
Starting Car...
Riding Bike
Flying Plane
Driving Car
Driving Car
Stopping Bike...
Stopping Plane...
Stopping Car...
Stopping Car...

-----------------Output when plane is removed------------------
Starting Bike...
Starting Car...
Starting Car...
Riding Bike
Driving Car
Driving Car
Stopping Bike...
Stopping Car...
Stopping Car...

Example #2: Player Group

Player Interface [Item Interface]

  • Create a file “Player.php”.
  • Define interface “Player”.
  • Declare method as per requirement, here we have – “printDetails”.
<?php
// Player.php

namespace BigBoxCodeDesignPatternCompositePlayerGroup;

interface Player {
    function printDetails(): void;
}

Basketball Player Class [Item Class]

  • Create a file “BasketballPlayer.php”.
<?php
// BasketballPlayer.php

namespace BigBoxCodeDesignPatternCompositePlayerGroup;


class BasketballPlayer implements Player {

    public function __construct(
        private string $name,
        private int $age,
        private int $point
    ) {

    }

    public function printDetails(): void {
        echo "nGame: Basketball";
        echo "nName: " . $this->name;
        echo "nAge: " . $this->age;
        echo "nPoints: " . $this->point;
    }
}

Football Player Class [Item Class]

  • Create a file “FootballPlayer.php”.
<?php
// FootballPlayer.php

namespace BigBoxCodeDesignPatternCompositePlayerGroup;


class FootballPlayer implements Player {
    public function __construct(
        private string $name,
        private int $age,
        private int $goal
    ) {

    }

    public function printDetails(): void {
        echo "nGame: Football";
        echo "nName: " . $this->name;
        echo "nAge: " . $this->age;
        echo "nGoals: " . $this->goal;
    }
}

Cricket Player Class [Item Class]

  • Create a file “CricketPlayer.php”.
<?php
// CricketPlayer.php

namespace BigBoxCodeDesignPatternCompositePlayerGroup;


class CricketPlayer implements Player {
    public function __construct(
        private string $name,
        private int $age,
        private int $run
    ) {
       
    }

    public function printDetails(): void {
        echo "nGame: Cricket";
        echo "nName: " . $this->name;
        echo "nAge: " . $this->age;
        echo "nRuns: " . $this->run;
    }
}

Player Group

  • Create file “PlayerGroup.php”.
<?php
// PlayerGroup.php

namespace BigBoxCodeDesignPatternCompositePlayerGroup;

class PlayerGroup implements Player {
    private array $playerList = [];

    public function printDetails(): void {
        foreach ($this->playerList as $player) {
            $player->printDetails();
        }
    }

    public function addElement(Player $player): void {
        array_push($this->playerList, $player);
    }

    public function removeElement(Player $player): void {
        $index = array_search($player, $this->playerList);

        if ($index !== false) {
            unset($this->playerList[$index]);
        }
    }
}

Demo

Now we can create some player objects and add them to the player group. and then call the “printDetails” method on a single object or the group the same way.

<?php 
// demo.php

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

use BigBoxCodeDesignPatternCompositePlayerGroupBasketballPlayer;
use BigBoxCodeDesignPatternCompositePlayerGroupCricketPlayer;
use BigBoxCodeDesignPatternCompositePlayerGroupFootballPlayer;
use BigBoxCodeDesignPatternCompositePlayerGroupPlayerGroup;

// Under 15 players
$under15Players = new PlayerGroup();

$under15Players->addElement(new FootballPlayer("FPlayer 15_1", 13, 23));
$under15Players->addElement(new FootballPlayer("FPlayer 15_2", 14, 30));

$under15Players->addElement(new BasketballPlayer("BPlayer 15_1", 12, 80));
$under15Players->addElement(new BasketballPlayer("BPlayer 15_2", 14, 100));

$under15Players->addElement(new CricketPlayer("CPlayer 15_1", 14, 467));


// Under 19 Players
$under19Players = new PlayerGroup();

$under19Players->addElement(new FootballPlayer("FPlayer 19_1", 18, 43));

$under19Players->addElement(new BasketballPlayer("BPlayer 19_1", 17, 77));

$under19Players->addElement(new CricketPlayer("CPlayer 19_1", 18, 654));
$under19Players->addElement(new CricketPlayer("CPlayer 19_2", 16, 789));


// National team players
$nationalTeamPlayers = new PlayerGroup();
$nationalTeamPlayers->addElement(new FootballPlayer("FPlayer N_1", 18, 43));
$nationalTeamPlayers->addElement(new BasketballPlayer("BPlayer N_1", 17, 77));
$nationalTeamPlayers->addElement(new CricketPlayer("CPlayer N_1", 18, 654));


// Create a group with all teams
$allTeams = new PlayerGroup();
$allTeams->addElement($under15Players);
$allTeams->addElement($under19Players);
$allTeams->addElement($nationalTeamPlayers);

// Print details of all players
// from each game and group
$allTeams->printDetails();

Output

We will get the following output-

Game: Football
Name: FPlayer 15_1
Age: 13
Goals: 23

Game: Football
Name: FPlayer 15_2
Age: 14
Goals: 30

Game: Basketball
Name: BPlayer 15_1
Age: 12
Points: 80

Game: Basketball
Name: BPlayer 15_2
Age: 14
Points: 100

Game: Cricket
Name: CPlayer 15_1
Age: 14
Runs: 467
Game: Football
Name: FPlayer 19_1
Age: 18
Goals: 43

Game: Basketball
Name: BPlayer 19_1
Age: 17
Points: 77

Game: Cricket
Name: CPlayer 19_1
Age: 18
Runs: 654

Game: Cricket
Name: CPlayer 19_2
Age: 16
Runs: 789

Game: Football
Name: FPlayer N_1
Age: 18
Goals: 43

Game: Basketball
Name: BPlayer N_1
Age: 17
Points: 77

Game: Cricket
Name: CPlayer N_1
Age: 18
Runs: 654

Example #3: Menu

Menu Interface

  • Create a file “Menu.php”.
  • Define interface “Menu”.
  • Declare method “print” for printing menu item.
<?php
// Menu.php

namespace BigBoxCodeDesignPatternCompositeMenu;


interface Menu {
    function print(): void;
}

Menu Item Class

  • Create a file “MenuItem.php”.
  • Define class “MenuItem”.
  • Define properties for storing the link and the text for the link.
  • Implement interface “Menu” for the class.
  • In the “print” method implementation, print the menu item as per requirement.
<?php
// MenuItem.php

namespace BigBoxCodeDesignPatternCompositeMenu;

class MenuItem implements Menu {
    public function __construct(private string $link, private string $text) {

    }

    public function print(): void {
        echo "[li][a link='" . $this->link . "']" . $this->text . "[/a][/li]n";
    }
}

Menu Item Group

  • Create file “MenuParent.php”.
  • Define class “MenuParent”.
  • Define property “$menuItem” to store a list of “Menu”.
  • Implement interface “Menu”.
  • In the “print” method implementation, loop through all the items in the list and call “print” method of those objects.
  • Define method for adding and removing items to the list.
<?php
// MenuParent.php

namespace BigBoxCodeDesignPatternCompositeMenu;

class MenuParent implements Menu {
    private array $menuItems = [];

    public function print(): void {
        echo "[ul]n";

        foreach ($this->menuItems as $menuItem) {
            $menuItem->print();
        }

        echo "[ul]n";
    }

    public function addItem(Menu $menuItem): void {
        array_push($this->menuItems, $menuItem);
    }

    public function removeItem(Menu $menuItem): void {
        $itemIndex = array_search($menuItem, $this->menuItems);

        if ($itemIndex !== false) {
            unset($this->menuItems, $itemIndex);
        }
    }
}

Demo

In the client we can create object of –

  • Menu Item (MenuItem)
  • A group of Menu items.
  • A nested group of menu items, where a group of items belong to another parent group of items.
<?php
// demo.php

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

use BigBoxCodeDesignPatternCompositeMenuMenuItem;
use BigBoxCodeDesignPatternCompositeMenuMenuParent;

// Define some menu items
$item1 = new MenuItem("http://firstlink.com", "First Item");
$item2 = new MenuItem("http://secondlink.com", "Second Item");
$item3 = new MenuItem("http://thirdlink.com", "Third Item");

// Define a group of items
$itemGroup1 = new MenuParent();
$itemGroup1->addItem(new MenuItem("http://group-item-1.com", "First group item"));
$itemGroup1->addItem(new MenuItem("http://group-item-2.com", "Second group item"));
$itemGroup1->addItem(new MenuItem("http://group-item-3.com", "Third group item"));
$itemGroup1->addItem(new MenuItem("http://group-item-4.com", "Fourth group item"));


$item4 = new MenuItem("http://item-4.com", "4th Item");

// Add items to menu
$mainMenu = new MenuParent();
$mainMenu->addItem($item1);
$mainMenu->addItem($item2);
$mainMenu->addItem($item3);
$mainMenu->addItem($itemGroup1);
$mainMenu->addItem($item4);

// Print menu
$mainMenu->print();

Output

Output will be as below-

[ul]
    [li][a link='http://firstlink.com']First Item[/a][/li]
    [li][a link='http://secondlink.com']Second Item[/a][/li]
    [li][a link='http://thirdlink.com']Third Item[/a][/li]
    [ul]
        [li][a link='http://group-item-1.com']First group item[/a][/li]
        [li][a link='http://group-item-2.com']Second group item[/a][/li]
        [li][a link='http://group-item-3.com']Third group item[/a][/li]
        [li][a link='http://group-item-4.com']Fourth group item[/a][/li]
    [ul]
    [li][a link='http://item-4.com']4th Item[/a][/li]
[ul]

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Composite pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.