tflori / verja
An validation tool for arrays filled by foreign input
Requires
- php: ^8.2
- ext-json: *
Requires (Dev)
- dev-master
- v2.0.0-rc.6
- v2.0.0-rc.5
- v2.0.0-rc.4
- v2.0.0-rc.3
- v2.0.0-rc.2
- v2.0.0-rc.1
- v2.0.0-alpha.2
- v2.0.0-alpha.1
- v1.2.1
- v1.2.0
- v1.1.0
- 1.0.x-dev
- v1.0.1
- v1.0.0
- v1.0.0-rc.1
- v1.0.0-alpha.4
- v1.0.0-alpha.3
- v1.0.0-alpha.2
- v1.0.0-alpha.1
- dev-union-and-combine
- dev-recursive
- dev-code-quality
- dev-github-actions
This package is auto-updated.
Last update: 2026-04-16 07:39:53 UTC
README
At the moment I'm cooking on version 2 - stay tuned!
Verja (Old Norse: defender) is a PHP validation library for external input — forms, JSON bodies, query parameters, and any other data you don't control. It runs a filter-then-validate pipeline: filters transform raw input into clean types first, validators enforce constraints on the result. Structured data is validated through a composable gate hierarchy that supports arbitrarily deep nesting, and every validation result carries a flat dot-notation error map ready for frontend consumption.
What makes Verja different
Filter → Validate pipeline
Most validation libraries only check constraints. Verja explicitly separates transformation from checking: filters run first and convert raw input into the correct type, validators then check constraints on the already-filtered value.
// "42" (string) → Integer filter → 42 (int) → Between checks numeric value
$gate = (new Gate())->int('age', 'between:0:150');
No validator needs to handle both strings and integers. No special-casing for type coercion.
Three gate types that reflect real data shapes
| Type | Use case |
|---|---|
Gate | Structured data — objects and arrays with named keys |
PropertyGate | A single scalar value (used internally and by Gate::assert()) |
ArrayGate | A list where every element is validated against the same rules |
Combined | Merges properties of multiple Gates into one schema (A & B) |
Gates compose naturally: Gate::object() accepts a Gate, modifiers, and union options.
Gate::array() accepts an ArrayGate-compatible definition. Multiple gates form an implicit
union — the first matching option wins.
Required and Nullable as first-class prefix validators
required and nullable are not just flags — they are prefix validators that run before
filters and validators and short-circuit the pipeline. They are mutually exclusive by design,
so an optional field that is absent never produces a misleading strLen error.
String definitions with a namespace registry
Define rules inline as strings:
->string('username', 'required', 'strLen:3:20')
->int('rating', 'between:1:5')
->array('tags', 'min:1', 'max:10', ['trim', 'slug'])
Register custom validators and filters once and use the same string syntax everywhere:
Validator::registerNamespace('App\\Validator');
// now 'domainEmail' resolves to App\Validator\DomainEmail
Immutable gate builder for reuse
Gate supports requires(), optional(), nullable(), without(), and only() — each
returns a cloned gate — so a single base definition can be adapted for create vs. update
without duplication:
$base = (new Gate())->string('name')->string('email')->array('tags')->string('role');
$createGate = $base->requires('name', 'email'); // enforce presence
$updateGate = $base->optional('tags')->without('role'); // nothing required, role locked
Smart polymorphic measurements
Between, Min, and Max measure the right thing for the type automatically:
integers by value, strings by character length, arrays by count.
No framework coupling
No HTTP layer, no ORM, no DI container. Returns a ValidationResult value object — what
you do with it (throw an exception, return JSON errors, render a form) is entirely up to
you.
Installation
The usual way...
composer require tflori/verja
Upgrading from v1
v2 introduces a breaking change in how validate() works. See the
upgrade guide for the three small changes required.
Usage
Initialize a gate, define filters and validators, validate the input data, get the filtered data.
$gate = (new Verja\Gate())
->string('username', 'required', 'strLen:3:20')
->string('password', 'required', 'strLen:8')
->string('email', 'required', 'emailAddress');
$result = $gate->validate($_POST);
if ($result->valid) {
// how ever your orm works..
$user = new User($result->data);
$user->save();
} else {
$errors = $result->errors;
}
You can also pass validators and filters as objects for IDE auto-completion:
use Verja\Validator;
$gate = (new Verja\Gate())
->string('username', new Validator\Required(), new Validator\StrLen(3, 20))
->string('password', new Validator\Required(), new Validator\Min(8))
->string('email', 'required', new App\Validator\DomainEmail('my-domain.com'));
Or define all properties at once via the constructor:
$gate = new Verja\Gate([
'username' => ['required', 'strLen:3:20'],
'password' => ['required', 'strLen:8'],
'email' => ['required', 'emailAddress'],
]);
For more information check the documentation.
Quick Assertions
Gate::assert() validates a single value against filters and validators without setting up a full gate.
It returns the filtered value on success and throws InvalidValue on failure.
// simple validation — throws if empty
$username = Gate::assert($input, 'notEmpty', 'strLen:3:20');
// with filters
$age = Gate::assert($input, 'integer', 'between:0:150');
// catch and inspect errors
try {
$email = Gate::assert($input, 'notEmpty', 'emailAddress');
} catch (Verja\Exception\InvalidValue $e) {
// $e->errors — flat Error[] for the value
foreach ($e->errors as $error) {
echo $error->message . "\n";
}
}
To pass context alongside the value, wrap the first argument as an array:
Gate::assert(['value' => $input, 'context' => $context], 'notEmpty');
Nested Validators
Nested validators allow you to validate complex data structures — objects within objects, arrays of typed items, or
even recursive structures. Pass a Gate instance as the nested gate to ->array() or ->object() to validate each
element or the nested object against a separate set of rules.
The following example validates a blog post submission with a required author object, a list of tags, and an optional list of attachments where each attachment is itself a validated object:
$gate = (new Verja\Gate())
->string('title', 'required', 'strLen:3:200')
->string('body', 'required')
// each tag must be trimmed and a valid slug, at least one tag required
->array('tags', 'min:1', 'max:10', ['trim', 'slug', 'min:2'])
// author is a required nested object (required by default)
->object('author', (new Gate())
->string('name', 'required')
->string('email', 'required', 'emailAddress')
)
// attachments are optional: pass 'nullable' to allow the field to be absent
->array('attachments', 'nullable', (new Gate())
->string('filename', 'required')
->string('url', 'required', 'url')
->int('size', 'required', 'max:10485760')
);
$result = $gate->validate($_POST);
if ($result->valid) {
$post = new Post($result->data);
$post->save();
} else {
$errors = $result->errors;
}
Error map
$result->errorMap is a flat map of dot-notation paths to error arrays, making it easy to target
specific fields in a UI without traversing nested structures:
$result = $gate->validate([
'title' => 'Hi',
'author' => ['name' => 'Alice', 'email' => 'not-an-email'],
'tags' => ['ok', 'x'],
]);
// $result->errorMap looks like:
// [
// 'title' => [Error(STRLEN_TOO_SHORT, ...)],
// 'author.email' => [Error(NO_EMAIL_ADDRESS, ...)],
// 'tags.1' => [Error(STRLEN_TOO_SHORT, ...)],
// ]
foreach ($result->errorMap as $path => $errors) {
echo "$path: " . $errors[0]->message . "\n";
}
// title: length should be at least 3
// author.email: value should be a valid email address
// tags.1: length should be at least 2
Predefined Validators
In this library the following validators are included:
After: Value must be a date time after$dateTimeAlpha: Value must contain only alphabetical charactersAlphaNumeric: Value must contain only alphabetic and numeric charactersBefore: Value must be a date time before$dateTimeBoolean: Value must be booleanBetween: Value must be between$minand$max(number, string length, or array count)Callback: Value must pass a custom$callbackContains: Value must contain$subStringCreditCard: Value must be a valid credit card numberDateTime: Value must be a valid date in$formatEmailAddress: Value must be a valid email addressEndsWith: Value must end with$suffixEquals: Field must match field$oppositeExactly: Value must be exactly$countitems (array), characters (string), or equal$count(number)InArray: Value must exist in$arrayInteger: Value must be integerIpAddress: Value must be a valid IP address of$versionIsArray: Value must be an arrayIsString: Value must be a stringIsStructured: Value must be an array or objectJson: Value must be valid JSON — decodes it for further validation (prefix validator)Max: Value must be at most$max(number, string length, or array count)Min: Value must be at least$min(number, string length, or array count)NotEmpty: Value must not be emptyNullable: Allows null/empty values — skips further validation if value is null/empty (prefix validator)Numeric: Value must be numericPregMatch: Value must match regular expression$patternRequired: Value must be present and non-empty (prefix validator)Slug: Value must contain only slug characters (a-z, 0-9, -, _)StartsWith: Value must start with$prefixStrLen: String length from value must be between$minand$maxTruthful: Converted to boolean the value must be trueUnique: Value must not exist in$existing(array or callable)Url: Value must be a valid URLUuid: Value must be a valid RFC 4122 UUID
Predefined Filters
The following filters are included in this library:
ArrayFilter: Removes elements from arrays (null and '' by default, or custom callback)Boolean: Converts integer and string values to booleanCallback: Converts value using a custom$callbackConvertCase: Converts case to$mode(upper, lower or title)DateTime: Converts string from$formatto aDateTimeobjectDefaultValue: Returns$defaultwhen value is nullEscape: Escapes special characters for use in HTMLInteger: Converts string values to integerNumeric: Converts string values to float or integerPregReplace: Replaces$patternwith$replace(replace can also be a callback)Replace: Replaces$searchin values with$replaceSplit: Splits a string into an array by$delimiterStripTags: Strips HTML tags from stringsTrim: Trims$characterMaskfrom values