draw/validator
PHP validation library providing a fluent API to define rules, validate arrays/inputs, and collect errors. Lightweight and framework-agnostic, suitable for Laravel or standalone apps.
Install the Package
composer require draw/validator
This installs the package and its Symfony dependencies (symfony/validator, symfony/dependency-injection).
Register the Service Provider
Add the provider to config/app.php under providers:
Draw\Validator\ValidatorServiceProvider::class,
First Use Case: Validate a Simple Object Define a class with Symfony constraints and validate it:
use Symfony\Component\Validator\Constraints as Assert;
class UserRegistration {
#[Assert\NotBlank]
#[Assert\Email]
public string $email;
#[Assert\Length(min: 8)]
public string $password;
}
// Validate the object
$validator = app(\Symfony\Component\Validator\ValidatorInterface::class);
$user = new UserRegistration();
$user->email = 'invalid-email';
$user->password = 'short';
$errors = $validator->validate($user);
foreach ($errors as $error) {
echo $error->getPropertyPath() . ': ' . $error->getMessage() . PHP_EOL;
}
Output:
email: This value should not be blank.
email: This value should be a valid email address.
password: This value should have 8 characters or more.
Integrate with Laravel Requests
Use the validator in a FormRequest or controller:
use Illuminate\Http\Request;
use Symfony\Component\Validator\Constraints as Assert;
class StoreUserRequest extends Request {
public function rules() {
return [
'email' => ['required', 'email'],
'password' => ['required', 'min:8'],
];
}
public function validateWithSymfony() {
$validator = app(\Symfony\Component\Validator\ValidatorInterface::class);
$data = $this->validate($this->rules());
// Manually map Laravel's validated data to an object
$user = new UserRegistration();
$user->email = $data['email'];
$user->password = $data['password'];
$errors = $validator->validate($user);
if ($errors->count() > 0) {
throw new \Illuminate\Validation\ValidationException($this->formatErrors($errors));
}
}
protected function formatErrors($errors) {
return array_map(function ($error) {
return $error->getPropertyPath() => $error->getMessage();
}, $errors->toArray());
}
}
Use Symfony’s constraints (@Assert\*) for reusable validation logic:
use Symfony\Component\Validator\Constraints as Assert;
class Order {
#[Assert\GreaterThan(0)]
public int $quantity;
#[Assert\All([
new Assert\NotBlank(),
new Assert\Type('string'),
])]
public array $items;
}
Validate specific subsets of properties:
$validator = app(\Symfony\Component\Validator\ValidatorInterface::class);
$errors = $validator->validate($order, ['order']);
Extend validation with business rules:
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UniqueUsernameConstraint extends Constraint {
public $message = 'This username is already taken.';
}
class UniqueUsernameValidator extends ConstraintValidator {
public function validate($value, Constraint $constraint) {
if (User::where('username', $value)->exists()) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
// Register the constraint (e.g., in a service provider)
$validatorBuilder = app(\Symfony\Component\Validator\Validation::class);
$validatorBuilder->addConstraintFactory(
new UniqueUsernameConstraintFactory()
);
Override Laravel’s validate() to use Symfony:
class CreateOrderRequest extends FormRequest {
public function validate() {
$validator = app(\Symfony\Component\Validator\ValidatorInterface::class);
$data = $this->all();
// Convert array to object (or use a DTO)
$order = (object) $data;
$errors = $validator->validate($order);
if ($errors->count() > 0) {
throw new \Illuminate\Validation\ValidationException(
$this->formatSymfonyErrors($errors)
);
}
}
protected function formatSymfonyErrors($errors) {
$messages = [];
foreach ($errors as $error) {
$messages[$error->getPropertyPath()] = $error->getMessage();
}
return $messages;
}
}
Convert Symfony errors to Laravel’s JSON response format:
public function handle() {
try {
$this->validateWithSymfony();
// Proceed with logic
} catch (\Illuminate\Validation\ValidationException $e) {
return response()->json([
'errors' => $e->errors(),
], 422);
}
}
Bind Symfony’s ValidatorInterface to Laravel’s container for easy access:
$this->app->bind(\Symfony\Component\Validator\ValidatorInterface::class, function ($app) {
return \Symfony\Component\Validator\Validation::createValidatorBuilder()
->enableAnnotationReading()
->getValidator();
});
Inject the validator into services or controllers:
use Symfony\Component\Validator\ValidatorInterface;
class OrderService {
public function __construct(private ValidatorInterface $validator) {}
public function createOrder(array $data) {
$order = (object) $data;
$errors = $this->validator->validate($order);
if ($errors->count() > 0) {
throw new \RuntimeException('Validation failed');
}
// Proceed
}
}
Use Symfony for complex rules and Laravel for simple ones:
public function rules() {
return [
'email' => 'required|email', // Simple Laravel rule
'password' => 'required|min:8',
];
}
public function validateWithSymfony() {
$validator = app(\Symfony\Component\Validator\ValidatorInterface::class);
$data = $this->validate($this->rules());
// Use Symfony for custom logic
$user = new UserRegistration();
$user->email = $data['email'];
$user->password = $data['password'];
$errors = $validator->validate($user, ['registration']);
if ($errors->count() > 0) {
throw new \Illuminate\Validation\ValidationException(
$this->formatErrors($errors)
);
}
}
Validate CLI input using the same constraints:
use Symfony\Component\Validator\ValidatorInterface;
class ImportUsersCommand extends Command {
protected $signature = 'users:import {file}';
protected $description = 'Import users from a CSV file';
public function handle(ValidatorInterface $validator) {
$data = $this->getInputData();
$user = (object) $data;
$errors = $validator->validate($user);
if ($errors->count() > 0) {
$this->error('Validation failed: ' . implode(', ', $errors));
return 1;
}
// Proceed with import
}
}
validator may conflict with Laravel’s dependencies (e.g., symfony/http-foundation).composer.json overrides or pin versions:
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"preferred-install": "dist",
"sort-packages": true
}
composer update carefully and test after updates.ConstraintViolation objects don’t match Laravel’s ValidationException format.public function formatSymfonyErrors($errors) {
return array_reduce($errors->toArray(), function ($carry, $error) {
$carry[$error->getPropertyPath()][] = $error->getMessage();
return $carry;
}, []);
}
How can I help you explore Laravel packages today?