Skip to content

From Validator

Benyamin Khalife edited this page Dec 19, 2025 · 2 revisions

Validator Class Documentation

The Validator class provides a fluent, intuitive interface for validating form data and request inputs in the Webrium framework. It includes comprehensive validation rules, security features, and localization support.

Table of Contents

Installation

The Validator class is included in the Webrium framework. No additional installation is required.

use Webrium\Validator;

Basic Usage

Simple Validation

use Webrium\Validator;

// Create validator instance (uses request data by default)
$validator = new Validator();

// Define validation rules
$validator->field('username')->required()->alphaNum()->min(3)->max(20);
$validator->field('email')->required()->email();
$validator->field('age')->required()->integer()->min(18);

// Execute validation
if ($validator->fails()) {
    echo $validator->getFirstErrorMessage();
    exit;
}

// Continue with valid data

With Custom Data

$data = [
    'username' => 'john_doe',
    'email' => 'john@example.com',
    'age' => 25
];

$validator = new Validator($data);

$validator->field('username')->required()->string();
$validator->field('email')->required()->email();

if ($validator->passes()) {
    echo "Validation passed!";
}

Custom Field Labels

$validator = new Validator();

// Use custom labels for better error messages
$validator->field('user_email', 'Email Address')->required()->email();
$validator->field('user_phone', 'Phone Number')->required()->phone();

// Error message will say: "Email Address is required" instead of "user_email is required"

Available Validation Rules

Basic Type Validation

required()

Field must be present and not empty.

$validator->field('name')->required();

nullable()

Allow field to be null or empty. When applied, all other rules are skipped if field is empty.

$validator->field('middle_name')->nullable()->string()->max(50);
// Valid: null, "", "John"

string()

Field must be a string.

$validator->field('description')->string();

numeric()

Field must be numeric (integer or float).

$validator->field('price')->numeric();
// Valid: 10, 10.5, "10", "10.5"

integer()

Field must be an integer (supports negative integers).

$validator->field('quantity')->integer();
// Valid: 10, -5, "10", "-5"

boolean()

Field must be a boolean value.

$validator->field('is_active')->boolean();
// Valid: true, false, 0, 1, "0", "1"

array()

Field must be an array.

$validator->field('tags')->array();

object()

Field must be an object.

$validator->field('settings')->object();

String Validation

alpha()

Field must contain only alphabetic characters.

$validator->field('firstname')->alpha();
// Valid: "John", "Mary"
// Invalid: "John123", "John-Doe"

alphaNum()

Field must contain only alphanumeric characters.

$validator->field('username')->alphaNum();
// Valid: "john123", "Mary456"
// Invalid: "john_doe", "mary-123"

min($length)

Minimum length for strings, minimum value for numbers, minimum count for arrays.

// String: minimum 3 characters
$validator->field('username')->string()->min(3);

// Number: minimum value 18
$validator->field('age')->integer()->min(18);

// Array: minimum 2 items
$validator->field('tags')->array()->min(2);

max($length)

Maximum length for strings, maximum value for numbers, maximum count for arrays.

// String: maximum 50 characters
$validator->field('username')->string()->max(50);

// Number: maximum value 100
$validator->field('percentage')->integer()->max(100);

// Array: maximum 10 items
$validator->field('tags')->array()->max(10);

between($min, $max)

Value must be between min and max (for strings, numbers, or arrays).

// String: 3-20 characters
$validator->field('username')->string()->between(3, 20);

// Number: 18-65
$validator->field('age')->integer()->between(18, 65);

// Array: 2-5 items
$validator->field('tags')->array()->between(2, 5);

Format Validation

email()

Field must be a valid email address.

$validator->field('email')->required()->email();
// Valid: user@example.com, user.name@example.co.uk
// Invalid: user@, @example.com, user@.com

phone()

Field must be a valid phone number (supports international formats).

$validator->field('phone')->required()->phone();
// Valid: +1234567890, 1234567890, (123) 456-7890, 123-456-7890
// Invalid: 123, abcd, ++123

