Essentials

Validation

Validate user input using rule objects

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()] ];

Email

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; add Required() 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