Validation
Validate request data before processing to ensure data integrity and security.
Glueful uses an object-based validation API. You compose a Validator with Rule objects per field, validate input to produce an error map, and work with sanitized data via filtered().
Basic Validation
Use Glueful\Validation\Support\Rules::of()
to build a Validator
with Rule objects. Throw Glueful\Validation\ValidationException
to trigger a 422 response via the ValidationMiddleware.
use Glueful\Validation\Support\Rules as RuleFactory;
use Glueful\Validation\ValidationException;
use Glueful\Validation\Rules\{Sanitize, Required, Email as EmailRule, Length, Range, Type};
public function store()
{
$input = $this->request->input();
$v = RuleFactory::of([
'email' => [new Sanitize(['trim','strip_tags']), new Required(), new EmailRule()],
'name' => [new Sanitize(['trim','strip_tags']), new Required(), new Length(2, 50)],
'age' => [new Type('integer'), new Range(18, 120)], // null is allowed unless Required() is added
]);
$errors = $v->validate($input);
if ($errors !== []) {
throw new ValidationException($errors); // Handled as 422 by middleware
}
$data = $v->filtered(); // sanitized values
$user = $this->db->table('users')->insert([
'email' => $data['email'],
'name' => $data['name'],
'age' => $data['age'] ?? null,
]);
return Response::created($user);
}
When ValidationException
is thrown and the ValidationMiddleware
is active, a 422
JSON is returned with a consistent shape:
{
"status": 422,
"message": "Validation failed",
"errors": {
"email": ["This field is required."],
"name": ["Must be at least 2 characters."]
}
}
Validation Rules
Glueful provides a focused set of Rule classes. Rules return an error message string or null if the value passes. Most rules treat null
as “skip” — add Required()
to enforce presence.
Required
use Glueful\Validation\Rules\Required;
$rules = [ 'email' => [new Required()] ];
use Glueful\Validation\Rules\Email as EmailRule;
$rules = [ 'email' => [new EmailRule()] ];
Length (strings)
use Glueful\Validation\Rules\Length;
$rules = [ 'name' => [new Length(2, 50)] ];
Range (integers)
use Glueful\Validation\Rules\Range;
$rules = [ 'age' => [new Range(18, 120)] ];
Type
use Glueful\Validation\Rules\Type;
$rules = [
'age' => [new Type('integer')],
'tags' => [new Type('array')],
];
InArray (enum-style)
use Glueful\Validation\Rules\InArray;
$rules = [ 'status' => [new InArray(['active','inactive','suspended'])] ];
DbUnique
use Glueful\Validation\Rules\DbUnique;
// Using a PDO instance (inject or resolve from container)
$pdo = container()->get(PDO::class);
$rules = [ 'email' => [new Required(), new EmailRule(), new DbUnique($pdo, 'users', 'email')] ];
Sanitize (mutating)
use Glueful\Validation\Rules\Sanitize;
$rules = [ 'name' => [new Sanitize(['trim','strip_tags','lower'])] ];
Available Rules
Supported out of the box:
- Required — field must be present and not empty
- Email — RFC-compliant email address
- Type — enforce PHP type, e.g. 'string', 'integer', 'array'
- Length — string length min/max
- Range — integer min/max
- InArray — value must be one of a given list (strict)
- DbUnique — value must be unique in a database table/column (requires PDO)
- Sanitize — mutate value (trim/strip_tags/upper/lower) before other rules
Notes:
- There is no string rule syntax (e.g.,
"required|min:2"
). Use Rule objects. - Most rules skip
null
; addRequired()
to enforce presence. - For unsupported cases (URL, dates, regex, files), create custom Rule classes or use dedicated services.
Custom Rules
To customize messages/logic, implement your own Rule:
use Glueful\Validation\Contracts\Rule;
final class RegexRule implements Rule
{
public function __construct(private string $pattern, private string $message)
{
}
public function validate(mixed $value, array $context = []): ?string
{
if ($value === null) return null;
return is_string($value) && preg_match($this->pattern, $value) === 1
? null
: $this->message;
}
}
Patterns
Nested/Array Data
There is no wildcard syntax. Validate containers and then loop items yourself:
use Glueful\Validation\Support\Rules as RuleFactory;
use Glueful\Validation\Rules\{Required, Type, Range};
use Glueful\Validation\ValidationException;
$v = RuleFactory::of([
'items' => [new Required(), new Type('array')],
]);
$errors = $v->validate($input);
if ($errors) throw new ValidationException($errors);
$data = $v->filtered();
$itemErrors = [];
foreach (($data['items'] ?? []) as $i => $item) {
$iv = RuleFactory::of([
'quantity' => [new Required(), new Type('integer'), new Range(1, 1000)],
]);
$errs = $iv->validate($item);
if ($errs) $itemErrors["items.$i"] = $errs;
}
if ($itemErrors) throw new ValidationException($itemErrors);
Conditional Rules
Compose additional validators depending on input state:
$base = RuleFactory::of([
'payment_method' => [new Required(), new InArray(['credit_card','paypal','bank_transfer'])],
]);
if ($errs = $base->validate($input)) throw new ValidationException($errs);
if (($input['payment_method'] ?? null) === 'credit_card') {
$cc = RuleFactory::of([
// provide your own RegexRule or specialized Rule implementation
// 'card_number' => [new RegexRule('/^\d{16}$/', 'Card must be 16 digits')],
]);
if ($errs = $cc->validate($input)) throw new ValidationException($errs);
}
Manual Validation
Use Validator
directly when needed:
use Glueful\Validation\Validator;
use Glueful\Validation\Rules\{Required, Email as EmailRule};
$validator = new Validator([
'email' => [new Required(), new EmailRule()],
]);
$errors = $validator->validate($input);
if ($errors) throw new \Glueful\Validation\ValidationException($errors);
$data = $validator->filtered();
// Continue processing with $data
Common Patterns
User Registration
use Glueful\Validation\Support\Rules as RuleFactory;
use Glueful\Validation\ValidationException;
use Glueful\Validation\Rules\{Sanitize, Required, Email as EmailRule, Length};
public function register()
{
$input = $this->request->input();
$v = RuleFactory::of([
'name' => [new Sanitize(['trim','strip_tags']), new Required(), new Length(2, 100)],
'email' => [new Sanitize(['trim']), new Required(), new EmailRule()],
'password' => [new Required(), new Length(8, 255)],
]);
if ($errors = $v->validate($input)) {
throw new ValidationException($errors);
}
$data = $v->filtered();
$user = $this->db->table('users')->insert([
'name' => $data['name'],
'email' => $data['email'],
'password' => password_hash($data['password'], PASSWORD_DEFAULT),
]);
return Response::created($user);
}
Profile Update
use Glueful\Validation\Support\Rules as RuleFactory;
use Glueful\Validation\ValidationException;
use Glueful\Validation\Rules\{Sanitize, Email as EmailRule, Length, InArray, DbUnique};
public function update(string $userId)
{
$input = $this->request->input();
$pdo = container()->get(PDO::class);
$v = RuleFactory::of([
'name' => [new Sanitize(['trim','strip_tags']), new Length(2, 100)],
'email' => [new Sanitize(['trim']), new EmailRule(), new DbUnique($pdo, 'users', 'email')],
'status' => [new InArray(['active','inactive','suspended'])],
'bio' => [new Length(0, 500)],
]);
if ($errors = $v->validate($input)) {
throw new ValidationException($errors);
}
$data = $v->filtered();
$this->db->table('users')
->where('uuid', $userId)
->update($data);
return Response::success(null, 'Profile updated');
}
File Upload
File validation (type/MIME/size) is handled by dedicated uploader and image services. Use those rather than validation rules:
use Glueful\Uploader\FileUploader;
public function uploadAvatar()
{
$fileUploader = container()->get(FileUploader::class);
$result = $fileUploader->upload($this->request->files()['avatar']);
// $result contains normalized file data and storage path per your uploader config
return Response::success($result);
}
Best Practices
Validate Early
// ✅ Validate before any processing
public function store()
{
$validated = $this->validate($this->request->input(), [...]);
// Now safe to use $validated
}
// ❌ Don't use unvalidated input
public function store()
{
$email = $this->request->input('email'); // Not validated!
// ...
}
Optional Fields
// Optional unless Required() is added
$v = RuleFactory::of([
'name' => [new Required()],
'bio' => [new Length(0, 500)], // if present, must be <= 500 chars
]);
Validate Arrays Properly
// ✅ Validate container and then each tag manually
$v = RuleFactory::of([
'tags' => [new Type('array')],
]);
$errors = $v->validate($input);
if (!$errors) {
foreach (($v->filtered()['tags'] ?? []) as $i => $tag) {
$tv = RuleFactory::of(['tag' => [new Length(1, 20)]]);
$te = $tv->validate(['tag' => $tag]);
if ($te) $errors["tags.$i"] = $te;
}
}
if ($errors) throw new ValidationException($errors);
Troubleshooting
Validation always fails?
- Check field names match exactly
- Ensure you’re using Rule objects (not string rules)
- Ensure data is being sent in request
Custom messages not showing?
- Messages are defined by each Rule. For custom text, write a custom Rule.
Unique rule not working?
- Verify table and column names are correct
- Ensure your PDO connection is valid
Next Steps
- Controllers - Organize validation logic
- Database - Store validated data
- Requests & Responses - Handle validated input