mirror of
https://github.com/Respect/Validation.git
synced 2026-03-16 23:35:45 +01:00
Currently, we’re using scalar values to trace paths. The problem with that approach is that we can’t create a reliable hierarchy with them, as we can’t know for sure when a path is the same for different rules. By using an object, we can easily compare and create a parent-child relationship with it. While making these changes, I deemed it necessary to also create objects to handle Name and Id, which makes the code simpler and more robust. By having Name and Path, we can create specific stringifiers that allow us to customise how we render those values. I didn’t manage to make those changes atomically, which is why this commit makes so many changes. I found myself moving back and forth, and making all those changes at once was the best solution I found.
111 lines
3.3 KiB
PHP
111 lines
3.3 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\Message\Translator;
|
|
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 Renderer $renderer,
|
|
private TemplateResolver $templateResolver,
|
|
) {
|
|
}
|
|
|
|
/** @param array<string|int, mixed> $templates */
|
|
public function format(Result $result, array $templates, Translator $translator): string
|
|
{
|
|
return $this->formatRecursively($result, $templates, $translator, 0);
|
|
}
|
|
|
|
/** @param array<string|int, mixed> $templates */
|
|
private function formatRecursively(
|
|
Result $result,
|
|
array $templates,
|
|
Translator $translator,
|
|
int $depth = 0,
|
|
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,
|
|
$this->renderer->render(
|
|
$this->templateResolver->resolve(
|
|
$result->withoutParentPath(),
|
|
$matchedTemplates,
|
|
),
|
|
$translator,
|
|
),
|
|
);
|
|
$depth++;
|
|
}
|
|
|
|
if (!$this->templateResolver->hasMatch($result, $matchedTemplates)) {
|
|
foreach ($result->children as $child) {
|
|
$formatted .= $this->formatRecursively(
|
|
$displayedName === $child->name ? $child->withoutName() : $child,
|
|
$matchedTemplates,
|
|
$translator,
|
|
$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,
|
|
);
|
|
}
|
|
}
|