mirror of
https://github.com/Respect/Validation.git
synced 2026-03-14 22:35:45 +01:00
Bump PHP support from 8.1 to 8.5
We want to release version 3.0 as fresh as possible, without having to maintain backward compatibility with the previous versions. Because that version will be on for some time, we decided it will be best to support only PHP version 8.5 or higher. Acked-by: Alexandre Gomes Gaigalas <alganet@gmail.com>
This commit is contained in:
parent
e2b0185acf
commit
7f66bcea10
48 changed files with 201 additions and 224 deletions
9
.github/workflows/continuous-integration.yml
vendored
9
.github/workflows/continuous-integration.yml
vendored
|
|
@ -19,10 +19,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
@ -61,7 +58,7 @@ jobs:
|
|||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.1
|
||||
php-version: 8.5
|
||||
coverage: pcov
|
||||
|
||||
- name: Install Localisation Files
|
||||
|
|
@ -93,7 +90,7 @@ jobs:
|
|||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.4
|
||||
php-version: 8.5
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"php": ">=8.5",
|
||||
"respect/stringifier": "^2.0.0",
|
||||
"symfony/polyfill-mbstring": "^1.33"
|
||||
},
|
||||
|
|
@ -30,12 +30,12 @@
|
|||
"malukenho/docheader": "^1.0",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"nette/php-generator": "^4.1",
|
||||
"pestphp/pest": "^2.36",
|
||||
"pestphp/pest": "^4.2",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"phpunit/phpunit": "^12.5",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"ramsey/uuid": "^4",
|
||||
"respect/coding-standard": "^4.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Package is available on [Packagist](http://packagist.org/packages/respect/valida
|
|||
you can install it using [Composer](http://getcomposer.org).
|
||||
|
||||
```shell
|
||||
composer require respect/validation
|
||||
composer require respect/validation:^3.0
|
||||
```
|
||||
|
||||
Works on PHP 8.1 or above.
|
||||
Works on PHP 8.5 or above.
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Message\Placeholder;
|
||||
|
||||
final class Listed
|
||||
final readonly class Listed
|
||||
{
|
||||
/** @param array<int, mixed> $values */
|
||||
public function __construct(
|
||||
public readonly array $values,
|
||||
public readonly string $lastGlue
|
||||
public array $values,
|
||||
public string $lastGlue
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Message\Placeholder;
|
||||
|
||||
final class Quoted
|
||||
final readonly class Quoted
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $value
|
||||
private string $value
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ use function str_repeat;
|
|||
|
||||
use const PHP_EOL;
|
||||
|
||||
final class StandardFormatter implements Formatter
|
||||
final readonly class StandardFormatter implements Formatter
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Renderer $renderer = new StandardRenderer(),
|
||||
private Renderer $renderer = new StandardRenderer(),
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ final class StandardFormatter implements Formatter
|
|||
|
||||
$messages = [];
|
||||
foreach ($deduplicatedChildren as $child) {
|
||||
$key = $child->getDeepestPath() ?? $child->id;
|
||||
$key = $child->getDeepestPath() ?? $child->id ?? 0;
|
||||
$messages[$key] = $this->array(
|
||||
$this->resultWithPath($result, $child),
|
||||
$this->selectTemplates($child, $selectedTemplates),
|
||||
|
|
@ -200,7 +200,7 @@ final class StandardFormatter implements Formatter
|
|||
}
|
||||
|
||||
foreach ([$result->path, $result->name, $result->id, '__root__'] as $key) {
|
||||
if (!isset($templates[$key])) {
|
||||
if ($key === null || !isset($templates[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +221,7 @@ final class StandardFormatter implements Formatter
|
|||
*/
|
||||
private function isFinalTemplate(Result $result, array $templates): bool
|
||||
{
|
||||
$keys = [$result->path, $result->name, $result->id];
|
||||
$keys = array_filter([$result->path, $result->name, $result->id], static fn($key) => $key !== null);
|
||||
foreach ($keys as $key) {
|
||||
if (isset($templates[$key]) && is_string($templates[$key])) {
|
||||
return true;
|
||||
|
|
@ -249,7 +249,7 @@ final class StandardFormatter implements Formatter
|
|||
private function selectTemplates(Result $result, array $templates): array
|
||||
{
|
||||
foreach ([$result->path, $result->name, $result->id] as $key) {
|
||||
if (isset($templates[$key]) && is_array($templates[$key])) {
|
||||
if ($key !== null && isset($templates[$key]) && is_array($templates[$key])) {
|
||||
return $templates[$key];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,17 +34,17 @@ use Respect\Stringifier\Stringifiers\ThrowableObjectStringifier;
|
|||
use Respect\Validation\Message\Stringifier\ListedStringifier;
|
||||
use Respect\Validation\Message\Stringifier\QuotedStringifier;
|
||||
|
||||
final class StandardStringifier implements Stringifier
|
||||
final readonly class StandardStringifier implements Stringifier
|
||||
{
|
||||
private const MAXIMUM_DEPTH = 3;
|
||||
private const MAXIMUM_NUMBER_OF_ITEMS = 5;
|
||||
private const MAXIMUM_NUMBER_OF_PROPERTIES = self::MAXIMUM_NUMBER_OF_ITEMS;
|
||||
private const MAXIMUM_LENGTH = 120;
|
||||
|
||||
private readonly Stringifier $stringifier;
|
||||
private Stringifier $stringifier;
|
||||
|
||||
public function __construct(
|
||||
private readonly Quoter $quoter = new StandardQuoter(self::MAXIMUM_LENGTH)
|
||||
private Quoter $quoter = new StandardQuoter(self::MAXIMUM_LENGTH)
|
||||
) {
|
||||
$this->stringifier = $this->createStringifier($quoter);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ use function count;
|
|||
use function implode;
|
||||
use function sprintf;
|
||||
|
||||
final class ListedStringifier implements Stringifier
|
||||
final readonly class ListedStringifier implements Stringifier
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Stringifier $stringifier
|
||||
private Stringifier $stringifier
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ use Respect\Stringifier\Quoter;
|
|||
use Respect\Stringifier\Stringifier;
|
||||
use Respect\Validation\Message\Placeholder\Quoted;
|
||||
|
||||
final class QuotedStringifier implements Stringifier
|
||||
final readonly class QuotedStringifier implements Stringifier
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Quoter $quoter
|
||||
private Quoter $quoter
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ use Attribute;
|
|||
use Respect\Validation\Rule;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final class Template
|
||||
final readonly class Template
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $default,
|
||||
public readonly string $inverted,
|
||||
public readonly string $id = Rule::TEMPLATE_STANDARD,
|
||||
public string $default,
|
||||
public string $inverted,
|
||||
public string $id = Rule::TEMPLATE_STANDARD,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ use Respect\Validation\Message\Translator;
|
|||
|
||||
use function is_string;
|
||||
|
||||
final class ArrayTranslator implements Translator
|
||||
final readonly class ArrayTranslator implements Translator
|
||||
{
|
||||
/** @param array<string, string> $messages */
|
||||
public function __construct(
|
||||
private readonly array $messages
|
||||
private array $messages
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,25 +22,25 @@ use function strrchr;
|
|||
use function substr;
|
||||
use function ucfirst;
|
||||
|
||||
final class Result
|
||||
final readonly class Result
|
||||
{
|
||||
/** @var array<Result> */
|
||||
public readonly array $children;
|
||||
public array $children;
|
||||
|
||||
public readonly string $id;
|
||||
public string $id;
|
||||
|
||||
/** @param array<string, mixed> $parameters */
|
||||
public function __construct(
|
||||
public readonly bool $hasPassed,
|
||||
public readonly mixed $input,
|
||||
public readonly Rule $rule,
|
||||
public readonly array $parameters = [],
|
||||
public readonly string $template = Rule::TEMPLATE_STANDARD,
|
||||
public readonly bool $hasInvertedMode = false,
|
||||
public readonly ?string $name = null,
|
||||
?string $id = null,
|
||||
public readonly ?Result $adjacent = null,
|
||||
public readonly string|int|null $path = null,
|
||||
public bool $hasPassed,
|
||||
public mixed $input,
|
||||
public Rule $rule,
|
||||
public array $parameters = [],
|
||||
public string $template = Rule::TEMPLATE_STANDARD,
|
||||
public bool $hasInvertedMode = false,
|
||||
public string|null $name = null,
|
||||
string|null $id = null,
|
||||
public Result|null $adjacent = null,
|
||||
public string|int|null $path = null,
|
||||
Result ...$children,
|
||||
) {
|
||||
$this->id = $id ?? lcfirst(substr((string) strrchr($rule::class, '\\'), 1));
|
||||
|
|
@ -52,7 +52,7 @@ final class Result
|
|||
mixed $input,
|
||||
Rule $rule,
|
||||
array $parameters = [],
|
||||
string $template = Rule::TEMPLATE_STANDARD
|
||||
string $template = Rule::TEMPLATE_STANDARD,
|
||||
): self {
|
||||
return new self(false, $input, $rule, $parameters, $template);
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ final class Result
|
|||
mixed $input,
|
||||
Rule $rule,
|
||||
array $parameters = [],
|
||||
string $template = Rule::TEMPLATE_STANDARD
|
||||
string $template = Rule::TEMPLATE_STANDARD,
|
||||
): self {
|
||||
return new self(true, $input, $rule, $parameters, $template);
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ final class Result
|
|||
Rule $rule,
|
||||
Result $adjacent,
|
||||
array $parameters = [],
|
||||
string $template = Rule::TEMPLATE_STANDARD
|
||||
string $template = Rule::TEMPLATE_STANDARD,
|
||||
): Result {
|
||||
if ($adjacent->allowsAdjacent()) {
|
||||
return (new Result($adjacent->hasPassed, $input, $rule, $parameters, $template, id: $adjacent->id))
|
||||
|
|
@ -84,7 +84,7 @@ final class Result
|
|||
|
||||
$childrenAsAdjacent = array_map(
|
||||
static fn(Result $child) => self::fromAdjacent($input, $prefix, $rule, $child, $parameters, $template),
|
||||
$adjacent->children
|
||||
$adjacent->children,
|
||||
);
|
||||
|
||||
return $adjacent->withInput($input)->withChildren(...$childrenAsAdjacent);
|
||||
|
|
@ -92,31 +92,32 @@ final class Result
|
|||
|
||||
public function withTemplate(string $template): self
|
||||
{
|
||||
return $this->clone(template: $template);
|
||||
return clone($this, ['template' => $template]);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $parameters */
|
||||
public function withExtraParameters(array $parameters): self
|
||||
{
|
||||
return $this->clone(parameters: $parameters + $this->parameters);
|
||||
// phpcs:ignore SlevomatCodingStandard.PHP.UselessParentheses
|
||||
return clone($this, ['parameters' => $parameters + $this->parameters]);
|
||||
}
|
||||
|
||||
public function withId(string $id): self
|
||||
{
|
||||
return $this->clone(id: $id);
|
||||
return clone($this, ['id' => $id]);
|
||||
}
|
||||
|
||||
public function withIdFrom(Rule $rule): self
|
||||
{
|
||||
return $this->clone(id: lcfirst(substr((string) strrchr($rule::class, '\\'), 1)));
|
||||
return clone($this, ['id' => lcfirst(substr((string) strrchr($rule::class, '\\'), 1))]);
|
||||
}
|
||||
|
||||
public function withPath(string|int $path): self
|
||||
{
|
||||
return $this->clone(
|
||||
adjacent: $this->adjacent?->withPath($path),
|
||||
path: $this->path === null ? $path : $path . '.' . $this->path,
|
||||
);
|
||||
return clone($this, [
|
||||
'adjacent' => $this->adjacent?->withPath($path),
|
||||
'path' => $this->path === null ? $path : $path . '.' . $this->path,
|
||||
]);
|
||||
}
|
||||
|
||||
public function withDeepestPath(): self
|
||||
|
|
@ -126,13 +127,13 @@ final class Result
|
|||
return $this;
|
||||
}
|
||||
|
||||
return $this->clone(
|
||||
adjacent: $this->adjacent?->withPath($path),
|
||||
path: $path,
|
||||
);
|
||||
return clone($this, [
|
||||
'adjacent' => $this->adjacent?->withPath($path),
|
||||
'path' => $path,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDeepestPath(): ?string
|
||||
public function getDeepestPath(): string|null
|
||||
{
|
||||
if ($this->path === null) {
|
||||
return null;
|
||||
|
|
@ -152,24 +153,25 @@ final class Result
|
|||
return $this;
|
||||
}
|
||||
|
||||
return $this->clone(id: $prefix . ucfirst($this->id));
|
||||
// phpcs:ignore SlevomatCodingStandard.PHP.UselessParentheses
|
||||
return clone($this, ['id' => $prefix . ucfirst($this->id)]);
|
||||
}
|
||||
|
||||
public function withChildren(Result ...$children): self
|
||||
{
|
||||
return $this->clone(children: $children);
|
||||
return clone($this, ['children' => $children]);
|
||||
}
|
||||
|
||||
public function withName(string $name): self
|
||||
{
|
||||
return $this->clone(
|
||||
name: $this->name ?? $name,
|
||||
adjacent: $this->adjacent?->withName($name),
|
||||
children: array_map(
|
||||
static fn (Result $child) => $child->path === null ? $child->withName($child->name ?? $name) : $child,
|
||||
$this->children
|
||||
return clone($this, [
|
||||
'name' => $this->name ?? $name,
|
||||
'adjacent' => $this->adjacent?->withName($name),
|
||||
'children' => array_map(
|
||||
static fn(Result $child) => $child->path === null ? $child->withName($child->name ?? $name) : $child,
|
||||
$this->children,
|
||||
),
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
public function withNameFrom(Rule $rule): self
|
||||
|
|
@ -185,37 +187,40 @@ final class Result
|
|||
{
|
||||
$currentInput = $this->input;
|
||||
|
||||
return $this->clone(
|
||||
input: $input,
|
||||
children: array_map(
|
||||
static fn (Result $child) => $child->input === $currentInput ? $child->withInput($input) : $child,
|
||||
$this->children
|
||||
return clone($this, [
|
||||
'input' => $input,
|
||||
'children' => array_map(
|
||||
static fn(Result $child) => $child->input === $currentInput ? $child->withInput($input) : $child,
|
||||
$this->children,
|
||||
),
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
public function withAdjacent(Result $adjacent): self
|
||||
{
|
||||
return $this->clone(adjacent: $adjacent);
|
||||
return clone($this, ['adjacent' => $adjacent]);
|
||||
}
|
||||
|
||||
public function withToggledValidation(): self
|
||||
{
|
||||
return $this->clone(
|
||||
hasPassed: !$this->hasPassed,
|
||||
adjacent: $this->adjacent?->withToggledValidation(),
|
||||
children: array_map(static fn (Result $child) => $child->withToggledValidation(), $this->children),
|
||||
);
|
||||
return clone($this, [
|
||||
'hasPassed' => !$this->hasPassed,
|
||||
'adjacent' => $this->adjacent?->withToggledValidation(),
|
||||
'children' => array_map(static fn(Result $child) => $child->withToggledValidation(), $this->children),
|
||||
]);
|
||||
}
|
||||
|
||||
public function withToggledModeAndValidation(): self
|
||||
{
|
||||
return $this->clone(
|
||||
hasPassed: !$this->hasPassed,
|
||||
mode: !$this->hasInvertedMode,
|
||||
adjacent: $this->adjacent?->withToggledModeAndValidation(),
|
||||
children: array_map(static fn (Result $child) => $child->withToggledModeAndValidation(), $this->children),
|
||||
);
|
||||
return clone($this, [
|
||||
'hasPassed' => !$this->hasPassed,
|
||||
'hasInvertedMode' => !$this->hasInvertedMode,
|
||||
'adjacent' => $this->adjacent?->withToggledModeAndValidation(),
|
||||
'children' => array_map(
|
||||
static fn(Result $child) => $child->withToggledModeAndValidation(),
|
||||
$this->children,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasCustomTemplate(): bool
|
||||
|
|
@ -231,40 +236,9 @@ final class Result
|
|||
|
||||
$childrenThatAllowAdjacent = array_filter(
|
||||
$this->children,
|
||||
static fn (Result $child) => $child->allowsAdjacent()
|
||||
static fn(Result $child) => $child->allowsAdjacent(),
|
||||
);
|
||||
|
||||
return count($childrenThatAllowAdjacent) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $parameters
|
||||
* @param array<Result>|null $children
|
||||
*/
|
||||
private function clone(
|
||||
?bool $hasPassed = null,
|
||||
mixed $input = null,
|
||||
?array $parameters = null,
|
||||
?string $template = null,
|
||||
?bool $mode = null,
|
||||
?string $name = null,
|
||||
?string $id = null,
|
||||
?Result $adjacent = null,
|
||||
string|int|null $path = null,
|
||||
?array $children = null
|
||||
): self {
|
||||
return new self(
|
||||
$hasPassed ?? $this->hasPassed,
|
||||
$input ?? $this->input,
|
||||
$this->rule,
|
||||
$parameters ?? $this->parameters,
|
||||
$template ?? $this->template,
|
||||
$mode ?? $this->hasInvertedMode,
|
||||
$name ?? $this->name,
|
||||
$id ?? $this->id,
|
||||
$adjacent ?? $this->adjacent,
|
||||
$path ?? $this->path,
|
||||
...($children ?? $this->children)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ use function preg_match;
|
|||
'{{name}} must be a number in base {{base|raw}}',
|
||||
'{{name}} must not be a number in base {{base|raw}}',
|
||||
)]
|
||||
final class Base implements Rule
|
||||
final readonly class Base implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly int $base,
|
||||
private readonly string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
private int $base,
|
||||
private string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
) {
|
||||
$max = mb_strlen($this->chars);
|
||||
if ($base > $max) {
|
||||
|
|
|
|||
|
|
@ -46,11 +46,13 @@ final class Call implements Rule
|
|||
});
|
||||
|
||||
try {
|
||||
return $this->rule->evaluate(call_user_func($this->callable, $input));
|
||||
$result = $this->rule->evaluate(call_user_func($this->callable, $input));
|
||||
} catch (Throwable) {
|
||||
restore_error_handler();
|
||||
|
||||
return Result::failed($input, $this, ['callable' => $this->callable]);
|
||||
$result = Result::failed($input, $this, ['callable' => $this->callable]);
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ use function mb_list_encodings;
|
|||
'{{name}} must only contain characters from the {{charset|raw}} charset',
|
||||
'{{name}} must not contain any characters from the {{charset|raw}} charset',
|
||||
)]
|
||||
final class Charset implements Rule
|
||||
final readonly class Charset implements Rule
|
||||
{
|
||||
/** @var non-empty-array<string> */
|
||||
private readonly array $charset;
|
||||
private array $charset;
|
||||
|
||||
public function __construct(string $charset, string ...$charsets)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ use function mb_strpos;
|
|||
'{{name}} must contain {{containsValue}}',
|
||||
'{{name}} must not contain {{containsValue}}',
|
||||
)]
|
||||
final class Contains implements Rule
|
||||
final readonly class Contains implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly mixed $containsValue,
|
||||
private readonly bool $identical = false
|
||||
private mixed $containsValue,
|
||||
private bool $identical = false
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ use function is_string;
|
|||
'{{name}} must be a valid country code',
|
||||
'{{name}} must not be a valid country code',
|
||||
)]
|
||||
final class CountryCode implements Rule
|
||||
final readonly class CountryCode implements Rule
|
||||
{
|
||||
private readonly Countries $countries;
|
||||
private Countries $countries;
|
||||
|
||||
/** @param "alpha-2"|"alpha-3"|"numeric" $set */
|
||||
public function __construct(
|
||||
private readonly string $set = 'alpha-2',
|
||||
private string $set = 'alpha-2',
|
||||
?Countries $countries = null
|
||||
) {
|
||||
if (!class_exists(Countries::class)) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ use function preg_replace;
|
|||
'{{name}} must not be a valid {{brand|raw}} credit card number',
|
||||
self::TEMPLATE_BRANDED,
|
||||
)]
|
||||
final class CreditCard implements Rule
|
||||
final readonly class CreditCard implements Rule
|
||||
{
|
||||
public const TEMPLATE_BRANDED = '__branded__';
|
||||
public const ANY = 'Any';
|
||||
|
|
@ -55,7 +55,7 @@ final class CreditCard implements Rule
|
|||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly string $brand = self::ANY
|
||||
private string $brand = self::ANY
|
||||
) {
|
||||
if (!isset(self::BRAND_REGEX_LIST[$brand])) {
|
||||
throw new InvalidRuleConstructorException(
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@ use function in_array;
|
|||
'{{name}} must be a valid currency code',
|
||||
'{{name}} must not be a valid currency code',
|
||||
)]
|
||||
final class CurrencyCode implements Rule
|
||||
final readonly class CurrencyCode implements Rule
|
||||
{
|
||||
private readonly Currencies $currencies;
|
||||
private Currencies $currencies;
|
||||
|
||||
/** @param "alpha-3"|"numeric" $set */
|
||||
public function __construct(
|
||||
private readonly string $set = 'alpha-3',
|
||||
private string $set = 'alpha-3',
|
||||
?Currencies $currencies = null
|
||||
) {
|
||||
if (!class_exists(Currencies::class)) {
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ use function strtotime;
|
|||
'{{name}} must be a valid date in the format {{sample}}',
|
||||
'{{name}} must not be a valid date in the format {{sample}}',
|
||||
)]
|
||||
final class Date implements Rule
|
||||
final readonly class Date implements Rule
|
||||
{
|
||||
use CanValidateDateTime;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $format = 'Y-m-d'
|
||||
private string $format = 'Y-m-d'
|
||||
) {
|
||||
if (!preg_match('/^[djSFmMnYy\W]+$/', $format)) {
|
||||
throw new InvalidRuleConstructorException('"%s" is not a valid date format', $format);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ use function ucfirst;
|
|||
'For comparison with {{now|raw}}, {{name}} must not be a valid datetime in the format {{sample|raw}}',
|
||||
self::TEMPLATE_WRONG_FORMAT
|
||||
)]
|
||||
final class DateTimeDiff implements Rule
|
||||
final readonly class DateTimeDiff implements Rule
|
||||
{
|
||||
use CanValidateDateTime;
|
||||
|
||||
|
|
@ -53,10 +53,10 @@ final class DateTimeDiff implements Rule
|
|||
|
||||
/** @param "years"|"months"|"days"|"hours"|"minutes"|"seconds"|"microseconds" $type */
|
||||
public function __construct(
|
||||
private readonly string $type,
|
||||
private readonly Rule $rule,
|
||||
private readonly ?string $format = null,
|
||||
private readonly ?DateTimeImmutable $now = null,
|
||||
private string $type,
|
||||
private Rule $rule,
|
||||
private ?string $format = null,
|
||||
private ?DateTimeImmutable $now = null,
|
||||
) {
|
||||
$availableTypes = ['years', 'months', 'days', 'hours', 'minutes', 'seconds', 'microseconds'];
|
||||
if (!in_array($this->type, $availableTypes, true)) {
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ use function var_export;
|
|||
'{{name}} must have {{decimals}} decimals',
|
||||
'{{name}} must not have {{decimals}} decimals',
|
||||
)]
|
||||
final class Decimal implements Rule
|
||||
final readonly class Decimal implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly int $decimals
|
||||
private int $decimals
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ use function mb_strrpos;
|
|||
'{{name}} must end with {{endValue}}',
|
||||
'{{name}} must not end with {{endValue}}',
|
||||
)]
|
||||
final class EndsWith implements Rule
|
||||
final readonly class EndsWith implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly mixed $endValue,
|
||||
private readonly bool $identical = false
|
||||
private mixed $endValue,
|
||||
private bool $identical = false
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ use function is_scalar;
|
|||
'{{name}} must be equal to {{compareTo}}',
|
||||
'{{name}} must not be equal to {{compareTo}}',
|
||||
)]
|
||||
final class Equals implements Rule
|
||||
final readonly class Equals implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly mixed $compareTo
|
||||
private mixed $compareTo
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ use const PATHINFO_EXTENSION;
|
|||
'{{name}} must have {{extension}} extension',
|
||||
'{{name}} must not have {{extension}} extension',
|
||||
)]
|
||||
final class Extension implements Rule
|
||||
final readonly class Extension implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $extension
|
||||
private string $extension
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,16 +17,17 @@ use Respect\Validation\Rule;
|
|||
use function abs;
|
||||
use function is_integer;
|
||||
use function is_numeric;
|
||||
use function preg_match;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
'{{name}} must be a factor of {{dividend|raw}}',
|
||||
'{{name}} must not be a factor of {{dividend|raw}}',
|
||||
)]
|
||||
final class Factor implements Rule
|
||||
final readonly class Factor implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly int $dividend
|
||||
private int $dividend
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ final class Factor implements Rule
|
|||
}
|
||||
|
||||
// Factors must be integers that are not zero.
|
||||
if (!is_numeric($input) || (int) $input != $input || $input == 0) {
|
||||
if (!is_numeric($input) || preg_match('/^-?\d+$/', (string) $input) === 0 || $input == 0) {
|
||||
return Result::failed($input, $this, $parameters);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ use Respect\Validation\Rule;
|
|||
'{{name}} must be identical to {{compareTo}}',
|
||||
'{{name}} must not be identical to {{compareTo}}',
|
||||
)]
|
||||
final class Identical implements Rule
|
||||
final readonly class Identical implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly mixed $compareTo
|
||||
private mixed $compareTo
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,11 +28,9 @@ use const FILEINFO_MIME_TYPE;
|
|||
)]
|
||||
final class Image extends Simple
|
||||
{
|
||||
private readonly finfo $fileInfo;
|
||||
|
||||
public function __construct(?finfo $fileInfo = null)
|
||||
{
|
||||
$this->fileInfo = $fileInfo ?: new finfo(FILEINFO_MIME_TYPE);
|
||||
public function __construct(
|
||||
private finfo $fileInfo = new finfo(FILEINFO_MIME_TYPE)
|
||||
) {
|
||||
}
|
||||
|
||||
public function isValid(mixed $input): bool
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ use function mb_strpos;
|
|||
'{{name}} must be in {{haystack}}',
|
||||
'{{name}} must not be in {{haystack}}',
|
||||
)]
|
||||
final class In implements Rule
|
||||
final readonly class In implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly mixed $haystack,
|
||||
private readonly bool $compareIdentical = false
|
||||
private mixed $haystack,
|
||||
private bool $compareIdentical = false
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ use Respect\Validation\Rule;
|
|||
'{{name}} must be an instance of {{class|quote}}',
|
||||
'{{name}} must not be an instance of {{class|quote}}',
|
||||
)]
|
||||
final class Instance implements Rule
|
||||
final readonly class Instance implements Rule
|
||||
{
|
||||
/** @param class-string $class */
|
||||
public function __construct(
|
||||
private readonly string $class
|
||||
private string $class
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ use function is_string;
|
|||
use function long2ip;
|
||||
use function mb_strpos;
|
||||
use function mb_substr_count;
|
||||
use function min;
|
||||
use function sprintf;
|
||||
use function str_repeat;
|
||||
use function str_replace;
|
||||
use function strtr;
|
||||
|
||||
use const FILTER_VALIDATE_IP;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
|
||||
#[Template(
|
||||
|
|
@ -89,7 +91,7 @@ final class Ip implements Rule
|
|||
}
|
||||
|
||||
if ($this->startAddress && $this->mask) {
|
||||
return $this->startAddress . '/' . long2ip((int) $this->mask);
|
||||
return $this->startAddress . '/' . long2ip((int) min($this->mask, PHP_INT_MAX));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -160,7 +162,7 @@ final class Ip implements Rule
|
|||
throw new InvalidRuleConstructorException('Invalid network mask');
|
||||
}
|
||||
|
||||
$this->mask = sprintf('%032b', ip2long((string) long2ip(~(2 ** (32 - (int) $parts[1]) - 1))));
|
||||
$this->mask = sprintf('%032b', ip2long(long2ip(~(2 ** (32 - (int) $parts[1]) - 1))));
|
||||
}
|
||||
|
||||
private function verifyAddress(string $address): bool
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ use function array_slice;
|
|||
'{{name}} contains no missing keys',
|
||||
self::TEMPLATE_MISSING_KEYS
|
||||
)]
|
||||
final class KeySet implements Rule
|
||||
final readonly class KeySet implements Rule
|
||||
{
|
||||
public const TEMPLATE_BOTH = '__both__';
|
||||
public const TEMPLATE_EXTRA_KEYS = '__extra_keys__';
|
||||
|
|
@ -54,13 +54,13 @@ final class KeySet implements Rule
|
|||
private const MAX_DIFF_KEYS = 10;
|
||||
|
||||
/** @var array<KeyRelated> */
|
||||
private readonly array $rules;
|
||||
private array $rules;
|
||||
|
||||
/** @var array<int|string> */
|
||||
private readonly array $allKeys;
|
||||
private array $allKeys;
|
||||
|
||||
/** @var array<int|string> */
|
||||
private readonly array $mandatoryKeys;
|
||||
private array $mandatoryKeys;
|
||||
|
||||
public function __construct(Rule $rule, Rule ...$rules)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ use function is_string;
|
|||
'{{name}} must be a valid language code',
|
||||
'{{name}} must not be a valid language code',
|
||||
)]
|
||||
final class LanguageCode implements Rule
|
||||
final readonly class LanguageCode implements Rule
|
||||
{
|
||||
private readonly Languages $languages;
|
||||
private Languages $languages;
|
||||
|
||||
/** @param "alpha-2"|"alpha-3" $set */
|
||||
public function __construct(
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ use const FILEINFO_MIME_TYPE;
|
|||
'{{name}} must have the {{mimetype}} MIME type',
|
||||
'{{name}} must not have the {{mimetype}} MIME type',
|
||||
)]
|
||||
final class Mimetype implements Rule
|
||||
final readonly class Mimetype implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $mimetype,
|
||||
private readonly finfo $fileInfo = new finfo()
|
||||
private string $mimetype,
|
||||
private finfo $fileInfo = new finfo()
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ use Respect\Validation\Rule;
|
|||
'{{name}} must be a multiple of {{multipleOf}}',
|
||||
'{{name}} must not be a multiple of {{multipleOf}}',
|
||||
)]
|
||||
final class Multiple implements Rule
|
||||
final readonly class Multiple implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly int $multipleOf
|
||||
private int $multipleOf
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ use function is_object;
|
|||
'{{name}} must be present',
|
||||
'{{name}} must not be present',
|
||||
)]
|
||||
final class PropertyExists implements Rule
|
||||
final readonly class PropertyExists implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $propertyName
|
||||
private string $propertyName
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ use function preg_match;
|
|||
'{{name}} must match the pattern {{regex|quote}}',
|
||||
'{{name}} must not match the pattern {{regex|quote}}',
|
||||
)]
|
||||
final class Regex implements Rule
|
||||
final readonly class Regex implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $regex
|
||||
private string $regex
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use function str_split;
|
|||
'{{name}} must not be sorted in descending order',
|
||||
self::TEMPLATE_DESCENDING,
|
||||
)]
|
||||
final class Sorted implements Rule
|
||||
final readonly class Sorted implements Rule
|
||||
{
|
||||
public const TEMPLATE_ASCENDING = '__ascending__';
|
||||
public const TEMPLATE_DESCENDING = '__descending__';
|
||||
|
|
@ -41,7 +41,7 @@ final class Sorted implements Rule
|
|||
public const DESCENDING = 'DESC';
|
||||
|
||||
public function __construct(
|
||||
private readonly string $direction
|
||||
private string $direction
|
||||
) {
|
||||
if ($direction !== self::ASCENDING && $direction !== self::DESCENDING) {
|
||||
throw new InvalidRuleConstructorException(
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ use function reset;
|
|||
'{{name}} must start with {{startValue}}',
|
||||
'{{name}} must not start with {{startValue}}',
|
||||
)]
|
||||
final class StartsWith implements Rule
|
||||
final readonly class StartsWith implements Rule
|
||||
{
|
||||
public function __construct(
|
||||
private readonly mixed $startValue,
|
||||
private readonly bool $identical = false
|
||||
private mixed $startValue,
|
||||
private bool $identical = false
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ use function class_exists;
|
|||
'{{name}} must be a subdivision code of {{countryName|trans}}',
|
||||
'{{name}} must not be a subdivision code of {{countryName|trans}}',
|
||||
)]
|
||||
final class SubdivisionCode implements Rule
|
||||
final readonly class SubdivisionCode implements Rule
|
||||
{
|
||||
use CanValidateUndefined;
|
||||
|
||||
private readonly Countries\Country $country;
|
||||
private Countries\Country $country;
|
||||
|
||||
private readonly Subdivisions $subdivisions;
|
||||
private Subdivisions $subdivisions;
|
||||
|
||||
public function __construct(string $countryCode, ?Countries $countries = null, ?Subdivisions $subdivisions = null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ use function is_array;
|
|||
'{{name}} must be subset of {{superset}}',
|
||||
'{{name}} must not be subset of {{superset}}',
|
||||
)]
|
||||
final class Subset implements Rule
|
||||
final readonly class Subset implements Rule
|
||||
{
|
||||
/** @param mixed[] $superset */
|
||||
public function __construct(
|
||||
private readonly array $superset
|
||||
private array $superset
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ use function strtotime;
|
|||
'{{name}} must be a valid time in the format {{sample}}',
|
||||
'{{name}} must not be a valid time in the format {{sample}}',
|
||||
)]
|
||||
final class Time implements Rule
|
||||
final readonly class Time implements Rule
|
||||
{
|
||||
use CanValidateDateTime;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $format = 'H:i:s'
|
||||
private string $format = 'H:i:s'
|
||||
) {
|
||||
if (!preg_match('/^[gGhHisuvaA\W]+$/', $format)) {
|
||||
throw new InvalidRuleConstructorException('"%s" is not a valid date format', $format);
|
||||
|
|
|
|||
|
|
@ -14,20 +14,13 @@ use Respect\Validation\Result;
|
|||
use Respect\Validation\Rule;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final class When implements Rule
|
||||
final readonly class When implements Rule
|
||||
{
|
||||
private readonly Rule $else;
|
||||
|
||||
public function __construct(
|
||||
private readonly Rule $when,
|
||||
private readonly Rule $then,
|
||||
?Rule $else = null
|
||||
private Rule $when,
|
||||
private Rule $then,
|
||||
private Rule $else = new Templated(new AlwaysInvalid(), AlwaysInvalid::TEMPLATE_SIMPLE)
|
||||
) {
|
||||
if ($else === null) {
|
||||
$else = new Templated(new AlwaysInvalid(), AlwaysInvalid::TEMPLATE_SIMPLE);
|
||||
}
|
||||
|
||||
$this->else = $else;
|
||||
}
|
||||
|
||||
public function evaluate(mixed $input): Result
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Transformers;
|
||||
|
||||
final class RuleSpec
|
||||
final readonly class RuleSpec
|
||||
{
|
||||
/** @param array<mixed> $arguments */
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public readonly array $arguments = [],
|
||||
public readonly ?RuleSpec $wrapper = null,
|
||||
public string $name,
|
||||
public array $arguments = [],
|
||||
public ?RuleSpec $wrapper = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ parameters:
|
|||
path: tests/library/Message/TestingStringifier.php
|
||||
- message: '/Parameter #1 \$messages of class .+\\ArrayTranslator constructor expects array<string, string>, array<string, int> given./'
|
||||
path: tests/unit/Message/Translator/ArrayTranslatorTest.php
|
||||
- message: '/Access to an undefined property PHPUnit\\Framework\\TestCase/'
|
||||
path: tests/feature/Rules/SizeTest.php
|
||||
level: 8
|
||||
treatPhpDocTypesAsCertain: false
|
||||
paths:
|
||||
|
|
|
|||
|
|
@ -70,7 +70,14 @@ function expectDeprecation(Closure $callback, string $error): Closure
|
|||
return true;
|
||||
});
|
||||
|
||||
$callback->call($this);
|
||||
try {
|
||||
$callback->call($this);
|
||||
} catch (Throwable $throwable) {
|
||||
restore_error_handler();
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
|
||||
restore_error_handler();
|
||||
expect($lastError)->toBe($error);
|
||||
};
|
||||
|
|
@ -93,8 +100,13 @@ function expectMessageAndDeprecation(Closure $callback, string $message, string
|
|||
test()->expectException(ValidationException::class);
|
||||
} catch (ValidationException $e) {
|
||||
expect($e->getMessage())->toBe($message, 'Validation message does not match');
|
||||
} catch (Throwable $throwable) {
|
||||
restore_error_handler();
|
||||
|
||||
throw $throwable;
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
expect($lastError)->toBe($error);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Helpers;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
#[Group('helper')]
|
||||
#[CoversClass(CanValidateDateTime::class)]
|
||||
final class CanValidateDateTimeTest extends TestCase
|
||||
{
|
||||
use CanValidateDateTime;
|
||||
|
|
|
|||
|
|
@ -9,14 +9,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Respect\Validation\Helpers;
|
||||
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Respect\Validation\Test\TestCase;
|
||||
|
||||
#[Group('helper')]
|
||||
#[CoversClass(CanValidateUndefined::class)]
|
||||
final class CanValidateUndefinedTest extends TestCase
|
||||
{
|
||||
use CanValidateUndefined;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue