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.
Installation:
composer require respect/validation
Add to composer.json if using Laravel's autoloader:
"autoload": {
"psr-4": {
"App\\": "app/",
"Respect\\Validation\\": "vendor/respect/validation/src/"
}
}
Run composer dump-autoload.
First Use Case: Validate a user input in a Laravel controller:
use Respect\Validation\Validator as v;
public function store(Request $request) {
$validator = v::stringType()->notEmpty()->validate($request->input('name'));
if ($validator->isValid()) {
// Proceed with logic
} else {
return response()->json(['error' => $validator->getFullMessage()], 422);
}
}
Key Entry Point:
The Validator facade (v::) is the primary interface. Start with the official documentation for validator lists and patterns.
Leverage fluent interfaces for complex rules:
v::numericVal()
->positive()
->between(1, 100)
->validate($input);
Validate arrays/objects with dot notation:
v::key('user.address.zip', v::numericVal()->length(5))
->validate($data);
Create validators based on runtime input:
v::factory(function ($input) {
return v::key('confirm_password', v::equals($input['password']));
})
->assert($request->all());
Annotate DTOs for automatic validation:
use Respect\Validation\Validators as v;
class UserDto {
#[v\Email] public string $email;
#[v\Between(18, 120)] public int $age;
}
// Validate all annotated properties
v::attributes()->assert($userDto);
Stop validation at first failure (optimization for dependent checks):
v::shortCircuit(
v::key('country', v::countryCode()),
v::factory(fn($input) => v::key('state', v::subdivisionCode($input['country'])))
)->assert($data);
Inspect validation failures granularly:
$result = v::email()->validate($input);
if ($result->hasFailed()) {
foreach ($result->getMessages() as $message) {
// Handle each error (e.g., log, translate)
}
}
use Respect\Validation\Validator as v;
public function rules() {
return [
'email' => v::email()->setMessage('Invalid email format'),
'age' => v::between(18, 120),
];
}
public function validateUser(UserRequest $request) {
$validator = v::allOf(
v::key('email', v::email()),
v::key('password', v::length(8, 32))
);
$validator->assert($request->validated());
}
Extend for domain-specific rules:
use Respect\Validation\Validator as v;
use Respect\Validation\Rules\Validator;
class StrongPasswordValidator extends Validator {
public function validate($input) {
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/', $input);
}
}
// Usage
v::newValidator()->setValidator(new StrongPasswordValidator())
->validate($password);
PHP 8.5 Requirement:
Respect/Validation 3.x requires PHP 8.5+. Use 2.x for older PHP versions.
composer require respect/validation:^2.4
validate() vs isValid():
validate() returns a ResultQuery (for inspection).isValid() returns a boolean (legacy alias).// Prefer:
$result = v::email()->validate($input);
if ($result->hasFailed()) { ... }
// Avoid (deprecated):
if (!v::email()->isValid($input)) { ... }
Strict Defaults:
Contains, In, StartsWith are strict by default (use notStrict() for loose checks).Each rejects non-iterables (use IterableVal for arrays/objects).Nested Paths:
Deeply nested keys (e.g., user.address.city) may fail if intermediate keys are missing. Use KeyOptional or KeyExists:
v::keyOptional('user.address.city', v::stringType());
Custom Messages:
templated() for dynamic messages:
v::numericVal()->templated('{{subject}} must be a number between {{min}} and {{max}}')
->between(1, 100);
{{subject}} (not {{name}} as in v2).Attribute Validation:
PropertyOptional for non-existent properties:
#[v\PropertyOptional('metadata', v\arrayType())] public ?array $metadata;
Short-Circuiting:
allOf() or anyOf() for parallel validation:
v::allOf(
v::key('email', v::email()),
v::key('phone', v::phoneNumber())
);
Inspect Results:
$result = v::email()->validate($input);
dd($result->getFullMessage()); // Full error tree
dd($result->getMessages()); // Flat array of messages
Enable Debug Mode:
Set RESPECT_VALIDATION_DEBUG=1 to see raw validation traces.
Validator Registry: Check registered validators with:
\Respect\Validation\Validator::getRegistry()->getAll();
Symfony Translation: Integrate with Laravel’s translator:
use Symfony\Contracts\Translation\TranslatorInterface;
$validator = v::email()
->setTranslator($app->make(TranslatorInterface::class));
Reuse Validators: Cache frequently used validators (e.g., in a service container):
$emailValidator = v::email();
// Reuse $emailValidator across requests.
Short-Circuit for Dependent Checks: Avoid validating unrelated fields when possible:
v::shortCircuit(
v::key('country', v::countryCode()),
v::factory(fn($input) => v::key('state', v::subdivisionCode($input['country'])))
);
Avoid allOf/anyOf Overhead:
Use all() or any() for lightweight checks:
v::all(v::numericVal())->validate([1, 2, 3]); // Faster than allOf()
Custom Validators:
Validator for reusable rules.#[Template] for custom error messages:
#[Template('{{subject}} must be a palindrome')]
class PalindromeValidator extends Validator {
public function validate($input) {
return $input === strrev($input);
}
}
Custom Formatters:
Override NestedListStringFormatter for custom error output:
use Respect\Validation\Exception\NestedListStringFormatter;
class CustomFormatter extends NestedListStringFormatter {
protected function formatMessage(string $message): string {
return strtoupper($message);
}
}
PSR-11 Container: Register custom validators via a PSR-11 container:
$container->set(StrongPasswordValidator::class, fn() => new StrongPasswordValidator());
\Respect\Validation\Validator::setContainer($container);
Laravel Service Provider: Bind the validator to Laravel’s container:
public function register() {
$this->app->singleton(\Respect\Validation\Validator::class, function () {
return \Respect\Validation\Validator::create();
});
}
ValidatorHow can I help you explore Laravel packages today?