Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/migrating-from-v2-to-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ final class Custom implements Validator
Base classes available in `Respect\Validation\Validators\Core`:

- `Simple` - For validators with simple boolean logic
- `Composite` - For validators that combine multiple validators
- `LogicComposite` - For validators that combine two or more validators
- `Envelope` - For validators that modify how another validator works

---
Expand Down
6 changes: 4 additions & 2 deletions docs/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ In this page you will find a list of validators by their category.

**Comparisons**: [All][] - [Between][] - [BetweenExclusive][] - [Equals][] - [Equivalent][] - [GreaterThan][] - [GreaterThanOrEqual][] - [Identical][] - [In][] - [Length][] - [LessThan][] - [LessThanOrEqual][] - [Max][] - [Min][]

**Composite**: [AllOf][] - [AnyOf][] - [Circuit][] - [NoneOf][] - [OneOf][]
**Composite**: [AllOf][] - [AnyOf][] - [Circuit][] - [Composite][] - [NoneOf][] - [OneOf][]

**Conditions**: [Circuit][] - [Not][] - [When][]

Expand All @@ -41,7 +41,7 @@ In this page you will find a list of validators by their category.

**Miscellaneous**: [Blank][] - [Falsy][] - [Masked][] - [Named][] - [Templated][] - [Undef][]

**Nesting**: [AllOf][] - [AnyOf][] - [Call][] - [Circuit][] - [Each][] - [Key][] - [KeySet][] - [Lazy][] - [NoneOf][] - [Not][] - [NullOr][] - [OneOf][] - [Property][] - [PropertyOptional][] - [UndefOr][] - [When][]
**Nesting**: [AllOf][] - [AnyOf][] - [Call][] - [Circuit][] - [Composite][] - [Each][] - [Key][] - [KeySet][] - [Lazy][] - [NoneOf][] - [Not][] - [NullOr][] - [OneOf][] - [Property][] - [PropertyOptional][] - [UndefOr][] - [When][]

**Numbers**: [Base][] - [Decimal][] - [Digit][] - [Even][] - [Factor][] - [Finite][] - [FloatType][] - [FloatVal][] - [Infinite][] - [IntType][] - [IntVal][] - [Multiple][] - [Negative][] - [Number][] - [NumericVal][] - [Odd][] - [Positive][] - [Roman][]

Expand Down Expand Up @@ -82,6 +82,7 @@ In this page you will find a list of validators by their category.
- [Circuit][] - `v::circuit(v::intVal(), v::floatVal())->assert(15);`
- [Cnh][] - `v::cnh()->assert('02650306461');`
- [Cnpj][] - `v::cnpj()->assert('00394460005887');`
- [Composite][] - `v::composite()->assert('anything');`
- [Consonant][] - `v::consonant()->assert('xkcd');`
- [Contains][] - `v::contains('ipsum')->assert('lorem ipsum');`
- [ContainsAny][] - `v::containsAny(['lorem', 'dolor'])->assert('lorem ipsum');`
Expand Down Expand Up @@ -238,6 +239,7 @@ In this page you will find a list of validators by their category.
[Circuit]: validators/Circuit.md "Validates the input against a series of validators until the first fails."
[Cnh]: validators/Cnh.md "Validates a Brazilian driver's license."
[Cnpj]: validators/Cnpj.md "Validates if the input is a Brazilian National Registry of Legal Entities (CNPJ) number."
[Composite]: validators/Composite.md "Consolidates zero or more validators into a single validator."
[Consonant]: validators/Consonant.md "Validates if the input contains only consonants."
[Contains]: validators/Contains.md "Validates if the input contains some value."
[ContainsAny]: validators/ContainsAny.md "Validates if the input contains at least one of defined values"
Expand Down
64 changes: 64 additions & 0 deletions docs/validators/Composite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!--
SPDX-FileCopyrightText: (c) Respect Project Contributors
SPDX-License-Identifier: MIT
-->

# Composite

- `Composite()`
- `Composite(Validator ...$validators)`

Consolidates zero or more validators into a single validator.

- When no validators are provided, it acts as [AlwaysValid](AlwaysValid.md).
- When a single validator is provided, it acts as a pass-through.
- When multiple validators are provided, they are combined using [AllOf](AllOf.md), meaning all validators must pass for the input to be considered valid.

