mirror of
https://github.com/Respect/Validation.git
synced 2026-03-15 06:45:44 +01:00
136 lines
4 KiB
PHP
136 lines
4 KiB
PHP
<?php
|
|
|
|
/*
|
|
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Respect\Validation\Rules;
|
|
|
|
use Attribute;
|
|
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
|
|
use Respect\Validation\Message\Template;
|
|
use Respect\Validation\Result;
|
|
use Respect\Validation\Rule;
|
|
use Respect\Validation\Rules\Core\KeyRelated;
|
|
use Respect\Validation\Rules\Core\Reducer;
|
|
use Respect\Validation\Validator;
|
|
|
|
use function array_diff;
|
|
use function array_filter;
|
|
use function array_keys;
|
|
use function array_map;
|
|
use function array_merge;
|
|
use function array_slice;
|
|
|
|
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
|
#[Template(
|
|
'{{name}} validation failed',
|
|
'{{name}} validation passed',
|
|
)]
|
|
#[Template(
|
|
'{{name}} contains both missing and extra keys',
|
|
'{{name}} contains no missing or extra keys.',
|
|
self::TEMPLATE_BOTH,
|
|
)]
|
|
#[Template(
|
|
'{{name}} contains extra keys',
|
|
'{{name}} contains no extra keys',
|
|
self::TEMPLATE_EXTRA_KEYS,
|
|
)]
|
|
#[Template(
|
|
'{{name}} contains missing keys',
|
|
'{{name}} contains no missing keys',
|
|
self::TEMPLATE_MISSING_KEYS,
|
|
)]
|
|
final readonly class KeySet implements Rule
|
|
{
|
|
public const string TEMPLATE_BOTH = '__both__';
|
|
public const string TEMPLATE_EXTRA_KEYS = '__extra_keys__';
|
|
public const string TEMPLATE_MISSING_KEYS = '__missing_keys__';
|
|
|
|
private const int MAX_DIFF_KEYS = 10;
|
|
|
|
/** @var array<KeyRelated> */
|
|
private array $rules;
|
|
|
|
/** @var array<int|string> */
|
|
private array $allKeys;
|
|
|
|
/** @var array<int|string> */
|
|
private array $mandatoryKeys;
|
|
|
|
public function __construct(Rule $rule, Rule ...$rules)
|
|
{
|
|
$this->rules = $this->extractKeyRelatedRules(array_merge([$rule], $rules));
|
|
$this->allKeys = array_map(static fn(KeyRelated $rule) => $rule->getKey(), $this->rules);
|
|
$this->mandatoryKeys = array_map(
|
|
static fn(KeyRelated $rule) => $rule->getKey(),
|
|
array_filter($this->rules, static fn(KeyRelated $rule) => !$rule instanceof KeyOptional),
|
|
);
|
|
}
|
|
|
|
public function evaluate(mixed $input): Result
|
|
{
|
|
$arrayResult = (new ArrayType())->evaluate($input);
|
|
if (!$arrayResult->hasPassed) {
|
|
return $arrayResult;
|
|
}
|
|
|
|
$keys = new Reducer(...array_merge($this->rules, array_map(
|
|
static fn(string|int $key) => new Not(new KeyExists($key)),
|
|
array_slice(array_diff(array_keys($input), $this->allKeys), 0, self::MAX_DIFF_KEYS),
|
|
)));
|
|
$keysResult = $keys->evaluate($input);
|
|
|
|
return (new Result($keysResult->hasPassed, $input, $this, [], $this->getTemplateFromKeys(array_keys($input))))
|
|
->withChildren(...($keysResult->children === [] ? [$keysResult] : $keysResult->children));
|
|
}
|
|
|
|
/**
|
|
* @param array<Rule> $rules
|
|
*
|
|
* @return array<KeyRelated>
|
|
*/
|
|
private function extractKeyRelatedRules(array $rules): array
|
|
{
|
|
$keyRelatedRules = [];
|
|
foreach ($rules as $rule) {
|
|
if ($rule instanceof KeyRelated) {
|
|
$keyRelatedRules[] = $rule;
|
|
continue;
|
|
}
|
|
|
|
if (!$rule instanceof Validator) {
|
|
throw new InvalidRuleConstructorException('You must provide only key-related rules');
|
|
}
|
|
|
|
$keyRelatedRules = array_merge($keyRelatedRules, $this->extractKeyRelatedRules($rule->getRules()));
|
|
}
|
|
|
|
return $keyRelatedRules;
|
|
}
|
|
|
|
/** @param array<int|string> $keys */
|
|
private function getTemplateFromKeys(array $keys): string
|
|
{
|
|
$extraKeys = array_diff($keys, $this->allKeys);
|
|
$missingKeys = array_diff($this->mandatoryKeys, $keys);
|
|
|
|
if ($extraKeys !== [] && $missingKeys !== []) {
|
|
return self::TEMPLATE_BOTH;
|
|
}
|
|
|
|
if ($extraKeys !== []) {
|
|
return self::TEMPLATE_EXTRA_KEYS;
|
|
}
|
|
|
|
if ($missingKeys !== []) {
|
|
return self::TEMPLATE_MISSING_KEYS;
|
|
}
|
|
|
|
return self::TEMPLATE_STANDARD;
|
|
}
|
|
}
|