/**
* INTERFAZ COMPONENTE (Component)
*
* Define la interfaz común que deben implementar tanto las hojas (ítems simples)
* como los compuestos (grupos/menús con subelementos).
*
* Gracias a esta interfaz, el cliente puede tratar a todos los elementos del árbol
* de forma uniforme, sin importar si son hojas o compuestos.
*/
interface MenuComponent
{
/**
* Renderiza el elemento (y sus hijos si los tiene) como texto o HTML.
* El parámetro $level ayuda a crear indentación visual para ver la jerarquía.
*/
public function render(int $level = 0): string;
/**
* Devuelve el nombre del elemento (útil para mostrar, depurar o buscar).
*/
public function getName(): string;
}
/**
* HOJA (Leaf)
*
* Representa un elemento final del árbol: un ítem de menú simple
* que NO tiene hijos (no puede contener otros menús).
*
* Implementa directamente la interfaz sin lógica de contenedor.
*/
final readonly class MenuItem implements MenuComponent
{
/**
* Constructor con promoción de propiedades (PHP 8.0+)
* - readonly → no se puede modificar después de creado (inmutabilidad)
* - private → encapsulamiento (solo accesible mediante métodos)
*/
public function __construct(
private string $name,
private string $url
) {}
public function getName(): string
{
return $this->name;
}
/**
* Render simple: solo muestra un enlace con indentación según nivel.
* No tiene hijos → no hay recursión aquí.
*/
public function render(int $level = 0): string
{
// str_repeat crea espacios para indentar visualmente la jerarquía
$indent = str_repeat(' ', $level);
// Formato básico de ítem de menú (puedes cambiar a si usas HTML real)
return $indent . "- url}\">{$this->name}\n";
}
}
/**
* COMPUESTO (Composite)
*
* Puede contener cero o más MenuComponent (hojas u otros compuestos).
* Implementa la misma interfaz que las hojas → uniformidad.
*
* Aquí está la recursividad: cuando se llama a render(),
* delega la renderización a todos sus hijos.
*/
final class MenuGroup implements MenuComponent
{
/**
* Array tipado (PHP 8.1+): solo acepta objetos que implementen MenuComponent
* @var list → indica que es una lista de elementos válidos
*/
private array $children = [];
public function __construct(
private string $name
) {}
public function getName(): string
{
return $this->name;
}
/**
* Añade un hijo al grupo (hoja o subgrupo).
* No hay restricción de tipo más allá de la interfaz → polimorfismo.
*/
public function add(MenuComponent $component): void
{
$this->children[] = $component;
}
/**
* Elimina un hijo específico (útil en editores dinámicos de menús).
* Usa comparación estricta (===) para objetos.
*/
public function remove(MenuComponent $component): void
{
$index = array_search($component, $this->children, true);
if ($index !== false) {
unset($this->children[$index]);
// Opcional: reindexar el array si es necesario
$this->children = array_values($this->children);
}
}
/**
* Método clave del patrón Composite:
* - Se comporta como hoja (muestra su nombre)
* - Pero además renderiza recursivamente a TODOS sus hijos
* - El $level + 1 crea la indentación de subniveles
*/
public function render(int $level = 0): string
{
$indent = str_repeat(' ', $level);
// Mostramos el nombre del grupo con un símbolo distintivo (+)
$output = $indent . "+ {$this->name}\n";
// Recorremos y renderizamos cada hijo (recursividad)
foreach ($this->children as $child) {
$output .= $child->render($level + 1);
}
return $output;
}
}