mirror of
https://github.com/Respect/Validation.git
synced 2026-03-16 23:35:45 +01:00
There are a few "problems" with the current engine: - Allowing each rule to execute assert() and check() means duplication in some cases. - Because we use exceptions to assert/check, we can only invert a validation (with Not) if there are errors. That means that we have limited granularity control. - There is a lot of logic in the exceptions. That means that even after it throws an exception, something could still happen. We're stable on that front, but I want to simplify them. Besides, debugging exception code is painful because the stack trace does not go beyond the exception. Apart from that, there are many limitations with templating, and working that out in the current implementation makes it much harder. These changes will improve the library in many aspects, but they will also change the behavior and break backward compatibility. However, that's a price I'm willing to pay for the improvements we'll have. Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
178 lines
5.3 KiB
PHP
178 lines
5.3 KiB
PHP
<?php
|
|
|
|
/*
|
|
* Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Respect\Validation;
|
|
|
|
use ReflectionClass;
|
|
use ReflectionException;
|
|
use ReflectionObject;
|
|
use Respect\Validation\Attributes\ExceptionClass;
|
|
use Respect\Validation\Exceptions\ComponentException;
|
|
use Respect\Validation\Exceptions\InvalidClassException;
|
|
use Respect\Validation\Exceptions\ValidationException;
|
|
use Respect\Validation\Message\Parameter\Processor;
|
|
use Respect\Validation\Message\Parameter\Raw;
|
|
use Respect\Validation\Message\Parameter\Stringify;
|
|
use Respect\Validation\Message\Parameter\Trans;
|
|
use Respect\Validation\Message\TemplateCollector;
|
|
use Respect\Validation\Message\TemplateRenderer;
|
|
|
|
use function count;
|
|
use function lcfirst;
|
|
use function sprintf;
|
|
use function trim;
|
|
use function ucfirst;
|
|
|
|
final class Factory
|
|
{
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
private array $rulesNamespaces = ['Respect\\Validation\\Rules'];
|
|
|
|
/**
|
|
* @var callable
|
|
*/
|
|
private $translator;
|
|
|
|
private Processor $processor;
|
|
|
|
private TemplateCollector $templateCollector;
|
|
|
|
private static Factory $defaultInstance;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->translator = static fn (string $message) => $message;
|
|
$this->processor = new Raw(new Trans($this->translator, new Stringify()));
|
|
$this->templateCollector = new TemplateCollector();
|
|
}
|
|
|
|
public static function getDefaultInstance(): self
|
|
{
|
|
if (!isset(self::$defaultInstance)) {
|
|
self::$defaultInstance = new self();
|
|
}
|
|
|
|
return self::$defaultInstance;
|
|
}
|
|
|
|
public function withRuleNamespace(string $rulesNamespace): self
|
|
{
|
|
$clone = clone $this;
|
|
$clone->rulesNamespaces[] = trim($rulesNamespace, '\\');
|
|
|
|
return $clone;
|
|
}
|
|
|
|
public function withTranslator(callable $translator): self
|
|
{
|
|
$clone = clone $this;
|
|
$clone->translator = $translator;
|
|
$clone->processor = new Raw(new Trans($translator, new Stringify()));
|
|
|
|
return $clone;
|
|
}
|
|
|
|
public function withParameterProcessor(Processor $processor): self
|
|
{
|
|
$clone = clone $this;
|
|
$clone->processor = $processor;
|
|
|
|
return $clone;
|
|
}
|
|
|
|
public function getTranslator(): callable
|
|
{
|
|
return $this->translator;
|
|
}
|
|
|
|
public function getParameterProcessor(): Processor
|
|
{
|
|
return $this->processor;
|
|
}
|
|
|
|
/**
|
|
* @param mixed[] $arguments
|
|
*/
|
|
public function rule(string $ruleName, array $arguments = []): Validatable
|
|
{
|
|
foreach ($this->rulesNamespaces as $namespace) {
|
|
try {
|
|
/** @var class-string<Validatable> $name */
|
|
$name = $namespace . '\\' . ucfirst($ruleName);
|
|
/** @var Validatable $rule */
|
|
$rule = $this
|
|
->createReflectionClass($name, Validatable::class)
|
|
->newInstanceArgs($arguments);
|
|
|
|
return $rule;
|
|
} catch (ReflectionException $exception) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
throw new ComponentException(sprintf('"%s" is not a valid rule name', $ruleName));
|
|
}
|
|
|
|
/**
|
|
* @param mixed[] $extraParams
|
|
*/
|
|
public function exception(Validatable $validatable, mixed $input, array $extraParams = []): ValidationException
|
|
{
|
|
$reflection = new ReflectionObject($validatable);
|
|
|
|
$params = ['input' => $input] + $extraParams + $validatable->getParams();
|
|
$id = lcfirst($reflection->getShortName());
|
|
if ($validatable->getName() !== null) {
|
|
$id = $params['name'] = $validatable->getName();
|
|
}
|
|
$standardTemplate = $reflection->getMethod('getStandardTemplate');
|
|
$template = $validatable->getTemplate() ?? $standardTemplate->invoke($validatable, $input);
|
|
$templates = $this->templateCollector->extract($validatable);
|
|
$formatter = new TemplateRenderer($this->translator, $this->processor);
|
|
|
|
$attributes = $reflection->getAttributes(ExceptionClass::class);
|
|
if (count($attributes) === 0) {
|
|
return new ValidationException($input, $id, $params, $template, $templates, $formatter);
|
|
}
|
|
|
|
/** @var ValidationException $exception */
|
|
$exception = $this
|
|
->createReflectionClass($attributes[0]->newInstance()->class, ValidationException::class)
|
|
->newInstance($input, $id, $params, $template, $templates, $formatter);
|
|
|
|
return $exception;
|
|
}
|
|
|
|
public static function setDefaultInstance(self $defaultInstance): void
|
|
{
|
|
self::$defaultInstance = $defaultInstance;
|
|
}
|
|
|
|
/**
|
|
* @param class-string $name
|
|
* @param class-string $parentName
|
|
*
|
|
* @return ReflectionClass<ValidationException|Validatable|object>
|
|
*/
|
|
private function createReflectionClass(string $name, string $parentName): ReflectionClass
|
|
{
|
|
$reflection = new ReflectionClass($name);
|
|
if (!$reflection->isSubclassOf($parentName) && $parentName !== $name) {
|
|
throw new InvalidClassException(sprintf('"%s" must be an instance of "%s"', $name, $parentName));
|
|
}
|
|
|
|
if (!$reflection->isInstantiable()) {
|
|
throw new InvalidClassException(sprintf('"%s" must be instantiable', $name));
|
|
}
|
|
|
|
return $reflection;
|
|
}
|
|
}
|