respect/validation
Powerful PHP validation engine with 150+ tested validators. Build readable, chainable rules like numeric()->positive()->between(). Includes advanced exception handling and thorough docs. Great for complex input validation in any PHP app.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require respect/validation
Requires PHP 8.5+ (Laravel 10+ compatible).
Basic Usage:
use Respect\Validation\Validator as v;
$result = v::stringType()->validate('test');
if ($result->isValid()) {
// Proceed
}
First Use Case: Validate a request input in Laravel:
use Respect\Validation\Validator as v;
$input = request()->input();
$result = v::key('email', v::email())->validate($input);
if ($result->hasFailed()) {
return response()->json(['errors' => $result->getFullMessage()], 422);
}
Validator facade: v::stringType() (shortcut for Validator::stringType()).validate(): Returns a ResultQuery object for detailed error inspection.assert(): Throws ValidationException on failure (use for strict validation).check(): Returns boolean (legacy, prefer validate() + isValid()).Leverage fluent chaining for complex rules:
$validator = v::stringVal()
->notEmpty()
->length(v::between(5, 255))
->matches('/^[a-z]+$/i');
$validator->assert($input['username']);
Validate nested arrays/objects with dot notation:
$validator = v::key('user.address', v::key('city', v::city()))
->key('user.address', v::key('zip', v::postalCode()));
$validator->assert($request->all());
Use factories for input-dependent rules:
$validator = v::factory(function ($input) {
return v::key('confirm_password', v::equals($input['password']));
});
$validator->assert($request->all());
Annotate DTOs/classes for automatic validation:
use Respect\Validation\Validators;
class UserDto {
#[Validators\Email] public string $email;
#[Validators\Between(18, 120)] public int $age;
}
// Validate all annotated properties
v::attributes()->assert(new UserDto());
Stop at first failure for performance:
v::shortCircuit(
v::key('email', v::email()),
v::key('password', v::length(v::between(8, 32)))
)->assert($input);
Request Validation:
use Respect\Validation\Validator as v;
public function store(Request $request) {
$validator = v::keySet([
'email' => v::email(),
'age' => v::between(18, 120),
]);
if ($validator->assert($request->all())) {
// Valid
}
}
Form Requests:
use Respect\Validation\Validator as v;
class StoreUserRequest extends FormRequest {
public function rules(): array {
return [
'email' => v::email(),
'password' => v::length(v::between(8, 32)),
];
}
}
Extend for domain-specific rules:
use Respect\Validation\Validator as v;
use Respect\Validation\Rules\Simple;
final class StrongPassword extends Simple {
#[Template('The password must contain at least one uppercase, one lowercase, and one number.')]
public function isValid(mixed $input): bool {
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/', $input);
}
}
// Usage
v::key('password', new StrongPassword())->assert($input);
Inspect validation results:
$result = v::email()->validate($input);
if ($result->hasFailed()) {
$errors = $result->getFullMessage(); // Array of error messages
$firstError = $result->getFirstMessage(); // First error only
}
validate() → ResultQuery:
v::email()->validate($input) → bool.v::email()->validate($input) → ResultQuery (use isValid() for boolean).validate() with isValid() in existing code.Renamed Validators:
notBlank() → not(v::blank()).nullable() → v::nullOr().min(5) → v::greaterThanOrEqual(5).Composite Validators:
v::all(v::stringType(), v::notEmpty())).Strict Defaults:
contains(), startsWith(), etc., are now strict (use v::contains()->notStrict() for case-insensitive).Inspect Full Error Paths:
$result->getFullMessage(); // Returns nested errors with dot notation (e.g., "user.address.city must be a string").
Custom Exceptions:
try {
v::email()->assert($input, new \InvalidArgumentException('Custom error'));
} catch (\InvalidArgumentException $e) {
// Handle custom exception
}
Placeholder Pipes: Customize error messages with pipes:
#[Template('The {{subject|lower}} must be valid.')]
class LowercaseTemplateValidator extends Simple { ... }
ShortCircuit:
Use v::shortCircuit() for dependent validations (e.g., validating subdivisionCode only if countryCode is valid).
Avoid Redundant Checks: Chain validators efficiently:
// Bad: Checks type twice
v::stringType()->length(v::between(5, 10))->stringType();
// Good
v::stringVal()->length(v::between(5, 10));
Form Requests: Combine with Laravel’s validation:
use Respect\Validation\Validator as v;
public function rules(): array {
return [
'email' => ['required', v::email()],
'age' => ['integer', v::between(18, 120)],
];
}
API Responses: Format errors for API consumers:
$result = v::keySet($rules)->validate($request->all());
return response()->json([
'errors' => $result->getFullMessage(),
], 422);
Service Container: Bind custom validators:
$this->app->bind(StrongPassword::class, function () {
return new StrongPassword();
});
Custom Validators:
Simple for basic rules.Wrapper for reusable validators (e.g., v::nullOrEmail()).Result Formatters:
Override ResultFormatter to customize error output:
use Respect\Validation\Result\ResultFormatter;
class JsonResultFormatter extends ResultFormatter {
public function format(array $errors): string {
return json_encode($errors);
}
}
Translation: Use Symfony’s translation system:
$translator = $this->app->make('translator');
$result->setTranslator($translator);
Nested Arrays:
v::each() rejects non-iterables (e.g., stdClass). Use v::iterableVal() first:
v::iterableVal()->each(v::stringType())->validate($input);
Case Sensitivity:
contains() is strict by default. Use v::contains()->notStrict() for case-insensitive checks.
Dynamic Factories: Ensure factory callbacks return validators, not booleans:
// Wrong: Returns bool
v::factory(fn($input) => v::email()->isValid($input));
// Right: Returns validator
v::factory(fn($input) => v::email());
Attribute Validation:
Only validates public properties. Use PropertyOptional for optional fields:
#[Validators\PropertyOptional] #[Validators\Email] public ?string $email;
PHP 8.5+ Requirement:
How can I help you explore Laravel packages today?