Skip to content
Merged
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
14 changes: 12 additions & 2 deletions docs/feature-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ Note that you can combine multiple validators for a complex validation.

### Validating using exceptions

The `assert()` method throws an exception when validation fails. You can handle those exceptions with `try/catch` for more robust error handling.
The `assert()` method throws an exception when validation fails. It evaluates all validators in the chain and collects every error before throwing. You can handle those exceptions with `try/catch` for more robust error handling.

```php
v::intType()->positive()->assert($input);
```

The `check()` method also throws an exception when validation fails, but it stops at the first failure instead of collecting all errors. Internally, it wraps the chain in a `ShortCircuit` validator.

```php
v::intType()->positive()->check($input);
```

The difference is visible when multiple validators fail. With `assert()`, you get all error messages; with `check()`, you get only the first one.

### Validating using results

You can validate data and handle the result manually without using exceptions:
Expand Down Expand Up @@ -131,7 +139,9 @@ Beyond the examples above, Respect\Validation provides specialized validators fo
- **Grouped validation**: Combine validators with AND/OR logic using [AllOf](validators/AllOf.md), [AnyOf](validators/AnyOf.md), [NoneOf](validators/NoneOf.md), [OneOf](validators/OneOf.md).
- **Iteration**: Validate every item in a collection with [Each](validators/Each.md).
- **Length, Min, Max**: Validate derived values with [Length](validators/Length.md), [Min](validators/Min.md), [Max](validators/Max.md).
- **Special cases**: Handle dynamic rules with [Factory](validators/Factory.md), short-circuit on first failure with [Circuit](validators/Circuit.md), or transform input before validation with [After](validators/After.md).
- **Special cases**: Handle dynamic rules with [Factory](validators/Factory.md), selectively short-circuit on first failure with [ShortCircuit](validators/ShortCircuit.md), or transform input before validation with [After](validators/After.md).

Note: While `check()` automatically short-circuits the entire chain, the `ShortCircuit` validator gives you fine-grained control over which specific group of validators should stop at the first failure. Use `check()` when you want the whole chain to fail fast, and `ShortCircuit` when you want only a specific part of your validation to fail fast while the rest continues collecting errors.

## Customizing error messages

Expand Down
14 changes: 13 additions & 1 deletion docs/handling-exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>

# Handling exceptions

The `ValidatorBuilder::assert()` method throws a `ValidationException` when validation fails. This exception provides detailed feedback on what went wrong.
Both `ValidatorBuilder::assert()` and `ValidatorBuilder::check()` throw a `ValidationException` when validation fails. This exception provides detailed feedback on what went wrong.

The difference between the two methods is that `assert()` evaluates all validators in the chain and collects every error, while `check()` stops at the first failure (using `ShortCircuit` internally).

## The `ValidationException`

Expand All @@ -21,6 +23,16 @@ try {
}
```

The same applies to `check()`:

```php
try {
v::alnum()->lowercase()->check($input);
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage(); // Only the first failure
}
```

### Helpful stack traces

When an exception is thrown, PHP reports where it was *created*, not where it was *caused*. In most validation libraries that means stack traces point deep inside library internals. You end up hunting through the trace to find your actual code.
Expand Down
65 changes: 30 additions & 35 deletions docs/migrating-from-v2-to-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,24 @@ In 2.x, `validate()` returned a boolean. In 3.0, it returns a `ResultQuery` obje
}
```

#### `check()` removed, `assert()` unified
#### `check()` and `assert()` behavior changes

In 2.x, there were two exception-based methods:
In 2.x, both `check()` and `assert()` threw validator-specific exception classes (e.g., `IntTypeException`), which were children of `ValidationException`. The difference was that `check()` threw these exceptions directly and stopped at the first failure, while `assert()` threw a `NestedValidationException` (also a child of `ValidationException`) that collected all errors and provided methods like `getFullMessage()` and `getMessages()` not available on the base `ValidationException`.

- `check()` threw rule-specific exceptions (e.g., `IntTypeException`)
- `assert()` threw `NestedValidationException`

In 3.0, both are unified into `assert()`, which throws `ValidationException`:
In 3.0, validator-specific exception classes and `NestedValidationException` no longer exist. Both `check()` and `assert()` throw a unified `ValidationException` that includes `getMessage()`, `getFullMessage()`, and `getMessages()`. The behavioral distinction is preserved: `check()` still fails fast (stopping at the first failure, using `ShortCircuit` internally), while `assert()` collects all errors.

```diff
-use Respect\Validation\Exceptions\IntTypeException;
+use Respect\Validation\Exceptions\ValidationException;

try {
- v::intType()->check($input);
v::intType()->check($input);
-} catch (IntTypeException $exception) {
+ v::intType()->assert($input);
+} catch (ValidationException $exception) {
echo $exception->getMessage();
}
```

