respect-validation/library/Message/Formatter/NestedListStringFormatter.php
Henrique Moody 644ecb5190
Change the contract of the formatters
Both `ArrayFormatter` and `StringFormatter` accept an instance of the
`Translator`. Thinking about it a bit better, I realised that a
formatter might not always need a `Translator`, but it will surely need
a `Renderer`.

Besides, the `InterpolationRenderer` needs to take translation into
account, so it seems more natural to me that this is the one that will
get an instance of the `Translator`, as other implementations of the
`Renderer` might not even deal with translations.
2025-12-22 13:43:59 +01:00

108 lines
3.2 KiB
PHP

<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* 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\Result;
use function array_filter;
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
{
public function __construct(
private TemplateResolver $templateResolver,
) {
}
/** @param array<string|int, mixed> $templates */
public function format(Result $result, Renderer $renderer, array $templates): string
{
return $this->formatRecursively($result, $renderer, $templates, 0);
}
/** @param array<string|int, mixed> $templates */
private function formatRecursively(
Result $result,
Renderer $renderer,
array $templates,
int $depth,
Result ...$siblings,
): string {
$matchedTemplates = $this->templateResolver->selectMatches($result, $templates);
$formatted = '';
$displayedName = null;
if ($this->isVisible($result, ...$siblings)) {
$indentation = str_repeat(' ', $depth * 2);
$displayedName = $result->name;
$formatted .= sprintf(
'%s- %s' . PHP_EOL,
$indentation,
$renderer->render(
$this->templateResolver->resolve(
$result->withoutParentPath(),
$matchedTemplates,
),
),
);
$depth++;
}
if (!$this->templateResolver->hasMatch($result, $matchedTemplates)) {
foreach ($result->children as $child) {
$formatted .= $this->formatRecursively(
$displayedName === $child->name ? $child->withoutName() : $child,
$renderer,
$matchedTemplates,
$depth,
...array_filter($result->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,
);
}
}