StructuralPHPverifiedVerified
Composite Pattern in PHP
Composes objects into tree structures to represent part-whole hierarchies, letting clients treat individual objects and compositions uniformly.
How to Implement the Composite Pattern in PHP
1Step 1: Define the component interface
interface FileSystemEntry
{
public function getName(): string;
public function getSize(): int;
public function display(string $indent = ''): string;
}2Step 2: Implement the leaf node
class File implements FileSystemEntry
{
public function __construct(
private readonly string $name,
private readonly int $size,
) {}
public function getName(): string { return $this->name; }
public function getSize(): int { return $this->size; }
public function display(string $indent = ''): string
{
return "{$indent}- {$this->name} ({$this->size}B)\n";
}
}3Step 3: Implement the composite node
class Directory implements FileSystemEntry
{
/** @var FileSystemEntry[] */
private array $children = [];
public function __construct(private readonly string $name) {}
public function add(FileSystemEntry $entry): self
{
$this->children[] = $entry;
return $this;
}
public function getName(): string { return $this->name; }
public function getSize(): int
{
return array_sum(array_map(fn(FileSystemEntry $e) => $e->getSize(), $this->children));
}
public function display(string $indent = ''): string
{
$output = "{$indent}+ {$this->name}/\n";
foreach ($this->children as $child) {
$output .= $child->display($indent . ' ');
}
return $output;
}
}
// Usage
$root = new Directory('root');
$root->add(new File('readme.md', 1024))
->add((new Directory('src'))
->add(new File('app.php', 2048))
->add(new File('index.php', 512)));
echo $root->display();
echo "Total size: {$root->getSize()}B\n";<?php
declare(strict_types=1);
// [step] Define the component interface for a permissions system
interface PermissionInterface
{
public function getName(): string;
public function hasPermission(string $action, string $resource): bool;
/** @return string[] */
public function listPermissions(): array;
}
// [step] Implement the leaf: a single permission
final readonly class Permission implements PermissionInterface
{
public function __construct(
private string $action,
private string $resource,
) {}
public function getName(): string
{
return "{$this->action}:{$this->resource}";
}
public function hasPermission(string $action, string $resource): bool
{
$actionMatch = $this->action === '*' || $this->action === $action;
$resourceMatch = $this->resource === '*' || $this->resource === $resource;
return $actionMatch && $resourceMatch;
}
public function listPermissions(): array
{
return [$this->getName()];
}
}
// [step] Implement the composite: a permission group (role)
final class PermissionGroup implements PermissionInterface
{
/** @var PermissionInterface[] */
private array $children = [];
public function __construct(
private readonly string $name,
private readonly string $description = '',
) {}
public function add(PermissionInterface $permission): self
{
$this->children[] = $permission;
return $this;
}
public function remove(PermissionInterface $permission): self
{
$this->children = array_values(array_filter(
$this->children,
fn(PermissionInterface $p) => $p !== $permission,
));
return $this;
}
public function getName(): string { return $this->name; }
public function hasPermission(string $action, string $resource): bool
{
foreach ($this->children as $child) {
if ($child->hasPermission($action, $resource)) {
return true;
}
}
return false;
}
public function listPermissions(): array
{
$permissions = [];
foreach ($this->children as $child) {
$permissions = array_merge($permissions, $child->listPermissions());
}
return array_unique($permissions);
}
}
// [step] Use the composite to build a role hierarchy
$readPosts = new Permission('read', 'posts');
$writePosts = new Permission('write', 'posts');
$deletePosts = new Permission('delete', 'posts');
$readUsers = new Permission('read', 'users');
$manageUsers = new Permission('*', 'users');
$editorRole = (new PermissionGroup('editor', 'Content editor'))
->add($readPosts)
->add($writePosts);
$adminRole = (new PermissionGroup('admin', 'Full administrator'))
->add($editorRole) // Inherits editor permissions
->add($deletePosts)
->add($manageUsers);
// Check permissions through the tree
var_dump($adminRole->hasPermission('write', 'posts')); // true (via editor)
var_dump($editorRole->hasPermission('delete', 'posts')); // falseComposite Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Composite Pattern in the Real World
“A company’s org chart is a composite structure. An individual employee (leaf) has a salary and a name. A department (composite) also has a name and a budget—calculated by summing the salaries of all its members, which may themselves be other departments. HR can call ‘get budget’ on the CEO’s node and the entire tree is summed recursively.”