url()

Field must be a valid URL.

$validator->field('website')->url();
// Valid: https://example.com, http://example.com/path
// Invalid: example.com, htp://example.com

domain()

Field must be a valid domain name.

$validator->field('domain')->domain();
// Valid: example.com, subdomain.example.com
// Invalid: https://example.com, example

ip()

Field must be a valid IP address (IPv4 or IPv6).

$validator->field('ip_address')->ip();
// Valid: 192.168.1.1, 2001:0db8:85a3:0000:0000:8a2e:0370:7334
// Invalid: 999.999.999.999, invalid-ip

mac()

Field must be a valid MAC address.

$validator->field('mac_address')->mac();
// Valid: 00:1B:44:11:3A:B7, 00-1B-44-11-3A-B7
// Invalid: 00:1B:44:11, invalid-mac

Numeric Validation

digits($length)

Field must have exact number of digits.

$validator->field('pin_code')->digits(4);
// Valid: "1234", "0000"
// Invalid: "123", "12345", "abcd"

digitsBetween($min, $max)

Field must have digits between min and max.

$validator->field('verification_code')->digitsBetween(4, 6);
// Valid: "1234", "12345", "123456"
// Invalid: "123", "1234567"

Comparison Validation

confirmed($otherField)

Field must match another field (commonly used for password confirmation).

$validator->field('password')->required()->min(8);
$validator->field('password_confirmation')->required()->confirmed('password');
// Both fields must match

different($otherField)

Field must be different from another field.

$validator->field('new_email')->required()->email()->different('old_email');

Pattern & List Validation

regex($pattern)

Field must match the given regular expression pattern.

Security Note: The validator includes protection against ReDoS (Regular Expression Denial of Service) attacks.

// Username: alphanumeric and underscore only
$validator->field('username')->regex('/^[a-zA-Z0-9_]+$/');

// Hexadecimal color code
$validator->field('color')->regex('/^#[0-9A-Fa-f]{6}$/');

Security Restrictions:

  • Maximum pattern length: 500 characters
  • Nested quantifiers not allowed
  • Nested lookahead/lookbehind not allowed
  • Recursive patterns not allowed
  • Maximum execution time: 2 seconds
// This will throw an exception (security)
$validator->field('test')->regex('/(\w+)*(\w+)*/'); // Nested quantifiers

in($values)

Field must be one of the given values.

$validator->field('status')->in(['active', 'inactive', 'pending']);
// Valid: "active", "inactive", "pending"
// Invalid: "deleted", "archived"

notIn($values)

Field must not be one of the given values.

$validator->field('username')->notIn(['admin', 'root', 'system']);

Date & JSON Validation

date($format)

Field must be a valid date in the specified format.

// Default format: Y-m-d
$validator->field('birth_date')->date();
// Valid: "2024-01-15"

// Custom format: d/m/Y
$validator->field('start_date')->date('d/m/Y');
// Valid: "15/01/2024"

// Format: Y-m-d H:i:s
$validator->field('created_at')->date('Y-m-d H:i:s');
// Valid: "2024-01-15 14:30:00"

json()

Field must be a valid JSON string.

$validator->field('settings')->json();
// Valid: '{"key":"value"}', '[1,2,3]'
// Invalid: '{key:value}', 'not-json'

Conditional Validation

sometimes()

Apply validation only when field is present in the request data.

// Optional field - only validates if provided
$validator->field('phone')->sometimes()->phone();

// If 'phone' is not in request, no validation
// If 'phone' is in request, must be valid phone number

Use Cases:

  • Optional API parameters
  • Conditional form fields
  • PATCH requests (partial updates)

nullable() vs sometimes()

// nullable: Field can be present but empty
$validator->field('middle_name')->nullable()->string();
// Valid: null, "", "John"

// sometimes: Field may not be present at all
$validator->field('optional_field')->sometimes()->email();
// Valid: not present, or valid email if present
// Invalid: present but empty ""

