Luminova Framework

PHP Luminova: Rule-Based Input Data Validation

Last updated: 2026-04-24 08:12:21

The Validation class allows you to define and apply rules to user-submitted data, ensuring inputs meet specific criteria before processing or saving to the database.

The input Validation class checks user data before it is processed or stored.It ensures each field in a request matches the format, type, and constraints you define.

You can apply built-in rules or define custom validation logic. Custom error messages are supported, allowing you to return clear and consistent feedback when validation fails.


Validation Rules

Luminova provides a comprehensive set of built-in rules such as required, min, max, email, integer, alpha, and url.

These rules enforce consistent input validation and help prevent invalid data from entering your application.

For the complete list of rules and their parameters, see:


Usage

Defining Validation Rules

Rules are assigned per field using either string syntax or fluent rules (recommended).


String Rules (Legacy)

Use pipe-separated rules. Pass parameters inside parentheses ().

$this->input->rules = [
    'username' => 'required|min(3)|max(20)|alphanumeric',
    'email'    => 'required|email',
    'age'      => 'integer(positive)',
];

Fluent Rules (Recommended)

Use the Rule class for a structured and IDE-friendly approach.

use Luminova\Security\Rule;

$this->input->rules = [
    'username' => [
        Rule::required(),
        Rule::min(3),
        Rule::max(20),
        Rule::alphanumeric()
    ],
    'email' => [
        Rule::required(),
        Rule::email()
    ],
    'age' => [
        Rule::integer('positive')
    ],
];

Note:

Rules can also be define using the setRules() method.


Adding Individual Field Rules

You can add or update validation rules for a single field at runtime using the addField() method.Custom error messages can be provided per rule.


String Rules (Legacy)

$this->input->addField(
    'username',
    'required|alphanumeric|max(20)',
    [
        'required'     => 'Username is required.',
        'alphanumeric' => 'Username must contain only letters and numbers.',
        'max'          => 'Username cannot exceed 20 characters.'
    ]
);

Fluent Rules (Recommended)

use Luminova\Security\Rule;

$this->input->addField(
    'username',
    [
        Rule::required(),
        Rule::alphanumeric(),
        Rule::max(20)
    ],
    [
        'required'     => 'Username is required.',
        'alphanumeric' => 'Username must contain only letters and numbers.',
        'max'          => 'Username cannot exceed 20 characters.'
    ]
);

This approach allows you to adjust validation dynamically without redefining the entire rule set, while keeping structure clear and maintainable.


Defining Error Messages

You can define custom error messages in two ways:

  • Centralized mapping using $messages or setMessages()
  • Inline (fluent rules only) by passing the error message directly to the rule

Using the $messages Property

$this->input->messages = [
    'username' => [
        'required'     => 'Username is required.',
        'alphanumeric' => 'Username must be alphanumeric.',
        'max'          => 'Username is too long.'
    ],
    'email' => [
        'required' => 'Email is required.',
        'email'    => 'Invalid email format.'
    ],
];

Using the setMessages() Method

$this->input->setMessages([
    'username' => [
        'required'     => 'Username is required.',
        'alphanumeric' => 'Username must be alphanumeric.',
        'max'          => 'Username is too long.'
    ],
    'email' => [
        'required' => 'Email is required.',
        'email'    => 'Invalid email format.'
    ],
]);

Inline Messages (Fluent Rules)

Fluent rules allow defining error messages directly within each rule.

use Luminova\Security\Rule;

$this->input->rules = [
    'username' => [
        Rule::required('Username is required.'),
        Rule::alphanumeric('Username must be alphanumeric.'),
        Rule::max(20, 'Username cannot exceed 20 characters.')
    ],
    'email' => [
        Rule::required('Email is required.'),
        Rule::email(['example.com'], true, 'Invalid email format.')
    ],
];

This keeps validation and its error message in one place, reducing the need for separate message mapping.


Message Formatting

Custom validation messages support dynamic placeholders for more contextual feedback.

PlaceholderDescription
{field}Field name being validated
{value}Actual value that failed validation
{rule}Validation rule that was violated

These placeholders help generate precise and readable error messages without hardcoding field values.


Example (Using $messages Property)

$this->input->messages = [
    'email' => [
        'required' => 'The {field} field is required.',
        'email'    => 'The value "{value}" is not a valid {rule}.'
    ]
];

Custom Validation

You can define custom validation rules using callback functions or callable methods.

Supported callable formats:

  • Closure: fn($value, $field): bool
  • Plain function name: 'myFunction'
  • Instance method: 'MyClass@method'
  • Static method string: 'MyClass::method'
  • Static array: [MyClass::class, 'method']

