Validation
Overview
Nexphant provides two validation approaches:
- Data Objects (recommended) - Type-safe DTOs with attribute-based rules
- Manual Validation - Fluent rule builders for dynamic validation
Data Object Validation
Define Rules
php
use Nexphant\Support\Data;
use Nexphant\Validation\Rules\{Email, Length, Required};
class RegisterData extends Data
{
#[Email]
#[Required]
public string $email;
#[Length(min: 3, max: 100)]
#[Required]
public string $name;
#[Length(min: 8)]
#[Required]
public string $password;
}Auto Validation
php
$app->post('/register', function (RegisterData $data) {
// Already validated
return ['success' => true];
});Manual Validation
Basic Usage
php
use Nexphant\Validation\Rule;
$app->post('/submit', function (Request $request) {
$validated = $request->validate([
'email' => Rule::email()->required(),
'name' => Rule::string()->min(3)->max(100)->required(),
'age' => Rule::integer()->min(18)->max(120),
]);
return ['data' => $validated];
});Available Rules
String Rules
php
Rule::string()
->min(3)
->max(100)
->length(10) // Exact length
->alpha() // Only letters
->alphaNum() // Letters and numbers
->alphaNumDash() // Letters, numbers, dashes, underscores
->lowercase()
->uppercase()
->trim()
->pattern('/^[a-z0-9]+$/') // Regex
->startsWith('prefix')
->endsWith('suffix')
->contains('substring')
->required();Email
php
Rule::email()
->required()
->unique('users', 'email'); // Check uniquenessInteger
php
Rule::integer()
->min(0)
->max(100)
->between(1, 10)
->positive()
->negative()
->required();Float
php
Rule::float()
->min(0.0)
->max(100.0)
->between(0.0, 10.0)
->decimals(2) // Max 2 decimal places
->required();Boolean
php
Rule::boolean()
->required();Array
php
Rule::array()
->min(1) // Min items
->max(10) // Max items
->unique() // All items unique
->required();In (Enum)
php
Rule::in(['active', 'inactive', 'pending'])
->required();Date
php
Rule::date()
->format('Y-m-d')
->before('2025-01-01')
->after('2024-01-01')
->required();URL
php
Rule::url()
->https() // HTTPS only
->required();File
php
Rule::file()
->maxSize(2048) // KB
->mimes(['jpg', 'png'])
->image()
->required();Required
php
Rule::required();Optional
php
Rule::string()->optional();Nullable
php
Rule::string()->nullable();Nested Validation
Nested Objects
php
$request->validate([
'user' => [
'name' => Rule::string()->required(),
'email' => Rule::email()->required(),
],
'address' => [
'street' => Rule::string()->required(),
'city' => Rule::string()->required(),
],
]);Array of Objects
php
$request->validate([
'items' => Rule::array()->min(1),
'items.*.name' => Rule::string()->required(),
'items.*.quantity' => Rule::integer()->min(1),
]);Custom Rules
Callback
php
Rule::string()->custom(function ($value) {
if (!preg_match('/^[A-Z]/', $value)) {
return 'Must start with uppercase letter';
}
return null; // Valid
});Custom Rule Class
php
use Nexphant\Validation\Rules\CustomRule;
class StrongPasswordRule extends CustomRule
{
public function validate($value): ?string
{
if (strlen($value) < 8) {
return 'Password must be at least 8 characters';
}
if (!preg_match('/[A-Z]/', $value)) {
return 'Password must contain uppercase letter';
}
if (!preg_match('/[a-z]/', $value)) {
return 'Password must contain lowercase letter';
}
if (!preg_match('/[0-9]/', $value)) {
return 'Password must contain number';
}
return null;
}
}
// Usage
$request->validate([
'password' => new StrongPasswordRule(),
]);Conditional Rules
php
$rules = [
'email' => Rule::email()->required(),
];
if ($request->json('subscribe')) {
$rules['newsletter'] = Rule::boolean()->required();
}
$validated = $request->validate($rules);Error Messages
Default
json
{
"error": "Validation failed",
"errors": {
"email": ["Invalid email format"],
"password": ["Must be at least 8 characters"]
}
}Custom Messages
php
Rule::email()
->required()
->message('email', 'Please provide a valid email address')
->message('required', 'Email is required');Validation Exceptions
php
use Nexphant\Validation\ValidationException;
try {
$validated = $request->validate($rules);
} catch (ValidationException $e) {
return [
'error' => 'Validation failed',
'errors' => $e->errors(),
];
}Complete Examples
Registration Form
php
$app->post('/register', function (Request $request) {
$validated = $request->validate([
'email' => Rule::email()
->required()
->unique('users', 'email'),
'name' => Rule::string()
->min(3)
->max(100)
->required(),
'password' => Rule::string()
->min(8)
->required(),
'password_confirm' => Rule::string()
->same('password')
->required(),
'age' => Rule::integer()
->min(18)
->required(),
'terms' => Rule::boolean()
->accepted()
->required(),
]);
$userId = User::query()->insertGetId([
'email' => $validated['email'],
'name' => $validated['name'],
'password' => password_hash($validated['password'], PASSWORD_DEFAULT),
'age' => $validated['age'],
]);
return ['id' => $userId];
});Update Profile
php
$app->put('/profile', function (Request $request) {
$userId = auth()->id();
$validated = $request->validate([
'name' => Rule::string()->min(3)->max(100)->optional(),
'bio' => Rule::string()->max(500)->optional(),
'website' => Rule::url()->optional(),
'age' => Rule::integer()->min(18)->max(120)->optional(),
]);
if (!empty($validated)) {
User::query()
->where('id', $userId)
->update($validated);
}
return ['success' => true];
})->middleware('auth');File Upload
php
$app->post('/upload', function (Request $request) {
$validated = $request->validate([
'avatar' => Rule::file()
->image()
->maxSize(2048)
->mimes(['jpg', 'jpeg', 'png'])
->required(),
]);
$file = $request->file('avatar');
$path = $file->store('avatars');
return ['path' => $path];
})->middleware('auth');Search with Filters
php
$app->get('/search', function (Request $request) {
$validated = $request->validate([
'q' => Rule::string()->min(2)->required(),
'page' => Rule::integer()->min(1)->optional(),
'per_page' => Rule::integer()->min(1)->max(100)->optional(),
'sort' => Rule::in(['created_at', 'updated_at', 'title'])->optional(),
'order' => Rule::in(['asc', 'desc'])->optional(),
'tags' => Rule::array()->optional(),
]);
$query = Post::query()
->where('title', 'LIKE', '%' . $validated['q'] . '%');
if (isset($validated['tags'])) {
$query->whereIn('tag', $validated['tags']);
}
$query->orderBy(
$validated['sort'] ?? 'created_at',
$validated['order'] ?? 'desc'
);
return $query->paginate(
$validated['page'] ?? 1,
$validated['per_page'] ?? 20
);
});Immutable Rules
All rule builders are immutable:
php
$base = Rule::string()->min(3);
$emailRule = $base->email(); // New instance
$lengthRule = $base->max(100); // New instance
// $base unchangedBenefits:
- Fiber safe
- Thread safe
- Reusable
- Predictable
Best Practices
- Use Data Objects for controllers
- Use manual validation for dynamic forms
- Validate early before processing
- Provide clear messages for users
- Sanitize input after validation
- Validate file uploads with size/type limits
- Use unique() rule for duplicate prevention
- Implement rate limiting on forms
