*/ private array $nodes = []; public function __construct( private string $title = '', #private readonly ConsoleOutput $output = new ConsoleOutput(), ) { $this->nodeStyle = ConsoleStyle::create(color: ConsoleColor::BRIGHT_YELLOW, format: ConsoleFormat::BOLD); $this->leafStyle = ConsoleStyle::create(color: ConsoleColor::WHITE); $this->lineStyle = ConsoleStyle::create(color: ConsoleColor::GRAY); } /** * Setzt den Stil für Knotentitel (Verzeichnisse/Kategorien). */ public function setNodeStyle(?ConsoleStyle $style): self { $this->nodeStyle = $style; return $this; } /** * Setzt den Stil für Blätter/Endpunkte. */ public function setLeafStyle(?ConsoleStyle $style): self { $this->leafStyle = $style; return $this; } /** * Setzt den Stil für Baumlinien. */ public function setLineStyle(?ConsoleStyle $style): self { $this->lineStyle = $style; return $this; } /** * Setzt den Haupttitel des Baums. */ public function setTitle(string $title): self { $this->title = $title; return $this; } /** * Fügt einen Unterknoten (z.B. Unterverzeichnis) hinzu. */ public function addNode(string $title): self { $node = new self($title); $node->nodeStyle = $this->nodeStyle; $node->leafStyle = $this->leafStyle; $node->lineStyle = $this->lineStyle; $this->nodes[] = [ 'title' => $title, 'node' => $node, 'isLeaf' => false, ]; return $node; } /** * Fügt einen Endpunkt (z.B. Datei) hinzu. */ public function addLeaf(string $title): self { $this->nodes[] = [ 'title' => $title, 'node' => null, 'isLeaf' => true, ]; return $this; } /** * Zeigt die vollständige Baumstruktur an. */ public function display(ConsoleOutput $output): void { if (! empty($this->title)) { $output->writeLine($this->title, $this->nodeStyle); } $this->displayTree($output); } /** * Rendert die Baumstruktur und gibt den Text zurück. */ public function render(): string { $output = ''; if (! empty($this->title)) { $output .= $this->nodeStyle->apply($this->title) . "\n"; } $output .= $this->renderTree(); return $output; } /** * Setzt den Präfix für die aktuelle Ebene. * (Interne Methode für rekursives Rendern) */ private function setPrefix(string $prefix, bool $isLastElement): self { $this->prefix = $prefix; $this->isLastElement = $isLastElement; return $this; } /** * Zeigt die Baumstruktur mit dem aktuellen Präfix an. * (Interne Methode für rekursives Rendern) */ private function displayTree(ConsoleOutput $output): void { $count = count($this->nodes); foreach ($this->nodes as $index => $item) { $isLast = ($index === $count - 1); $nodePrefix = $this->prefix . ($this->isLastElement ? ' ' : '│ '); // Baumlinien und Verbindungen $connector = $isLast ? '└── ' : '├── '; $linePrefix = $this->prefix . $connector; // Titel anzeigen $style = $item['isLeaf'] ? $this->leafStyle : $this->nodeStyle; $title = $linePrefix . $item['title']; $output->writeLine( $this->lineStyle->apply($linePrefix) . $style->apply($item['title']) ); // Unterelemente rekursiv anzeigen if (! $item['isLeaf'] && $item['node'] !== null) { $item['node'] ->setPrefix($nodePrefix, $isLast) ->displayTree($output); } } } /** * Rendert die Baumstruktur mit dem aktuellen Präfix und gibt den Text zurück. * (Interne Methode für rekursives Rendern) */ private function renderTree(): string { $output = ''; $count = count($this->nodes); foreach ($this->nodes as $index => $item) { $isLast = ($index === $count - 1); $nodePrefix = $this->prefix . ($this->isLastElement ? ' ' : '│ '); // Baumlinien und Verbindungen $connector = $isLast ? '└── ' : '├── '; $linePrefix = $this->prefix . $connector; // Titel formatieren $style = $item['isLeaf'] ? $this->leafStyle : $this->nodeStyle; $title = $item['title']; $output .= $this->lineStyle->apply($linePrefix) . $style->apply($title) . "\n"; // Unterelemente rekursiv rendern if (! $item['isLeaf'] && $item['node'] !== null) { $childOutput = $item['node'] ->setPrefix($nodePrefix, $isLast) ->renderTree(); $output .= $childOutput; } } return $output; } public function getOutputFormat(): OutputFormat { return OutputFormat::CONSOLE; } /** * Build tree from Display ArrayStructure */ public function fromArrayStructure(ArrayStructure $structure, string $separator = ': '): self { foreach ($structure->items as $key => $value) { if (isset($structure->nestedArrays[$key])) { $nested = $structure->nestedArrays[$key]; if ($nested->isCircularReference) { $this->addLeaf((string) $key . $separator . '[circular reference]'); } else { $node = $this->addNode((string) $key); $node->fromArrayStructure($nested, $separator); } } else { $valueStr = is_scalar($value) ? (string) $value : get_debug_type($value); $this->addLeaf((string) $key . $separator . $valueStr); } } return $this; } /** * Build tree from Display ObjectStructure */ public function fromObjectStructure(ObjectStructure $structure, string $separator = ': '): self { foreach ($structure->properties as $property) { if (isset($structure->nestedObjects[$property->name])) { $nested = $structure->nestedObjects[$property->name]; if ($nested->isCircularReference) { $this->addLeaf($property->name . $separator . '[circular reference]'); } else { $node = $this->addNode($property->name . $separator . $nested->className); $node->fromObjectStructure($nested, $separator); } } else { $valueStr = $property->isValueObject && is_object($property->value) ? (string) $property->value : (is_scalar($property->value) ? (string) $property->value : get_debug_type($property->value)); $this->addLeaf($property->name . $separator . $valueStr); } } return $this; } }