Composite Pattern in PHP

Previously I covered all of the creational patterns which are useful with PHP and the majority of structural patterns. One structural pattern I never covered was the Composite pattern and so that’s what I’m covering today. The purpose of this pattern is to allow trees of objects (composites) to be handled interchangeably, regardless of if the node is a branch of a leaf node. What branch and leaf nodes are is revealed below.

Let’s use a hard drive as an example, you have directories which you can think of as branches, and you have files, you can think of these as leaf nodes. Branches contain leaves just like directories contain files. Directories and files are both separate entities, but the goal of the composite pattern has them interchangeable, as always this means they should implement the same interface. Example methods that they both share include updating the owner and group, changing permissions, fetching size, created date or last modified date, etc.

Components of the Composite pattern

  1. Component – This is the interface for your composite and entity, it represents the contract that both your branch and leaf nodes must implement.
  2. Composite – The composite is a collection, each item in the collection could be another composite or a leaf node. Composites typically loop over and delegate responsibility to their composites and leaf nodes. Composites must implement the component interface.
  3. Leaf Nodes – These are the individual entities, they represent the end of a tree path, meaning they have no other leaf nodes below them. These must also implement the component interface.
  4. Client – The client/controller is what creates instances to the composite and leaf nodes to generate some output.

When should the composite pattern be used

The ideal use case is when you have a collection of composites and entities that have a similar interface. By using the composite pattern you allow the higher level objects to delegate their work to the lower level leaf nodes using a consistent interface.

Rules

  1. Your leaf nodes and composites must implement the same component interface.
  2. Your composite classes must have methods to both add and remove leaf nodes.

Advantages

  1. The client code is kept simple, it doesn’t need to worry if it’s dealing with an individual objects or a collection.
  2. Leaf nodes can be created and handled directly or via the the composite objects.

Drawbacks

As both the composites and leaf nodes implement the same interface, you will likely have method signatures in your component, which you do not need in either the composite or leaf nodes. This can be countered by returning false on methods which are not required, this would, however, violate the interface segregation principle.

Safety vs Transparency

When implementing the composite pattern, you can either take the safe or the transparent approach. Taking the safe approach means to prevent the add and remove methods from being available in the leaf nodes. This is safe because the leaf nodes do not need these methods and so they cannot be inappropriately called, but it also means the composite and leaf nodes are no longer transparent because they have slightly different interfaces. Conversely, the transparent approach means both the composite and leaf nodes implement exactly the same interface, meaning methods will be available on the leaf nodes which are not required, this also breaks the interface segregation principle.

The approach you take is entirely up to you and you really be taken on a case by case basis, however, in PHP the transparent approach is generally more common.

Example

abstract class ComponentInterface
{
    public $id;
    public $name;

    abstract public function getFile($offset = 0);
    abstract public function addFile(ComponentInterface $file);
    abstract public function removeFile(ComponentInterface $file);

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }
}

class DirectoryComponent extends ComponentInterface
{
    private $files = [];

    public function getFile($offset = 0)
    {
        $content = str_repeat("-", $offset) . ' ' . $this->name . "/\n";

        foreach ($this->files as $file) {
            $content .= $file->getFile(++$offset);
        }

        return $content;
    }
 
    public function addFile(ComponentInterface $file)
    {
        $this->files[$file->id] = $file;

        return $this;
    }

    public function removeFile(ComponentInterface $file)
    {
        unset($this->files[$file->id]);
    }
}

class FileComponent extends ComponentInterface
{
    public function getFile($offset = 0)
    {
        return str_repeat("-", $offset-1) . '> ' . $this->name . "\n";
    }

    public function addFile(ComponentInterface $file)
    {
        return false;
    }

    public function removeFile(ComponentInterface $file)
    {
        return false;
    }
}

$root = new DirectoryComponent(1, "/");

$etc = new DirectoryComponent(2, "etc");
$var = new DirectoryComponent(3, "var");

$root->addFile($etc)->addFile($var);

$etc->addFile(
    new FileComponent(2, "php.ini", "simon")
);
$var->addFile(
    new FileComponent(3, "nginx.log", "simon")
);
$var->addFile(
    new FileComponent(3, "hadoop.log", "simon")
);

echo $root->getFile();

Output

/
– etc
-> php.ini
– var
-> nginx.log
-> hadoop.log

4 Love This

4 Comments Composite Pattern in PHP

    1. Simon

      Hi Bruno, I really should write about the strategy pattern. All behavioural pattern largely revolve around the notion of strategies and polymorphism. I never really quite got onto the behavioural patterns, but it’s definitely something I’m going to pursue. Thanks for your kind words.

      In the mean time you might want to read the article I wrote on the SOLID principles, which indirect covers a lot of strategy based development practices. http://www.jakowicz.com/solid-principles-revisited/

      Reply
  1. arunas

    hello,

    nice example, but I got error. Maybe must define interface ‘HardDriveContent’ ?
    Example works if I delete 2 abstract functions:

    abstract public function addFile(HardDriveContent $file);
    abstract public function removeFile(HardDriveContent $file);

    Reply
    1. Simon

      Hello Arunas, thank you for point this out. I probably should have tested the example, before publishing it. I’ve fixed this now. Thank you.

      Reply

Leave a Reply to Simon Cancel reply

Your email address will not be published.