mirror of
https://github.com/Respect/Validation.git
synced 2026-03-14 14:25:45 +01:00
Extract CodeGen namespace from LintMixinCommand
Replace hardcoded validator class lists with a declarative #[Mixin] attribute and extract the mixin generation logic into a reusable CodeGen namespace under src-dev/CodeGen/. The new MixinGenerator discovers prefix definitions and filtering rules by scanning #[Mixin] attributes on the target namespace's classes, removing the need for hardcoded configuration. It supports configurable interface types (Builder for __callStatic, Chain for __call) with custom suffixes, return types, and root extends. This is the first step toward extracting the code generation into a standalone package that can map __call/__callStatic to any namespace, possibly for Respect/StringFormatter and any kind of project in the future.
This commit is contained in:
parent
d1d2d186f8
commit
dd4b9acd43
44 changed files with 671 additions and 334 deletions
30
src-dev/CodeGen/Attributes/Mixin.php
Normal file
30
src-dev/CodeGen/Attributes/Mixin.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
||||
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Dev\CodeGen\Attributes;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
final readonly class Mixin
|
||||
{
|
||||
/**
|
||||
* @param array<string> $exclude
|
||||
* @param array<string> $include
|
||||
*/
|
||||
public function __construct(
|
||||
public string|null $prefix = null,
|
||||
public bool $prefixParameter = false,
|
||||
public bool $requireInclusion = false,
|
||||
public array $exclude = [],
|
||||
public array $include = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
28
src-dev/CodeGen/InterfaceConfig.php
Normal file
28
src-dev/CodeGen/InterfaceConfig.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
||||
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Dev\CodeGen;
|
||||
|
||||
final readonly class InterfaceConfig
|
||||
{
|
||||
/**
|
||||
* @param array<string> $rootExtends
|
||||
* @param array<string> $rootUses
|
||||
*/
|
||||
public function __construct(
|
||||
public string $suffix,
|
||||
public string $returnType,
|
||||
public bool $static = false,
|
||||
public array $rootExtends = [],
|
||||
public string|null $rootComment = null,
|
||||
public array $rootUses = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
169
src-dev/CodeGen/MethodBuilder.php
Normal file
169
src-dev/CodeGen/MethodBuilder.php
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
||||
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Dev\CodeGen;
|
||||
|
||||
use Nette\PhpGenerator\Method;
|
||||
use Nette\PhpGenerator\PhpNamespace;
|
||||
use ReflectionClass;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionUnionType;
|
||||
|
||||
use function count;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_object;
|
||||
use function lcfirst;
|
||||
use function preg_replace;
|
||||
use function sort;
|
||||
use function str_starts_with;
|
||||
use function ucfirst;
|
||||
|
||||
final class MethodBuilder
|
||||
{
|
||||
/**
|
||||
* @param array<string> $excludedTypePrefixes
|
||||
* @param array<string> $excludedTypeNames
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $excludedTypePrefixes = [],
|
||||
private readonly array $excludedTypeNames = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function build(
|
||||
PhpNamespace $namespace,
|
||||
ReflectionClass $validatorReflection,
|
||||
string $returnType,
|
||||
string|null $prefix = null,
|
||||
bool $static = false,
|
||||
ReflectionParameter|null $prefixParameter = null,
|
||||
): Method {
|
||||
$originalName = $validatorReflection->getShortName();
|
||||
$name = $prefix ? $prefix . ucfirst($originalName) : lcfirst($originalName);
|
||||
|
||||
$method = new Method($name);
|
||||
$method->setPublic()->setReturnType($returnType);
|
||||
|
||||
if ($static) {
|
||||
$method->setStatic();
|
||||
}
|
||||
|
||||
if ($prefixParameter !== null) {
|
||||
$this->addPrefixParameter($method, $prefixParameter);
|
||||
}
|
||||
|
||||
$constructor = $validatorReflection->getConstructor();
|
||||
if ($constructor === null) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
$comment = $constructor->getDocComment();
|
||||
if ($comment) {
|
||||
$method->addComment(preg_replace('@(/\*\* *| +\* +| +\*/)@', '', $comment));
|
||||
}
|
||||
|
||||
foreach ($constructor->getParameters() as $reflectionParameter) {
|
||||
$this->addParameter($method, $reflectionParameter, $namespace);
|
||||
}
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
private function addPrefixParameter(Method $method, ReflectionParameter $reflectionParameter): void
|
||||
{
|
||||
$type = $reflectionParameter->getType();
|
||||
$types = [];
|
||||
|
||||
if ($type instanceof ReflectionUnionType) {
|
||||
foreach ($type->getTypes() as $subType) {
|
||||
$types[] = $subType->getName();
|
||||
}
|
||||
|
||||
sort($types);
|
||||
} elseif ($type instanceof ReflectionNamedType) {
|
||||
$types[] = $type->getName();
|
||||
}
|
||||
|
||||
$method->addParameter($reflectionParameter->getName())->setType(implode('|', $types));
|
||||
}
|
||||
|
||||
private function addParameter(
|
||||
Method $method,
|
||||
ReflectionParameter $reflectionParameter,
|
||||
PhpNamespace $namespace,
|
||||
): void {
|
||||
if ($reflectionParameter->isVariadic()) {
|
||||
$method->setVariadic();
|
||||
}
|
||||
|
||||
$type = $reflectionParameter->getType();
|
||||
$types = [];
|
||||
|
||||
if ($type instanceof ReflectionUnionType) {
|
||||
foreach ($type->getTypes() as $subType) {
|
||||
$types[] = $subType->getName();
|
||||
if ($subType->isBuiltin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$namespace->addUse($subType->getName());
|
||||
}
|
||||
} elseif ($type instanceof ReflectionNamedType) {
|
||||
$types[] = $type->getName();
|
||||
|
||||
if ($this->isExcludedType($type->getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$type->isBuiltin()) {
|
||||
$namespace->addUse($type->getName());
|
||||
}
|
||||
}
|
||||
|
||||
$parameter = $method->addParameter($reflectionParameter->getName());
|
||||
$parameter->setType(implode('|', $types));
|
||||
|
||||
if (!$reflectionParameter->isDefaultValueAvailable()) {
|
||||
$parameter->setNullable($reflectionParameter->isOptional());
|
||||
}
|
||||
|
||||
if (count($types) > 1 || $reflectionParameter->isVariadic()) {
|
||||
$parameter->setNullable(false);
|
||||
}
|
||||
|
||||
if (!$reflectionParameter->isDefaultValueAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultValue = $reflectionParameter->getDefaultValue();
|
||||
if (is_object($defaultValue)) {
|
||||
$parameter->setDefaultValue(null);
|
||||
$parameter->setNullable(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$parameter->setDefaultValue($defaultValue);
|
||||
$parameter->setNullable(false);
|
||||
}
|
||||
|
||||
private function isExcludedType(string $typeName): bool
|
||||
{
|
||||
foreach ($this->excludedTypePrefixes as $excludedPrefix) {
|
||||
if (str_starts_with($typeName, $excludedPrefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return in_array($typeName, $this->excludedTypeNames, true);
|
||||
}
|
||||
}
|
||||
279
src-dev/CodeGen/MixinGenerator.php
Normal file
279
src-dev/CodeGen/MixinGenerator.php
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
||||
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Dev\CodeGen;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Nette\PhpGenerator\PhpNamespace;
|
||||
use Nette\PhpGenerator\Printer;
|
||||
use ReflectionClass;
|
||||
use ReflectionParameter;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
|
||||
use function file_get_contents;
|
||||
use function in_array;
|
||||
use function is_file;
|
||||
use function is_readable;
|
||||
use function ksort;
|
||||
use function sprintf;
|
||||
|
||||
final class MixinGenerator
|
||||
{
|
||||
/** @param array<InterfaceConfig> $interfaces */
|
||||
public function __construct(
|
||||
private readonly string $sourceDir,
|
||||
private readonly string $sourceNamespace,
|
||||
private readonly string $outputDir,
|
||||
private readonly string $outputNamespace,
|
||||
private readonly array $interfaces,
|
||||
private readonly MethodBuilder $methodBuilder = new MethodBuilder(),
|
||||
private readonly OutputFormatter $outputFormatter = new OutputFormatter(),
|
||||
) {
|
||||
}
|
||||
|
||||
/** @return array<string, string> filename => content */
|
||||
public function generate(): array
|
||||
{
|
||||
$validators = $this->scanValidators();
|
||||
$prefixes = $this->discoverPrefixes($validators);
|
||||
$filters = $this->discoverFilters($validators);
|
||||
|
||||
$files = [];
|
||||
|
||||
foreach ($this->interfaces as $interfaceConfig) {
|
||||
$prefixInterfaceNames = [];
|
||||
|
||||
foreach ($prefixes as $prefix) {
|
||||
$interfaceName = $prefix['name'] . $interfaceConfig->suffix;
|
||||
$prefixInterfaceNames[] = $this->outputNamespace . '\\' . $interfaceName;
|
||||
|
||||
$this->generateInterface(
|
||||
$interfaceName,
|
||||
$interfaceConfig,
|
||||
$validators,
|
||||
$filters,
|
||||
$prefix,
|
||||
$files,
|
||||
);
|
||||
}
|
||||
|
||||
$this->generateRootInterface(
|
||||
$interfaceConfig,
|
||||
$prefixInterfaceNames,
|
||||
$validators,
|
||||
$filters,
|
||||
$files,
|
||||
);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/** @return array<string, ReflectionClass> */
|
||||
private function scanValidators(): array
|
||||
{
|
||||
$validators = [];
|
||||
|
||||
foreach (new DirectoryIterator($this->sourceDir) as $file) {
|
||||
if (!$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$className = $this->sourceNamespace . '\\' . $file->getBasename('.php');
|
||||
$reflection = new ReflectionClass($className);
|
||||
|
||||
if ($reflection->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$validators[$reflection->getShortName()] = $reflection;
|
||||
}
|
||||
|
||||
ksort($validators);
|
||||
|
||||
return $validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, ReflectionClass> $validators
|
||||
*
|
||||
* @return array<array{name: string, prefix: string, requireInclusion: bool, prefixParameter: ?ReflectionParameter}>
|
||||
*/
|
||||
private function discoverPrefixes(array $validators): array
|
||||
{
|
||||
$prefixes = [];
|
||||
|
||||
foreach ($validators as $reflection) {
|
||||
$attributes = $reflection->getAttributes(Mixin::class);
|
||||
if ($attributes === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mixin = $attributes[0]->newInstance();
|
||||
if ($mixin->prefix === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefixParameter = null;
|
||||
if ($mixin->prefixParameter) {
|
||||
$constructor = $reflection->getConstructor();
|
||||
if ($constructor !== null) {
|
||||
$params = $constructor->getParameters();
|
||||
if ($params !== []) {
|
||||
$prefixParameter = $params[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$prefixes[$mixin->prefix] = [
|
||||
'name' => $reflection->getShortName(),
|
||||
'prefix' => $mixin->prefix,
|
||||
'requireInclusion' => $mixin->requireInclusion,
|
||||
'prefixParameter' => $prefixParameter,
|
||||
];
|
||||
}
|
||||
|
||||
ksort($prefixes);
|
||||
|
||||
return $prefixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, ReflectionClass> $validators
|
||||
*
|
||||
* @return array<string, Mixin>
|
||||
*/
|
||||
private function discoverFilters(array $validators): array
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
foreach ($validators as $name => $reflection) {
|
||||
$attributes = $reflection->getAttributes(Mixin::class);
|
||||
if ($attributes === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filters[$name] = $attributes[0]->newInstance();
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, ReflectionClass> $validators
|
||||
* @param array<string, Mixin> $filters
|
||||
* @param array{name: string, prefix: string, requireInclusion: bool, prefixParameter: ?ReflectionParameter} $prefix
|
||||
* @param array<string, string> $files
|
||||
*/
|
||||
private function generateInterface(
|
||||
string $interfaceName,
|
||||
InterfaceConfig $config,
|
||||
array $validators,
|
||||
array $filters,
|
||||
array $prefix,
|
||||
array &$files,
|
||||
): void {
|
||||
$namespace = new PhpNamespace($this->outputNamespace);
|
||||
$interface = $namespace->addInterface($interfaceName);
|
||||
|
||||
foreach ($validators as $name => $reflection) {
|
||||
$mixin = $filters[$name] ?? null;
|
||||
|
||||
if ($prefix['requireInclusion']) {
|
||||
if ($mixin === null || !in_array($prefix['prefix'], $mixin->include, true)) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($mixin !== null && in_array($prefix['prefix'], $mixin->exclude, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$method = $this->methodBuilder->build(
|
||||
$namespace,
|
||||
$reflection,
|
||||
$config->returnType,
|
||||
$prefix['prefix'],
|
||||
$config->static,
|
||||
$prefix['prefixParameter'],
|
||||
);
|
||||
|
||||
$interface->addMember($method);
|
||||
}
|
||||
|
||||
$this->addFile($interfaceName, $namespace, $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $prefixInterfaceNames
|
||||
* @param array<string, ReflectionClass> $validators
|
||||
* @param array<string, Mixin> $filters
|
||||
* @param array<string, string> $files
|
||||
*/
|
||||
private function generateRootInterface(
|
||||
InterfaceConfig $config,
|
||||
array $prefixInterfaceNames,
|
||||
array $validators,
|
||||
array $filters,
|
||||
array &$files,
|
||||
): void {
|
||||
$interfaceName = $config->suffix;
|
||||
$namespace = new PhpNamespace($this->outputNamespace);
|
||||
$interface = $namespace->addInterface($interfaceName);
|
||||
|
||||
foreach ($config->rootExtends as $extend) {
|
||||
$interface->addExtend($extend);
|
||||
}
|
||||
|
||||
foreach ($prefixInterfaceNames as $prefixInterface) {
|
||||
$interface->addExtend($prefixInterface);
|
||||
}
|
||||
|
||||
if ($config->rootComment !== null) {
|
||||
$interface->addComment($config->rootComment);
|
||||
}
|
||||
|
||||
foreach ($config->rootUses as $use) {
|
||||
$namespace->addUse($use);
|
||||
}
|
||||
|
||||
foreach ($validators as $reflection) {
|
||||
$method = $this->methodBuilder->build(
|
||||
$namespace,
|
||||
$reflection,
|
||||
$config->returnType,
|
||||
null,
|
||||
$config->static,
|
||||
);
|
||||
|
||||
$interface->addMember($method);
|
||||
}
|
||||
|
||||
$this->addFile($interfaceName, $namespace, $files);
|
||||
}
|
||||
|
||||
/** @param array<string, string> $files */
|
||||
private function addFile(string $interfaceName, PhpNamespace $namespace, array &$files): void
|
||||
{
|
||||
$printer = new Printer();
|
||||
$printer->wrapLength = 300;
|
||||
|
||||
$filename = sprintf('%s/%s.php', $this->outputDir, $interfaceName);
|
||||
$existingContent = '';
|
||||
if (is_file($filename) && is_readable($filename)) {
|
||||
$existingContent = file_get_contents($filename) ?: '';
|
||||
}
|
||||
|
||||
$formattedContent = $this->outputFormatter->format(
|
||||
$printer->printNamespace($namespace),
|
||||
$existingContent,
|
||||
);
|
||||
|
||||
$files[$filename] = $formattedContent;
|
||||
}
|
||||
}
|
||||
47
src-dev/CodeGen/OutputFormatter.php
Normal file
47
src-dev/CodeGen/OutputFormatter.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
||||
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Respect\Dev\CodeGen;
|
||||
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function trim;
|
||||
|
||||
use const PHP_EOL;
|
||||
|
||||
final class OutputFormatter
|
||||
{
|
||||
public function format(string $content, string $existingContent): string
|
||||
{
|
||||
preg_match('/^<\?php\s*\/\*[\s\S]*?\*\//', $existingContent, $matches);
|
||||
$existingHeader = $matches[0] ?? '';
|
||||
|
||||
$replacements = [
|
||||
'/\n\n\t(public|\/\*\*)/m' => PHP_EOL . ' $1',
|
||||
'/\t/m' => ' ',
|
||||
'/\?([a-zA-Z]+) \$/' => '$1|null $',
|
||||
'/\/\*\*\n +\* (.+)\n +\*\//m' => '/** $1 */',
|
||||
];
|
||||
|
||||
return implode(PHP_EOL, [
|
||||
trim($existingHeader) . PHP_EOL,
|
||||
'declare(strict_types=1);',
|
||||
'',
|
||||
preg_replace(
|
||||
array_keys($replacements),
|
||||
array_values($replacements),
|
||||
$content,
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,36 +11,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Dev\Commands;
|
||||
|
||||
use DirectoryIterator;
|
||||
use Nette\PhpGenerator\InterfaceType;
|
||||
use Nette\PhpGenerator\Method;
|
||||
use Nette\PhpGenerator\PhpNamespace;
|
||||
use Nette\PhpGenerator\Printer;
|
||||
use ReflectionClass;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionUnionType;
|
||||
use Respect\Dev\CodeGen\InterfaceConfig;
|
||||
use Respect\Dev\CodeGen\MethodBuilder;
|
||||
use Respect\Dev\CodeGen\MixinGenerator;
|
||||
use Respect\Dev\Differ\ConsoleDiffer;
|
||||
use Respect\Dev\Differ\Item;
|
||||
use Respect\Validation\Mixins\AllBuilder;
|
||||
use Respect\Validation\Mixins\AllChain;
|
||||
use Respect\Validation\Mixins\Chain;
|
||||
use Respect\Validation\Mixins\KeyBuilder;
|
||||
use Respect\Validation\Mixins\KeyChain;
|
||||
use Respect\Validation\Mixins\LengthBuilder;
|
||||
use Respect\Validation\Mixins\LengthChain;
|
||||
use Respect\Validation\Mixins\MaxBuilder;
|
||||
use Respect\Validation\Mixins\MaxChain;
|
||||
use Respect\Validation\Mixins\MinBuilder;
|
||||
use Respect\Validation\Mixins\MinChain;
|
||||
use Respect\Validation\Mixins\NotBuilder;
|
||||
use Respect\Validation\Mixins\NotChain;
|
||||
use Respect\Validation\Mixins\NullOrBuilder;
|
||||
use Respect\Validation\Mixins\NullOrChain;
|
||||
use Respect\Validation\Mixins\PropertyBuilder;
|
||||
use Respect\Validation\Mixins\PropertyChain;
|
||||
use Respect\Validation\Mixins\UndefOrBuilder;
|
||||
use Respect\Validation\Mixins\UndefOrChain;
|
||||
use Respect\Validation\Validator;
|
||||
use Respect\Validation\ValidatorBuilder;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
|
@ -48,27 +24,13 @@ use Symfony\Component\Console\Command\Command;
|
|||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function array_keys;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_object;
|
||||
use function ksort;
|
||||
use function lcfirst;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function is_file;
|
||||
use function is_readable;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_starts_with;
|
||||
use function trim;
|
||||
use function ucfirst;
|
||||
|
||||
use const PHP_EOL;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'lint:mixin',
|
||||
|
|
@ -76,46 +38,6 @@ use const PHP_EOL;
|
|||
)]
|
||||
final class LintMixinCommand extends Command
|
||||
{
|
||||
private const array NUMBER_RELATED_VALIDATORS = [
|
||||
'Between',
|
||||
'BetweenExclusive',
|
||||
'Equals',
|
||||
'Equivalent',
|
||||
'Even',
|
||||
'Factor',
|
||||
'Fibonacci',
|
||||
'Finite',
|
||||
'GreaterThan',
|
||||
'Identical',
|
||||
'In',
|
||||
'Infinite',
|
||||
'LessThan',
|
||||
'LessThanOrEqual',
|
||||
'GreaterThanOrEqual',
|
||||
'Multiple',
|
||||
'Odd',
|
||||
'PerfectSquare',
|
||||
'Positive',
|
||||
'PrimeNumber',
|
||||
];
|
||||
|
||||
private const array STRUCTURE_RELATED_VALIDATORS = [
|
||||
'Exists',
|
||||
'Key',
|
||||
'KeyExists',
|
||||
'KeyOptional',
|
||||
'KeySet',
|
||||
'NullOr',
|
||||
'UndefOr',
|
||||
'Property',
|
||||
'PropertyExists',
|
||||
'PropertyOptional',
|
||||
'Attributes',
|
||||
'Formatted',
|
||||
'Templated',
|
||||
'Named',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly ConsoleDiffer $differ,
|
||||
) {
|
||||
|
|
@ -134,102 +56,51 @@ final class LintMixinCommand extends Command
|
|||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// Scan validators directory
|
||||
$srcDir = dirname(__DIR__, 2) . '/src';
|
||||
$validatorsDir = $srcDir . '/Validators';
|
||||
$validators = $this->scanValidators($validatorsDir);
|
||||
|
||||
// Define mixins
|
||||
$mixins = [
|
||||
['All', 'all', [], array_merge(['All'], self::STRUCTURE_RELATED_VALIDATORS)],
|
||||
['Key', 'key', [], self::STRUCTURE_RELATED_VALIDATORS],
|
||||
['Length', 'length', self::NUMBER_RELATED_VALIDATORS, []],
|
||||
['Max', 'max', self::NUMBER_RELATED_VALIDATORS, []],
|
||||
['Min', 'min', self::NUMBER_RELATED_VALIDATORS, []],
|
||||
['Not', 'not', [], ['Not', 'NullOr', 'UndefOr', 'Attributes', 'Templated', 'Named']],
|
||||
['NullOr', 'nullOr', [], ['NullOr', 'Blank', 'Undef', 'UndefOr', 'Templated', 'Named']],
|
||||
['Property', 'property', [], self::STRUCTURE_RELATED_VALIDATORS],
|
||||
['UndefOr', 'undefOr', [], ['NullOr', 'Blank', 'Undef', 'UndefOr', 'Attributes', 'Templated', 'Named']],
|
||||
[null, null, [], []],
|
||||
];
|
||||
$generator = new MixinGenerator(
|
||||
sourceDir: $srcDir . '/Validators',
|
||||
sourceNamespace: 'Respect\\Validation\\Validators',
|
||||
outputDir: $srcDir . '/Mixins',
|
||||
outputNamespace: 'Respect\\Validation\\Mixins',
|
||||
methodBuilder: new MethodBuilder(
|
||||
excludedTypePrefixes: ['Sokil', 'Egulias'],
|
||||
excludedTypeNames: ['finfo'],
|
||||
),
|
||||
interfaces: [
|
||||
new InterfaceConfig(
|
||||
suffix: 'Builder',
|
||||
returnType: Chain::class,
|
||||
static: true,
|
||||
),
|
||||
new InterfaceConfig(
|
||||
suffix: 'Chain',
|
||||
returnType: Chain::class,
|
||||
rootExtends: [Validator::class],
|
||||
rootComment: '@mixin ValidatorBuilder',
|
||||
rootUses: [ValidatorBuilder::class],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
$files = $generator->generate();
|
||||
|
||||
$updatableFiles = [];
|
||||
|
||||
foreach ($mixins as [$name, $prefix, $allowList, $denyList]) {
|
||||
$chainedNamespace = new PhpNamespace('Respect\\Validation\\Mixins');
|
||||
$chainedInterface = $chainedNamespace->addInterface($name . 'Chain');
|
||||
|
||||
$staticNamespace = new PhpNamespace('Respect\\Validation\\Mixins');
|
||||
$staticInterface = $staticNamespace->addInterface($name . 'Builder');
|
||||
|
||||
if ($name === null) {
|
||||
$chainedInterface->addExtend(Validator::class);
|
||||
$chainedInterface->addExtend(AllChain::class);
|
||||
$chainedInterface->addExtend(KeyChain::class);
|
||||
$chainedInterface->addExtend(LengthChain::class);
|
||||
$chainedInterface->addExtend(MaxChain::class);
|
||||
$chainedInterface->addExtend(MinChain::class);
|
||||
$chainedInterface->addExtend(NotChain::class);
|
||||
$chainedInterface->addExtend(NullOrChain::class);
|
||||
$chainedInterface->addExtend(PropertyChain::class);
|
||||
$chainedInterface->addExtend(UndefOrChain::class);
|
||||
$chainedInterface->addComment('@mixin ValidatorBuilder');
|
||||
$chainedNamespace->addUse(ValidatorBuilder::class);
|
||||
|
||||
$staticInterface->addExtend(AllBuilder::class);
|
||||
$staticInterface->addExtend(KeyBuilder::class);
|
||||
$staticInterface->addExtend(LengthBuilder::class);
|
||||
$staticInterface->addExtend(MaxBuilder::class);
|
||||
$staticInterface->addExtend(MinBuilder::class);
|
||||
$staticInterface->addExtend(NotBuilder::class);
|
||||
$staticInterface->addExtend(NullOrBuilder::class);
|
||||
$staticInterface->addExtend(PropertyBuilder::class);
|
||||
$staticInterface->addExtend(UndefOrBuilder::class);
|
||||
foreach ($files as $filename => $content) {
|
||||
$existingContent = '';
|
||||
if (is_file($filename) && is_readable($filename)) {
|
||||
$existingContent = file_get_contents($filename) ?: '';
|
||||
}
|
||||
|
||||
foreach ($validators as $originalName => $reflection) {
|
||||
$this->addMethodToInterface(
|
||||
$staticNamespace,
|
||||
$originalName,
|
||||
$staticInterface,
|
||||
$reflection,
|
||||
$prefix,
|
||||
$allowList,
|
||||
$denyList,
|
||||
);
|
||||
$this->addMethodToInterface(
|
||||
$chainedNamespace,
|
||||
$originalName,
|
||||
$chainedInterface,
|
||||
$reflection,
|
||||
$prefix,
|
||||
$allowList,
|
||||
$denyList,
|
||||
);
|
||||
if ($content === $existingContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$printer = new Printer();
|
||||
$printer->wrapLength = 300;
|
||||
|
||||
foreach (
|
||||
[
|
||||
[$staticNamespace, $staticInterface],
|
||||
[$chainedNamespace, $chainedInterface],
|
||||
] as [$namespace, $interface]
|
||||
) {
|
||||
$filename = sprintf('%s/Mixins/%s.php', $srcDir, $interface->getName());
|
||||
$existingContent = file_get_contents($filename);
|
||||
$formattedContent = $this->getFormattedContent($printer->printNamespace($namespace), $existingContent);
|
||||
if ($formattedContent === $existingContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$updatableFiles[$filename] = $formattedContent;
|
||||
$output->writeln($this->differ->diff(
|
||||
new Item($filename, $existingContent),
|
||||
new Item($filename, $formattedContent),
|
||||
));
|
||||
}
|
||||
$updatableFiles[$filename] = $content;
|
||||
$output->writeln($this->differ->diff(
|
||||
new Item($filename, $existingContent),
|
||||
new Item($filename, $content),
|
||||
));
|
||||
}
|
||||
|
||||
if ($updatableFiles === []) {
|
||||
|
|
@ -248,167 +119,4 @@ final class LintMixinCommand extends Command
|
|||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/** @return array<string, ReflectionClass> */
|
||||
private function scanValidators(string $directory): array
|
||||
{
|
||||
$names = [];
|
||||
|
||||
foreach (new DirectoryIterator($directory) as $file) {
|
||||
if (!$file->isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$className = 'Respect\\Validation\\Validators\\' . $file->getBasename('.php');
|
||||
$reflection = new ReflectionClass($className);
|
||||
|
||||
if ($reflection->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$names[$reflection->getShortName()] = $reflection;
|
||||
}
|
||||
|
||||
ksort($names);
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $allowList
|
||||
* @param array<string> $denyList
|
||||
*/
|
||||
private function addMethodToInterface(
|
||||
PhpNamespace $namespace,
|
||||
string $originalName,
|
||||
InterfaceType $interfaceType,
|
||||
ReflectionClass $reflection,
|
||||
string|null $prefix,
|
||||
array $allowList,
|
||||
array $denyList,
|
||||
): void {
|
||||
if ($allowList !== [] && !in_array($reflection->getShortName(), $allowList, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($denyList !== [] && in_array($reflection->getShortName(), $denyList, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $prefix ? $prefix . ucfirst($originalName) : lcfirst($originalName);
|
||||
$method = $interfaceType->addMethod($name)->setPublic()->setReturnType(Chain::class);
|
||||
|
||||
if (str_contains($interfaceType->getName(), 'Builder')) {
|
||||
$method->setStatic();
|
||||
}
|
||||
|
||||
if ($prefix === 'key') {
|
||||
$method->addParameter('key')->setType('int|string');
|
||||
}
|
||||
|
||||
if ($prefix === 'property') {
|
||||
$method->addParameter('propertyName')->setType('string');
|
||||
}
|
||||
|
||||
$reflectionConstructor = $reflection->getConstructor();
|
||||
if ($reflectionConstructor === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$comment = $reflectionConstructor->getDocComment();
|
||||
if ($comment) {
|
||||
$method->addComment(preg_replace('@(/\*\* *| +\* +| +\*/)@', '', $comment));
|
||||
}
|
||||
|
||||
foreach ($reflectionConstructor->getParameters() as $reflectionParameter) {
|
||||
$this->addParameterToMethod($method, $reflectionParameter, $namespace);
|
||||
}
|
||||
}
|
||||
|
||||
private function addParameterToMethod(
|
||||
Method $method,
|
||||
ReflectionParameter $reflectionParameter,
|
||||
PhpNamespace $namespace,
|
||||
): void {
|
||||
if ($reflectionParameter->isVariadic()) {
|
||||
$method->setVariadic();
|
||||
}
|
||||
|
||||
$type = $reflectionParameter->getType();
|
||||
$types = [];
|
||||
|
||||
if ($type instanceof ReflectionUnionType) {
|
||||
foreach ($type->getTypes() as $subType) {
|
||||
$types[] = $subType->getName();
|
||||
if ($subType->isBuiltin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$namespace->addUse($subType->getName());
|
||||
}
|
||||
} elseif ($type instanceof ReflectionNamedType) {
|
||||
$types[] = $type->getName();
|
||||
if (
|
||||
str_starts_with($type->getName(), 'Sokil')
|
||||
|| str_starts_with($type->getName(), 'Egulias')
|
||||
|| $type->getName() === 'finfo'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$type->isBuiltin()) {
|
||||
$namespace->addUse($type->getName());
|
||||
}
|
||||
}
|
||||
|
||||
$parameter = $method->addParameter($reflectionParameter->getName());
|
||||
$parameter->setType(implode('|', $types));
|
||||
|
||||
if (!$reflectionParameter->isDefaultValueAvailable()) {
|
||||
$parameter->setNullable($reflectionParameter->isOptional());
|
||||
}
|
||||
|
||||
if (count($types) > 1 || $reflectionParameter->isVariadic()) {
|
||||
$parameter->setNullable(false);
|
||||
}
|
||||
|
||||
if (!$reflectionParameter->isDefaultValueAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultValue = $reflectionParameter->getDefaultValue();
|
||||
if (is_object($defaultValue)) {
|
||||
$parameter->setDefaultValue(null);
|
||||
$parameter->setNullable(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$parameter->setDefaultValue($defaultValue);
|
||||
$parameter->setNullable(false);
|
||||
}
|
||||
|
||||
private function getFormattedContent(string $content, string $existingContent): string
|
||||
{
|
||||
preg_match('/^<\?php\s*\/\*[\s\S]*?\*\//', $existingContent, $matches);
|
||||
$existingHeader = $matches[0] ?? '';
|
||||
|
||||
$replacements = [
|
||||
'/\n\n\t(public|\/\*\*)/m' => PHP_EOL . ' $1',
|
||||
'/\t/m' => ' ',
|
||||
'/\?([a-zA-Z]+) \$/' => '$1|null $',
|
||||
'/\/\*\*\n +\* (.+)\n +\*\//m' => '/** $1 */',
|
||||
];
|
||||
|
||||
return implode(PHP_EOL, [
|
||||
trim($existingHeader) . PHP_EOL,
|
||||
'declare(strict_types=1);',
|
||||
'',
|
||||
preg_replace(
|
||||
array_keys($replacements),
|
||||
array_values($replacements),
|
||||
$content,
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Path;
|
||||
|
|
@ -22,6 +23,7 @@ use Respect\Validation\Result;
|
|||
use Respect\Validation\Validators\Core\FilteredArray;
|
||||
use Respect\Validation\Validators\Core\ShortCircuitable;
|
||||
|
||||
#[Mixin(prefix: 'all', exclude: ['all'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template('Every item in', 'Every item in')]
|
||||
final class All extends FilteredArray implements ShortCircuitable
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ use ReflectionAttribute;
|
|||
use ReflectionClass;
|
||||
use ReflectionObject;
|
||||
use ReflectionProperty;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Id;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
use Respect\Validation\Validators\Core\Reducer;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property', 'not', 'undefOr'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final class Attributes implements Validator
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Helpers\CanCompareValues;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Envelope;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be between {{minValue}} and {{maxValue}}',
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Helpers\CanCompareValues;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Envelope;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be greater than {{minValue}} and less than {{maxValue}}',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
|
@ -26,6 +27,7 @@ use function is_numeric;
|
|||
use function is_string;
|
||||
use function trim;
|
||||
|
||||
#[Mixin(exclude: ['nullOr', 'undefOr'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be blank',
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
use function is_scalar;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be equal to {{compareTo}}',
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Comparison;
|
||||
|
||||
use function is_scalar;
|
||||
use function mb_strtoupper;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be equivalent to {{compareTo}}',
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Simple;
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ use function filter_var;
|
|||
|
||||
use const FILTER_VALIDATE_INT;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be an even number',
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Simple;
|
||||
use SplFileInfo;
|
||||
|
|
@ -22,6 +23,7 @@ use SplFileInfo;
|
|||
use function file_exists;
|
||||
use function is_string;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be an existing file',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
|
@ -23,6 +24,7 @@ use function is_int;
|
|||
use function is_numeric;
|
||||
use function preg_match;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be a factor of {{dividend|raw}}',
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Simple;
|
||||
|
||||
use function is_finite;
|
||||
use function is_numeric;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be a finite number',
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\StringFormatter\Formatter;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final readonly class Formatted implements Validator
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Comparison;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be greater than {{compareTo}}',
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Comparison;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be greater than or equal to {{compareTo}}',
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be identical to {{compareTo}}',
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
|
@ -24,6 +25,7 @@ use function in_array;
|
|||
use function is_array;
|
||||
use function mb_strpos;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be in {{haystack}}',
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Simple;
|
||||
|
||||
use function is_infinite;
|
||||
use function is_numeric;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be an infinite number',
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Path;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
use Respect\Validation\Validators\Core\KeyRelated;
|
||||
|
||||
#[Mixin(prefix: 'key', prefixParameter: true, exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final readonly class Key implements KeyRelated
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Respect\Validation\Validators;
|
|||
|
||||
use ArrayAccess;
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Path;
|
||||
use Respect\Validation\Result;
|
||||
|
|
@ -22,6 +23,7 @@ use Respect\Validation\Validators\Core\KeyRelated;
|
|||
use function array_key_exists;
|
||||
use function is_array;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be present',
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
use Respect\Validation\Validators\Core\KeyRelated;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final readonly class KeyOptional implements KeyRelated
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Exceptions\InvalidValidatorException;
|
||||
use Respect\Validation\Helpers\CanEvaluateShortCircuit;
|
||||
use Respect\Validation\Message\Template;
|
||||
|
|
@ -32,6 +33,7 @@ use function array_map;
|
|||
use function array_merge;
|
||||
use function array_slice;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} validation failed',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Respect\Validation\Validators;
|
|||
|
||||
use Attribute;
|
||||
use Countable as PhpCountable;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
|
@ -29,6 +30,7 @@ use function is_array;
|
|||
use function is_string;
|
||||
use function mb_strlen;
|
||||
|
||||
#[Mixin(prefix: 'length', requireInclusion: true)]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'The length of',
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Comparison;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be less than {{compareTo}}',
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Comparison;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be less than or equal to {{compareTo}}',
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validators\Core\FilteredArray;
|
||||
|
||||
use function max;
|
||||
|
||||
#[Mixin(prefix: 'max', requireInclusion: true)]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template('The maximum of', 'The maximum of')]
|
||||
final class Max extends FilteredArray
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validators\Core\FilteredArray;
|
||||
|
||||
use function min;
|
||||
|
||||
#[Mixin(prefix: 'min', requireInclusion: true)]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template('The minimum of', 'The minimum of')]
|
||||
final class Min extends FilteredArray
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be a multiple of {{multipleOf}}',
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Name;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
|
@ -19,6 +20,7 @@ use Respect\Validation\Validators\Core\Nameable;
|
|||
|
||||
use function is_string;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final readonly class Named implements Nameable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(prefix: 'not', exclude: ['not'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final readonly class Not implements Validator
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
use function array_map;
|
||||
|
||||
#[Mixin(prefix: 'nullOr', exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'or must be null',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Simple;
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ use function is_numeric;
|
|||
|
||||
use const FILTER_VALIDATE_INT;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be an odd number',
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Validators\Core\Simple;
|
||||
|
||||
use function is_numeric;
|
||||
|
||||
#[Mixin(include: ['length', 'max', 'min'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be a positive number',
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ namespace Respect\Validation\Validators;
|
|||
use Attribute;
|
||||
use ReflectionClass;
|
||||
use ReflectionObject;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Path;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(prefix: 'property', prefixParameter: true, exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final readonly class Property implements Validator
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Respect\Validation\Validators;
|
|||
use Attribute;
|
||||
use ReflectionClass;
|
||||
use ReflectionObject;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Path;
|
||||
use Respect\Validation\Result;
|
||||
|
|
@ -21,6 +22,7 @@ use Respect\Validation\Validator;
|
|||
|
||||
use function is_object;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be present',
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
final readonly class PropertyOptional implements Validator
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final readonly class Templated implements Validator
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Helpers\CanValidateUndefined;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
use Respect\Validation\Validator;
|
||||
|
||||
#[Mixin(exclude: ['nullOr', 'undefOr'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{subject}} must be undefined',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||
namespace Respect\Validation\Validators;
|
||||
|
||||
use Attribute;
|
||||
use Respect\Dev\CodeGen\Attributes\Mixin;
|
||||
use Respect\Validation\Helpers\CanValidateUndefined;
|
||||
use Respect\Validation\Message\Template;
|
||||
use Respect\Validation\Result;
|
||||
|
|
@ -20,6 +21,7 @@ use Respect\Validation\Validator;
|
|||
|
||||
use function array_map;
|
||||
|
||||
#[Mixin(prefix: 'undefOr', exclude: ['all', 'key', 'property', 'not', 'nullOr', 'undefOr'])]
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'or must be undefined',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue