diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6d022f5f..d15a2ae7 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -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 diff --git a/composer.json b/composer.json index 97fe8471..c6e89c95 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/docs/01-installation.md b/docs/01-installation.md index c0d1ada0..958b091c 100644 --- a/docs/01-installation.md +++ b/docs/01-installation.md @@ -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. diff --git a/library/Message/Placeholder/Listed.php b/library/Message/Placeholder/Listed.php index 22e68396..80ad024a 100644 --- a/library/Message/Placeholder/Listed.php +++ b/library/Message/Placeholder/Listed.php @@ -9,12 +9,12 @@ declare(strict_types=1); namespace Respect\Validation\Message\Placeholder; -final class Listed +final readonly class Listed { /** @param array $values */ public function __construct( - public readonly array $values, - public readonly string $lastGlue + public array $values, + public string $lastGlue ) { } } diff --git a/library/Message/Placeholder/Quoted.php b/library/Message/Placeholder/Quoted.php index 381d922f..6c8e570c 100644 --- a/library/Message/Placeholder/Quoted.php +++ b/library/Message/Placeholder/Quoted.php @@ -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 ) { } diff --git a/library/Message/StandardFormatter.php b/library/Message/StandardFormatter.php index 1ce6b2b4..9bd59b27 100644 --- a/library/Message/StandardFormatter.php +++ b/library/Message/StandardFormatter.php @@ -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]; } } diff --git a/library/Message/StandardStringifier.php b/library/Message/StandardStringifier.php index 3f3d27c4..4df97688 100644 --- a/library/Message/StandardStringifier.php +++ b/library/Message/StandardStringifier.php @@ -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); } diff --git a/library/Message/Stringifier/ListedStringifier.php b/library/Message/Stringifier/ListedStringifier.php index b6cded8c..394fc8c5 100644 --- a/library/Message/Stringifier/ListedStringifier.php +++ b/library/Message/Stringifier/ListedStringifier.php @@ -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 ) { } diff --git a/library/Message/Stringifier/QuotedStringifier.php b/library/Message/Stringifier/QuotedStringifier.php index 437a8a33..6a5a9564 100644 --- a/library/Message/Stringifier/QuotedStringifier.php +++ b/library/Message/Stringifier/QuotedStringifier.php @@ -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 ) { } diff --git a/library/Message/Template.php b/library/Message/Template.php index a8787963..45bc1572 100644 --- a/library/Message/Template.php +++ b/library/Message/Template.php @@ -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, ) { } } diff --git a/library/Message/Translator/ArrayTranslator.php b/library/Message/Translator/ArrayTranslator.php index 3e895b40..20c20796 100644 --- a/library/Message/Translator/ArrayTranslator.php +++ b/library/Message/Translator/ArrayTranslator.php @@ -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 $messages */ public function __construct( - private readonly array $messages + private array $messages ) { } diff --git a/library/Result.php b/library/Result.php index bac85425..be86d36c 100644 --- a/library/Result.php +++ b/library/Result.php @@ -22,25 +22,25 @@ use function strrchr; use function substr; use function ucfirst; -final class Result +final readonly class Result { /** @var array */ - public readonly array $children; + public array $children; - public readonly string $id; + public string $id; /** @param array $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 $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 $parameters - * @param array|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) - ); - } } diff --git a/library/Rules/Base.php b/library/Rules/Base.php index 6cb372e0..0955bd62 100644 --- a/library/Rules/Base.php +++ b/library/Rules/Base.php @@ -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) { diff --git a/library/Rules/Call.php b/library/Rules/Call.php index ff3a677a..53ca9773 100644 --- a/library/Rules/Call.php +++ b/library/Rules/Call.php @@ -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; } } diff --git a/library/Rules/Charset.php b/library/Rules/Charset.php index 752c0854..6df388f8 100644 --- a/library/Rules/Charset.php +++ b/library/Rules/Charset.php @@ -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 */ - private readonly array $charset; + private array $charset; public function __construct(string $charset, string ...$charsets) { diff --git a/library/Rules/Contains.php b/library/Rules/Contains.php index ec23eb07..d03350a4 100644 --- a/library/Rules/Contains.php +++ b/library/Rules/Contains.php @@ -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 ) { } diff --git a/library/Rules/CountryCode.php b/library/Rules/CountryCode.php index a56e49b0..6c795fee 100644 --- a/library/Rules/CountryCode.php +++ b/library/Rules/CountryCode.php @@ -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)) { diff --git a/library/Rules/CreditCard.php b/library/Rules/CreditCard.php index da870770..e379b4df 100644 --- a/library/Rules/CreditCard.php +++ b/library/Rules/CreditCard.php @@ -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( diff --git a/library/Rules/CurrencyCode.php b/library/Rules/CurrencyCode.php index 774c128d..9b04a0cc 100644 --- a/library/Rules/CurrencyCode.php +++ b/library/Rules/CurrencyCode.php @@ -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)) { diff --git a/library/Rules/Date.php b/library/Rules/Date.php index 8f19d7c0..5718dcc7 100644 --- a/library/Rules/Date.php +++ b/library/Rules/Date.php @@ -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); diff --git a/library/Rules/DateTimeDiff.php b/library/Rules/DateTimeDiff.php index a9247e11..6696c6ec 100644 --- a/library/Rules/DateTimeDiff.php +++ b/library/Rules/DateTimeDiff.php @@ -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)) { diff --git a/library/Rules/Decimal.php b/library/Rules/Decimal.php index 5080302c..d1d2b6f0 100644 --- a/library/Rules/Decimal.php +++ b/library/Rules/Decimal.php @@ -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 ) { } diff --git a/library/Rules/EndsWith.php b/library/Rules/EndsWith.php index 7c72adf1..68273d6d 100644 --- a/library/Rules/EndsWith.php +++ b/library/Rules/EndsWith.php @@ -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 ) { } diff --git a/library/Rules/Equals.php b/library/Rules/Equals.php index 63f8a749..08fc476a 100644 --- a/library/Rules/Equals.php +++ b/library/Rules/Equals.php @@ -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 ) { } diff --git a/library/Rules/Extension.php b/library/Rules/Extension.php index f9a7f725..dbb5ae99 100644 --- a/library/Rules/Extension.php +++ b/library/Rules/Extension.php @@ -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 ) { } diff --git a/library/Rules/Factor.php b/library/Rules/Factor.php index 389a8f6d..0c9bb2f2 100644 --- a/library/Rules/Factor.php +++ b/library/Rules/Factor.php @@ -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); } diff --git a/library/Rules/Identical.php b/library/Rules/Identical.php index 57be8c81..c5a47b19 100644 --- a/library/Rules/Identical.php +++ b/library/Rules/Identical.php @@ -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 ) { } diff --git a/library/Rules/Image.php b/library/Rules/Image.php index d4daf7b3..45f8b2e6 100644 --- a/library/Rules/Image.php +++ b/library/Rules/Image.php @@ -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 diff --git a/library/Rules/In.php b/library/Rules/In.php index ac9ba016..bbfdd895 100644 --- a/library/Rules/In.php +++ b/library/Rules/In.php @@ -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 ) { } diff --git a/library/Rules/Instance.php b/library/Rules/Instance.php index 7e2051e5..8c41b40b 100644 --- a/library/Rules/Instance.php +++ b/library/Rules/Instance.php @@ -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 ) { } diff --git a/library/Rules/Ip.php b/library/Rules/Ip.php index 66b1b707..a5948b31 100644 --- a/library/Rules/Ip.php +++ b/library/Rules/Ip.php @@ -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 diff --git a/library/Rules/KeySet.php b/library/Rules/KeySet.php index 009b3a40..8bb4c7f7 100644 --- a/library/Rules/KeySet.php +++ b/library/Rules/KeySet.php @@ -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 */ - private readonly array $rules; + private array $rules; /** @var array */ - private readonly array $allKeys; + private array $allKeys; /** @var array */ - private readonly array $mandatoryKeys; + private array $mandatoryKeys; public function __construct(Rule $rule, Rule ...$rules) { diff --git a/library/Rules/LanguageCode.php b/library/Rules/LanguageCode.php index 76635265..70071d26 100644 --- a/library/Rules/LanguageCode.php +++ b/library/Rules/LanguageCode.php @@ -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( diff --git a/library/Rules/Mimetype.php b/library/Rules/Mimetype.php index 09e39fd6..2478665c 100644 --- a/library/Rules/Mimetype.php +++ b/library/Rules/Mimetype.php @@ -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() ) { } diff --git a/library/Rules/Multiple.php b/library/Rules/Multiple.php index 6e18bc57..cdb61b17 100644 --- a/library/Rules/Multiple.php +++ b/library/Rules/Multiple.php @@ -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 ) { } diff --git a/library/Rules/PropertyExists.php b/library/Rules/PropertyExists.php index 51356de4..00f6d0d2 100644 --- a/library/Rules/PropertyExists.php +++ b/library/Rules/PropertyExists.php @@ -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 ) { } diff --git a/library/Rules/Regex.php b/library/Rules/Regex.php index 9b9d4355..8248fb43 100644 --- a/library/Rules/Regex.php +++ b/library/Rules/Regex.php @@ -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 ) { } diff --git a/library/Rules/Sorted.php b/library/Rules/Sorted.php index ff6ba938..d7e5a338 100644 --- a/library/Rules/Sorted.php +++ b/library/Rules/Sorted.php @@ -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( diff --git a/library/Rules/StartsWith.php b/library/Rules/StartsWith.php index fc34cd60..0e04d543 100644 --- a/library/Rules/StartsWith.php +++ b/library/Rules/StartsWith.php @@ -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 ) { } diff --git a/library/Rules/SubdivisionCode.php b/library/Rules/SubdivisionCode.php index 54fc67e0..8735e9d2 100644 --- a/library/Rules/SubdivisionCode.php +++ b/library/Rules/SubdivisionCode.php @@ -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) { diff --git a/library/Rules/Subset.php b/library/Rules/Subset.php index 12cab880..8a04e701 100644 --- a/library/Rules/Subset.php +++ b/library/Rules/Subset.php @@ -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 ) { } diff --git a/library/Rules/Time.php b/library/Rules/Time.php index 13a2b829..4fea1488 100644 --- a/library/Rules/Time.php +++ b/library/Rules/Time.php @@ -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); diff --git a/library/Rules/When.php b/library/Rules/When.php index 0d833ae2..3b153ff2 100644 --- a/library/Rules/When.php +++ b/library/Rules/When.php @@ -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 diff --git a/library/Transformers/RuleSpec.php b/library/Transformers/RuleSpec.php index 94ba06e5..ce7a8232 100644 --- a/library/Transformers/RuleSpec.php +++ b/library/Transformers/RuleSpec.php @@ -9,13 +9,13 @@ declare(strict_types=1); namespace Respect\Validation\Transformers; -final class RuleSpec +final readonly class RuleSpec { /** @param array $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, ) { } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1a112b7e..d3a2d532 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -19,6 +19,8 @@ parameters: path: tests/library/Message/TestingStringifier.php - message: '/Parameter #1 \$messages of class .+\\ArrayTranslator constructor expects array, array 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: diff --git a/tests/Pest.php b/tests/Pest.php index 890da2f6..90a02754 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -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); }; } diff --git a/tests/unit/Helpers/CanValidateDateTimeTest.php b/tests/unit/Helpers/CanValidateDateTimeTest.php index 689a3fa9..7d81617a 100644 --- a/tests/unit/Helpers/CanValidateDateTimeTest.php +++ b/tests/unit/Helpers/CanValidateDateTimeTest.php @@ -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; diff --git a/tests/unit/Helpers/CanValidateUndefinedTest.php b/tests/unit/Helpers/CanValidateUndefinedTest.php index 53b87ee2..b025b0c0 100644 --- a/tests/unit/Helpers/CanValidateUndefinedTest.php +++ b/tests/unit/Helpers/CanValidateUndefinedTest.php @@ -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;