Skip to content

Verja

Build Status Coverage Status Maintainability Rating Total Downloads
Latest Stable Version License

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:

  1. Converter — optional type coercion: "42"42, "true"true, JSON string → array.
  2. Null policy — decides what happens when the value is null or '': reject (required), pass through (nullable), substitute a default, or skip the field entirely.
  3. Filters — transform the value in order: trim whitespace, change case, strip tags, etc.
  4. 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

composer require tflori/verja

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.