// Combination
$validator->field('optional_email')->sometimes()->nullable()->email();
// Valid: not present, present but empty, or valid email

Error Handling

Check Validation Results

$validator = new Validator();
$validator->field('email')->required()->email();

// Method 1: fails()
if ($validator->fails()) {
    // Validation failed
}

// Method 2: passes()
if ($validator->passes()) {
    // Validation passed
}

// Method 3: validate() or isValid()
if (!$validator->validate()) {
    // Validation failed
}

Get Errors

// Get first error
$error = $validator->getFirstError();
// Returns: ['field' => 'email', 'message' => 'The email field is required']

// Get first error message only
$message = $validator->getFirstErrorMessage();
// Returns: "The email field is required"

// Get all errors
$errors = $validator->getErrors();
// Returns array of all errors

// Get errors for specific field
$emailErrors = $validator->getFieldErrors('email');

// Check if specific field has errors
if ($validator->hasError('email')) {
    echo "Email field has validation errors";
}

Custom Error Messages

// Per-field custom message
$validator->field('email')
    ->required('Please provide your email address')
    ->email('Please enter a valid email address');

$validator->field('age')
    ->required('Age is required')
    ->integer('Age must be a number')
    ->min(18, 'You must be at least 18 years old');

Security Features

1. ReDoS Protection

The validator includes comprehensive protection against Regular Expression Denial of Service (ReDoS) attacks:

// Security checks on regex patterns
$validator->field('test')->regex('/^[a-z]+$/'); // ✓ Safe

// These will throw exceptions:
$validator->field('test')->regex('/(\w+)*(\w+)*/'); // ✗ Nested quantifiers
$validator->field('test')->regex('/(?!.*(?!.*))/'); // ✗ Nested lookahead

Protection Measures:

  • Pattern complexity analysis
  • Maximum pattern length (500 chars)
  • Execution timeout (2 seconds)
  • Dangerous pattern detection

2. Input Sanitization

All input values are automatically sanitized:

// Automatic sanitization in getValue()
- Trim whitespace
- Remove null bytes (\0)
- Security against null byte injection

3. SQL Injection Prevention

While validator doesn't directly interact with database, it helps prevent SQL injection by:

  • Type validation (integer, numeric)
  • Format validation (email, url)
  • Input sanitization (null bytes removal)

Localization

Setup Language Files

Create validation message files in langs/{locale}/validation.php:

langs/en/validation.php:

<?php
return [
    'required' => 'The :attribute field is required.',
    'email' => 'The :attribute must be a valid email address.',
    'min' => [
        'string' => 'The :attribute must be at least :min characters.',
        'numeric' => 'The :attribute must be at least :min.',
        'array' => 'The :attribute must have at least :min items.',
    ],
    'max' => [
        'string' => 'The :attribute may not be greater than :max characters.',
        'numeric' => 'The :attribute may not be greater than :max.',
        'array' => 'The :attribute may not have more than :max items.',
    ],
    'alpha' => 'The :attribute must only contain letters.',
    'alpha_num' => 'The :attribute must only contain letters and numbers.',
    'phone' => 'The :attribute must be a valid phone number.',
    // ... more messages
];

langs/fa/validation.php:

<?php
return [
    'required' => 'فیلد :attribute الزامی است.',
    'email' => 'فیلد :attribute باید یک آدرس ایمیل معتبر باشد.',
    'min' => [
        'string' => 'فیلد :attribute باید حداقل :min کاراکتر باشد.',
        'numeric' => 'فیلد :attribute باید حداقل :min باشد.',
        'array' => 'فیلد :attribute باید حداقل :min مورد داشته باشد.',
    ],
    'alpha' => 'فیلد :attribute فقط باید حروف داشته باشد.',
    'phone' => 'فیلد :attribute باید یک شماره تلفن معتبر باشد.',
    // ... more messages
];

Set Application Locale

use Webrium\App;

