Verja¶
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.
Core concept: four-stage pipeline¶
Each value passes through up to four stages before a result is produced:
- Converter — optional type coercion:
"42"→42,"true"→true, JSON string → array. - Null policy — decides what happens when the value is
nullor'': reject (required), pass through (nullable), substitute a default, or skip the field entirely. - Filters — transform the value in order: trim whitespace, change case, strip tags, etc.
- Validators — check constraints on the already-clean value.
Separating conversion from validation means a validator never has to handle both "42" and 42.
// Raw input: ["age" => " 42 "]
// Converter: " 42 " → 42 (int)
// Between validator: checks 42 is within 0–150
$gate = (new Verja\Gate())
->int('age', 'between:0:150');
$result = $gate->validate($_POST);
// $result->data['age'] === 42 (int, not string)
Gate hierarchy¶
Verja provides three gate types that map to real data shapes:
| Type | Use case |
|---|---|
Gate |
Structured data — objects and arrays with named keys |
PropertyGate |
A single scalar value |
ArrayGate |
A list where every element follows the same rules |
Gates compose: Gate::object() accepts a nested Gate, Gate::array() wraps an ArrayGate.
Errors from nested structures are automatically flattened to dot-notation paths
(author.email, tags.1) in $result->errorMap.
Installation¶
Requires PHP 8.2 or later.
A more complete example¶
// POST /api/articles
$gate = (new Gate())
->string('title', 'required', 'trim', 'strLen:3:200')
->string('body', 'required', 'trim')
->string('status', 'required', 'inArray:["draft","published","scheduled"]')
->date('publish_at', 'required:status = "scheduled"') // only required when scheduled
->array('tags', 'nullable', 'max:10', ['trim', 'slug', 'notEmpty'])
->object('author', (new Gate())
->string('name', 'required', 'strLen:2:100')
->string('email', 'required', 'emailAddress')
)
->array('attachments', 'nullable', (new Gate())
->string('filename', 'required')
->string('url', 'required', 'url')
->int('size', 'required', 'max:10485760')
);
$result = $gate->validate($request->json());
if ($result->valid) {
// $result->data is fully filtered and typed — publish_at is a \DateTime,
// size is an int, tags are trimmed slugs
$article->fill($result->data)->save();
} else {
// flat dot-notation paths: 'author.email', 'attachments.1.url', …
return response()->json($result->errorMap, 422);
}
Upgrading from v1? See the migration guide.