```php
v::composite()->assert('anything');
// Validation passes successfully

v::composite(v::intType())->assert(42);
// Validation passes successfully

v::composite(v::intType(), v::positive(), v::lessThan(100))->assert(42);
// Validation passes successfully

v::composite(v::intType(), v::positive())->assert(-5);
// → -5 must be a positive number
```

## Use cases

This validator is useful for dynamically building validation chains or when you
have a variable number of validators that need to be applied together.

```php
$validators = [v::stringType(), v::notSpaced()];

if (true) { // some condition
$validators[] = v::email();
}

v::composite(...$validators)->assert('respectpanda#example.com');
// → "respectpanda#example.com" must be a valid email address
```

## Categorization

- Composite
- Nesting

## Changelog

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

## See Also

- [AllOf](AllOf.md)
- [AlwaysValid](AlwaysValid.md)
- [AnyOf](AnyOf.md)
- [NoneOf](NoneOf.md)
- [OneOf](OneOf.md)
2 changes: 2 additions & 0 deletions src/Mixins/AllBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public static function allCnh(): Chain;

public static function allCnpj(): Chain;

public static function allComposite(Validator ...$validators): Chain;

public static function allConsonant(string ...$additionalChars): Chain;

public static function allContains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/AllChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public function allCnh(): Chain;

public function allCnpj(): Chain;

public function allComposite(Validator ...$validators): Chain;

public function allConsonant(string ...$additionalChars): Chain;

public function allContains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public static function cnh(): Chain;

public static function cnpj(): Chain;

public static function composite(Validator ...$validators): Chain;

public static function consonant(string ...$additionalChars): Chain;

public static function contains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/Chain.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public function cnh(): Chain;

public function cnpj(): Chain;

public function composite(Validator ...$validators): Chain;

public function consonant(string ...$additionalChars): Chain;

public function contains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/KeyBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public static function keyCnh(int|string $key): Chain;

public static function keyCnpj(int|string $key): Chain;

public static function keyComposite(int|string $key, Validator ...$validators): Chain;

public static function keyConsonant(int|string $key, string ...$additionalChars): Chain;