// Set locale before validation
App::setLocale('fa'); // Persian
App::setLocale('en'); // English
App::setLocale('es'); // Spanish

$validator = new Validator();
// Validation messages will be in the selected locale

Complete Examples

Example 1: User Registration Form

use Webrium\Validator;

$validator = new Validator();

$validator->field('username', 'Username')
    ->required()
    ->alphaNum()
    ->between(3, 20);

$validator->field('email', 'Email Address')
    ->required()
    ->email();

$validator->field('password', 'Password')
    ->required()
    ->string()
    ->min(8);

$validator->field('password_confirmation', 'Password Confirmation')
    ->required()
    ->confirmed('password');

$validator->field('age', 'Age')
    ->required()
    ->integer()
    ->min(18);

$validator->field('phone', 'Phone Number')
    ->nullable()
    ->phone();

if ($validator->fails()) {
    // Return errors as JSON
    header('Content-Type: application/json');
    echo json_encode([
        'success' => false,
        'errors' => $validator->getErrors()
    ]);
    exit;
}

// Process registration
echo "Registration successful!";

Example 2: Profile Update (PATCH Request)

$validator = new Validator();

// All fields are optional (sometimes)
$validator->field('username')->sometimes()->alphaNum()->between(3, 20);
$validator->field('email')->sometimes()->email();
$validator->field('phone')->sometimes()->phone();
$validator->field('bio')->sometimes()->string()->max(500);

// Only validate fields that are present in request
if ($validator->passes()) {
    // Update only provided fields
    $updates = [];
    if (isset($_POST['username'])) $updates['username'] = $_POST['username'];
    if (isset($_POST['email'])) $updates['email'] = $_POST['email'];
    // ...
}

Example 3: API Request Validation

use Webrium\Validator;
use Webrium\App;

// JSON request data
$validator = new Validator(App::input());

$validator->field('action')->required()->in(['create', 'update', 'delete']);
$validator->field('id')->sometimes()->integer()->min(1);
$validator->field('data')->required()->json();

if ($validator->fails()) {
    App::returnData([
        'error' => true,
        'message' => $validator->getFirstErrorMessage(),
        'errors' => $validator->getErrors()
    ], 400);
}

// Process API request
$action = App::input('action');
$data = json_decode(App::input('data'), true);
// ...

Example 4: Complex Form with Conditional Rules

$validator = new Validator();

// Basic required fields
$validator->field('order_type')->required()->in(['delivery', 'pickup']);
$validator->field('customer_name')->required()->string()->min(2);
$validator->field('customer_phone')->required()->phone();

// Conditional: delivery address required only for delivery orders
$orderType = $_POST['order_type'] ?? null;

if ($orderType === 'delivery') {
    $validator->field('delivery_address')->required()->string()->min(10);
    $validator->field('delivery_city')->required()->string();
    $validator->field('delivery_zip')->required()->digits(5);
}

// Optional fields
$validator->field('special_instructions')->nullable()->string()->max(200);
$validator->field('coupon_code')->sometimes()->alphaNum()->between(4, 10);

if ($validator->passes()) {
    echo "Order validated successfully!";
}

Example 5: Multi-step Form Validation

// Step 1: Personal Information
function validateStep1($data) {
    $validator = new Validator($data);
    
    $validator->field('firstname')->required()->alpha()->min(2);
    $validator->field('lastname')->required()->alpha()->min(2);
    $validator->field('email')->required()->email();
    
    return $validator;
}

// Step 2: Account Details
function validateStep2($data) {
    $validator = new Validator($data);
    
    $validator->field('username')->required()->alphaNum()->between(3, 20);
    $validator->field('password')->required()->min(8);
    $validator->field('password_confirmation')->required()->confirmed('password');
    
    return $validator;
}

// Step 3: Additional Info
function validateStep3($data) {
    $validator = new Validator($data);
    
    $validator->field('phone')->nullable()->phone();
    $validator->field('bio')->nullable()->string()->max(500);
    
    return $validator;
}

