tflori/verja

An validation tool for arrays filled by foreign input

Maintainers

Package info

gitlab.w00tserver.org/tflori/verja

pkg:composer/tflori/verja

Statistics

Installs: 2 559

Dependents: 0

Suggesters: 0

v2.0.0-rc.6 2026-04-15 15:23 UTC

README

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

At the moment I'm cooking on version 2 - stay tuned!

Latest Stable Version

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

TypeUse case
GateStructured data — objects and arrays with named keys
PropertyGateA single scalar value (used internally and by Gate::assert())
ArrayGateA list where every element is validated against the same rules
CombinedMerges 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 $dateTime
  • Alpha: Value must contain only alphabetical characters
  • AlphaNumeric: Value must contain only alphabetic and numeric characters
  • Before: Value must be a date time before $dateTime
  • Boolean: Value must be boolean
  • Between: Value must be between $min and $max (number, string length, or array count)
  • Callback: Value must pass a custom $callback
  • Contains: Value must contain $subString
  • CreditCard: Value must be a valid credit card number
  • DateTime: Value must be a valid date in $format
  • EmailAddress: Value must be a valid email address
  • EndsWith: Value must end with $suffix
  • Equals: Field must match field $opposite
  • Exactly: Value must be exactly $count items (array), characters (string), or equal $count (number)
  • InArray: Value must exist in $array
  • Integer: Value must be integer
  • IpAddress: Value must be a valid IP address of $version
  • IsArray: Value must be an array
  • IsString: Value must be a string
  • IsStructured: Value must be an array or object
  • Json: 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 empty
  • Nullable: Allows null/empty values — skips further validation if value is null/empty (prefix validator)
  • Numeric: Value must be numeric
  • PregMatch: Value must match regular expression $pattern
  • Required: 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 $prefix
  • StrLen: String length from value must be between $min and $max
  • Truthful: Converted to boolean the value must be true
  • Unique: Value must not exist in $existing (array or callable)
  • Url: Value must be a valid URL
  • Uuid: 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 boolean
  • Callback: Converts value using a custom $callback
  • ConvertCase: Converts case to $mode (upper, lower or title)
  • DateTime: Converts string from $format to a DateTime object
  • DefaultValue: Returns $default when value is null
  • Escape: Escapes special characters for use in HTML
  • Integer: Converts string values to integer
  • Numeric: Converts string values to float or integer
  • PregReplace: Replaces $pattern with $replace (replace can also be a callback)
  • Replace: Replaces $search in values with $replace
  • Split: Splits a string into an array by $delimiter
  • StripTags: Strips HTML tags from strings
  • Trim: Trims $characterMask from values