* SPDX-License-Identifier: MIT */ declare(strict_types=1); namespace Respect\Validation\Message\Formatter; use Respect\Validation\Message\Renderer; use Respect\Validation\Message\StringFormatter; use Respect\Validation\Message\Translator; use Respect\Validation\Result; use function array_filter; use function array_map; use function array_reduce; use function count; use function rtrim; use function sprintf; use function str_repeat; use const PHP_EOL; final readonly class NestedListStringFormatter implements StringFormatter { use PathProcessor; public function __construct( private Renderer $renderer, private TemplateResolver $templateResolver, ) { } /** @param array $templates */ public function format(Result $result, array $templates, Translator $translator): string { return $this->formatRecursively($result, $templates, $translator, 0); } /** @param array $templates */ private function formatRecursively( Result $result, array $templates, Translator $translator, int $depth = 0, Result ...$siblings, ): string { $matchedTemplates = $this->templateResolver->selectMatches($result, $templates); $formatted = ''; if ($this->isVisible($result, ...$siblings)) { $indentation = str_repeat(' ', $depth * 2); $formatted .= sprintf( '%s- %s' . PHP_EOL, $indentation, $this->renderer->render( $this->templateResolver->resolve( $depth > 0 ? $result->withDeepestPath() : $result, $matchedTemplates, ), $translator, ), ); $depth++; } if (!$this->templateResolver->hasMatch($result, $matchedTemplates)) { $children = array_map( fn(Result $child) => $this->overwritePath($result, $child), $result->children, ); foreach ($children as $child) { $formatted .= $this->formatRecursively( $child, $matchedTemplates, $translator, $depth, ...array_filter($children, static fn(Result $sibling) => $sibling !== $child), ); $formatted .= PHP_EOL; } } return rtrim($formatted, PHP_EOL); } private function isVisible(Result $result, Result ...$siblings): bool { if ($result->hasCustomTemplate()) { return true; } // Parents of an only child are not visible by default if (count($result->children) !== 1) { return true; } // Only children are always visible if (count($siblings) === 0) { return false; } // The visibility of a result then will depend on whether any of its siblings is visible return array_reduce( $siblings, fn(bool $carry, Result $currentSibling) => $carry || $this->isVisible( $currentSibling, ...array_filter($siblings, static fn(Result $sibling) => $sibling !== $currentSibling), ), true, ); } }