A custom rule must return a boolean value:

  • true → validation passed
  • false → validation failed

Each callback receives two arguments:

ParameterTypeDescription
$valuemixedThe value being validated
$fieldstringThe name of the field being validated

Named Function Rule

Define a reusable validation function:

// app/Utils/Global.php

function myCustomRule(mixed $value, string $field): bool
{
    return $value === 'expected_value';
}

Use it in string-based rules:

$this->input->rules = [
   'custom_field' => 'required|callback(myCustomRule)',
];

Fluent Callback Rule

You can also define validation logic directly inside the rule set:

use Luminova\Security\Rule;

$this->input->rules = [
    'custom_field' => [
        Rule::required(),
        Rule::callback(function (mixed $value, string $field): bool {
            return $value === 'expected_value';
        })
    ],
];

Validating Input

After defining validation rules, call validate() to check incoming data.If validation fails, you can retrieve the first error message or inspect all errors.

// Create a new Validation instance
$input = new Validation();

// Request object
$request = new Request(
   method: 'POST', 
   uri: '/',
   body: [
      'username' => 'peter123',
      'email'    => '[email protected]',
      'age'      => '25',
      'tags'     => 'php,luminova',
      'role'  => ''
   ]
);

// Validation rules
$rules = [
    'username' => 'username(false)',               // Lowercase username, no reserved words
    'email'    => 'required|email',                // Must be present and valid
    'age'      => 'required|integer(positive)|between(18,60)', // Integer between 18-60
    'tags'     => 'is_list(1)'                     // Comma-separated list, minimum 1 item
    'role'     => 'default(users)'                 // Set default role to user if not specified
];

$input->setRules($rules);
$input->setRequest($request);

if (!$input->validate()) {
    print_r([
        'message' => $input->getErrorMessage(),
        'error'   => $input->getError(),
        'errors'  => $input->getErrors(),
    ]);
}

If validation passes, the input is considered safe to process.


Controller Validation (Fluent Rules)

Full example using fluent validation rules with inline structure and custom messages.

// /app/Controllers/Http/SignUpController.php

namespace App\Controllers\Http;

use App\Models\User;
use Luminova\Security\Rule;
use Luminova\Base\Controller;
use Luminova\Attributes\Route;
use Luminova\Attributes\Prefix;
use function Luminova\Funcs\response;
use App\Errors\Controllers\ErrorController;

#[Prefix(pattern: '/api/(:root)', onError: [ErrorController::class, 'onApiError'])]
class SignUpController extends Controller
{
    protected function onCreate(): void
    {
        $this->input->rules = [
            'email' => [
                Rule::required('Email is required.'),
                Rule::email(error: 'Invalid email address.')
            ],
            'phone' => [
                Rule::required('Phone number is required.'),
                Rule::phone(10, 12, 'Invalid phone number.')
            ],
            'name' => [
                Rule::required('Name is required.'),
                Rule::alphabet('Name contains unsupported characters.')
            ],
        ];
    }

    #[Route('/api/signup/', methods: ['POST'])]
    public function signup(User $user): int
    {
        if (!$this->input->validate()) {
            return response()->json([
                'message' => $this->input->getErrorMessage(),
                'error'   => $this->input->getError(),
                'errors'  => $this->input->getErrors(),
            ]);
        }

        $result = $user->createAccount($this->request);

        return response()->json([
            'status' => 'ok',
            'result' => $result,
        ]);
    }
}

Class Definition


Properties

rules

Validation rules for input fields.

Keys are the field names, and values are the rules applied as:

  • string - Multiple rules can be combined with pipe | separators.
  • array - Each rule as an array entry using Luminova\Security\Rule.
public array<string,string> $rules = [];

Examples:

$input->rules = [
    'username' => 'required|alphanumeric|max(20)',
    'email'    => 'required|email([example.com], true)',
];

Fluent Rules:

use Luminova\Security\Rule;

$input->rules = [
    'username' => [
         Rule::required(),
         Rule::alphanumeric(),
         Rule::max(20)
     ],
    'email' => [
         Rule::required(),
         Rule::email(['example.com'], true),
     ]
];

messages

Custom validation error messages.

Keys are field names, and values are arrays mapping rule names to messages.Placeholders like {field}, {value}, {rule} can be used for dynamic messages.

public array<string,array<string,string>> $messages = [];

Example:

$input->messages = [
    'username' => [
        'required'    => 'Username is required',
        'alphanumeric'=> 'Username must be alphanumeric',
        'max'         => 'Username cannot exceed 20 characters',
    ],
    'email' => [
        'required' => 'Email is required',
        'email'    => 'Invalid email address',
    ],
];