public static function keyContains(int|string $key, mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/KeyChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public function keyCnh(int|string $key): Chain;

public function keyCnpj(int|string $key): Chain;

public function keyComposite(int|string $key, Validator ...$validators): Chain;

public function keyConsonant(int|string $key, string ...$additionalChars): Chain;

public function keyContains(int|string $key, mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/NotBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public static function notCnh(): Chain;

public static function notCnpj(): Chain;

public static function notComposite(Validator ...$validators): Chain;

public static function notConsonant(string ...$additionalChars): Chain;

public static function notContains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/NotChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public function notCnh(): Chain;

public function notCnpj(): Chain;

public function notComposite(Validator ...$validators): Chain;

public function notConsonant(string ...$additionalChars): Chain;

public function notContains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/NullOrBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public static function nullOrCnh(): Chain;

public static function nullOrCnpj(): Chain;

public static function nullOrComposite(Validator ...$validators): Chain;

public static function nullOrConsonant(string ...$additionalChars): Chain;

public static function nullOrContains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/NullOrChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public function nullOrCnh(): Chain;

public function nullOrCnpj(): Chain;

public function nullOrComposite(Validator ...$validators): Chain;

public function nullOrConsonant(string ...$additionalChars): Chain;

public function nullOrContains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/PropertyBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public static function propertyCnh(string $propertyName): Chain;

public static function propertyCnpj(string $propertyName): Chain;

public static function propertyComposite(string $propertyName, Validator ...$validators): Chain;

public static function propertyConsonant(string $propertyName, string ...$additionalChars): Chain;

public static function propertyContains(string $propertyName, mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/PropertyChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public function propertyCnh(string $propertyName): Chain;

public function propertyCnpj(string $propertyName): Chain;

public function propertyComposite(string $propertyName, Validator ...$validators): Chain;

public function propertyConsonant(string $propertyName, string ...$additionalChars): Chain;

public function propertyContains(string $propertyName, mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/UndefOrBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public static function undefOrCnh(): Chain;

public static function undefOrCnpj(): Chain;

public static function undefOrComposite(Validator ...$validators): Chain;

public static function undefOrConsonant(string ...$additionalChars): Chain;

public static function undefOrContains(mixed $containsValue): Chain;
Expand Down
2 changes: 2 additions & 0 deletions src/Mixins/UndefOrChain.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public function undefOrCnh(): Chain;

public function undefOrCnpj(): Chain;

public function undefOrComposite(Validator ...$validators): Chain;

public function undefOrConsonant(string ...$additionalChars): Chain;

public function undefOrContains(mixed $containsValue): Chain;
Expand Down
37 changes: 2 additions & 35 deletions src/Validators/AllOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,9 @@
namespace Respect\Validation\Validators;

use Attribute;
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use Respect\Validation\Validators\Core\Composite;

use function array_filter;
use function array_map;
use function array_reduce;
use function count;
use Respect\Validation\Validators\Core\LogicalComposite;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
#[Template(
'{{subject}} must pass the rules',
'{{subject}} must pass the rules',
self::TEMPLATE_SOME,
)]
#[Template(
'{{subject}} must pass all the rules',
'{{subject}} must pass all the rules',
self::TEMPLATE_ALL,
)]
final class AllOf extends Composite
final readonly class AllOf extends LogicalComposite
{
public const string TEMPLATE_ALL = '__all__';
public const string TEMPLATE_SOME = '__some__';

public function evaluate(mixed $input): Result
{
$children = array_map(static fn(Validator $validator) => $validator->evaluate($input), $this->validators);
$valid = array_reduce($children, static fn(bool $carry, Result $result) => $carry && $result->hasPassed, true);
$failed = array_filter($children, static fn(Result $result): bool => !$result->hasPassed);
$template = self::TEMPLATE_SOME;
if (count($children) === count($failed)) {
$template = self::TEMPLATE_ALL;
}

return Result::of($valid, $input, $this, [], $template)->withChildren(...$children);
}
}
4 changes: 2 additions & 2 deletions src/Validators/AnyOf.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use Respect\Validation\Message\Template;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use Respect\Validation\Validators\Core\Composite;
use Respect\Validation\Validators\Core\LogicalComposite;

use function array_map;
use function array_reduce;
Expand All @@ -28,7 +28,7 @@
'{{subject}} must pass at least one of the rules',
'{{subject}} must pass at least one of the rules',
)]
final class AnyOf extends Composite
final readonly class AnyOf extends LogicalComposite
{
public function evaluate(mixed $input): Result
{
Expand Down
8 changes: 2 additions & 6 deletions src/Validators/Attributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
use Respect\Validation\Id;
use Respect\Validation\Result;
use Respect\Validation\Validator;
use Respect\Validation\Validators\Core\Reducer;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class Attributes implements Validator
Expand All @@ -34,11 +33,8 @@ public function evaluate(mixed $input): Result

$reflection = new ReflectionObject($input);
$validators = [...$this->getClassValidators($reflection), ...$this->getPropertyValidators($reflection)];
if ($validators === []) {
return (new AlwaysValid())->evaluate($input)->withId($id);
}

return (new Reducer(...$validators))->evaluate($input)->withId($id);
return (new Composite(...$validators))->evaluate($input)->withId($id);
}

/** @return array<Validator> */
Expand Down Expand Up @@ -72,7 +68,7 @@ private function getPropertyValidators(ReflectionObject $reflection): array

$allowsNull = $property->getType()?->allowsNull() ?? false;

$childRule = new Reducer(...$propertyValidators);
$childRule = new Composite(...$propertyValidators);
$validators[] = new Property($propertyName, $allowsNull ? new NullOr($childRule) : $childRule);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Validators/Circuit.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@

use Attribute;
use Respect\Validation\Result;
use Respect\Validation\Validators\Core\Composite;
use Respect\Validation\Validators\Core\LogicalComposite;

#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Circuit extends Composite
final readonly class Circuit extends LogicalComposite
{
public function evaluate(mixed $input): Result
{
Expand All @@ -27,6 +27,6 @@
}
}

return $result;

Check failure on line 30 in src/Validators/Circuit.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Variable $result might not be defined.
}
}
Loading
Loading