Search by

h2lsoft / validator

h2lsoft

Fluent data validator with chainable rules and i18n support

Package info

github.com/h2lsoft/validator

pkg:composer/h2lsoft/validator

Statistics

Installs: 77

Dependents: 1

Suggesters: 0

Stars: 2

Open Issues: 0

v3.0.0 2026-10-16 13:02 UTC

This package is auto-updated.

Last update: 2026-10-16 13:04:31 UTC


README

A fluent, chainable PHP data validator with built-in internationalization support.

Version

Requirements

  • PHP >= 7.4 (PHP 8+ recommended)
  • ext-mbstring

Installation

Install via Composer:

composer require h2lsoft/validator

Upgrade from 2.x

Breaking change: required(), accepted(), csrfToken() and fileRequired() are now checked even when the data is empty. In 2.x an empty POST (or JSON body) skipped them, so an empty request passed the validation. If you build the validator on a GET request to display a form, run the rules only on submit:

if($request->isMethod('POST'))
{
    $v->input('email')->required()->email();
}

Quick Start

$validator = new \h2lsoft\Data\Validator();

$validator->input('name')->required()->alpha(' ');
$validator->input('email', 'email address')->required()->email();
$validator->input('age')->required()->integer()->between(18, 99);

if($validator->fails())
{
    print_r($validator->result());
}

Usage

Creating a validator

// default: locale 'en', data from $_POST
$v = new \h2lsoft\Data\Validator();

// with locale
$v = new \h2lsoft\Data\Validator('fr');

// with custom data
$v = new \h2lsoft\Data\Validator('en', ['name' => 'John', 'age' => 25]);

Defining rules

Chain rules fluently after selecting an input with input():

$v->input('email', 'Email address')->required()->email();
$v->input('zip_code', 'Zip code')->required()->mask('99999');
$v->input('password')->required()->minLength(8);
$v->input('password_confirm')->required()->sameAs('password');
$v->input('age')->required()->integer()->between(18, 120);
$v->input('website')->url();
$v->input('start_date')->date('Y-m-d');
$v->input('config')->json();

Checking results

if($v->success())
{
    // validation passed
}

if($v->fails())
{
    $result = $v->result();
    // $result['error']            => true/false
    // $result['error_count']      => int
    // $result['error_stack']      => ['message 1', 'message 2', ...]
    // $result['error_stack_html'] => HTML formatted errors
    // $result['error_stack_deep'] => ['field_name' => ['message 1', ...]]
    // $result['error_fields']     => ['field1', 'field2', ...]
}

Custom error messages

Every rule accepts an optional $message parameter:

$v->input('name')->required('Please enter your name');
$v->input('age')->integer(true, 'Age must be a positive number');

Use [FIELD] placeholder to reference the field label:

$v->input('email', 'Email')->required('`[FIELD]` cannot be empty');
// => "`Email` cannot be empty"

Custom errors

$v->addError('Something went wrong', [], 'field_name');

Custom validation with callbacks

Use custom() for business logic that doesn't fit standard rules:

// check that a promo code exists in database
$v->input('promo_code')->required()->custom(function($value) {
    return PromoCode::findOne(['code' => $value]) !== null;
}, 'Invalid promo code');

// check that an email is not already taken
$v->input('email')->required()->email()->custom(function($value) {
    return User::count(['email' => $value]) === 0;
}, '`[FIELD]` is already taken');

// business rule: minimum amount depends on country
$v->input('amount')->required()->float()->custom(function($value) {
    $minimums = ['FR' => 10, 'US' => 5, 'UK' => 8];
    return $value >= ($minimums[$_POST['country']] ?? 0);
}, '`[FIELD]` is below the minimum for your country');

Available Rules

String / Value Rules