Methods

validate

Validate input data against defined rules.

Uses rules defined via $this->rules, {@see setRules()}, or {@see addField()}.Input data is resolved from the provided request or from previously set body data.

Behavior:

  • If no rules are defined, validation passes immediately.
  • Rules may be defined as pipe-separated strings or structured arrays.
  • Empty rules and special rules (nullable) are ignored.
  • Validation failures are collected and reset on each call.
public validate(HttpRequestInterface|LazyObjectInterface|null $request = null): bool

Parameters:

ParameterTypeDescription
$bodyHttpRequestInterface|nullOptional request instance used to resolve input data (default: null).

Return Value:

bool - Returns true if validation passes (no errors), false otherwise.

Throws:

\Luminova\Exceptions\RuntimeException - If:

  • A LazyObject does not resolve to a RequestInterface
  • No request is provided and no input body has been set

Examples:

$request = new Luminova\Http\Request();

$isValid = $input->validate($request);

In HTTP Controllers.

// /app/Controller/Http/*

$isValid = $this->input->validate($this->request);

Validate From Input Array:

// /app/Controller/Http/*

$body = [...];

$this->input->setRules([...]);
$this->input->setBody($body);

$isValid = $this->input->validate();

isPassed

Determine if validation passed.

public isPassed(): bool

Return Value:

bool - Returns true if no validation errors exist, false otherwise.


isUsername

Validate a username against format, length, and reserved constraints.

This method performs a strict validation check to ensure a username followsacceptable system rules. It returns early on the first failure encountered.

Validation behavior:

  • Enforces length between 3 and 64 characters (UTF-8 safe).
  • Allows only letters (any language), numbers, underscore (_), hyphen (-), and dot (.).
  • Rejects any whitespace characters.
  • Controls uppercase usage:
    • If $allowUppercase is false, any uppercase letter will fail validation.
    • If $allowUppercase is true, usernames cannot be entirely uppercase.
  • Prevents use of reserved usernames (case-insensitive match).
  • Applies pattern-based restrictions:
    • Cannot be all numbers.
    • Cannot start or end with dot (.) or hyphen (-).
    • Cannot contain consecutive dots (..), hyphens (--), or underscores (__).
public static isUsername(
   string $username,
   bool $allowUppercase = true,
   array $reservedUsernames = []
): array

Parameters:

ParameterTypeDescription
$usernamestringThe input username to validate.
$allowUppercaseboolWhether to allow uppercase characters (default: true).
$reservedUsernamesarray<int,string>List of reserved usernames (case-insensitive).

Return Value:

array{0: bool, 1: string|null} - Returns validation result containing:

  • (bool) Whether the username is valid
  • (string|null) Error message if invalid

Example:

[$valid, $error] = Validation::isUsername('admin', true, ['admin']);

if(!$valid){
   echo $error;
}

setBody

Set the input body to validate from array.

When the body is passed by reference, any fields modified by default or fallback rules are written back to the original array.

public setBody(array<string,mixed> &$body): self

Parameters:

ParameterTypeDescription
$bodyarray<string,mixed>The input data to validate passed by reference.

Return Value:

self - Return instance of validation object.

Example:

Normal usage.

$body = [
    'name' => 'Peter',
    'email' => '[email protected]'
];

$isValid = $input->setBody($body)->validate();

Optional: pass by reference to allow body mutation

$input->setRules([
     'name' => Rule::default('John');
]);

$body = [
    'name' => '',
    'email' => '[email protected]'
];

$input->setBody($body);

$isValid = $input->validate();

echo $body['name']; // John

setRequest

Set the HTTP request instance used for validation.

This allows the validator to resolve input data from a request objectwithout passing it into {@see validate()} each time.

If a request is already set, it will be replaced.

public setRequest(Psr\Http\Message\RequestInterface|Luminova\Interface\LazyObjectInterface $request): self

Parameters:

ParameterTypeDescription
$bodyRequestInterface|LazyObjectInterfaceThe HTTP request instance.

Return Value:

self - Return instance of validation object.

Throws:

\Luminova\Exceptions\RuntimeException - If a LazyObject cannot be resolved into a RequestInterface.

Example:

Normal usage.

use Luminova\Http\Request;

$request = new Request();

$isValid = $input->setRequest($request)->validate();

Optional: pass by reference to allow body mutation

$input->setRules([
     'name' => Rule::default('John');
]);

$isValid = $input->validate();

echo $request->post('name'); // John

setRules

Set validation rules for input fields.

Each key represents a field name, and the value defines its validation rules.Rules can be provided as:

  • A pipe-separated string (e.g. "required|email")
  • An array of rule definitions