The `ValidationException` provides all methods previously split between exceptions:

```diff
-use Respect\Validation\Exceptions\NestedValidationException;
+use Respect\Validation\Exceptions\ValidationException;
Expand Down Expand Up @@ -589,9 +583,9 @@ Version 3.0 introduces several new validators:
| `All` | Validates that every item in an iterable passes validation |
| `Attributes` | Validates object properties using PHP attributes |
| `BetweenExclusive` | Validates that a value is between two bounds (exclusive) |
| `Circuit` | Short-circuit validation, stops at first failure |
| `ContainsCount` | Validates the count of occurrences in a value |
| `DateTimeDiff` | Validates date/time differences (replaces Age validators) |
| `ShortCircuit` | Stops at first failure instead of collecting all errors |
| `Hetu` | Validates Finnish personal identity codes (henkilötunnus) |
| `KeyExists` | Checks if an array key exists |
| `KeyOptional` | Validates an array key only if it exists |
Expand Down Expand Up @@ -647,26 +641,6 @@ v::betweenExclusive(1, 10)->assert(1); // fails (1 is not > 1)
v::betweenExclusive(1, 10)->assert(10); // fails (10 is not < 10)
```

#### Circuit

Validates input against a series of validators, stopping at the first failure. Useful for dependent validations:

```php
$validator = v::circuit(
v::key('countryCode', v::countryCode()),
v::factory(
fn($input) => v::key(
'subdivisionCode',
v::subdivisionCode($input['countryCode'])
)
),
);

$validator->assert([]); // → `.countryCode` must be present
$validator->assert(['countryCode' => 'US']); // → `.subdivisionCode` must be present
$validator->assert(['countryCode' => 'US', 'subdivisionCode' => 'CA']); // passes
```

#### ContainsCount

Validates the count of occurrences of a value:
Expand All @@ -685,6 +659,26 @@ v::dateTimeDiff('years', v::greaterThanOrEqual(18))->assert('2000-01-01'); // pa
v::dateTimeDiff('days', v::lessThan(30))->assert('2024-01-15'); // passes if less than 30 days ago
```

#### ShortCircuit

Validates input against a series of validators, stopping at the first failure. Useful for dependent validations:

```php
$validator = v::shortCircuit(
v::key('countryCode', v::countryCode()),
v::factory(
fn($input) => v::key(
'subdivisionCode',
v::subdivisionCode($input['countryCode'])
)
),
);

$validator->assert([]); // → `.countryCode` must be present
$validator->assert(['countryCode' => 'US']); // → `.subdivisionCode` must be present
$validator->assert(['countryCode' => 'US', 'subdivisionCode' => 'CA']); // passes
```

#### Hetu

Validates Finnish personal identity codes (henkilötunnus):
Expand Down Expand Up @@ -983,11 +977,12 @@ v::templated(
- v::intType()->validate($input); // bool
+ v::intType()->isValid($input); // bool

// Exception-based validation
- v::intType()->check($input); // IntTypeException
+ v::intType()->assert($input); // ValidationException
// Exception-based validation (fail-fast)
- v::intType()->check($input); // IntTypeException (child of ValidationException)
+ v::intType()->check($input); // ValidationException

- v::intType()->assert($input); // NestedValidationException
// Exception-based validation (collect all errors)
- v::intType()->assert($input); // AllOfExceptopn (child of NestedValidationException)
+ v::intType()->assert($input); // ValidationException

// Renamed validators
Expand Down
10 changes: 5 additions & 5 deletions docs/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ 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][] - [NoneOf][] - [OneOf][] - [ShortCircuit][]

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

**Core**: [Named][] - [Not][] - [Templated][]

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

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

**Nesting**: [After][] - [AllOf][] - [AnyOf][] - [Circuit][] - [Each][] - [Factory][] - [Key][] - [KeySet][] - [NoneOf][] - [Not][] - [NullOr][] - [OneOf][] - [Property][] - [PropertyOptional][] - [UndefOr][] - [When][]
**Nesting**: [After][] - [AllOf][] - [AnyOf][] - [Each][] - [Factory][] - [Key][] - [KeySet][] - [NoneOf][] - [Not][] - [NullOr][] - [OneOf][] - [Property][] - [PropertyOptional][] - [ShortCircuit][] - [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 @@ -80,7 +80,6 @@ In this page you will find a list of validators by their category.
- [Bsn][] - `v::bsn()->assert('612890053');`
- [CallableType][] - `v::callableType()->assert(function () {});`
- [Charset][] - `v::charset('ASCII')->assert('sugar');`
- [Circuit][] - `v::circuit(v::intVal(), v::floatVal())->assert(15);`
- [Cnh][] - `v::cnh()->assert('02650306461');`
- [Cnpj][] - `v::cnpj()->assert('00394460005887');`
- [Consonant][] - `v::consonant()->assert('xkcd');`
Expand Down Expand Up @@ -189,6 +188,7 @@ In this page you will find a list of validators by their category.
- [Roman][] - `v::roman()->assert('IV');`
- [Satisfies][] - `v::satisfies(fn (int $input): bool => $input % 5 === 0,)->assert(10);`
- [ScalarVal][] - `v::scalarVal()->assert(135.0);`
- [ShortCircuit][] - `v::shortCircuit(v::intVal(), v::positive())->assert(15);`
- [Size][] - `v::size('KB', v::greaterThan(1))->assert('/path/to/file');`
- [Slug][] - `v::slug()->assert('my-wordpress-title');`
- [Sorted][] - `v::sorted('ASC')->assert([1, 2, 3]);`
Expand Down Expand Up @@ -237,7 +237,6 @@ In this page you will find a list of validators by their category.
[Bsn]: validators/Bsn.md "Validates a Dutch citizen service number (BSN)."
[CallableType]: validators/CallableType.md "Validates whether the pseudo-type of the input is callable."
[Charset]: validators/Charset.md "Validates if a string is in a specific charset."
[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 the structure and mathematical integrity of Brazilian CNPJ identifiers."
[Consonant]: validators/Consonant.md "Validates if the input contains only consonants."
Expand Down Expand Up @@ -346,6 +345,7 @@ In this page you will find a list of validators by their category.
[Roman]: validators/Roman.md "Validates if the input is a Roman numeral."
[Satisfies]: validators/Satisfies.md "Validates the input using the return of a given callable."
[ScalarVal]: validators/ScalarVal.md "Validates whether the input is a scalar value or not."
[ShortCircuit]: validators/ShortCircuit.md "Validates the input against a series of validators, stopping at the first failure."
[Size]: validators/Size.md "Validates whether the input is a file that is of a certain size or not."
[Slug]: validators/Slug.md "Validates whether the input is a valid slug."
[Sorted]: validators/Sorted.md "Validates whether the input is sorted in a certain order or not."
Expand Down
6 changes: 3 additions & 3 deletions docs/validators/After.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ v::after(
```

