|array */ private array $filterConstraints; /** * @param ?callable(Parameters): (string|bool) $criteria * @param array|array $filterConstraints */ private function __construct( ?callable $criteria = null, int $type = self::USE_KEYS, array $filterConstraints = [], ) { $this->criteria = $criteria; $this->type = $type; $this->filterConstraints = $filterConstraints; } public static function new(): self { return new self(); } /** * Validates the Item parameters as a whole. * * On failure populates the result errors property * * @param ?callable(Parameters): (string|bool) $criteria */ public function filterByCriteria(?callable $criteria, int $type = self::USE_KEYS): self { return new self($criteria, [] === $this->filterConstraints ? $type : $this->type, $this->filterConstraints); } /** * Validate each parameters value per name. * * On success populate the result item property * On failure populates the result errors property * * @param array $constraints */ public function filterByKeys(array $constraints): self { return new self($this->criteria, self::USE_KEYS, $constraints); } /** * Validate each parameters value per indices. * * On success populate the result item property * On failure populates the result errors property * * @param array $constraints */ public function filterByIndices(array $constraints): self { return new self($this->criteria, self::USE_INDICES, $constraints); } public function __invoke(Parameters|Stringable|string $parameters): bool|string { $result = $this->validate($parameters); return $result->isSuccess() ? true : (string) $result->errors; } /** * Validates the structured field Item. */ public function validate(Parameters|Stringable|string $parameters): Result { $violations = new ViolationList(); if (!$parameters instanceof Parameters) { try { $parameters = Parameters::fromHttpValue($parameters); } catch (SyntaxError $exception) { $violations->add(ErrorCode::ParametersFailedParsing->value, new Violation('The parameters string could not be parsed.', previous: $exception)); return Result::failed($violations); } } if ([] === $this->filterConstraints && null === $this->criteria) { $violations->add(ErrorCode::ParametersMissingConstraints->value, new Violation('The parameters constraints are missing.')); } $parsedParameters = new ValidatedParameters(); if ([] !== $this->filterConstraints) { $parsedParameters = match ($this->type) { self::USE_INDICES => $this->validateByIndices($parameters), default => $this->validateByKeys($parameters), }; if ($parsedParameters->isFailed()) { $violations->addAll($parsedParameters->errors); } else { $parsedParameters = $parsedParameters->data; } } $errorMessage = $this->validateByCriteria($parameters); if (!is_bool($errorMessage)) { $violations->add(ErrorCode::ParametersFailedCriteria->value, new Violation($errorMessage)); } /** @var ValidatedParameters $parsedParameters */ $parsedParameters = $parsedParameters ?? new ValidatedParameters(); if ([] === $this->filterConstraints && true === $errorMessage) { $parsedParameters = new ValidatedParameters(match ($this->type) { self::USE_KEYS => $this->toAssociative($parameters), default => $this->toList($parameters), }); } return match ($violations->isNotEmpty()) { true => Result::failed($violations), default => Result::success($parsedParameters), }; } private function validateByCriteria(Parameters $parameters): bool|string { if (null === $this->criteria) { return true; } $errorMessage = ($this->criteria)($parameters); if (true === $errorMessage) { return true; } if (!is_string($errorMessage) || '' === trim($errorMessage)) { $errorMessage = 'The parameters constraints are not met.'; } return $errorMessage; } /** * Validate the current parameter object using its keys and return the parsed values and the errors. * * @return Result|Result */ private function validateByKeys(Parameters $parameters): Result /* @phpstan-ignore-line */ { $data = []; $violations = new ViolationList(); /** * @var string $key * @var SfParameterKeyRule $rule */ foreach ($this->filterConstraints as $key => $rule) { try { $data[$key] = $parameters->valueByKey($key, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? null); } catch (Violation $exception) { $violations[$key] = $exception; } } return match ($violations->isNotEmpty()) { true => Result::failed($violations), default => Result::success(new ValidatedParameters($data)), }; } /** * Validate the current parameter object using its indices and return the parsed values and the errors. */ public function validateByIndices(Parameters $parameters): Result { $data = []; $violations = new ViolationList(); /** * @var int $index * @var SfParameterIndexRule $rule */ foreach ($this->filterConstraints as $index => $rule) { try { $data[$index] = $parameters->valueByIndex($index, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? []); } catch (Violation $exception) { $violations[$index] = $exception; } } return match ($violations->isNotEmpty()) { true => Result::failed($violations), default => Result::success(new ValidatedParameters($data)), }; } /** * @return array */ private function toAssociative(Parameters $parameters): array { $assoc = []; foreach ($parameters as $parameter) { $assoc[$parameter[0]] = $parameter[1]->value(); } return $assoc; } /** * @return array */ private function toList(Parameters $parameters): array { $list = []; foreach ($parameters as $index => $parameter) { $list[$index] = [$parameter[0], $parameter[1]->value()]; } return $list; } }