Rule definitions may be:

  • A string rule (e.g. "required", "min(3)")
  • An array returned by {@see Rule} static methods

Both formats can be mixed across fields.

public setRules(array<string,string|array<int,string|array>> $rules): self

Parameters:

ParameterTypeDescription
$rulesarray<int,string|array>Validation rules mapped by field name.

Return Value:

self - Return instance of the Validation class.

$input->setRules([
    'email' => 'required|email',
    'name'  => 'required',
]);

Fluent rule builder.

use Luminova\Security\Rule;

$input->setRules([
   'email' => [
        Rule::required(),
        Rule::email(),
   ],
   'name' => [
        Rule::required(),
   ],
]);

setMessages

Set custom validation error messages.

Messages are defined per field and per rule. When validation fails,the message for the matching field/rule pair is used. If no custommessage is defined, a default message is returned.

Supported placeholders:

  • {field} → Field name
  • {rule} → Rule name
  • {value} → Field value at time of validation
public setMessages(array<string, array<string, string>> $messages): self

Parameters:

ParameterTypeDescription
$messagesarray<string,array<string,string>>Custom messages mapped by field and rule..

Return Value:

self - Return instance of the Validation class.

Example:

$input->setMessages([
   'email' => [
      'required' => 'Email is required.',
      'email'    => 'Invalid email address: {value}',
   ],
   'name' => [
      'required' => 'Name is required.',
   ],
]);

addField

Add a field with its validation rules and optional custom messages.

This is a convenience method that combines {@see setRules()} and

Rules can be provided as:

  • A pipe-separated string (e.g. "required|string|email")
  • An array of rule definitions

Messages are defined per rule using the rule name as the key or using fluent rule builder.If a message is not provided, the default message for the rule is used.

public addField(string $field, array<int,string|array>|string $rules, array<string,string> $messages = []): self

Parameters:

ParameterTypeDescription
$fieldstringThe form input field name (e.g, name, email).
$rulesarray|stringThe field validation rules (e.g, required|string|email).
$messagesarray<string,string>Optional custom messages per rule.

Return Value:

self - Return instance of the Validation class.

Example:

use Luminova\Security\Rule;

$input->addField(
    'email',
    [Rule::required(), Rule::email()],
    [
        'required' => 'Email is required.',
        'email'    => 'Invalid email address.',
    ]
);

getErrors

Get all validation error messages.

public getErrors(): array<string,array>

Return Value:

array<string, array<int, array{message:string, rule:string, field:string}>> - Return array of errors grouped by field name. Returns an empty array if none exist.


getError

Get a validation error message.

Retrieves an error message by field name or by index. If no field is specified,the first field with an error is used. If the field has multiple errors, themessage is selected by its index.

public getError(string|int $field = 0, int $error = 0): string

Parameters:

ParameterTypeDescription
$fieldstring|intField name or field index (default: 0 first field with error).
$errorintError index within the field (default: 0 first error).

Return Value:

string - Return the error message, or an empty string if not found.

Examples:

Get first error (default)

$input->getError();

Get first error for a specific field

$input->getError('email');

Get second error for a field

$input->getError('email', 1);

getErrorMessage

Get the first validation error message for a field.

If an integer is provided, the field is resolved by its position in theerror list. If a string is provided, it is treated as the field name.

public getErrorMessage(string|int $field = 0): string;

Parameters:

ParameterTypeDescription
$fieldstring|intField name or index (default: 0 first field with error).

Return Value:

string - Return an error message, or an empty string if not found.


getField

Get the name of a field with a validation error.

If an integer is provided, the field is resolved by its position in theerror list. If a string is provided, it is treated as the field name.

public getField(string|int $field = 0): string

Parameters:

ParameterTypeDescription
$fieldstring|intField name or index (default: 0 first field with error).

Return Value:

string - Return the field name that has a validation error, or an empty string if not found.


getFields

Get validation errors for a field by name or index.

If an integer is provided, the field is resolved by its position in theerror list. If a string is provided, it is treated as the field name.

Once retrieved, the field’s errors are removed from the internal failure list.

  • If $field is an integer, the corresponding field name is resolved by position.
  • If $field is a string, the method fetches the error data for that field.
public getFields(string|int $field = 0): array<int,array>

Parameters:

ParameterTypeDescription
$fieldstring|intField name or index (default: 0 first field with errors).

Return Value:

array<int,array{message:string,rule:string,field:string}> - Returns an array list of error entries for the field.

Example:

Get first field errors:

$errors = $input->getFields();

Get errors by field name:

$errors = $input->getFields('email');

Note:

Once retrieved, the field's error data is removed from $this->failures.