`After` does not handle possible errors (type mismatches). If you need to
ensure that your callback is of a certain type, use [Circuit](Circuit.md) or
ensure that your callback is of a certain type, use [ShortCircuit](ShortCircuit.md) or
handle it using a closure:

```php
v::after('strtolower', v::equals('ABC'))->assert(123);
// 𝙭 strtolower(): Argument #1 ($string) must be of type string, int given

v::circuit(v::stringType(), v::after('strtolower', v::equals('abc')))->assert(123);
v::shortCircuit(v::stringType(), v::after('strtolower', v::equals('abc')))->assert(123);
// → 123 must be a string

v::circuit(v::stringType(), v::after('strtolower', v::equals('abc')))->assert('ABC');
v::shortCircuit(v::stringType(), v::after('strtolower', v::equals('abc')))->assert('ABC');
// Validation passes successfully
```

Expand Down
2 changes: 1 addition & 1 deletion docs/validators/AllOf.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Used when all validators have failed.
## See Also

- [AnyOf](AnyOf.md)
- [Circuit](Circuit.md)
- [NoneOf](NoneOf.md)
- [OneOf](OneOf.md)
- [ShortCircuit](ShortCircuit.md)
- [When](When.md)
2 changes: 1 addition & 1 deletion docs/validators/AnyOf.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ so `AnyOf()` returns true.
## See Also

- [AllOf](AllOf.md)
- [Circuit](Circuit.md)
- [ContainsAny](ContainsAny.md)
- [NoneOf](NoneOf.md)
- [OneOf](OneOf.md)
- [ShortCircuit](ShortCircuit.md)
- [When](When.md)
72 changes: 0 additions & 72 deletions docs/validators/Circuit.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/validators/Factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,4 @@ on the input itself (`$_POST`), but it will use any input that’s given to the

- [After](After.md)
- [CallableType](CallableType.md)
- [Circuit](Circuit.md)
- [ShortCircuit](ShortCircuit.md)
2 changes: 1 addition & 1 deletion docs/validators/NoneOf.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Used when all validators have passed.

- [AllOf](AllOf.md)
- [AnyOf](AnyOf.md)
- [Circuit](Circuit.md)
- [Not](Not.md)
- [OneOf](OneOf.md)
- [ShortCircuit](ShortCircuit.md)
- [When](When.md)
2 changes: 1 addition & 1 deletion docs/validators/OneOf.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@ Used when more than one validator has passed.

- [AllOf](AllOf.md)
- [AnyOf](AnyOf.md)
- [Circuit](Circuit.md)
- [NoneOf](NoneOf.md)
- [ShortCircuit](ShortCircuit.md)
- [When](When.md)
Loading