| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- <?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\Exceptions\ComponentException;
- use Respect\Validation\Exceptions\InvalidClassException;
- use Respect\Validation\Exceptions\ValidationException;
- use Respect\Validation\Message\Formatter;
- use Respect\Validation\Message\ParameterStringifier;
- use Respect\Validation\Message\Stringifier\KeepOriginalStringName;
- use function array_merge;
- use function lcfirst;
- use function sprintf;
- use function str_replace;
- use function trim;
- use function ucfirst;
- /**
- * Factory of objects.
- *
- * @author Augusto Pascutti <augusto@phpsp.org.br>
- * @author Henrique Moody <henriquemoody@gmail.com>
- */
- final class Factory
- {
- /**
- * @var string[]
- */
- private $rulesNamespaces = ['Respect\\Validation\\Rules'];
- /**
- * @var string[]
- */
- private $exceptionsNamespaces = ['Respect\\Validation\\Exceptions'];
- /**
- * @var callable
- */
- private $translator = 'strval';
- /**
- * @var ParameterStringifier
- */
- private $parameterStringifier;
- /**
- * Default instance of the Factory.
- *
- * @var Factory
- */
- private static $defaultInstance;
- public function __construct()
- {
- $this->parameterStringifier = new KeepOriginalStringName();
- }
- /**
- * Returns the default instance of the Factory.
- */
- public static function getDefaultInstance(): self
- {
- if (self::$defaultInstance === null) {
- self::$defaultInstance = new self();
- }
- return self::$defaultInstance;
- }
- public function withRuleNamespace(string $rulesNamespace): self
- {
- $clone = clone $this;
- $clone->rulesNamespaces[] = trim($rulesNamespace, '\\');
- return $clone;
- }
- public function withExceptionNamespace(string $exceptionsNamespace): self
- {
- $clone = clone $this;
- $clone->exceptionsNamespaces[] = trim($exceptionsNamespace, '\\');
- return $clone;
- }
- public function withTranslator(callable $translator): self
- {
- $clone = clone $this;
- $clone->translator = $translator;
- return $clone;
- }
- public function withParameterStringifier(ParameterStringifier $parameterStringifier): self
- {
- $clone = clone $this;
- $clone->parameterStringifier = $parameterStringifier;
- return $clone;
- }
- /**
- * Creates a rule.
- *
- * @param mixed[] $arguments
- *
- * @throws ComponentException
- */
- 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));
- }
- /**
- * Creates an exception.
- *
- * @param mixed $input
- * @param mixed[] $extraParams
- *
- * @throws ComponentException
- */
- public function exception(Validatable $validatable, $input, array $extraParams = []): ValidationException
- {
- $formatter = new Formatter($this->translator, $this->parameterStringifier);
- $reflection = new ReflectionObject($validatable);
- $ruleName = $reflection->getShortName();
- $params = ['input' => $input] + $extraParams + $this->extractPropertiesValues($validatable, $reflection);
- $id = lcfirst($ruleName);
- if ($validatable->getName() !== null) {
- /*$id = */$params['name'] = $validatable->getName();
- }
- $exceptionNamespace = str_replace('\\Rules', '\\Exceptions', $reflection->getNamespaceName());
- foreach (array_merge([$exceptionNamespace], $this->exceptionsNamespaces) as $namespace) {
- try {
- /** @var class-string<ValidationException> $exceptionName */
- $exceptionName = $namespace . '\\' . $ruleName . 'Exception';
- return $this->createValidationException(
- $exceptionName,
- $id,
- $input,
- $params,
- $formatter
- );
- } catch (ReflectionException $exception) {
- continue;
- }
- }
- return new ValidationException($input, $id, $params, $formatter);
- }
- /**
- * Define the default instance of the Factory.
- */
- public static function setDefaultInstance(self $defaultInstance): void
- {
- self::$defaultInstance = $defaultInstance;
- }
- /**
- * Creates a reflection based on class name.
- *
- * @param class-string $name
- * @param class-string $parentName
- *
- * @throws InvalidClassException
- * @throws ReflectionException
- *
- * @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;
- }
- /**
- * Creates a Validation exception.
- *
- * @param class-string<ValidationException> $exceptionName
- *
- * @param mixed $input
- * @param mixed[] $params
- *
- * @throws InvalidClassException
- * @throws ReflectionException
- */
- private function createValidationException(
- string $exceptionName,
- string $id,
- $input,
- array $params,
- Formatter $formatter
- ): ValidationException {
- /** @var ValidationException $exception */
- $exception = $this
- ->createReflectionClass($exceptionName, ValidationException::class)
- ->newInstance($input, $id, $params, $formatter);
- if (isset($params['template'])) {
- $exception->updateTemplate($params['template']);
- }
- return $exception;
- }
- /**
- * @param ReflectionObject|ReflectionClass<Validatable> $reflection
- * @return mixed[]
- */
- private function extractPropertiesValues(Validatable $validatable, ReflectionClass $reflection): array
- {
- $values = [];
- foreach ($reflection->getProperties() as $property) {
- $property->setAccessible(true);
- $propertyValue = $property->getValue($validatable);
- if ($propertyValue === null) {
- continue;
- }
- $values[$property->getName()] = $propertyValue;
- }
- $parentReflection = $reflection->getParentClass();
- if ($parentReflection !== false) {
- return $values + $this->extractPropertiesValues($validatable, $parentReflection);
- }
- return $values;
- }
- }
|