Validation
PivotPHP provides a powerful and flexible validation system for validating incoming data. The validator supports a wide variety of validation rules and allows you to easily validate form data, API requests, and more.
Basic Usage
Quick Validation
The simplest way to validate data:
$validator = validate($request->all(), [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'age' => 'required|integer|min:18',
]);
if ($validator->fails()) {
return response()->json([
'errors' => $validator->errors()
], 422);
}
// Get validated data
$validated = $validator->validated();
Manual Validation
Create a validator instance manually:
use PivotPHP\Validation\Validator;
$validator = new Validator($data, [
'title' => 'required|min:5|max:255',
'body' => 'required',
'publish_at' => 'nullable|date|after:now',
]);
// Check if validation passes
if ($validator->passes()) {
// Process valid data
}
// Check if validation fails
if ($validator->fails()) {
$errors = $validator->errors();
}
Available Validation Rules
String Validation
[
'username' => 'alpha', // Only alphabetic characters
'slug' => 'alpha_dash', // Letters, numbers, dashes, underscores
'code' => 'alpha_num', // Alphanumeric characters
'name' => 'string|min:2|max:50', // String with length constraints
'bio' => 'nullable|string', // Optional string
]
Numeric Validation
[
'age' => 'integer|min:0|max:150',
'price' => 'numeric|min:0',
'quantity' => 'digits:5', // Exactly 5 digits
'year' => 'digits_between:2,4', // Between 2 and 4 digits
'rating' => 'between:1,5', // Numeric value between 1 and 5
]
Date Validation
[
'birthday' => 'date',
'appointment' => 'date_format:Y-m-d H:i:s',
'start_date' => 'date|after:today',
'end_date' => 'date|after:start_date',
'created_at' => 'date|before:now',
'meeting_time' => 'date|between:2024-01-01,2024-12-31',
]
File Validation
[
'avatar' => 'required|image|max:2048', // Max 2MB image
'document' => 'file|mimes:pdf,doc,docx|max:10240', // Max 10MB
'video' => 'file|mimetypes:video/mp4|max:102400', // Max 100MB
'images.*' => 'image|dimensions:min_width=100,min_height=100',
]
Array Validation
[
'tags' => 'array|min:1|max:5',
'tags.*' => 'string|distinct',
'options' => 'array',
'options.*.name' => 'required|string',
'options.*.value' => 'required',
'matrix' => 'array|size:3',
'matrix.*' => 'array|size:3',
'matrix.*.*' => 'integer',
]
Database Validation
[
'email' => 'unique:users,email',
'slug' => 'unique:posts,slug,'.$post->id,
'category_id' => 'exists:categories,id',
'user_id' => 'exists:users,id,active,1', // Where active = 1
]
Other Common Rules
[
'email' => 'required|email:rfc,dns',
'website' => 'url|active_url',
'password' => 'required|confirmed|min:8',
'terms' => 'accepted',
'gender' => 'in:male,female,other',
'status' => 'not_in:suspended,banned',
'username' => 'regex:/^[a-z0-9_-]+$/i',
'json_data' => 'json',
'ip' => 'ip',
'ipv4' => 'ipv4',
'ipv6' => 'ipv6',
'uuid' => 'uuid',
]
Custom Validation Rules
Inline Rules
Create custom validation rules on the fly:
$validator = validate($data, [
'phone' => [
'required',
function ($attribute, $value, $fail) {
if (!preg_match('/^[0-9]{10}$/', $value)) {
$fail('The '.$attribute.' must be a 10-digit phone number.');
}
},
],
]);
Rule Classes
Create reusable validation rule classes:
namespace App\Rules;
use PivotPHP\Contracts\Validation\Rule;
class Uppercase implements Rule
{
public function passes($attribute, $value): bool
{
return strtoupper($value) === $value;
}
public function message(): string
{
return 'The :attribute must be uppercase.';
}
}
// Usage
$validator = validate($data, [
'code' => ['required', new Uppercase],
]);
Implicit Rules
Rules that run even when the attribute is not present:
namespace App\Rules;
use PivotPHP\Contracts\Validation\ImplicitRule;
class RequiredIf implements ImplicitRule
{
public function __construct(
private string $field,
private mixed $value
) {}
public function passes($attribute, $value): bool
{
if (request($this->field) === $this->value) {
return !empty($value);
}
return true;
}
public function message(): string
{
return "The :attribute field is required when {$this->field} is {$this->value}.";
}
}
Conditional Validation
Required If/Unless
$rules = [
'email' => 'required_if:contact_method,email',
'phone' => 'required_if:contact_method,phone',
'company' => 'required_if:account_type,business',
'tax_id' => 'required_unless:country,us',
];
Sometimes Rules
Only validate when the field is present:
$validator = validate($data, [
'email' => 'sometimes|required|email',
'phone' => 'sometimes|required|digits:10',
]);
// Or with callback
$validator->sometimes('credit_card', 'required|cc_number', function ($input) {
return $input->payment_type === 'credit';
});
$validator->sometimes(['credit_card', 'cvv'], 'required', function ($input) {
return $input->payment_type === 'credit';
});
Complex Conditional Rules
use PivotPHP\Validation\Rule;
$rules = [
'shipping_method' => 'required',
'express_reason' => Rule::requiredIf(function () use ($request) {
return $request->shipping_method === 'express'
&& $request->total < 100;
}),
];
Validation Messages
Custom Messages
$messages = [
'required' => 'The :attribute field is required.',
'email.required' => 'We need your email address.',
'email.email' => 'Please provide a valid email address.',
'age.min' => 'You must be at least :min years old.',
];
$validator = validate($data, $rules, $messages);
Custom Attributes
$attributes = [
'email' => 'email address',
'dob' => 'date of birth',
];
$validator = validate($data, $rules, $messages, $attributes);
Localization
// resources/lang/en/validation.php
return [
'required' => 'The :attribute field is required.',
'email' => 'The :attribute must be a valid email address.',
'custom' => [
'email' => [
'required' => 'We need to know your email address!',
],
],
'attributes' => [
'email' => 'email address',
],
];
Working with Error Messages
Retrieving Errors
// Get all errors
$errors = $validator->errors();
// Get all messages
$messages = $errors->all();
// ['The name field is required.', 'The email must be a valid email address.']
// Get first error for a field
$emailError = $errors->first('email');
// Get all errors for a field
$emailErrors = $errors->get('email');
// Check if field has error
if ($errors->has('email')) {
// Handle email error
}
// Get errors as JSON
$json = $errors->toJson();
Error Bags
Group validation errors:
$validator = validate($loginData, $loginRules)
->errorBag('login');
$validator = validate($registerData, $registerRules)
->errorBag('register');
// In views
@if($errors->login->has('email'))
<span class="error">{{ $errors->login->first('email') }}</span>
@endif
Form Request Validation
Create dedicated request classes:
namespace App\Http\Requests;
use PivotPHP\Http\FormRequest;
class StoreUserRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', User::class);
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|confirmed|min:8',
'role' => 'required|in:admin,user',
];
}
public function messages(): array
{
return [
'email.unique' => 'This email is already registered.',
'password.confirmed' => 'Password confirmation does not match.',
];
}
public function attributes(): array
{
return [
'email' => 'email address',
];
}
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->name),
]);
}
protected function passedValidation(): void
{
$this->replace([
'password' => bcrypt($this->password),
]);
}
}
// In controller
public function store(StoreUserRequest $request)
{
// Validation is automatic
$user = User::create($request->validated());
return response()->json($user, 201);
}
Advanced Validation
Validating Arrays
$validator = validate($request->all(), [
'users' => 'required|array',
'users.*.name' => 'required|string',
'users.*.email' => 'required|email|distinct',
'users.*.roles' => 'array',
'users.*.roles.*' => 'exists:roles,id',
]);
Nested Data Validation
$rules = [
'company.name' => 'required|string',
'company.address.street' => 'required|string',
'company.address.city' => 'required|string',
'company.address.country' => 'required|string|size:2',
'company.employees.*.name' => 'required|string',
'company.employees.*.position' => 'required|string',
];
Bail Rule
Stop validation on first failure:
$rules = [
'email' => 'bail|required|email|unique:users',
// If required fails, email and unique won't run
];
After Validation Hook
$validator = validate($data, $rules);
$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong!');
}
});
Validation in Different Contexts
API Validation
class ApiValidator
{
public function validate(array $data, array $rules): array
{
$validator = validate($data, $rules);
if ($validator->fails()) {
throw new ValidationException(
'Validation failed',
$validator->errors()->toArray()
);
}
return $validator->validated();
}
}
Batch Validation
$results = collect($users)->map(function ($userData) {
$validator = validate($userData, [
'email' => 'required|email',
'name' => 'required|string',
]);
return [
'data' => $userData,
'valid' => $validator->passes(),
'errors' => $validator->errors()->toArray(),
];
});
$invalid = $results->where('valid', false);
Best Practices
- Use Form Requests: For complex validation, use dedicated form request classes
- Validate Early: Validate data as early as possible in the request lifecycle
- Be Specific: Use specific validation rules rather than generic ones
- Custom Messages: Provide user-friendly error messages
- Sanitize vs Validate: Validation checks data, sanitization cleans it
- Client + Server: Always validate on the server, even with client-side validation
- Test Validation: Write tests for your validation rules
- Document Rules: Document complex validation rules for other developers