// Usage
$step = $_POST['step'] ?? 1;

switch ($step) {
    case 1:
        $validator = validateStep1($_POST);
        break;
    case 2:
        $validator = validateStep2($_POST);
        break;
    case 3:
        $validator = validateStep3($_POST);
        break;
}

if ($validator->fails()) {
    // Show errors for current step
}

API Reference

Constructor

Method Parameters Description
__construct($data) array|null $data Create validator with optional custom data

Field Definition

Method Parameters Returns
field($name, $label) string $name, string|null $label self

Conditional Rules

Method Description Returns
nullable() Allow field to be null/empty self
sometimes() Validate only if field present self

Type Validation

Method Description Returns
required($message) Field must be present self
string($message) Must be string self
numeric($message) Must be numeric self
integer($message) Must be integer self
boolean($message) Must be boolean self
array($message) Must be array self
object($message) Must be object self

String Validation

Method Parameters Returns
alpha($message) string|null $message self
alphaNum($message) string|null $message self
min($min, $message) int|float $min, string|null $message self
max($max, $message) int|float $max, string|null $message self
between($min, $max, $message) int|float $min, $max, string|null $message self

Format Validation

Method Parameters Returns
email($message) string|null $message self
phone($message) string|null $message self
url($message) string|null $message self
domain($message) string|null $message self
ip($message) string|null $message self
mac($message) string|null $message self

Numeric Validation

Method Parameters Returns
digits($length, $message) int $length, string|null $message self
digitsBetween($min, $max, $message) int $min, $max, string|null $message self

Comparison

Method Parameters Returns
confirmed($field, $message) string $field, string|null $message self
different($field, $message) string $field, string|null $message self

Pattern & List

Method Parameters Returns
regex($pattern, $message) string $pattern, string|null $message self
in($values, $message) array $values, string|null $message self
notIn($values, $message) array $values, string|null $message self

Date & JSON

Method Parameters Returns
date($format, $message) string $format, string|null $message self
json($message) string|null $message self

Validation Execution

Method Returns Description
validate() bool Execute validation
isValid() bool Alias for validate()
fails() bool Check if validation failed
passes() bool Check if validation passed

Error Retrieval

Method Returns Description
getErrors() array Get all errors
getFirstError() array|null Get first error
getFirstErrorMessage() string|null Get first error message
getFieldErrors($field) array Get errors for specific field
hasError($field) bool Check if field has errors

Best Practices

1. Always Use Field Labels

// ✓ GOOD - Clear error messages
$validator->field('user_email', 'Email Address')->required()->email();
// Error: "Email Address is required"

// ✗ BAD - Technical error messages
$validator->field('user_email')->required()->email();
// Error: "user_email is required"

2. Validate Early

// ✓ GOOD - Validate before processing
$validator = new Validator();
$validator->field('email')->required()->email();

if ($validator->fails()) {
    return; // Stop here
}

// Process data...

3. Use Appropriate Types

// ✓ GOOD - Specific validation
$validator->field('age')->integer()->min(18);
$validator->field('price')->numeric()->min(0);

// ✗ BAD - Too generic
$validator->field('age')->required();
$validator->field('price')->required();

4. Combine Rules Logically

// ✓ GOOD - Logical rule order
$validator->field('username')
    ->required()           // First check if present
    ->string()            // Then check type
    ->alphaNum()          // Then check format
    ->between(3, 20);     // Finally check length

// ✗ BAD - Illogical order
$validator->field('username')
    ->between(3, 20)      // Length check before knowing if it's a string
    ->alphaNum()
    ->string()
    ->required();

5. Use nullable() for Optional Fields

// ✓ GOOD - Clear intent
$validator->field('middle_name')->nullable()->string()->max(50);

// ✗ BAD - Confusing
$validator->field('middle_name')->string()->max(50); // Fails on empty

6. Use sometimes() for PATCH Requests

// ✓ GOOD - Update only provided fields
$validator->field('username')->sometimes()->alphaNum();
$validator->field('email')->sometimes()->email();

