respect-validation/library/Validator.php
Henrique Moody 82e0722443
Split the Formatter into different formatters
I've noticed that the `StandardFormatter` was quite bloated, which made
it difficult to maintain. Understanding what each method was doing was
quite complicated. Besides, the name "Standard" doesn't mean anything,
because it doesn't say what the implementation does.

I split the `Formatter` into two different interfaces: `StringFormatter`
and `ArrayFormatter`, and I moved some code around:

*  `StandardFormatter::main()` -> `FirstResultStringFormatter`
* `StandardFormatter::full()` -> `NestedListStringFormatter`
* `StandardFormatter::array()` -> `NestedArrayFormatter`

That opens up new ways of handling error messages, potentially
introducing features like `JsonStringFormatter` or `FlatArrayFormatter`
in the future.

While working on this, I removed a significant amount of unnecessary
code, which also improved my overall understanding of those formatters.

I'm not very happy with all the methods in `ValidatorDefaults`, but I
will refactor that later.
2025-12-19 16:20:28 +01:00

174 lines
4.7 KiB
PHP

<?php
/*
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
* SPDX-License-Identifier: MIT
*/
declare(strict_types=1);
namespace Respect\Validation;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Message\ArrayFormatter;
use Respect\Validation\Message\StringFormatter;
use Respect\Validation\Message\Translator;
use Respect\Validation\Mixins\Builder;
use Respect\Validation\Rules\Core\Nameable;
use Respect\Validation\Rules\Core\Reducer;
use Throwable;
use function is_array;
use function is_callable;
use function is_string;
/** @mixin Builder */
final class Validator implements Rule, Nameable
{
/** @var array<Rule> */
private array $rules = [];
/** @var array<string, mixed> */
private array $templates = [];
private string|null $name = null;
private string|null $template = null;
/** @param array<string> $ignoredBacktracePaths */
public function __construct(
private readonly Factory $factory,
private readonly StringFormatter $mainMessageFormatter,
private readonly StringFormatter $fullMessageFormatter,
private readonly ArrayFormatter $messagesFormatter,
private readonly Translator $translator,
private readonly ResultFilter $resultFilter,
private readonly array $ignoredBacktracePaths,
) {
}
public static function create(Rule ...$rules): self
{
$validator = new self(
ValidatorDefaults::getFactory(),
ValidatorDefaults::getMainMessageFormatter(),
ValidatorDefaults::getFullMessageFormatter(),
ValidatorDefaults::getMessagesFormatter(),
ValidatorDefaults::getTranslator(),
new OnlyFailedChildrenResultFilter(),
ValidatorDefaults::getIgnoredBacktracePaths(),
);
$validator->rules = $rules;
return $validator;
}
public function evaluate(mixed $input): Result
{
return $this->getRule()->evaluate($input);
}
public function isValid(mixed $input): bool
{
return $this->evaluate($input)->hasPassed;
}
/** @param array<string, mixed>|callable(ValidationException): Throwable|string|Throwable|null $template */
public function assert(mixed $input, array|string|Throwable|callable|null $template = null): void
{
$result = $this->evaluate($input);
if ($result->hasPassed) {
return;
}
if ($template instanceof Throwable) {
throw $template;
}
$templates = $this->templates;
if (is_array($template)) {
$templates = $template;
} elseif (is_string($template)) {
$templates = ['__root__' => $template];
} elseif ($this->getTemplate() != null) {
$templates = ['__root__' => $this->getTemplate()];
}
$failedResult = $this->resultFilter->filter($result);
$exception = new ValidationException(
$this->mainMessageFormatter->format($failedResult, $templates, $this->translator),
$this->fullMessageFormatter->format($failedResult, $templates, $this->translator),
$this->messagesFormatter->format($failedResult, $templates, $this->translator),
$this->ignoredBacktracePaths,
);
if (!is_callable($template)) {
throw $exception;
}
throw $template($exception);
}
/** @param array<string, mixed> $templates */
public function setTemplates(array $templates): self
{
$this->templates = $templates;
return $this;
}
public function addRule(Rule $rule): self
{
$this->rules[] = $rule;
return $this;
}
public function getRule(): Reducer
{
return (new Reducer(...$this->rules))->withTemplate($this->template)->withName($this->name);
}
/** @return array<Rule> */
public function getRules(): array
{
return $this->rules;
}
public function getName(): string|null
{
return $this->getRule()->getName() ?? $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getTemplate(): string|null
{
return $this->template;
}
public function setTemplate(string $template): static
{
$this->template = $template;
return $this;
}
/** @param mixed[] $arguments */
public static function __callStatic(string $ruleName, array $arguments): self
{
return self::create()->__call($ruleName, $arguments);
}
/** @param mixed[] $arguments */
public function __call(string $ruleName, array $arguments): self
{
return $this->addRule($this->factory->rule($ruleName, $arguments));
}
}