Composite Design Pattern in PHP

A file system doesn't distinguish much between "a file" and "a folder" when you ask a simple question like "how much space does this take up?" — a folder's size is just the sum of whatever is inside it, files and subfolders alike, recursively. If your code has to special-case "is this a single file or a folder full of things?" every time it wants an answer, that logic gets duplicated and fragile. The Composite pattern solves this by giving both the single item and the group of items the same interface, so client code never has to ask which one it's holding.

What Is the Composite Pattern?

Composite is a structural pattern that composes objects into tree structures to represent part-whole hierarchies. It defines a shared component interface used by both:

RoleWhat it is
LeafAn individual object with no children — it implements the interface's operations directly on itself
CompositeA container that holds a list of child components (leaves or other composites) and implements the same operations by delegating to, and combining the results of, its children

Because both roles implement the exact same interface, client code can call the same method on a single leaf or on an entire nested tree and get a correct, meaningful answer either way — without ever checking which kind of object it has.

Real-world analogy: an org chart. Asking "how many people report up through this manager?" works the same way whether you're asking about an individual contributor (zero reports) or a director with several managers, each with their own teams underneath — the question and the counting logic are identical at every level.

The Problem It Solves

Without Composite, code that needs to total up a folder's size typically ends up branching on type at every level: "if this is a file, return its size; if it's a folder, loop over its contents and check each one's type too." That branching has to be repeated (or recursed through) anywhere the size is needed, and it gets more fragile every time a new component type is added. Composite removes the branching by making the leaf and the container answer to the identical method.

Worked Example: File System Sizes

The shared interface every component — file or folder — implements:

<?php
interface FileSystemComponent
{
    // Returns size in bytes
    public function getSize(): int;
    public function getName(): string;
}
?>

The leaf — a single file simply knows and returns its own size:

<?php
class File implements FileSystemComponent
{
    private string $name;
    private int $sizeInBytes;

    public function __construct(string $name, int $sizeInBytes)
    {
        $this->name = $name;
        $this->sizeInBytes = $sizeInBytes;
    }

    public function getSize(): int
    {
        return $this->sizeInBytes;
    }

    public function getName(): string
    {
        return $this->name;
    }
}
?>

The composite — a folder holds any number of child components (files or nested folders) and sums their sizes recursively:

<?php
class Folder implements FileSystemComponent
{
    private string $name;
    private array $children = [];

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

    public function add(FileSystemComponent $component): void
    {
        $this->children[] = $component;
    }

    public function getSize(): int
    {
        $total = 0;
        foreach ($this->children as $child) {
            // Doesn't matter if $child is a File or another Folder
            $total += $child->getSize();
        }
        return $total;
    }

    public function getName(): string
    {
        return $this->name;
    }
}
?>

Building a small tree and asking for its size — one folder holding a file, and a nested subfolder holding two more files:

<?php
$photos = new Folder('photos');
$photos->add(new File('beach.jpg', 204800));
$photos->add(new File('mountains.jpg', 309200));

$vacation = new Folder('vacation-2026');
$vacation->add(new File('itinerary.pdf', 51200));
$vacation->add($photos); // a folder can hold another folder

echo $vacation->getSize(); // 51200 + 204800 + 309200 = 565200
?>
Note: getSize() was called once, on the top-level $vacation folder. It didn't need to know the tree was two levels deep, or that photos even existed — the recursion happens naturally because every Folder just asks its immediate children the same question.

Client Code Doesn't Care What It's Holding

This is the core payoff of Composite: a function that accepts a FileSystemComponent works correctly whether it's handed a single File or a Folder containing a thousand nested files:

<?php
function printSizeInKb(FileSystemComponent $component): void
{
    $kb = round($component->getSize() / 1024, 1);
    echo $component->getName() . ": {$kb} KB";
}

printSizeInKb($photos);   // works on a single File...
printSizeInKb($vacation); // ...or an entire nested tree, no changes needed
?>

Real-World Tie-Ins

Beyond file systems, this pattern shows up anywhere a "whole" is naturally built from parts that follow the same rules: an org chart where a manager's headcount is the sum of their direct reports' headcounts (including reports who are themselves managers), a UI toolkit where a panel's rendered output is the combination of its child widgets' rendered output, or a shopping cart where a "bundle" product's price is the sum of the individual products inside it.

Next up: Strategy moves into the Behavioral category — swapping an algorithm's implementation at runtime without changing the code that uses it.