// ✗ BAD - Requires all fields
$validator->field('username')->required()->alphaNum();
$validator->field('email')->required()->email();

7. Provide Custom Error Messages for Critical Fields

// ✓ GOOD - User-friendly messages
$validator->field('credit_card')
    ->required('Please provide your credit card number')
    ->digits(16, 'Credit card number must be 16 digits');

// ✗ BAD - Generic messages
$validator->field('credit_card')->required()->digits(16);

8. Don't Trust Client-Side Validation

// ✓ GOOD - Always validate server-side
$validator = new Validator();
$validator->field('email')->required()->email();

if ($validator->fails()) {
    // Server-side validation failed
}

// ✗ BAD - Relying only on JavaScript validation
// (Client-side validation can be bypassed)

9. Use Localization for Multi-language Apps

// Set locale based on user preference
App::setLocale($userPreferredLanguage);

// Validator will use localized messages
$validator = new Validator();
$validator->field('email')->required()->email();

Common Patterns

Pattern 1: REST API Validation

function validateCreateRequest() {
    $validator = new Validator(App::input());
    
    $validator->field('title')->required()->string()->max(100);
    $validator->field('content')->required()->string();
    $validator->field('status')->required()->in(['draft', 'published']);
    
    return $validator;
}

function validateUpdateRequest() {
    $validator = new Validator(App::input());
    
    $validator->field('title')->sometimes()->string()->max(100);
    $validator->field('content')->sometimes()->string();
    $validator->field('status')->sometimes()->in(['draft', 'published']);
    
    return $validator;
}

Pattern 2: Conditional Validation Based on Another Field

$data = App::input();
$validator = new Validator($data);

$validator->field('payment_method')
    ->required()
    ->in(['credit_card', 'paypal', 'cash']);

// Add conditional validation
if (isset($data['payment_method']) && $data['payment_method'] === 'credit_card') {
    $validator->field('card_number')->required()->digits(16);
    $validator->field('cvv')->required()->digits(3);
    $validator->field('expiry_date')->required()->date('m/Y');
}

if (isset($data['payment_method']) && $data['payment_method'] === 'paypal') {
    $validator->field('paypal_email')->required()->email();
}

Troubleshooting

Issue: Custom Error Messages Not Showing

// ✗ Problem
$validator->field('email')->email('Invalid email');
// Still shows default message

// ✓ Solution - Pass message to each rule
$validator->field('email')
    ->required('Email is required')
    ->email('Please enter a valid email');

Issue: Validation Passes But Should Fail

// ✗ Problem - Field is nullable
$validator->field('email')->nullable()->email();
// Empty string passes (because nullable)

// ✓ Solution - Use required if field shouldn't be empty
$validator->field('email')->required()->email();

Issue: Integer Validation Fails for Negative Numbers

// ✓ Fixed in latest version
$validator->field('temperature')->integer();
// Now accepts: -5, -10, etc.

Issue: Regex Too Complex Error

// ✗ Problem - Complex pattern triggers security check
$validator->field('test')->regex('/(\w+)*(\w+)*/');
// Exception: Complex regex patterns not allowed

// ✓ Solution - Simplify pattern
$validator->field('test')->regex('/^\w+$/');

Security Considerations

  1. Always validate server-side - Never trust client-side validation alone
  2. Use type validation - Prevents type juggling attacks
  3. Limit string lengths - Prevents buffer overflow and DoS
  4. Use whitelists - Prefer in() over notIn() for security
  5. Sanitize regex - Framework protects against ReDoS, but be careful
  6. Validate numeric ranges - Prevent integer overflow

Performance Tips

  1. Reuse validator instances when validating multiple similar datasets
  2. Use sometimes() to skip unnecessary validations
  3. Validate only required fields in PATCH requests
  4. Cache locale files (done automatically)
  5. Stop at first error (done automatically per field)

License

Part of the Webrium Framework. See framework license for details.

Clone this wiki locally