diff --git a/bin/create-mixin b/bin/create-mixin
index ad0d80c8b..51e87b4fc 100755
--- a/bin/create-mixin
+++ b/bin/create-mixin
@@ -170,6 +170,7 @@ function overwriteFile(string $content, string $basename): void
'Property',
'PropertyExists',
'PropertyOptional',
+ 'Attributes',
];
$mixins = [
@@ -177,10 +178,10 @@ function overwriteFile(string $content, string $basename): void
['Length', 'length', $numberRelatedRules, []],
['Max', 'max', $numberRelatedRules, []],
['Min', 'min', $numberRelatedRules, []],
- ['Not', 'not', [], ['Not', 'NotEmpty', 'NotBlank', 'NotEmoji', 'NotUndef', 'NotOptional', 'NullOr', 'UndefOr', 'Optional']],
+ ['Not', 'not', [], ['Not', 'NotEmpty', 'NotBlank', 'NotEmoji', 'NotUndef', 'NotOptional', 'NullOr', 'UndefOr', 'Optional', 'Attributes']],
['NullOr', 'nullOr', [], ['Nullable', 'NullOr', 'Optional', 'NotOptional', 'NotUndef', 'UndefOr']],
['Property', 'property', [], $structureRelatedRules],
- ['UndefOr', 'undefOr', [], ['Nullable', 'NullOr', 'NotOptional', 'NotUndef', 'Optional', 'UndefOr']],
+ ['UndefOr', 'undefOr', [], ['Nullable', 'NullOr', 'NotOptional', 'NotUndef', 'Optional', 'UndefOr', 'Attributes']],
['Validator', null, [], []],
];
diff --git a/docs/02-feature-guide.md b/docs/02-feature-guide.md
index 9f133e958..3758694ea 100644
--- a/docs/02-feature-guide.md
+++ b/docs/02-feature-guide.md
@@ -29,6 +29,22 @@ The `assert()` method throws an exception when validation fails. You can handle
v::intType()->positive()->assert($input);
```
+## Smart validation
+
+Respect\Validation offers over 150 rules, many of which are designed to address common scenarios. Here’s a quick guide to some specific use cases and the rules that make validation straightforward.
+
+* Using rules as **PHP Attributes**: [Attributes](rules/Attributes.md).
+* Validating **Arrays**: [Key](rules/Key.md), [KeyOptional](rules/KeyOptional.md), [KeyExists](rules/KeyExists.md).
+* Validating **Array structures**: [KeySet](rules/KeySet.md).
+* Validating **Object properties**: [Property](rules/Property.md), [PropertyOptional](rules/PropertyOptional.md), [PropertyExists](rules/PropertyExists.md).
+* Using **Conditional validation**: [NullOr](rules/NullOr.md), [UndefOr](rules/UndefOr.md), [When](rules/When.md).
+* Using **Grouped validation**: [AllOf](rules/AllOf.md), [AnyOf](rules/AnyOf.md), [NoneOf](rules/NoneOf.md), [OneOf](rules/OneOf.md)
+* Validating **Each** value in the input: [Each](rules/Each.md).
+* Validating the **Length** of the input: [Length](rules/Length.md).
+* Validating the **Maximum** value in the input: [Max](rules/Max.md).
+* Validating the **Minimum** value in the input: [Min](rules/Min.md).
+* Handling **Special cases**: [Lazy](rules/Lazy.md), [Consecutive](rules/Consecutive.md), [Call](rules/Call.md).
+
### Custom templates
Define your own error message when the validation fails:
@@ -101,16 +117,3 @@ v::dateTime('Y-m-d')
->setName('Age')
->assert($input);
```
-
-## Smart input handling
-
-Respect\Validation offers over 150 rules, many of which are designed to address common input handling scenarios. Here’s a quick guide to some specific use cases and the rules that make validation straightforward.
-
-* Validating arrays: [Key](rules/Key.md), [KeyOptional](rules/KeyOptional.md), and [KeyExists](rules/KeyExists.md).
-* Validating array structures: [KeySet](rules/KeySet.md).
-* Validating object properties: [Property](rules/Property.md), [PropertyOptional](rules/PropertyOptional.md), and [PropertyExists](rules/PropertyExists.md).
-* Validating only when input is not `null`: [NullOr](rules/NullOr.md).
-* Validating only when input is not `null` or an empty string: [UndefOr](rules/UndefOr.md).
-* Validating the length of the input: [Length](rules/Length.md).
-* Validating the maximum value of the input: [Max](rules/Max.md).
-* Validating the minimum value of the input: [Min](rules/Min.md).
diff --git a/docs/09-list-of-rules-by-category.md b/docs/09-list-of-rules-by-category.md
index 50f457c40..0e2a6aeab 100644
--- a/docs/09-list-of-rules-by-category.md
+++ b/docs/09-list-of-rules-by-category.md
@@ -203,6 +203,7 @@
## Objects
+- [Attributes](rules/Attributes.md)
- [Instance](rules/Instance.md)
- [ObjectType](rules/ObjectType.md)
- [Property](rules/Property.md)
@@ -248,6 +249,7 @@
## Structures
+- [Attributes](rules/Attributes.md)
- [Key](rules/Key.md)
- [KeyExists](rules/KeyExists.md)
- [KeyOptional](rules/KeyOptional.md)
@@ -297,6 +299,7 @@
- [AnyOf](rules/AnyOf.md)
- [ArrayType](rules/ArrayType.md)
- [ArrayVal](rules/ArrayVal.md)
+- [Attributes](rules/Attributes.md)
- [Base](rules/Base.md)
- [Base64](rules/Base64.md)
- [Between](rules/Between.md)
diff --git a/docs/rules/Attributes.md b/docs/rules/Attributes.md
new file mode 100644
index 000000000..13da19838
--- /dev/null
+++ b/docs/rules/Attributes.md
@@ -0,0 +1,85 @@
+# Attributes
+
+- `Attributes()`
+
+Validates the PHP attributes defined in the properties of the input.
+
+Example of object:
+
+```php
+use Respect\Validation\Rules;
+
+final class Person
+{
+ public function __construct(
+ #[Rules\NotEmpty]
+ public readonly string $name,
+ #[Rules\Email]
+ public readonly string $email,
+ #[Rules\Date('Y-m-d')]
+ #[Rules\DateTimeDiff('years', new Rules\LessThanOrEqual(25))]
+ public readonly string $birthdate,
+ #[Rules\Phone]
+ public readonly ?string $phone
+ ) {
+ }
+}
+```
+
+Here is how you can validate the attributes of the object:
+
+```php
+v::attributes()->assert(new Person('John Doe', 'john.doe@gmail.com', '2020-06-23'));
+// No exception
+
+v::attributes()->assert(new Person('John Doe', 'john.doe@gmail.com', '2020-06-23', '+31 20 624 1111'));
+// No exception
+
+v::attributes()->assert(new Person('', 'john.doe@gmail.com', '2020-06-23', '+1234567890'));
+// Message: name must not be empty
+
+v::attributes()->assert(new Person('John Doe', 'not an email', '2020-06-23', '+1234567890'));
+// Message: email must be a valid email address
+
+v::attributes()->assert(new Person('John Doe', 'john.doe@gmail.com', 'not a date', '+1234567890'));
+// Message: birthdate must be a valid date in the format "2005-12-30"
+
+v::attributes()->assert(new Person('John Doe', 'john.doe@gmail.com', '2020-06-23', 'not a phone number'));
+// Message: phone must be a valid telephone number or must be null
+
+v::attributes()->assert(new Person('', 'not an email', 'not a date', 'not a phone number'));
+// Full message:
+// - All of the required rules must pass for `Person { +$name="" +$email="not an email" +$birthdate="not a date" +$phone="not a phone number" }`
+// - name must not be empty
+// - email must be a valid email address
+// - All of the required rules must pass for birthdate
+// - birthdate must be a valid date in the format "2005-12-30"
+// - For comparison with now, birthdate must be a valid datetime
+// - phone must be a valid telephone number or must be null
+```
+
+## Caveats
+
+* If the object has no attributes, the validation will always pass.
+* When the property is nullable, this rule will wrap the rule on the property into [NullOr](NullOr.md) rule.
+* This rule has no templates because it uses the templates of the rules that are applied to the properties.
+
+## Categorization
+
+- Objects
+- Structures
+
+## Changelog
+
+| Version | Description |
+|--------:|-------------|
+| 3.0.0 | Created |
+
+***
+See also:
+
+- [NullOr](NullOr.md)
+- [ObjectType](ObjectType.md)
+- [Property](Property.md)
+- [PropertyExists](PropertyExists.md)
+- [PropertyOptional](PropertyOptional.md)
diff --git a/docs/rules/Date.md b/docs/rules/Date.md
index 3c64cd241..1a2b8fc55 100644
--- a/docs/rules/Date.md
+++ b/docs/rules/Date.md
@@ -1,4 +1,4 @@
-[]()# Date
+# Date
- `Date()`
- `Date(string $format)`
diff --git a/docs/rules/NullOr.md b/docs/rules/NullOr.md
index f502f4b57..41576b77c 100644
--- a/docs/rules/NullOr.md
+++ b/docs/rules/NullOr.md
@@ -60,5 +60,6 @@ v::not(v::nullOr(v::alpha()))->assert("alpha");
***
See also:
+- [Attributes](Attributes.md)
- [NullType](NullType.md)
- [UndefOr](UndefOr.md)
diff --git a/docs/rules/ObjectType.md b/docs/rules/ObjectType.md
index 93a57f933..ea8cc2fa5 100644
--- a/docs/rules/ObjectType.md
+++ b/docs/rules/ObjectType.md
@@ -39,6 +39,7 @@ v::objectType()->isValid(new stdClass); // true
See also:
- [ArrayType](ArrayType.md)
+- [Attributes](Attributes.md)
- [BoolType](BoolType.md)
- [BoolVal](BoolVal.md)
- [CallableType](CallableType.md)
diff --git a/docs/rules/Property.md b/docs/rules/Property.md
index 8bf1f5f44..30219abd2 100644
--- a/docs/rules/Property.md
+++ b/docs/rules/Property.md
@@ -69,6 +69,7 @@ This rule will validate public, private, protected, uninitialised, and static pr
***
See also:
+- [Attributes](Attributes.md)
- [Key](Key.md)
- [KeyExists](KeyExists.md)
- [KeyOptional](KeyOptional.md)
diff --git a/docs/rules/PropertyExists.md b/docs/rules/PropertyExists.md
index b04406513..df36dcd80 100644
--- a/docs/rules/PropertyExists.md
+++ b/docs/rules/PropertyExists.md
@@ -50,6 +50,7 @@ This rule will validate public, private, protected, uninitialised, and static pr
***
See also:
+- [Attributes](Attributes.md)
- [Key](Key.md)
- [KeyExists](KeyExists.md)
- [KeyOptional](KeyOptional.md)
diff --git a/docs/rules/PropertyOptional.md b/docs/rules/PropertyOptional.md
index 3f2a62004..ff9ae3e10 100644
--- a/docs/rules/PropertyOptional.md
+++ b/docs/rules/PropertyOptional.md
@@ -62,6 +62,7 @@ v::objectType()->propertyOptional('name', v::notEmpty())->isValid('Not an object
***
See also:
+- [Attributes](Attributes.md)
- [Key](Key.md)
- [KeyExists](KeyExists.md)
- [KeyOptional](KeyOptional.md)
diff --git a/library/Mixins/ChainedNullOr.php b/library/Mixins/ChainedNullOr.php
index ab749f4f7..3fec3045a 100644
--- a/library/Mixins/ChainedNullOr.php
+++ b/library/Mixins/ChainedNullOr.php
@@ -30,6 +30,8 @@ public function nullOrArrayType(): ChainedValidator;
public function nullOrArrayVal(): ChainedValidator;
+ public function nullOrAttributes(): ChainedValidator;
+
public function nullOrBase(
int $base,
string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
diff --git a/library/Mixins/ChainedValidator.php b/library/Mixins/ChainedValidator.php
index 565735509..7bd0a03aa 100644
--- a/library/Mixins/ChainedValidator.php
+++ b/library/Mixins/ChainedValidator.php
@@ -58,6 +58,8 @@ public function arrayType(): ChainedValidator;
public function arrayVal(): ChainedValidator;
+ public function attributes(): ChainedValidator;
+
public function base(
int $base,
string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
diff --git a/library/Mixins/StaticNullOr.php b/library/Mixins/StaticNullOr.php
index 4788510c5..f2d9c7ccd 100644
--- a/library/Mixins/StaticNullOr.php
+++ b/library/Mixins/StaticNullOr.php
@@ -30,6 +30,8 @@ public static function nullOrArrayType(): ChainedValidator;
public static function nullOrArrayVal(): ChainedValidator;
+ public static function nullOrAttributes(): ChainedValidator;
+
public static function nullOrBase(
int $base,
string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
diff --git a/library/Mixins/StaticValidator.php b/library/Mixins/StaticValidator.php
index 787e53eaa..3f9ac7893 100644
--- a/library/Mixins/StaticValidator.php
+++ b/library/Mixins/StaticValidator.php
@@ -38,6 +38,8 @@ public static function arrayType(): ChainedValidator;
public static function arrayVal(): ChainedValidator;
+ public static function attributes(): ChainedValidator;
+
public static function base(
int $base,
string $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
diff --git a/library/Rules/AllOf.php b/library/Rules/AllOf.php
index 94070d904..af7b41e82 100644
--- a/library/Rules/AllOf.php
+++ b/library/Rules/AllOf.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
@@ -19,6 +20,7 @@
use function array_reduce;
use function count;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'These rules must pass for {{name}}',
'These rules must not pass for {{name}}',
diff --git a/library/Rules/Alnum.php b/library/Rules/Alnum.php
index dc142eb65..63b7c3a8c 100644
--- a/library/Rules/Alnum.php
+++ b/library/Rules/Alnum.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_alnum;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only letters (a-z) and digits (0-9)',
'{{name}} must not contain letters (a-z) or digits (0-9)',
diff --git a/library/Rules/Alpha.php b/library/Rules/Alpha.php
index 21e15d5ad..1eefdb3f6 100644
--- a/library/Rules/Alpha.php
+++ b/library/Rules/Alpha.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_alpha;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only letters (a-z)',
'{{name}} must not contain letters (a-z)',
diff --git a/library/Rules/AlwaysInvalid.php b/library/Rules/AlwaysInvalid.php
index c2e6e95c3..791cbe7d3 100644
--- a/library/Rules/AlwaysInvalid.php
+++ b/library/Rules/AlwaysInvalid.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be valid',
'{{name}} must be invalid',
diff --git a/library/Rules/AlwaysValid.php b/library/Rules/AlwaysValid.php
index 9e764dfa3..dbca8bd44 100644
--- a/library/Rules/AlwaysValid.php
+++ b/library/Rules/AlwaysValid.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be valid',
'{{name}} must be invalid',
diff --git a/library/Rules/AnyOf.php b/library/Rules/AnyOf.php
index 25117563d..adf170587 100644
--- a/library/Rules/AnyOf.php
+++ b/library/Rules/AnyOf.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
@@ -17,6 +18,7 @@
use function array_map;
use function array_reduce;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'At least one of these rules must pass for {{name}}',
'At least one of these rules must not pass for {{name}}',
diff --git a/library/Rules/ArrayType.php b/library/Rules/ArrayType.php
index 2f5415976..eff84f9d8 100644
--- a/library/Rules/ArrayType.php
+++ b/library/Rules/ArrayType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_array;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an array',
'{{name}} must not be an array',
diff --git a/library/Rules/ArrayVal.php b/library/Rules/ArrayVal.php
index 9c1674623..7d2e32686 100644
--- a/library/Rules/ArrayVal.php
+++ b/library/Rules/ArrayVal.php
@@ -10,12 +10,14 @@
namespace Respect\Validation\Rules;
use ArrayAccess;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use SimpleXMLElement;
use function is_array;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an array value',
'{{name}} must not be an array value',
diff --git a/library/Rules/Attributes.php b/library/Rules/Attributes.php
new file mode 100644
index 000000000..09f2f2419
--- /dev/null
+++ b/library/Rules/Attributes.php
@@ -0,0 +1,54 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+namespace Respect\Validation\Rules;
+
+use Attribute;
+use ReflectionAttribute;
+use ReflectionObject;
+use Respect\Validation\Result;
+use Respect\Validation\Rule;
+use Respect\Validation\Rules\Core\Binder;
+use Respect\Validation\Rules\Core\Reducer;
+use Respect\Validation\Rules\Core\Standard;
+
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
+final class Attributes extends Standard
+{
+ public function evaluate(mixed $input): Result
+ {
+ $objectType = (new Binder($this, new ObjectType()))->evaluate($input);
+ if (!$objectType->isValid) {
+ return $objectType->withId('attributes');
+ }
+
+ $rules = [];
+ foreach ((new ReflectionObject($input))->getProperties() as $property) {
+ $childrenRules = [];
+ foreach ($property->getAttributes(Rule::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
+ $childrenRules[] = $attribute->newInstance();
+ }
+
+ if ($childrenRules === []) {
+ continue;
+ }
+
+ $allowsNull = $property->getType()?->allowsNull() ?? false;
+
+ $childRule = new Reducer(...$childrenRules);
+ $rules[] = new Property($property->getName(), $allowsNull ? new NullOr($childRule) : $childRule);
+ }
+
+ if ($rules === []) {
+ return (new AlwaysValid())->evaluate($input)->withId('attributes');
+ }
+
+ return (new Binder($this, new Reducer(...$rules)))->evaluate($input)->withId('attributes');
+ }
+}
diff --git a/library/Rules/Base.php b/library/Rules/Base.php
index 2d545fa57..700c050ad 100644
--- a/library/Rules/Base.php
+++ b/library/Rules/Base.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -18,6 +19,7 @@
use function mb_substr;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a number in base {{base|raw}}',
'{{name}} must not be a number in base {{base|raw}}',
diff --git a/library/Rules/Base64.php b/library/Rules/Base64.php
index 64e5bc02c..ced23218c 100644
--- a/library/Rules/Base64.php
+++ b/library/Rules/Base64.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function mb_strlen;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a base64 encoded string',
'{{name}} must not be a base64 encoded string',
diff --git a/library/Rules/Between.php b/library/Rules/Between.php
index 45ddf8ecd..9473ef982 100644
--- a/library/Rules/Between.php
+++ b/library/Rules/Between.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Helpers\CanCompareValues;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be between {{minValue}} and {{maxValue}}',
'{{name}} must not be between {{minValue}} and {{maxValue}}',
diff --git a/library/Rules/BetweenExclusive.php b/library/Rules/BetweenExclusive.php
index 73579e5f7..e0efdd84f 100644
--- a/library/Rules/BetweenExclusive.php
+++ b/library/Rules/BetweenExclusive.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Helpers\CanCompareValues;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be greater than {{minValue}} and less than {{maxValue}}',
'{{name}} must not be greater than {{minValue}} or less than {{maxValue}}',
diff --git a/library/Rules/BoolType.php b/library/Rules/BoolType.php
index 0b3ed43fc..45049b31e 100644
--- a/library/Rules/BoolType.php
+++ b/library/Rules/BoolType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_bool;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a boolean',
'{{name}} must not be a boolean',
diff --git a/library/Rules/BoolVal.php b/library/Rules/BoolVal.php
index cfee7b99a..ee765fec6 100644
--- a/library/Rules/BoolVal.php
+++ b/library/Rules/BoolVal.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -18,6 +19,7 @@
use const FILTER_NULL_ON_FAILURE;
use const FILTER_VALIDATE_BOOLEAN;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a boolean value',
'{{name}} must not be a boolean value',
diff --git a/library/Rules/Bsn.php b/library/Rules/Bsn.php
index b7d4daf4f..f29c71134 100644
--- a/library/Rules/Bsn.php
+++ b/library/Rules/Bsn.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -21,6 +22,7 @@
/**
* @see https://nl.wikipedia.org/wiki/Burgerservicenummer
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid BSN',
'{{name}} must not be a valid BSN',
diff --git a/library/Rules/Call.php b/library/Rules/Call.php
index b14c9d580..1d27a58b1 100644
--- a/library/Rules/Call.php
+++ b/library/Rules/Call.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use ErrorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -20,6 +21,7 @@
use function restore_error_handler;
use function set_error_handler;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{input}} must be a suitable argument for {{callable}}',
'{{input}} must not be a suitable argument for {{callable}}',
diff --git a/library/Rules/CallableType.php b/library/Rules/CallableType.php
index c6ac26baf..253c0993d 100644
--- a/library/Rules/CallableType.php
+++ b/library/Rules/CallableType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_callable;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a callable',
'{{name}} must not be a callable',
diff --git a/library/Rules/Callback.php b/library/Rules/Callback.php
index 874ddd408..87e12c5c3 100644
--- a/library/Rules/Callback.php
+++ b/library/Rules/Callback.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function call_user_func_array;
use function count;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be valid',
'{{name}} must be invalid',
diff --git a/library/Rules/Charset.php b/library/Rules/Charset.php
index 7b07bcf75..430593ef6 100644
--- a/library/Rules/Charset.php
+++ b/library/Rules/Charset.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -22,6 +23,7 @@
use function mb_detect_encoding;
use function mb_list_encodings;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must only contain characters from the {{charset|raw}} charset',
'{{name}} must not contain any characters from the {{charset|raw}} charset',
diff --git a/library/Rules/Cnh.php b/library/Rules/Cnh.php
index d1af6cde5..c82dd78d4 100644
--- a/library/Rules/Cnh.php
+++ b/library/Rules/Cnh.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function mb_strlen;
use function preg_replace;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid CNH number',
'{{name}} must not be a valid CNH number',
diff --git a/library/Rules/Cnpj.php b/library/Rules/Cnpj.php
index 8f68230a1..08937e8c3 100644
--- a/library/Rules/Cnpj.php
+++ b/library/Rules/Cnpj.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -19,6 +20,7 @@
use function preg_replace;
use function str_split;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid CNPJ number',
'{{name}} must not be a valid CNPJ number',
diff --git a/library/Rules/Consecutive.php b/library/Rules/Consecutive.php
index 247d6dd1b..11f2705fa 100644
--- a/library/Rules/Consecutive.php
+++ b/library/Rules/Consecutive.php
@@ -9,10 +9,12 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Binder;
use Respect\Validation\Rules\Core\Composite;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Consecutive extends Composite
{
public function evaluate(mixed $input): Result
diff --git a/library/Rules/Consonant.php b/library/Rules/Consonant.php
index b13fbe151..e046e90c3 100644
--- a/library/Rules/Consonant.php
+++ b/library/Rules/Consonant.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must only contain consonants',
'{{name}} must not contain consonants',
diff --git a/library/Rules/Contains.php b/library/Rules/Contains.php
index 5cb5a50cc..9242fd4fc 100644
--- a/library/Rules/Contains.php
+++ b/library/Rules/Contains.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -19,6 +20,7 @@
use function mb_stripos;
use function mb_strpos;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain {{containsValue}}',
'{{name}} must not contain {{containsValue}}',
diff --git a/library/Rules/ContainsAny.php b/library/Rules/ContainsAny.php
index d5c12f8fa..6681d1b25 100644
--- a/library/Rules/ContainsAny.php
+++ b/library/Rules/ContainsAny.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
@@ -16,6 +17,7 @@
use function array_map;
use function count;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain at least one value from {{needles}}',
'{{name}} must not contain any value from {{needles}}',
diff --git a/library/Rules/Control.php b/library/Rules/Control.php
index 31a5c2022..df4e288d9 100644
--- a/library/Rules/Control.php
+++ b/library/Rules/Control.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_cntrl;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must only contain control characters',
'{{name}} must not contain control characters',
diff --git a/library/Rules/Countable.php b/library/Rules/Countable.php
index 589635997..caa08e1ab 100644
--- a/library/Rules/Countable.php
+++ b/library/Rules/Countable.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Countable as CountableInterface;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_array;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a countable value',
'{{name}} must not be a countable value',
diff --git a/library/Rules/CountryCode.php b/library/Rules/CountryCode.php
index bef7e754a..83fd04cf2 100644
--- a/library/Rules/CountryCode.php
+++ b/library/Rules/CountryCode.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
use Respect\Validation\Message\Template;
@@ -20,6 +21,7 @@
use function in_array;
use function is_string;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid country code',
'{{name}} must not be a valid country code',
diff --git a/library/Rules/Cpf.php b/library/Rules/Cpf.php
index 287387004..41694ca05 100644
--- a/library/Rules/Cpf.php
+++ b/library/Rules/Cpf.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use function preg_match;
use function preg_replace;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid CPF number',
'{{name}} must not be a valid CPF number',
diff --git a/library/Rules/CreditCard.php b/library/Rules/CreditCard.php
index aceec7c90..e73a2e2a5 100644
--- a/library/Rules/CreditCard.php
+++ b/library/Rules/CreditCard.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -19,6 +20,7 @@
use function preg_match;
use function preg_replace;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid credit card number',
'{{name}} must not be a valid credit card number',
diff --git a/library/Rules/CurrencyCode.php b/library/Rules/CurrencyCode.php
index f45eca23d..88a1907eb 100644
--- a/library/Rules/CurrencyCode.php
+++ b/library/Rules/CurrencyCode.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
use Respect\Validation\Message\Template;
@@ -19,6 +20,7 @@
use function class_exists;
use function in_array;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid currency code',
'{{name}} must not be a valid currency code',
diff --git a/library/Rules/Date.php b/library/Rules/Date.php
index f5854002c..41c12bbfe 100644
--- a/library/Rules/Date.php
+++ b/library/Rules/Date.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
@@ -20,6 +21,7 @@
use function preg_match;
use function strtotime;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid date in the format {{sample}}',
'{{name}} must not be a valid date in the format {{sample}}',
diff --git a/library/Rules/DateTime.php b/library/Rules/DateTime.php
index ef96a71cb..990b1094b 100644
--- a/library/Rules/DateTime.php
+++ b/library/Rules/DateTime.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use DateTimeInterface;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
@@ -19,6 +20,7 @@
use function is_scalar;
use function strtotime;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid date/time',
'{{name}} must not be a valid date/time',
diff --git a/library/Rules/DateTimeDiff.php b/library/Rules/DateTimeDiff.php
index ece203a41..8f92116c0 100644
--- a/library/Rules/DateTimeDiff.php
+++ b/library/Rules/DateTimeDiff.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use DateTimeImmutable;
use DateTimeInterface;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
@@ -23,6 +24,7 @@
use function in_array;
use function ucfirst;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'The number of {{type|trans}} between now and',
'The number of {{type|trans}} between now and',
diff --git a/library/Rules/Decimal.php b/library/Rules/Decimal.php
index 2053bdf45..dfcdbd92c 100644
--- a/library/Rules/Decimal.php
+++ b/library/Rules/Decimal.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -19,6 +20,7 @@
use function preg_replace;
use function var_export;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must have {{decimals}} decimals',
'{{name}} must not have {{decimals}} decimals',
diff --git a/library/Rules/Digit.php b/library/Rules/Digit.php
index 4c1234904..6740daa2f 100644
--- a/library/Rules/Digit.php
+++ b/library/Rules/Digit.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_digit;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only digits (0-9)',
'{{name}} must not contain digits (0-9)',
diff --git a/library/Rules/Directory.php b/library/Rules/Directory.php
index 68b8bf59f..036f305b1 100644
--- a/library/Rules/Directory.php
+++ b/library/Rules/Directory.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Directory as NativeDirectory;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use function is_dir;
use function is_scalar;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a directory',
'{{name}} must not be a directory',
diff --git a/library/Rules/Domain.php b/library/Rules/Domain.php
index 6ad487809..e35510e90 100644
--- a/library/Rules/Domain.php
+++ b/library/Rules/Domain.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
@@ -19,6 +20,7 @@
use function explode;
use function mb_substr_count;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid domain',
'{{name}} must not be a valid domain',
diff --git a/library/Rules/Each.php b/library/Rules/Each.php
index c4497ba5f..19397c0f0 100644
--- a/library/Rules/Each.php
+++ b/library/Rules/Each.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\FilteredNonEmptyArray;
@@ -16,6 +17,7 @@
use function array_map;
use function array_reduce;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'Each item in {{name}} must be valid',
'Each item in {{name}} must be invalid',
diff --git a/library/Rules/Email.php b/library/Rules/Email.php
index 8bef9b0ec..34e44a787 100644
--- a/library/Rules/Email.php
+++ b/library/Rules/Email.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
use Respect\Validation\Message\Template;
@@ -21,6 +22,7 @@
use const FILTER_VALIDATE_EMAIL;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid email address',
'{{name}} must not be an email address',
diff --git a/library/Rules/EndsWith.php b/library/Rules/EndsWith.php
index 49a10d9fe..5908ae9a3 100644
--- a/library/Rules/EndsWith.php
+++ b/library/Rules/EndsWith.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -19,6 +20,7 @@
use function mb_strripos;
use function mb_strrpos;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must end with {{endValue}}',
'{{name}} must not end with {{endValue}}',
diff --git a/library/Rules/Equals.php b/library/Rules/Equals.php
index cb795cbf1..b7e8d3dca 100644
--- a/library/Rules/Equals.php
+++ b/library/Rules/Equals.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
use function is_scalar;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be equal to {{compareTo}}',
'{{name}} must not be equal to {{compareTo}}',
diff --git a/library/Rules/Equivalent.php b/library/Rules/Equivalent.php
index a3f4f1423..36c1b2b73 100644
--- a/library/Rules/Equivalent.php
+++ b/library/Rules/Equivalent.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Comparison;
use function is_scalar;
use function mb_strtoupper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be equivalent to {{compareTo}}',
'{{name}} must not be equivalent to {{compareTo}}',
diff --git a/library/Rules/Even.php b/library/Rules/Even.php
index 20542dada..3e67c319b 100644
--- a/library/Rules/Even.php
+++ b/library/Rules/Even.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use const FILTER_VALIDATE_INT;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an even number',
'{{name}} must be an odd number',
diff --git a/library/Rules/Executable.php b/library/Rules/Executable.php
index e87674e50..93bea4644 100644
--- a/library/Rules/Executable.php
+++ b/library/Rules/Executable.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use SplFileInfo;
@@ -16,6 +17,7 @@
use function is_executable;
use function is_scalar;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an executable file',
'{{name}} must not be an executable file',
diff --git a/library/Rules/Exists.php b/library/Rules/Exists.php
index ddb0b9765..4bef376a8 100644
--- a/library/Rules/Exists.php
+++ b/library/Rules/Exists.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use SplFileInfo;
@@ -16,6 +17,7 @@
use function file_exists;
use function is_string;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an existing file',
'{{name}} must not be an existing file',
diff --git a/library/Rules/Extension.php b/library/Rules/Extension.php
index eb8487b5f..a61d72a50 100644
--- a/library/Rules/Extension.php
+++ b/library/Rules/Extension.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -19,6 +20,7 @@
use const PATHINFO_EXTENSION;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must have {{extension}} extension',
'{{name}} must not have {{extension}} extension',
diff --git a/library/Rules/Factor.php b/library/Rules/Factor.php
index 29abb90c7..fe473c62a 100644
--- a/library/Rules/Factor.php
+++ b/library/Rules/Factor.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -17,6 +18,7 @@
use function is_integer;
use function is_numeric;
+#[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}}',
diff --git a/library/Rules/FalseVal.php b/library/Rules/FalseVal.php
index 8efcc1215..1cc65cac7 100644
--- a/library/Rules/FalseVal.php
+++ b/library/Rules/FalseVal.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use const FILTER_NULL_ON_FAILURE;
use const FILTER_VALIDATE_BOOLEAN;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must evaluate to `false`',
'{{name}} must not evaluate to `false`',
diff --git a/library/Rules/Fibonacci.php b/library/Rules/Fibonacci.php
index 275be3c24..705a72f7a 100644
--- a/library/Rules/Fibonacci.php
+++ b/library/Rules/Fibonacci.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_numeric;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid Fibonacci number',
'{{name}} must not be a valid Fibonacci number',
diff --git a/library/Rules/File.php b/library/Rules/File.php
index d2e050f9f..b6f892467 100644
--- a/library/Rules/File.php
+++ b/library/Rules/File.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use SplFileInfo;
@@ -16,6 +17,7 @@
use function is_file;
use function is_string;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid file',
'{{name}} must be an invalid file',
diff --git a/library/Rules/FilterVar.php b/library/Rules/FilterVar.php
index 764a70fd8..b035917fc 100644
--- a/library/Rules/FilterVar.php
+++ b/library/Rules/FilterVar.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
@@ -27,6 +28,7 @@
use const FILTER_VALIDATE_REGEXP;
use const FILTER_VALIDATE_URL;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be valid',
'{{name}} must not be valid',
diff --git a/library/Rules/Finite.php b/library/Rules/Finite.php
index 7723c94c3..20c1fa04f 100644
--- a/library/Rules/Finite.php
+++ b/library/Rules/Finite.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_finite;
use function is_numeric;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a finite number',
'{{name}} must not be a finite number',
diff --git a/library/Rules/FloatType.php b/library/Rules/FloatType.php
index 13a5eda99..2ad8ffd17 100644
--- a/library/Rules/FloatType.php
+++ b/library/Rules/FloatType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_float;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be float',
'{{name}} must not be float',
diff --git a/library/Rules/FloatVal.php b/library/Rules/FloatVal.php
index 839aa481f..dd9aa907d 100644
--- a/library/Rules/FloatVal.php
+++ b/library/Rules/FloatVal.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use const FILTER_VALIDATE_FLOAT;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a float value',
'{{name}} must not be a float value',
diff --git a/library/Rules/Graph.php b/library/Rules/Graph.php
index b5824cd2f..5865fc68b 100644
--- a/library/Rules/Graph.php
+++ b/library/Rules/Graph.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_graph;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only graphical characters',
'{{name}} must not contain graphical characters',
diff --git a/library/Rules/GreaterThan.php b/library/Rules/GreaterThan.php
index 75fe67887..8d788d863 100644
--- a/library/Rules/GreaterThan.php
+++ b/library/Rules/GreaterThan.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Comparison;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be greater than {{compareTo}}',
'{{name}} must not be greater than {{compareTo}}',
diff --git a/library/Rules/GreaterThanOrEqual.php b/library/Rules/GreaterThanOrEqual.php
index ddbc34941..e0301e9a4 100644
--- a/library/Rules/GreaterThanOrEqual.php
+++ b/library/Rules/GreaterThanOrEqual.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Comparison;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be greater than or equal to {{compareTo}}',
'{{name}} must be less than {{compareTo}}',
diff --git a/library/Rules/Hetu.php b/library/Rules/Hetu.php
index 8d50ec479..08211a8af 100644
--- a/library/Rules/Hetu.php
+++ b/library/Rules/Hetu.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -20,6 +21,7 @@
/**
* @see https://en.wikipedia.org/wiki/National_identification_number#Finland
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid Finnish personal identity code',
'{{name}} must not be a valid Finnish personal identity code',
diff --git a/library/Rules/HexRgbColor.php b/library/Rules/HexRgbColor.php
index 8809e1007..d6f898fc5 100644
--- a/library/Rules/HexRgbColor.php
+++ b/library/Rules/HexRgbColor.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a hex RGB color',
'{{name}} must not be a hex RGB color',
diff --git a/library/Rules/Iban.php b/library/Rules/Iban.php
index 51db37d74..9bb9a749c 100644
--- a/library/Rules/Iban.php
+++ b/library/Rules/Iban.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -22,6 +23,7 @@
use function strval;
use function substr;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid IBAN',
'{{name}} must not be a valid IBAN',
diff --git a/library/Rules/Identical.php b/library/Rules/Identical.php
index 2d4c3934f..50a5e4c91 100644
--- a/library/Rules/Identical.php
+++ b/library/Rules/Identical.php
@@ -9,10 +9,12 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be identical to {{compareTo}}',
'{{name}} must not be identical to {{compareTo}}',
diff --git a/library/Rules/Image.php b/library/Rules/Image.php
index 03fcfa56b..27a12320e 100644
--- a/library/Rules/Image.php
+++ b/library/Rules/Image.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use finfo;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -20,6 +21,7 @@
use const FILEINFO_MIME_TYPE;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid image file',
'{{name}} must not be a valid image file',
diff --git a/library/Rules/Imei.php b/library/Rules/Imei.php
index c5047f6bc..16d83302d 100644
--- a/library/Rules/Imei.php
+++ b/library/Rules/Imei.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function mb_strlen;
use function preg_replace;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid IMEI number',
'{{name}} must not be a valid IMEI number',
diff --git a/library/Rules/In.php b/library/Rules/In.php
index ef1818a1c..705858375 100644
--- a/library/Rules/In.php
+++ b/library/Rules/In.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -18,6 +19,7 @@
use function mb_stripos;
use function mb_strpos;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be in {{haystack}}',
'{{name}} must not be in {{haystack}}',
diff --git a/library/Rules/Infinite.php b/library/Rules/Infinite.php
index c1601d831..f5a7b56f0 100644
--- a/library/Rules/Infinite.php
+++ b/library/Rules/Infinite.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_infinite;
use function is_numeric;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an infinite number',
'{{name}} must not be an infinite number',
diff --git a/library/Rules/Instance.php b/library/Rules/Instance.php
index e38b8b909..b62412109 100644
--- a/library/Rules/Instance.php
+++ b/library/Rules/Instance.php
@@ -9,10 +9,12 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an instance of `{{class|raw}}`',
'{{name}} must not be an instance of `{{class|raw}}`',
diff --git a/library/Rules/IntType.php b/library/Rules/IntType.php
index c2b465983..154d610c1 100644
--- a/library/Rules/IntType.php
+++ b/library/Rules/IntType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_int;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an integer',
'{{name}} must not be an integer',
diff --git a/library/Rules/IntVal.php b/library/Rules/IntVal.php
index 285df58de..d2f5d83fd 100644
--- a/library/Rules/IntVal.php
+++ b/library/Rules/IntVal.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function is_string;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an integer value',
'{{name}} must not be an integer value',
diff --git a/library/Rules/Ip.php b/library/Rules/Ip.php
index 50ee556e3..0ddd4ce10 100644
--- a/library/Rules/Ip.php
+++ b/library/Rules/Ip.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -29,6 +30,7 @@
use const FILTER_VALIDATE_IP;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an IP address',
'{{name}} must not be an IP address',
diff --git a/library/Rules/Isbn.php b/library/Rules/Isbn.php
index fe6a784c8..494087df7 100644
--- a/library/Rules/Isbn.php
+++ b/library/Rules/Isbn.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use function preg_match;
use function sprintf;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid ISBN',
'{{name}} must not be a valid ISBN',
diff --git a/library/Rules/IterableType.php b/library/Rules/IterableType.php
index 0b3f54553..58b4285e9 100644
--- a/library/Rules/IterableType.php
+++ b/library/Rules/IterableType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_iterable;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be iterable',
'{{name}} must not iterable',
diff --git a/library/Rules/IterableVal.php b/library/Rules/IterableVal.php
index 729eb0245..f0ab3feae 100644
--- a/library/Rules/IterableVal.php
+++ b/library/Rules/IterableVal.php
@@ -9,10 +9,12 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Helpers\CanValidateIterable;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an iterable value',
'{{name}} must not be an iterable value',
diff --git a/library/Rules/Json.php b/library/Rules/Json.php
index dd4f4166f..d68424486 100644
--- a/library/Rules/Json.php
+++ b/library/Rules/Json.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -20,6 +21,7 @@
use const JSON_ERROR_NONE;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid JSON string',
'{{name}} must not be a valid JSON string',
diff --git a/library/Rules/Key.php b/library/Rules/Key.php
index 08eaf0758..81b48e267 100644
--- a/library/Rules/Key.php
+++ b/library/Rules/Key.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Binder;
use Respect\Validation\Rules\Core\KeyRelated;
use Respect\Validation\Rules\Core\Wrapper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Key extends Wrapper implements KeyRelated
{
public function __construct(
diff --git a/library/Rules/KeyExists.php b/library/Rules/KeyExists.php
index 10620487a..b9b2fda56 100644
--- a/library/Rules/KeyExists.php
+++ b/library/Rules/KeyExists.php
@@ -10,6 +10,7 @@
namespace Respect\Validation\Rules;
use ArrayAccess;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\KeyRelated;
@@ -18,6 +19,7 @@
use function array_key_exists;
use function is_array;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be present',
'{{name}} must not be present',
diff --git a/library/Rules/KeyOptional.php b/library/Rules/KeyOptional.php
index ae9a7781c..36d030749 100644
--- a/library/Rules/KeyOptional.php
+++ b/library/Rules/KeyOptional.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Binder;
use Respect\Validation\Rules\Core\KeyRelated;
use Respect\Validation\Rules\Core\Wrapper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class KeyOptional extends Wrapper implements KeyRelated
{
public function __construct(
diff --git a/library/Rules/KeySet.php b/library/Rules/KeySet.php
index eba2865c4..00c9c69c7 100644
--- a/library/Rules/KeySet.php
+++ b/library/Rules/KeySet.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -26,6 +27,7 @@
use function array_merge;
use function array_slice;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} validation failed',
'{{name}} validation passed',
diff --git a/library/Rules/LanguageCode.php b/library/Rules/LanguageCode.php
index 05cd4e1cb..9b876bf6a 100644
--- a/library/Rules/LanguageCode.php
+++ b/library/Rules/LanguageCode.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
use Respect\Validation\Message\Template;
@@ -21,6 +22,7 @@
use function in_array;
use function is_string;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid language code',
'{{name}} must not be a valid language code',
diff --git a/library/Rules/Lazy.php b/library/Rules/Lazy.php
index de39191fa..16e5c980a 100644
--- a/library/Rules/Lazy.php
+++ b/library/Rules/Lazy.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Result;
use Respect\Validation\Rule;
@@ -17,6 +18,7 @@
use function call_user_func;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Lazy extends Standard
{
/** @var callable(mixed): Rule */
diff --git a/library/Rules/LeapDate.php b/library/Rules/LeapDate.php
index 0e620a7aa..ffd435b9e 100644
--- a/library/Rules/LeapDate.php
+++ b/library/Rules/LeapDate.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use DateTimeImmutable;
use DateTimeInterface;
use Respect\Validation\Message\Template;
@@ -16,6 +17,7 @@
use function is_scalar;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid leap date',
'{{name}} must not be a leap date',
diff --git a/library/Rules/LeapYear.php b/library/Rules/LeapYear.php
index 0276c3e01..73cc65695 100644
--- a/library/Rules/LeapYear.php
+++ b/library/Rules/LeapYear.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use DateTimeInterface;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -19,6 +20,7 @@
use function sprintf;
use function strtotime;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid leap year',
'{{name}} must not be a leap year',
diff --git a/library/Rules/Length.php b/library/Rules/Length.php
index cbe35ff64..a61704d44 100644
--- a/library/Rules/Length.php
+++ b/library/Rules/Length.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Countable as PhpCountable;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -20,6 +21,7 @@
use function mb_strlen;
use function ucfirst;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template('The length of', 'The length of')]
final class Length extends Wrapper
{
diff --git a/library/Rules/LessThan.php b/library/Rules/LessThan.php
index 28248d99b..4c8519cf6 100644
--- a/library/Rules/LessThan.php
+++ b/library/Rules/LessThan.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Comparison;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be less than {{compareTo}}',
'{{name}} must not be less than {{compareTo}}',
diff --git a/library/Rules/LessThanOrEqual.php b/library/Rules/LessThanOrEqual.php
index 5ee71f716..c7b4924f6 100644
--- a/library/Rules/LessThanOrEqual.php
+++ b/library/Rules/LessThanOrEqual.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Comparison;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be less than or equal to {{compareTo}}',
'{{name}} must be greater than {{compareTo}}',
diff --git a/library/Rules/Lowercase.php b/library/Rules/Lowercase.php
index 00a72f892..29de040ee 100644
--- a/library/Rules/Lowercase.php
+++ b/library/Rules/Lowercase.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_string;
use function mb_strtolower;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only lowercase letters',
'{{name}} must not contain only lowercase letters',
diff --git a/library/Rules/Luhn.php b/library/Rules/Luhn.php
index 11abd08a9..70ccf3083 100644
--- a/library/Rules/Luhn.php
+++ b/library/Rules/Luhn.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -19,6 +20,7 @@
/**
* @see https://en.wikipedia.org/wiki/Luhn_algorithm
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid Luhn number',
'{{name}} must not be a valid Luhn number',
diff --git a/library/Rules/MacAddress.php b/library/Rules/MacAddress.php
index 2ec8a2b1e..9a954c9c1 100644
--- a/library/Rules/MacAddress.php
+++ b/library/Rules/MacAddress.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_string;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid MAC address',
'{{name}} must not be a valid MAC address',
diff --git a/library/Rules/Max.php b/library/Rules/Max.php
index 7977bf16a..3f2cf62bc 100644
--- a/library/Rules/Max.php
+++ b/library/Rules/Max.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\FilteredNonEmptyArray;
use function max;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template('As the maximum of {{name}},', 'As the maximum of {{name}},')]
#[Template('The maximum of', 'The maximum of', self::TEMPLATE_NAMED)]
final class Max extends FilteredNonEmptyArray
diff --git a/library/Rules/Mimetype.php b/library/Rules/Mimetype.php
index 40e7c4ef5..71a0ee81e 100644
--- a/library/Rules/Mimetype.php
+++ b/library/Rules/Mimetype.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use finfo;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -20,6 +21,7 @@
use const FILEINFO_MIME_TYPE;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must have the {{mimetype}} MIME type',
'{{name}} must not have the {{mimetype}} MIME type',
diff --git a/library/Rules/Min.php b/library/Rules/Min.php
index 8e619a6a3..2180da2c6 100644
--- a/library/Rules/Min.php
+++ b/library/Rules/Min.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\FilteredNonEmptyArray;
use function min;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template('As the minimum from {{name}},', 'As the minimum from {{name}},')]
#[Template('The minimum from', 'The minimum from', self::TEMPLATE_NAMED)]
final class Min extends FilteredNonEmptyArray
diff --git a/library/Rules/Multiple.php b/library/Rules/Multiple.php
index f5866112e..725f17b70 100644
--- a/library/Rules/Multiple.php
+++ b/library/Rules/Multiple.php
@@ -9,10 +9,12 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a multiple of {{multipleOf}}',
'{{name}} must not be a multiple of {{multipleOf}}',
diff --git a/library/Rules/Negative.php b/library/Rules/Negative.php
index 12b70d7a6..fb2183e4b 100644
--- a/library/Rules/Negative.php
+++ b/library/Rules/Negative.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_numeric;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a negative number',
'{{name}} must not be a negative number',
diff --git a/library/Rules/NfeAccessKey.php b/library/Rules/NfeAccessKey.php
index 09d834664..f991dbdd7 100644
--- a/library/Rules/NfeAccessKey.php
+++ b/library/Rules/NfeAccessKey.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -20,6 +21,7 @@
/**
* @see (pt-br) Manual de Integração do Contribuinte v4.0.1 em http://www.nfe.fazenda.gov.br
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid NFe access key',
'{{name}} must not be a valid NFe access key',
diff --git a/library/Rules/Nif.php b/library/Rules/Nif.php
index 7670158c2..ce58e8c5c 100644
--- a/library/Rules/Nif.php
+++ b/library/Rules/Nif.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -23,6 +24,7 @@
/**
* @see https://es.wikipedia.org/wiki/N%C3%BAmero_de_identificaci%C3%B3n_fiscal
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid NIF',
'{{name}} must not be a valid NIF',
diff --git a/library/Rules/Nip.php b/library/Rules/Nip.php
index 7043d84b0..f75da0bc0 100644
--- a/library/Rules/Nip.php
+++ b/library/Rules/Nip.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -20,6 +21,7 @@
/**
* @see https://en.wikipedia.org/wiki/VAT_identification_number
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid Polish VAT identification number',
'{{name}} must not be a valid Polish VAT identification number',
diff --git a/library/Rules/No.php b/library/Rules/No.php
index ccad57cd4..ae363c060 100644
--- a/library/Rules/No.php
+++ b/library/Rules/No.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
@@ -16,6 +17,7 @@
use const NOEXPR;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be similar to "No"',
'{{name}} must not be similar to "No"',
diff --git a/library/Rules/NoWhitespace.php b/library/Rules/NoWhitespace.php
index 4fbc5da25..fb4d7de26 100644
--- a/library/Rules/NoWhitespace.php
+++ b/library/Rules/NoWhitespace.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function is_scalar;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must not contain whitespaces',
'{{name}} must contain at least one whitespace',
diff --git a/library/Rules/NoneOf.php b/library/Rules/NoneOf.php
index 7e749efdc..953a4d1b8 100644
--- a/library/Rules/NoneOf.php
+++ b/library/Rules/NoneOf.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
@@ -17,6 +18,7 @@
use function array_map;
use function array_reduce;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'None of these rules must pass for {{name}}',
'All of these rules must pass for {{name}}',
diff --git a/library/Rules/Not.php b/library/Rules/Not.php
index 21f3a4d64..33cb3cc0a 100644
--- a/library/Rules/Not.php
+++ b/library/Rules/Not.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Wrapper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Not extends Wrapper
{
public function evaluate(mixed $input): Result
diff --git a/library/Rules/NotBlank.php b/library/Rules/NotBlank.php
index 093467100..f268c3bd8 100644
--- a/library/Rules/NotBlank.php
+++ b/library/Rules/NotBlank.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -20,6 +21,7 @@
use function is_string;
use function trim;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'The value must not be blank',
'The value must be blank',
diff --git a/library/Rules/NotEmoji.php b/library/Rules/NotEmoji.php
index 9dcce0ffc..9e3d6d70f 100644
--- a/library/Rules/NotEmoji.php
+++ b/library/Rules/NotEmoji.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function is_string;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must not contain an emoji',
'{{name}} must contain an emoji',
diff --git a/library/Rules/NotEmpty.php b/library/Rules/NotEmpty.php
index a5ed3eff4..3e351c6f8 100644
--- a/library/Rules/NotEmpty.php
+++ b/library/Rules/NotEmpty.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -16,6 +17,7 @@
use function is_string;
use function trim;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'The value must not be empty',
'The value must be empty',
diff --git a/library/Rules/NotUndef.php b/library/Rules/NotUndef.php
index f23da883c..6a61c3cec 100644
--- a/library/Rules/NotUndef.php
+++ b/library/Rules/NotUndef.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Helpers\CanValidateUndefined;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'The value must be defined',
'The value must be undefined',
diff --git a/library/Rules/NullOr.php b/library/Rules/NullOr.php
index 9faa2f4c7..91af36c9d 100644
--- a/library/Rules/NullOr.php
+++ b/library/Rules/NullOr.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Wrapper;
use function array_map;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'or must be null',
'and must not be null',
diff --git a/library/Rules/NullType.php b/library/Rules/NullType.php
index 9fd7c27c0..ad5322d71 100644
--- a/library/Rules/NullType.php
+++ b/library/Rules/NullType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_null;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be null',
'{{name}} must not be null',
diff --git a/library/Rules/Number.php b/library/Rules/Number.php
index 228911451..816697f0c 100644
--- a/library/Rules/Number.php
+++ b/library/Rules/Number.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_nan;
use function is_numeric;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid number',
'{{name}} must not be a number',
diff --git a/library/Rules/NumericVal.php b/library/Rules/NumericVal.php
index bab493584..223b8dee6 100644
--- a/library/Rules/NumericVal.php
+++ b/library/Rules/NumericVal.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_numeric;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a numeric value',
'{{name}} must not be a numeric value',
diff --git a/library/Rules/ObjectType.php b/library/Rules/ObjectType.php
index 3f18e8e1b..9dd4ba9e9 100644
--- a/library/Rules/ObjectType.php
+++ b/library/Rules/ObjectType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_object;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an object',
'{{name}} must not be an object',
diff --git a/library/Rules/Odd.php b/library/Rules/Odd.php
index ea615d8a6..2842a57d3 100644
--- a/library/Rules/Odd.php
+++ b/library/Rules/Odd.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use const FILTER_VALIDATE_INT;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an odd number',
'{{name}} must be an even number',
diff --git a/library/Rules/OneOf.php b/library/Rules/OneOf.php
index f0442b8dd..567d5c0bf 100644
--- a/library/Rules/OneOf.php
+++ b/library/Rules/OneOf.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
@@ -17,6 +18,7 @@
use function array_map;
use function array_reduce;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'Only one of these rules must pass for {{name}}',
'Only one of these rules must not pass for {{name}}',
diff --git a/library/Rules/PerfectSquare.php b/library/Rules/PerfectSquare.php
index 353c0f37b..53b806661 100644
--- a/library/Rules/PerfectSquare.php
+++ b/library/Rules/PerfectSquare.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function is_numeric;
use function sqrt;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a perfect square number',
'{{name}} must not be a perfect square number',
diff --git a/library/Rules/Pesel.php b/library/Rules/Pesel.php
index be145726c..6095ff8ba 100644
--- a/library/Rules/Pesel.php
+++ b/library/Rules/Pesel.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_scalar;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid PESEL',
'{{name}} must not be a valid PESEL',
diff --git a/library/Rules/Phone.php b/library/Rules/Phone.php
index ec02dea62..03e57d08a 100644
--- a/library/Rules/Phone.php
+++ b/library/Rules/Phone.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
@@ -21,6 +22,7 @@
use function class_exists;
use function is_scalar;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid telephone number',
'{{name}} must not be a valid telephone number',
diff --git a/library/Rules/PhpLabel.php b/library/Rules/PhpLabel.php
index b9e2cd3b4..21298dd13 100644
--- a/library/Rules/PhpLabel.php
+++ b/library/Rules/PhpLabel.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_string;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid PHP label',
'{{name}} must not be a valid PHP label',
diff --git a/library/Rules/Pis.php b/library/Rules/Pis.php
index 5dd58a8b1..b9ccd78fd 100644
--- a/library/Rules/Pis.php
+++ b/library/Rules/Pis.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use function preg_match;
use function preg_replace;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid PIS number',
'{{name}} must not be a valid PIS number',
diff --git a/library/Rules/PolishIdCard.php b/library/Rules/PolishIdCard.php
index a510fdd06..791f020a0 100644
--- a/library/Rules/PolishIdCard.php
+++ b/library/Rules/PolishIdCard.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -19,6 +20,7 @@
/**
* @see https://en.wikipedia.org/wiki/Polish_identity_card
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid Polish Identity Card number',
'{{name}} must not be a valid Polish Identity Card number',
diff --git a/library/Rules/PortugueseNif.php b/library/Rules/PortugueseNif.php
index 1f265d67e..5df64abb2 100644
--- a/library/Rules/PortugueseNif.php
+++ b/library/Rules/PortugueseNif.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -25,6 +26,7 @@
/**
* @see https://pt.wikipedia.org/wiki/N%C3%BAmero_de_identifica%C3%A7%C3%A3o_fiscal
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a Portuguese NIF',
'{{name}} must not be a Portuguese NIF',
diff --git a/library/Rules/Positive.php b/library/Rules/Positive.php
index 8f7254207..626b47039 100644
--- a/library/Rules/Positive.php
+++ b/library/Rules/Positive.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_numeric;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a positive number',
'{{name}} must not be a positive number',
diff --git a/library/Rules/PostalCode.php b/library/Rules/PostalCode.php
index cc4afa1f0..495801102 100644
--- a/library/Rules/PostalCode.php
+++ b/library/Rules/PostalCode.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
@@ -16,6 +17,7 @@
/**
* @see http://download.geonames.org/export/dump/countryInfo.txt
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid postal code on {{countryCode}}',
'{{name}} must not be a valid postal code on {{countryCode}}',
diff --git a/library/Rules/PrimeNumber.php b/library/Rules/PrimeNumber.php
index a26f050f6..7edb0269e 100644
--- a/library/Rules/PrimeNumber.php
+++ b/library/Rules/PrimeNumber.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function is_numeric;
use function sqrt;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a prime number',
'{{name}} must not be a prime number',
diff --git a/library/Rules/Printable.php b/library/Rules/Printable.php
index bd2ad4784..e2fdce137 100644
--- a/library/Rules/Printable.php
+++ b/library/Rules/Printable.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_print;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only printable characters',
'{{name}} must not contain printable characters',
diff --git a/library/Rules/Property.php b/library/Rules/Property.php
index 90b1180c9..ea057540c 100644
--- a/library/Rules/Property.php
+++ b/library/Rules/Property.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Helpers\CanExtractPropertyValue;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Binder;
use Respect\Validation\Rules\Core\Wrapper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Property extends Wrapper
{
use CanExtractPropertyValue;
diff --git a/library/Rules/PropertyExists.php b/library/Rules/PropertyExists.php
index b517df961..438b8a20f 100644
--- a/library/Rules/PropertyExists.php
+++ b/library/Rules/PropertyExists.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use ReflectionObject;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -16,6 +17,7 @@
use function is_object;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be present',
'{{name}} must not be present',
diff --git a/library/Rules/PropertyOptional.php b/library/Rules/PropertyOptional.php
index 27a9e2c6b..88cd90eda 100644
--- a/library/Rules/PropertyOptional.php
+++ b/library/Rules/PropertyOptional.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Helpers\CanExtractPropertyValue;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Binder;
use Respect\Validation\Rules\Core\Wrapper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class PropertyOptional extends Wrapper
{
use CanExtractPropertyValue;
diff --git a/library/Rules/PublicDomainSuffix.php b/library/Rules/PublicDomainSuffix.php
index e6442cb00..c46a503bd 100644
--- a/library/Rules/PublicDomainSuffix.php
+++ b/library/Rules/PublicDomainSuffix.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Helpers\CanValidateUndefined;
use Respect\Validation\Helpers\DomainInfo;
use Respect\Validation\Message\Template;
@@ -20,6 +21,7 @@
use function is_scalar;
use function strtoupper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a public domain suffix',
'{{name}} must not be a public domain suffix',
diff --git a/library/Rules/Punct.php b/library/Rules/Punct.php
index 9a2767585..28b30cb8a 100644
--- a/library/Rules/Punct.php
+++ b/library/Rules/Punct.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_punct;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only punctuation characters',
'{{name}} must not contain punctuation characters',
diff --git a/library/Rules/Readable.php b/library/Rules/Readable.php
index 4baa60094..5cdb25b20 100644
--- a/library/Rules/Readable.php
+++ b/library/Rules/Readable.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Psr\Http\Message\StreamInterface;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use function is_readable;
use function is_string;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be readable',
'{{name}} must not be readable',
diff --git a/library/Rules/Regex.php b/library/Rules/Regex.php
index 2870358e3..1364d6739 100644
--- a/library/Rules/Regex.php
+++ b/library/Rules/Regex.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -16,6 +17,7 @@
use function is_scalar;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must match the pattern `{{regex|raw}}`',
'{{name}} must not match the pattern `{{regex|raw}}`',
diff --git a/library/Rules/ResourceType.php b/library/Rules/ResourceType.php
index 99268b001..2b545e187 100644
--- a/library/Rules/ResourceType.php
+++ b/library/Rules/ResourceType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_resource;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a resource',
'{{name}} must not be a resource',
diff --git a/library/Rules/Roman.php b/library/Rules/Roman.php
index d7400fc0c..7f9f421ca 100644
--- a/library/Rules/Roman.php
+++ b/library/Rules/Roman.php
@@ -9,9 +9,11 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid Roman numeral',
'{{name}} must not be a valid Roman numeral',
diff --git a/library/Rules/ScalarVal.php b/library/Rules/ScalarVal.php
index 8cd8c9dd3..50524404b 100644
--- a/library/Rules/ScalarVal.php
+++ b/library/Rules/ScalarVal.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_scalar;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a scalar value',
'{{name}} must not be a scalar value',
diff --git a/library/Rules/Size.php b/library/Rules/Size.php
index 03605c11e..53d10479a 100644
--- a/library/Rules/Size.php
+++ b/library/Rules/Size.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
@@ -23,6 +24,7 @@
use function is_string;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be between {{minSize}} and {{maxSize}}',
'{{name}} must not be between {{minSize}} and {{maxSize}}',
diff --git a/library/Rules/Slug.php b/library/Rules/Slug.php
index f61e1622f..c47f618d5 100644
--- a/library/Rules/Slug.php
+++ b/library/Rules/Slug.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function mb_strstr;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid slug',
'{{name}} must not be a valid slug',
diff --git a/library/Rules/Sorted.php b/library/Rules/Sorted.php
index 2f6824efe..5979083e1 100644
--- a/library/Rules/Sorted.php
+++ b/library/Rules/Sorted.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -20,6 +21,7 @@
use function is_string;
use function str_split;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be sorted in ascending order',
'{{name}} must not be sorted in ascending order',
diff --git a/library/Rules/Space.php b/library/Rules/Space.php
index c94a71fdf..c4831b8d2 100644
--- a/library/Rules/Space.php
+++ b/library/Rules/Space.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_space;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only space characters',
'{{name}} must not contain space characters',
diff --git a/library/Rules/StartsWith.php b/library/Rules/StartsWith.php
index b0f800f2c..500131b11 100644
--- a/library/Rules/StartsWith.php
+++ b/library/Rules/StartsWith.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -19,6 +20,7 @@
use function mb_strpos;
use function reset;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must start with {{startValue}}',
'{{name}} must not start with {{startValue}}',
diff --git a/library/Rules/StringType.php b/library/Rules/StringType.php
index 879112bf9..de112cdad 100644
--- a/library/Rules/StringType.php
+++ b/library/Rules/StringType.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_string;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a string',
'{{name}} must not be a string',
diff --git a/library/Rules/StringVal.php b/library/Rules/StringVal.php
index 6685d2e80..6058987e7 100644
--- a/library/Rules/StringVal.php
+++ b/library/Rules/StringVal.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function is_scalar;
use function method_exists;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a string value',
'{{name}} must not be a string value',
diff --git a/library/Rules/SubdivisionCode.php b/library/Rules/SubdivisionCode.php
index 5efb796f6..fdc810f3f 100644
--- a/library/Rules/SubdivisionCode.php
+++ b/library/Rules/SubdivisionCode.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Exceptions\MissingComposerDependencyException;
use Respect\Validation\Helpers\CanValidateUndefined;
@@ -20,6 +21,7 @@
use function class_exists;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a subdivision code of {{countryName|trans}}',
'{{name}} must not be a subdivision code of {{countryName|trans}}',
diff --git a/library/Rules/Subset.php b/library/Rules/Subset.php
index 801c4d777..3d7069aee 100644
--- a/library/Rules/Subset.php
+++ b/library/Rules/Subset.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rules\Core\Standard;
@@ -16,6 +17,7 @@
use function array_diff;
use function is_array;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be subset of {{superset}}',
'{{name}} must not be subset of {{superset}}',
diff --git a/library/Rules/SymbolicLink.php b/library/Rules/SymbolicLink.php
index b7deaea64..59f2a7500 100644
--- a/library/Rules/SymbolicLink.php
+++ b/library/Rules/SymbolicLink.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use SplFileInfo;
@@ -16,6 +17,7 @@
use function is_link;
use function is_string;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a symbolic link',
'{{name}} must not be a symbolic link',
diff --git a/library/Rules/Time.php b/library/Rules/Time.php
index 886c87de7..161e65f67 100644
--- a/library/Rules/Time.php
+++ b/library/Rules/Time.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
@@ -20,6 +21,7 @@
use function preg_match;
use function strtotime;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid time in the format {{sample}}',
'{{name}} must not be a valid time in the format {{sample}}',
diff --git a/library/Rules/Tld.php b/library/Rules/Tld.php
index b117be5ec..0d7512253 100644
--- a/library/Rules/Tld.php
+++ b/library/Rules/Tld.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -16,6 +17,7 @@
use function is_scalar;
use function mb_strtoupper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid top-level domain name',
'{{name}} must not be a valid top-level domain name',
diff --git a/library/Rules/TrueVal.php b/library/Rules/TrueVal.php
index 01d79146c..b70681070 100644
--- a/library/Rules/TrueVal.php
+++ b/library/Rules/TrueVal.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use const FILTER_NULL_ON_FAILURE;
use const FILTER_VALIDATE_BOOLEAN;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must evaluate to `true`',
'{{name}} must not evaluate to `true`',
diff --git a/library/Rules/UndefOr.php b/library/Rules/UndefOr.php
index 8c2bae7d2..2938e2b08 100644
--- a/library/Rules/UndefOr.php
+++ b/library/Rules/UndefOr.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Helpers\CanValidateUndefined;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -16,6 +17,7 @@
use function array_map;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'or must be undefined',
'and must not be undefined',
diff --git a/library/Rules/Unique.php b/library/Rules/Unique.php
index 13dd4342e..e3c047722 100644
--- a/library/Rules/Unique.php
+++ b/library/Rules/Unique.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use const SORT_REGULAR;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must not contain duplicates',
'{{name}} must contain duplicates',
diff --git a/library/Rules/Uploaded.php b/library/Rules/Uploaded.php
index 64a58373c..ee49c98b0 100644
--- a/library/Rules/Uploaded.php
+++ b/library/Rules/Uploaded.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Psr\Http\Message\UploadedFileInterface;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use function is_scalar;
use function is_uploaded_file;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be an uploaded file',
'{{name}} must not be an uploaded file',
diff --git a/library/Rules/Uppercase.php b/library/Rules/Uppercase.php
index bc4e8f5fe..1b9c25e97 100644
--- a/library/Rules/Uppercase.php
+++ b/library/Rules/Uppercase.php
@@ -9,12 +9,14 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
use function is_string;
use function mb_strtoupper;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must contain only uppercase letters',
'{{name}} must not contain only uppercase letters',
diff --git a/library/Rules/Url.php b/library/Rules/Url.php
index 74dd38e53..16c9c4be8 100644
--- a/library/Rules/Url.php
+++ b/library/Rules/Url.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Envelope;
use const FILTER_VALIDATE_URL;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a URL',
'{{name}} must not be a URL',
diff --git a/library/Rules/Uuid.php b/library/Rules/Uuid.php
index 8957a65c9..38ee6924a 100644
--- a/library/Rules/Uuid.php
+++ b/library/Rules/Uuid.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -18,6 +19,7 @@
use function preg_match;
use function sprintf;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid UUID',
'{{name}} must not be a valid UUID',
diff --git a/library/Rules/Version.php b/library/Rules/Version.php
index 35d084497..d1750c59f 100644
--- a/library/Rules/Version.php
+++ b/library/Rules/Version.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -18,6 +19,7 @@
/**
* @see http://semver.org/
*/
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a version',
'{{name}} must not be a version',
diff --git a/library/Rules/VideoUrl.php b/library/Rules/VideoUrl.php
index 7ed7c9de2..758f48594 100644
--- a/library/Rules/VideoUrl.php
+++ b/library/Rules/VideoUrl.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
@@ -19,6 +20,7 @@
use function mb_strtolower;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be a valid video URL',
'{{name}} must not be a valid video URL',
diff --git a/library/Rules/Vowel.php b/library/Rules/Vowel.php
index dc220edc5..47cf969d6 100644
--- a/library/Rules/Vowel.php
+++ b/library/Rules/Vowel.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function preg_match;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must consist of vowels only',
'{{name}} must not consist of vowels only',
diff --git a/library/Rules/When.php b/library/Rules/When.php
index f9a356ff6..558bb6987 100644
--- a/library/Rules/When.php
+++ b/library/Rules/When.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Binder;
use Respect\Validation\Rules\Core\Standard;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class When extends Standard
{
private readonly Rule $else;
diff --git a/library/Rules/Writable.php b/library/Rules/Writable.php
index 7d00b06c9..d9a1f78e3 100644
--- a/library/Rules/Writable.php
+++ b/library/Rules/Writable.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Psr\Http\Message\StreamInterface;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -17,6 +18,7 @@
use function is_string;
use function is_writable;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be writable',
'{{name}} must not be writable',
diff --git a/library/Rules/Xdigit.php b/library/Rules/Xdigit.php
index 30a8ed94f..9cf828c0f 100644
--- a/library/Rules/Xdigit.php
+++ b/library/Rules/Xdigit.php
@@ -9,11 +9,13 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\FilteredString;
use function ctype_xdigit;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must only contain hexadecimal digits',
'{{name}} must not only contain hexadecimal digits',
diff --git a/library/Rules/Yes.php b/library/Rules/Yes.php
index 8e6c6d885..75b45a135 100644
--- a/library/Rules/Yes.php
+++ b/library/Rules/Yes.php
@@ -9,6 +9,7 @@
namespace Respect\Validation\Rules;
+use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;
@@ -18,6 +19,7 @@
use const YESEXPR;
+#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'{{name}} must be similar to "Yes"',
'{{name}} must not be similar to "Yes"',
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 73346a727..3257a8c0d 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -18,6 +18,14 @@
tests/integration/
+
+
+
+
+
+
+
+
tests/integration/
diff --git a/tests/fixtures/data-provider.php b/tests/fixtures/data-provider.php
index 607a8de15..80d3df00d 100644
--- a/tests/fixtures/data-provider.php
+++ b/tests/fixtures/data-provider.php
@@ -7,6 +7,7 @@
declare(strict_types=1);
+use Respect\Validation\Test\Stubs\WithAttributes;
use Respect\Validation\Test\Stubs\WithProperties;
use Respect\Validation\Test\Stubs\WithStaticProperties;
use Respect\Validation\Test\Stubs\WithUninitialized;
@@ -98,45 +99,55 @@
// Array values
'ArrayObject' => [
'value' => [new ArrayObject([1, 2, 3])],
- 'tags' => ['objectType', 'iterableType', 'ArrayObject', 'countable'],
+ 'tags' => ['objectType', 'iterableType', 'ArrayObject', 'countable', 'withoutAttributes'],
],
'empty ArrayObject' => [
'value' => [new ArrayObject([])],
- 'tags' => ['objectType', 'iterableType', 'ArrayObject', 'countable', 'empty'],
+ 'tags' => ['objectType', 'iterableType', 'ArrayObject', 'countable', 'empty', 'withoutAttributes'],
],
// Iterable types
'generator' => [
'value' => [(static fn() => yield 7)()], // phpcs:ignore
- 'tags' => ['objectType', 'iterableType', 'generator'],
+ 'tags' => ['objectType', 'iterableType', 'generator', 'withoutAttributes'],
],
'empty generator' => [
'value' => [(static fn() => yield from [])()],
- 'tags' => ['objectType', 'iterableType', 'generator', 'empty'],
+ 'tags' => ['objectType', 'iterableType', 'generator', 'empty', 'withoutAttributes'],
],
// Callable types
'closure' => [
'value' => [static fn() => 'foo'],
- 'tags' => ['objectType', 'callable'],
+ 'tags' => ['objectType', 'callable', 'withoutAttributes'],
],
// Object types
'object' => [
'value' => [new stdClass()],
- 'tags' => ['objectType'],
+ 'tags' => ['objectType', 'withoutAttributes'],
],
'object with properties' => [
'value' => [new WithProperties()],
- 'tags' => ['objectType'],
+ 'tags' => ['objectType', 'withoutAttributes'],
],
'object with uninitialized properties' => [
'value' => [new WithUninitialized()],
- 'tags' => ['objectType'],
+ 'tags' => ['objectType', 'withoutAttributes'],
],
'object with static properties' => [
'value' => [new WithStaticProperties()],
- 'tags' => ['objectType'],
+ 'tags' => ['objectType', 'withoutAttributes'],
+ ],
+ 'object with Rule attributes' => [
+ 'value' => [new WithAttributes('John Doe', 'john.doe@gmail.com', '1912-06-23')],
+ 'tags' => ['objectType', 'withAttributes'],
+ ],
+ 'anonymous class' => [
+ 'value' => [new class {
+ },
+ ],
+ 'tags' => ['objectType', 'withoutAttributes'],
],
// Resource types
diff --git a/tests/integration/assert-with-attributes.phpt b/tests/integration/assert-with-properties.phpt
similarity index 100%
rename from tests/integration/assert-with-attributes.phpt
rename to tests/integration/assert-with-properties.phpt
diff --git a/tests/integration/rules/attributes.phpt b/tests/integration/rules/attributes.phpt
new file mode 100644
index 000000000..236d1aeb8
--- /dev/null
+++ b/tests/integration/rules/attributes.phpt
@@ -0,0 +1,78 @@
+--FILE--
+ [v::attributes(), new WithAttributes('', 'john.doe@gmail.com', '2024-06-23')],
+ 'Inverted' => [v::attributes(), new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', '+1234567890')],
+ 'Not an object' => [v::attributes(), []],
+ 'Nullable' => [v::attributes(), new WithAttributes('John Doe', 'john.doe@gmail.com', '2024-06-23', 'not a phone number')],
+ 'Multiple attributes, all failed' => [v::attributes(), new WithAttributes('', 'not an email', 'not a date', 'not a phone number')],
+ 'Multiple attributes, one failed' => [v::attributes(), new WithAttributes('John Doe', 'john.doe@gmail.com', '22 years ago')],
+]);
+?>
+--EXPECTF--
+Default
+⎺⎺⎺⎺⎺⎺⎺
+name must not be empty
+- name must not be empty
+[
+ 'name' => 'name must not be empty',
+]
+
+Inverted
+⎺⎺⎺⎺⎺⎺⎺⎺
+phone must be a valid telephone number or must be null
+- phone must be a valid telephone number or must be null
+[
+ 'phone' => 'phone must be a valid telephone number or must be null',
+]
+
+Not an object
+⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
+`[]` must be an object
+- `[]` must be an object
+[
+ 'attributes' => '`[]` must be an object',
+]
+
+Nullable
+⎺⎺⎺⎺⎺⎺⎺⎺
+phone must be a valid telephone number or must be null
+- phone must be a valid telephone number or must be null
+[
+ 'phone' => 'phone must be a valid telephone number or must be null',
+]
+
+Multiple attributes, all failed
+⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
+name must not be empty
+- All of the required rules must pass for `Respect\Validation\Test\Stubs\WithAttributes { +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }`
+ - name must not be empty
+ - email must be a valid email address
+ - All of the required rules must pass for birthdate
+ - birthdate must be a valid date in the format "%d-%d-%d"
+ - For comparison with now, birthdate must be a valid datetime
+ - phone must be a valid telephone number or must be null
+[
+ '__root__' => 'All of the required rules must pass for `Respect\\Validation\\Test\\Stubs\\WithAttributes { +$name="" +$email="not an email" +$birthdate="not a date" +$phone ... }`',
+ 'name' => 'name must not be empty',
+ 'email' => 'email must be a valid email address',
+ 'birthdate' => [
+ '__root__' => 'All of the required rules must pass for birthdate',
+ 'date' => 'birthdate must be a valid date in the format "%d-%d-%d"',
+ 'dateTimeDiffLessThanOrEqual' => 'For comparison with now, birthdate must be a valid datetime',
+ ],
+ 'phone' => 'phone must be a valid telephone number or must be null',
+]
+
+Multiple attributes, one failed
+⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
+birthdate must be a valid date in the format "%d-%d-%d"
+- birthdate must be a valid date in the format "%d-%d-%d"
+[
+ 'birthdate' => 'birthdate must be a valid date in the format "%d-%d-%d"',
+]
diff --git a/tests/library/Stubs/WithAttributes.php b/tests/library/Stubs/WithAttributes.php
new file mode 100644
index 000000000..19f49a6fe
--- /dev/null
+++ b/tests/library/Stubs/WithAttributes.php
@@ -0,0 +1,34 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+namespace Respect\Validation\Test\Stubs;
+
+use Respect\Validation\Rules\Date;
+use Respect\Validation\Rules\DateTimeDiff;
+use Respect\Validation\Rules\Email;
+use Respect\Validation\Rules\LessThanOrEqual;
+use Respect\Validation\Rules\NotEmpty;
+use Respect\Validation\Rules\Phone;
+
+final class WithAttributes
+{
+ public function __construct(
+ #[NotEmpty]
+ public string $name,
+ #[Email]
+ public string $email,
+ #[Date('Y-m-d')]
+ #[DateTimeDiff('years', new LessThanOrEqual(25))]
+ public string $birthdate,
+ #[Phone]
+ public ?string $phone = null,
+ public ?string $address = null,
+ ) {
+ }
+}
diff --git a/tests/library/TestCase.php b/tests/library/TestCase.php
index e1a3281d3..6ed17877c 100644
--- a/tests/library/TestCase.php
+++ b/tests/library/TestCase.php
@@ -146,6 +146,16 @@ public static function providerForNonArrayTypes(): DataProvider
return self::providerForAnyValues()->without('arrayType');
}
+ public static function providerForNonObjectTypes(): DataProvider
+ {
+ return self::providerForAnyValues()->without('objectType');
+ }
+
+ public static function providerForObjectTypesWithoutAttributes(): DataProvider
+ {
+ return self::providerForAnyValues()->with('objectType')->without('withAttributes');
+ }
+
public static function providerForNonArrayValues(): DataProvider
{
return self::providerForAnyValues()->without('arrayType', 'ArrayObject');
diff --git a/tests/unit/Rules/AttributesTest.php b/tests/unit/Rules/AttributesTest.php
new file mode 100644
index 000000000..f52b5728e
--- /dev/null
+++ b/tests/unit/Rules/AttributesTest.php
@@ -0,0 +1,79 @@
+
+ * SPDX-License-Identifier: MIT
+ */
+
+declare(strict_types=1);
+
+namespace Respect\Validation\Rules;
+
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\Test;
+use Respect\Validation\Test\Stubs\WithAttributes;
+use Respect\Validation\Test\TestCase;
+
+#[Group(' rule')]
+#[CoversClass(Attributes::class)]
+final class AttributesTest extends TestCase
+{
+ #[Test]
+ #[DataProvider('providerForNonObjectTypes')]
+ public function shouldNotEvaluateNonObjects(mixed $input): void
+ {
+ self::assertInvalidInput(new Attributes(), $input);
+ }
+
+ #[Test]
+ #[DataProvider('providerForObjectTypesWithoutAttributes')]
+ public function shouldEvaluateObjectsWithoutPhpAttributes(object $input): void
+ {
+ self::assertValidInput(new Attributes(), $input);
+ }
+
+ #[Test]
+ #[DataProvider('providerForObjectsWithValidPropertyValues')]
+ public function shouldNotEvaluateObjectsWithValidPropertyValues(object $input): void
+ {
+ self::assertValidInput(new Attributes(), $input);
+ }
+
+ #[Test]
+ #[DataProvider('providerForObjectsWithInvalidPropertyValues')]
+ public function shouldNotEvaluateObjectsWithInvalidPropertyValues(object $input): void
+ {
+ self::assertInvalidInput(new Attributes(), $input);
+ }
+
+ /** @return array */
+ public static function providerForObjectsWithValidPropertyValues(): array
+ {
+ return [
+ 'All' => [
+ new WithAttributes(
+ 'John Doe',
+ 'john.doe@gmail.com',
+ '2020-06-23',
+ '+31206241111',
+ 'Amstel 1 1011 PN AMSTERDAM Noord-Holland'
+ ),
+ ],
+ 'Only required' => [new WithAttributes('Jane Doe', 'janedoe@yahoo.com', '2017-11-30')],
+ ];
+ }
+
+ /** @return array */
+ public static function providerForObjectsWithInvalidPropertyValues(): array
+ {
+ return [
+ [new WithAttributes('', 'not an email', 'not a date', 'not a phone number')],
+ [new WithAttributes('', 'john.doe@gmail.com', '1912-06-23', '+1234567890')],
+ [new WithAttributes('John Doe', 'not an email', '1912-06-23', '+1234567890')],
+ [new WithAttributes('John Doe', 'john.doe@gmail.com', 'not a date', '+1234567890')],
+ [new WithAttributes('John Doe', 'john.doe@gmail.com', '1912-06-23', 'not a phone number')],
+ ];
+ }
+}