Rule Description Example
required($msg) Must be present and not empty ->required()
email($msg) Must be a valid email ->email()
mask($mask, $msg) Must match a mask (9=digit, a=letter, *=any) ->mask('99-aaa')
in($list, $msg) Value must be in the list ->in(['a', 'b', 'c'])
notIn($list, $msg) Value must not be in the list ->notIn(['x', 'y'])
integer($unsigned, $msg) Must be an integer ->integer()
float($unsigned, $msg) Must be a float ->float(false)
min($min, $msg) Value >= min (or array count) ->min(1)
max($max, $msg) Value <= max (or array count) ->max(100)
between($min, $max, $msg) Value between min and max ->between(1, 10)
length($len, $msg) Exact character length ->length(5)
minLength($len, $msg) Minimum character length ->minLength(3)
maxLength($len, $msg) Maximum character length ->maxLength(255)
equal($value, $msg) Must equal a given value ->equal('yes')
accepted($msg) Must be 'yes', 'YES' or 1 ->accepted()
boolean($msg) Must be a boolean-like value (true/false/1/0/yes/no) ->boolean()
password($min, $upper, $digit, $special, $msg) Password strength validation ->password(8, true, true, true)
url($flags, $msg) Must be a valid URL ->url()
alpha($exc, $latin, $min, $cap, $msg) Alphabetic only ->alpha(' -')
alphaNumeric($exc, $latin, $min, $cap, $msg) Alphanumeric only ->alphaNumeric('_')
date($format, $msg) Valid date ->date('d/m/Y')
datetime($format, $msg) Valid datetime ->datetime('d/m/Y H:i')
dateBefore($date, $format, $msg) Must be before a given date ->dateBefore('2025-12-31', 'Y-m-d')
dateAfter($date, $format, $msg) Must be after a given date ->dateAfter(date('Y-m-d'), 'Y-m-d')
time($format, $msg) Valid time ->time('H:i')
timezone($msg) Valid timezone identifier ->timezone()
country($msg) Valid ISO 3166-1 alpha-2 country code ->country()
language($msg) Valid ISO 639-1 language code ->language()
currency($msg) Valid ISO 4217 currency code ->currency()
iban($msg) Valid IBAN with MOD-97 checksum ->iban()
bic($msg) Valid BIC/SWIFT code (8 or 11 chars) ->bic()
uuid($msg) Valid UUID (RFC 4122, v1-v5) ->uuid()
macAddress($msg) Valid MAC address (colon or hyphen) ->macAddress()
cssColor($msg) Valid CSS color (hex, rgb, hsl, named) ->cssColor()
regex($pattern, $msg) Must match a regex ->regex('/^\d+$/', 'Digits only')
notRegex($pattern, $msg) Must not match a regex ->notRegex('/admin/i', 'Forbidden')
ip($flags, $msg) Valid IP address ->ip()
ipV4($msg) Valid IPv4 address ->ipV4()
ipV6($msg) Valid IPv6 address ->ipV6()
json($msg) Valid JSON string ->json()
jsonArray($msg) Valid JSON array ([...]) ->jsonArray()
jsonObject($msg) Valid JSON object ({...}) ->jsonObject()
sameAs($field, $msg) Must equal another field's value ->sameAs('password')
different($field, $msg) Must differ from another field's value ->different('old_password')
requiredIf($field, $value, $msg) Required only when another field matches ->requiredIf('type', 'pro')
csrfToken($token, $msg, $regenerate) CSRF token validation (auto-regenerates on failure by default) ->csrfToken()
custom($callback, $msg) Custom validation via callback ->custom(fn($v) => $v > 10, 'Too low')

Array Rules (checkboxes, multi-select)

Rule Description Example
multiple($msg) Must be an array ->multiple()
min($min, $msg) Minimum selections ->min(2)
max($max, $msg) Maximum selections ->max(5)
between($min, $max, $msg) Selections between min and max ->between(1, 3)

File Rules

Rule Description Example
fileRequired($msg) File must be uploaded ->fileRequired()
fileExtension($ext, $msg) Allowed extensions ->fileExtension(['jpg', 'png'])
fileMaxSize($size, $msg) Max file size (kb, mb, gb) ->fileMaxSize('2mb')
fileImage($ext) Must be an image ->fileImage()
fileMime($mimes, $msg) Allowed MIME types ->fileMime(['application/pdf'])
fileUploaded($msg) Must be a real upload ->fileUploaded()
fileImageWidth($w, $strict, $msg) Image width constraint ->fileImageWidth(800, true)
fileImageHeight($h, $strict, $msg) Image height constraint ->fileImageHeight(600, true)
fileImageBase64($ext, $create, $msg) Validate base64 image ->fileImageBase64('png')

Internationalization

Supported locales

Code Language
en English (default)
fr French
es Spanish
pt Portuguese
de German
it Italian

Setting the locale

$v = new \h2lsoft\Data\Validator('fr');

// or after creation
$v->setLocale('es');

Adding custom translations

$v->addLocaleMessages('fr', [
    "`[FIELD]` is required" => "`[FIELD]` est obligatoire",
]);

Utility Methods

Method Description
setData(array $data) Replace the data to validate
inputGet($name, $default) Get a value from the data
inputSet($name, $value) Set a value in the data
inputGetAll() Get all data values
setInputNames(array $names) Set display labels for multiple fields
setInputName($name, $label) Set a display label for one field
hasErrors() Get the error count
toJson($flags) Get result as JSON (auto-sets Content-Type header)
reset() Reset errors and state for reuse with new data

Full Example

$_POST['name'] = 'the king !';
$_POST['email'] = 'bad@email';
$_POST['zip_code'] = 'a2345';
$_POST['choices'] = ['apple'];
$_POST['job'] = 'webdesigner';
$_POST['days'] = '-50';
$_POST['date'] = '31/20/2020';
$_POST['website'] = 'text.com';
$_POST['config'] = '{"valid": true}';
$_POST['ip'] = '192.168.1.1';
$_POST['conditions'] = 0;

$v = new \h2lsoft\Data\Validator('en');

$v->input('name')->required()->alpha(' ');
$v->input('email', 'email address')->required()->email();
$v->input('zip_code', 'zip code')->required()->mask('99999');
$v->input('choices')->required()->multiple()->in(['banana', 'pear'])->minLength(2);
$v->input('job')->required()->equal('CEO');
$v->input('days')->required()->integer()->between(1, 60);
$v->input('date')->date('m/d/Y');
$v->input('website')->url(FILTER_FLAG_PATH_REQUIRED);
$v->input('config')->json();
$v->input('ip', 'IP address')->ipV4();
$v->input('job')->custom(fn($v) => strlen($v) >= 2, '`[FIELD]` is too short');
$v->input('conditions')->required()->accepted();

if($v->fails())
{
    print_r($v->result());
}

License

MIT. See full license.