Skip to content

Commit

Permalink
Create "Attribute" rule
Browse files Browse the repository at this point in the history
  • Loading branch information
henriquemoody committed Dec 11, 2024
1 parent 4a16ad3 commit 3dae64a
Show file tree
Hide file tree
Showing 167 changed files with 476 additions and 13 deletions.
29 changes: 16 additions & 13 deletions docs/02-feature-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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).
3 changes: 3 additions & 0 deletions docs/09-list-of-rules-by-category.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@

## Objects

- [Attributes](rules/Attributes.md)
- [Instance](rules/Instance.md)
- [ObjectType](rules/ObjectType.md)
- [Property](rules/Property.md)
Expand Down Expand Up @@ -248,6 +249,7 @@

## Structures

- [Attributes](rules/Attributes.md)
- [Key](rules/Key.md)
- [KeyExists](rules/KeyExists.md)
- [KeyOptional](rules/KeyOptional.md)
Expand Down Expand Up @@ -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)
Expand Down
81 changes: 81 additions & 0 deletions docs/rules/Attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 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\Phone]
public readonly string $phone
) {
}
}
```

Here is how you can validate the attributes of the object:

```php
v::attributes()->assert(new Person('John Doe', '[email protected]', '+1234567890'));
// No exception

v::attributes()->assert(new Person('', '[email protected]', '+1234567890'));
// Message: name must not be empty

v::attributes()->isValid(new Person('John Doe', 'not an email', '+1234567890'));
// Message: email must be a valid email address

v::attributes()->isValid(new Person('John Doe', '[email protected]', 'not a phone number'));
// Message: phone must be a valid telephone number

v::attributes()->assert(new Person('', 'not an email', 'not a phone number'));
// Full message:
// - The properties of `Person { +$name="" +$email="not an email" +$phone="not a phone number" }` must be valid
// - name must not be empty
// - email must be a valid email address
// - phone must be a valid telephone number
```

## Templates

### `Attributes::TEMPLATE_STANDARD`

| Mode | Template |
|------------|----------------------------------------------|
| `default` | The properties of {{name}} must be valid |
| `inverted` | The properties of {{name}} must not be valid |

## Template placeholders

| Placeholder | Description |
|-------------|------------------------------------------------------------------|
| `name` | The validated input or the custom validator name (if specified). |

## Categorization

- Objects
- Structures

## Changelog

| Version | Description |
|--------:|-----------------------------------------|
| 3.0.0 | Created |

***
See also:

- [ObjectType](ObjectType.md)
- [Property](Property.md)
- [PropertyExists](PropertyExists.md)
- [PropertyOptional](PropertyOptional.md)
1 change: 1 addition & 0 deletions docs/rules/ObjectType.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/Property.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/PropertyExists.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/PropertyOptional.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/AllOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Respect\Validation\Rules;

use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
Expand All @@ -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}}',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/Alnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/Alpha.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/AlwaysInvalid.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/AlwaysValid.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/AnyOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Respect\Validation\Rules;

use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
Expand All @@ -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}}',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/ArrayVal.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
48 changes: 48 additions & 0 deletions library/Rules/Attributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Rules;

use Attribute;
use ReflectionClass;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Rule;
use Respect\Validation\Rules\Core\Standard;

use function array_reduce;
use function is_object;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
#[Template(
'The properties of {{name}} must be valid',
'The properties of {{name}} must not be valid',
)]
final class Attributes extends Standard
{
public function evaluate(mixed $input): Result
{
if (!is_object($input)) {
return Result::failed($input, $this);
}

$reflection = new ReflectionClass($input);
$properties = $reflection->getProperties();
$children = [];
foreach ($properties as $property) {
foreach ($property->getAttributes(Rule::class) as $propertyAttribute) {
$children[] = (new Property($property->getName(), $propertyAttribute->newInstance()))->evaluate($input);
}
}

$isValid = array_reduce($children, static fn (bool $carry, Result $result) => $carry && $result->isValid, true);

return (new Result($isValid, $input, $this))->withChildren(...$children);
}
}
2 changes: 2 additions & 0 deletions library/Rules/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Respect\Validation\Rules;

use Attribute;
use Respect\Validation\Exceptions\InvalidRuleConstructorException;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
Expand All @@ -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}}',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/Base64.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@

namespace Respect\Validation\Rules;

use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;

use function is_string;
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',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/Between.php
Original file line number Diff line number Diff line change
Expand Up @@ -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}}',
Expand Down
2 changes: 2 additions & 0 deletions library/Rules/BetweenExclusive.php
Original file line number Diff line number Diff line change
Expand Up @@ -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}}',
Expand Down
Loading

0 comments on commit 3dae64a

Please sign in to comment.