rougin/valla

A simple validation package in PHP.

Maintainers

Package info

github.com/rougin/valla

pkg:composer/rougin/valla

Statistics

Installs: 411

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-05-14 16:13 UTC

This package is auto-updated.

Last update: 2026-05-14 16:13:47 UTC


README

Latest Version on Packagist Software License Build Status Coverage Status Total Downloads

A simple validation package for PHP inspired by Valitron.

use Rougin\Valla\Valid;

$data = array('name' => '', 'email' => 'not-an-email');

$valid = new Valid($data);

$valid->addRule('name', 'required');
$valid->addRule('email', 'required|email');

if (! $valid->passed())
{
    // "Name is required"
    echo $valid->firstError();
}

Installation

Install the package using Composer:

$ composer require rougin/valla

Basic usage

The core of Valla is the Valid class. Create an instance with the data to validate, then add rules with addRule():

use Rougin\Valla\Valid;

$data = array('age' => 'abc');
$data['email'] = 'not-an-email';
$data['name'] = '';

$valid = new Valid($data);

$valid->addRule('age', 'required|numeric');
$valid->addRule('email', 'required|email');
$valid->addRule('name', 'required');

if (! $valid->passed())
{
    $errors = $valid->getErrors();

    // "Age must be numeric"
    echo $valid->firstError();
}

Setting labels

Labels provide user-friendly field names in error messages:

use Rougin\Valla\Valid;

$data = array('email' => 'not-an-email');

$valid = new Valid($data);

$labels = array('email' => 'Email Address');

$valid->setLabels($labels);

$valid->addRule('email', 'email');

if (! $valid->passed())
{
    // "Email Address is not a valid email address"
    echo $valid->firstError();
}

Adding custom rules

Custom rules can be added by implementing to RuleInterface:

namespace Rougin\Test\Rules;

use Rougin\Valla\RuleInterface;
use Rougin\Valla\Ruleset;

class Uppercase implements RuleInterface
{
    public function getError()
    {
        return 'must be uppercase';
    }

    public function getName()
    {
        return 'uppercase';
    }

    public function passed($value, array $data)
    {
        return strtoupper($value) === $value;
    }

    public function setValue(array $values)
    {
        return $this;
    }
}

To register the custom rule, add it to a Ruleset class then set it to the Valid class:

use Rougin\Test\Rules\Uppercase;
use Rougin\Valla\Valid;
use Rougin\Valla\Ruleset;

// Register the custom rule ---
$rules = new Ruleset;

$rules->addRule(new Uppercase);
// ----------------------------

// Inject the ruleset to check ---
$data = array('name' => 'Valla');

$valid = new Valid($data);

$valid->setRuleset($rules);
// -------------------------------

$valid->addRule('name', 'uppercase');

// Returns "Name must contain Doe" ---
if (! $valid->passed())
{
    echo $valid->firstError();
}
// -----------------------------------

Using Check class

The Check class provides a declarative, class-based approach to validation by defining rules and labels as properties. It wraps a Valid instance internally.

Using labels, rules

The $labels property defines user-friendly names for the fields, while $rules specifies the validation rules:

use Rougin\Valla\Check;

class UserCheck extends Check
{
    protected $labels = array(
        'age' => 'Age',
        'email' => 'Email',
        'name' => 'Name',
    );

    protected $rules = array(
        'age' => 'required|numeric',
        'email' => 'required|email',
        'name' => 'required',
    );
}

Validating data

Once the Check class is created, validate an array of data using valid():

$check = new UserCheck;

$data = array('name' => 'John');
$data['age'] = 20;
$data['email'] = 'john@example.com';

if (! $check->valid($data))
{
    echo $check->firstError();
}

Dynamic labels, rules

For more complex scenarios, the labels and rules methods can be overridden to define them dynamically:

use Rougin\Valla\Check;

class UserCheck extends Check
{
    /**
     * Returns the specified labels.
     *
     * @return array<string, string>
     */
    public function labels()
    {
        $this->labels['is_company'] = 'Is a Company?';

        return $this->labels;
    }

    /**
     * Returns the specified rules based on the data.
     *
     * @param array<string, mixed> $data
     *
     * @return array<string, string>
     */
    public function rules(array $data)
    {
        if (array_key_exists('is_company', $data))
        {
            $this->rules['company_name'] = 'required';
        }

        return $this->rules;
    }
}

Using PSR-7 requests

If using ServerRequestInterface from PSR-7, the Request class provides a convenient way to validate request data:

use Rougin\Valla\Request;

class UserCheck extends Request
{
    /**
     * @var array<string, string>
     */
    protected $aliases = array(

        'username' => 'name',
        'email_add' => 'email',
        'new_age' => 'age',

    );

    // ...
}

The Request class provides two methods for validation: isParamsValid for validating query parameters and isParsedValid for validating the parsed body:

$check = new UserCheck;

// Should return the ServerRequestInterface ---
$request = Http::getServerRequest();
// --------------------------------------------

// Checks against data from "getQueryParams" ---
if ($check->isParamsValid($request))
{
    // Query parameters are valid
}
// ---------------------------------------------

// Checks against data from "getParsedBody" ---
if ($check->isParsedValid($request))
{
    // Parsed body is valid
}
// --------------------------------------------

When an alias is specified, it will be used to look for the field in the ServerRequestInterface data. For example, if the request data contains a username field, it will be validated against the rules defined for the name field.

Overriding valid

When extending the Request class and overriding the valid method, the setAlias method must be called to apply the defined aliases:

use Rougin\Valla\Request;

class UserCheck extends Request
{
    // ...

    public function valid($data)
    {
        // Always include this if aliases are defined ---
        $data = $this->setAlias($data);
        // ----------------------------------------------

        if (! parent::valid($data))
        {
            return count($this->errors) === 0;
        }

        // Add extra custom validation conditions here

        return count($this->errors) === 0;
    }
}

Built-in rules

Valla ships with 43 built-in rules, each pre-loaded in the default Ruleset:

contains

Checks that the value contains the given substring with non-string values fail automatically:

use Rougin\Valla\Valid;

$data = array('name' => 'Jane');

$valid = new Valid($data);

$valid->addRule('name', 'contains:Doe');

if (! $valid->passed())
{
    // "Name must contain Doe"
    echo $valid->firstError();
}

creditCard

Validates a credit card number using the Luhn algorithm with dashes and spaces stripped before checking:

use Rougin\Valla\Valid;

$data = array('card' => '123456789');

$valid = new Valid($data);

$valid->addRule('card', 'creditCard');

if (! $valid->passed())
{
    // "Card must be a valid credit card number"
    echo $valid->firstError();
}

email

Validates a properly formatted email address with non-string values fail automatically:

use Rougin\Valla\Valid;

$data = array('email' => 'not-an-email');

$valid = new Valid($data);

$valid->addRule('email', 'email');

if (! $valid->passed())
{
    // "Email is not a valid email address"
    echo $valid->firstError();
}

in

Checks that the value matches one of the listed values:

use Rougin\Valla\Valid;

$data = array('role' => 'guest');

$valid = new Valid($data);

$valid->addRule('role', 'in:admin,editor');

if (! $valid->passed())
{
    // "Role contains invalid value"
    echo $valid->firstError();
}

instanceOf

Validates that the value is an instance of the given class with non-object values fail automatically:

use Rougin\Valla\Valid;

$data = array('obj' => new \stdClass);

$valid = new Valid($data);

$valid->addRule('obj', 'instanceOf:\DateTime');

if (! $valid->passed())
{
    // "Obj must be an instance of 'DateTime'"
    echo $valid->firstError();
}

lengthMax

Checks that the value does not exceed the given character length with non-string values fail automatically:

use Rougin\Valla\Valid;

$data = array('name' => 'too long');

$valid = new Valid($data);

$valid->addRule('name', 'lengthMax:5');

if (! $valid->passed())
{
    // "Name must not exceed 5 characters"
    echo $valid->firstError();
}

lengthMin

Checks that the value meets the given minimum character length with non-string values fail automatically:

use Rougin\Valla\Valid;

$data = array('name' => 'Jo');

$valid = new Valid($data);

$valid->addRule('name', 'lengthMin:5');

if (! $valid->passed())
{
    // "Name must be at least 5 characters long"
    echo $valid->firstError();
}

notIn

Checks that the value does not match any of the listed values:

use Rougin\Valla\Valid;

$data = array('role' => 'admin');

$valid = new Valid($data);

$valid->addRule('role', 'notIn:admin,editor');

if (! $valid->passed())
{
    // "Role contains invalid value"
    echo $valid->firstError();
}

numeric

Checks that the value is numeric:

use Rougin\Valla\Valid;

$data = array('age' => 'abc');

$valid = new Valid($data);

$valid->addRule('age', 'numeric');

if (! $valid->passed())
{
    // "Age must be numeric"
    echo $valid->firstError();
}

required

Ensures the field is present and not empty with an optional strict mode rejecting only null:

use Rougin\Valla\Valid;

$data = array('name' => '');

$valid = new Valid($data);

$valid->addRule('name', 'required');

if (! $valid->passed())
{
    // "Name is required"
    echo $valid->firstError();
}

requiredWith

Makes the field required when at least one of the listed fields is present and non-empty:

use Rougin\Valla\Valid;

$data = array('company_name' => '', 'is_company' => 'yes');

$valid = new Valid($data);

$valid->addRule('company_name', 'requiredWith:is_company');

if (! $valid->passed())
{
    // "Company_name is required"
    echo $valid->firstError();
}

requiredWithout

Makes the field required when at least one of the listed fields is absent or empty:

use Rougin\Valla\Valid;

$data = array('phone' => '');

$valid = new Valid($data);

$valid->addRule('phone', 'requiredWithout:email');

if (! $valid->passed())
{
    // "Phone is required"
    echo $valid->firstError();
}

subset

Checks that every item in the array belongs to the listed set with non-array values fail automatically:

use Rougin\Valla\Valid;

$data = array('options' => array('a', 'd'));

$valid = new Valid($data);

$valid->addRule('options', 'subset:a,b,c');

if (! $valid->passed())
{
    // "Options contains an item that is not in the list"
    echo $valid->firstError();
}

accepted

Checks that the value is one of yes, on, 1, or true:

use Rougin\Valla\Valid;

$data = array('terms' => 'no');

$valid = new Valid($data);

$valid->addRule('terms', 'accepted');

if (! $valid->passed())
{
    // "Terms is not accepted"
    echo $valid->firstError();
}

alpha

Checks that the value contains only alphabetic characters:

use Rougin\Valla\Valid;

$data = array('name' => '123');

$valid = new Valid($data);

$valid->addRule('name', 'alpha');

if (! $valid->passed())
{
    // "Name must contain only alphabetic characters"
    echo $valid->firstError();
}

alphaNum

Checks that the value contains only alpha-numeric characters:

use Rougin\Valla\Valid;

$data = array('name' => 'test!@#');

$valid = new Valid($data);

$valid->addRule('name', 'alphaNum');

if (! $valid->passed())
{
    // "Name must contain only alpha-numeric characters"
    echo $valid->firstError();
}

array

Checks that the value is an array:

use Rougin\Valla\Valid;

$data = array('items' => 'string');

$valid = new Valid($data);

$valid->addRule('items', 'array');

if (! $valid->passed())
{
    // "Items must be an array"
    echo $valid->firstError();
}

arrayHasKeys

Checks that the array value contains all of the specified keys:

use Rougin\Valla\Valid;

$data = array('data' => array('a' => 1));

$valid = new Valid($data);

$valid->addRule('data', 'arrayHasKeys:a,b');

if (! $valid->passed())
{
    // "Data must contain the required keys"
    echo $valid->firstError();
}

ascii

Checks that the value contains only ASCII characters:

use Rougin\Valla\Valid;

$data = array('name' => "J\xC3\xA1ne");

$valid = new Valid($data);

$valid->addRule('name', 'ascii');

if (! $valid->passed())
{
    // "Name must contain only ASCII characters"
    echo $valid->firstError();
}

between

Checks that the numeric value falls between the given minimum and maximum:

use Rougin\Valla\Valid;

$data = array('age' => 25);

$valid = new Valid($data);

$valid->addRule('age', 'between:1,20');

if (! $valid->passed())
{
    // "Age must be between 1 and 20"
    echo $valid->firstError();
}

boolean

Checks that the value is a boolean:

use Rougin\Valla\Valid;

$data = array('flag' => 'yes');

$valid = new Valid($data);

$valid->addRule('flag', 'boolean');

if (! $valid->passed())
{
    // "Flag must be a boolean"
    echo $valid->firstError();
}

containsUnique

Checks that the array value contains only unique values:

use Rougin\Valla\Valid;

$data = array('items' => array('a', 'b', 'a'));

$valid = new Valid($data);

$valid->addRule('items', 'containsUnique');

if (! $valid->passed())
{
    // "Items must contain unique values only"
    echo $valid->firstError();
}

date

Checks that the value is a valid date string:

use Rougin\Valla\Valid;

$data = array('birthday' => 'not-a-date');

$valid = new Valid($data);

$valid->addRule('birthday', 'date');

if (! $valid->passed())
{
    // "Birthday must be a valid date"
    echo $valid->firstError();
}

dateAfter

Checks that the value is a date after the specified date:

use Rougin\Valla\Valid;

$data = array('date' => '2020-01-01');

$valid = new Valid($data);

$valid->addRule('date', 'dateAfter:2023-01-01');

if (! $valid->passed())
{
    // "Date must be a date after 2023-01-01"
    echo $valid->firstError();
}

dateBefore

Checks that the value is a date before the specified date:

use Rougin\Valla\Valid;

$data = array('date' => '2024-01-01');

$valid = new Valid($data);

$valid->addRule('date', 'dateBefore:2023-01-01');

if (! $valid->passed())
{
    // "Date must be a date before 2023-01-01"
    echo $valid->firstError();
}

dateFormat

Checks that the value matches the specified date format (e.g., Y-m-d):

use Rougin\Valla\Valid;

$data = array('date' => '01-01-2023');

$valid = new Valid($data);

$valid->addRule('date', 'dateFormat:Y-m-d');

if (! $valid->passed())
{
    // "Date must be a valid date format"
    echo $valid->firstError();
}

different

Checks that the value is different from the value of another field:

use Rougin\Valla\Valid;

$data = array('a' => 'hello', 'b' => 'hello');

$valid = new Valid($data);

$valid->addRule('a', 'different:b');

if (! $valid->passed())
{
    // "A must be different from b"
    echo $valid->firstError();
}

emailDNS

Checks that the value is an email address with an active domain (MX record):

use Rougin\Valla\Valid;

$data = array('email' => 'test@nonexistent-domain.invalid');

$valid = new Valid($data);

$valid->addRule('email', 'emailDNS');

if (! $valid->passed())
{
    // "Email must be a valid email address with active domain"
    echo $valid->firstError();
}

equals

Checks that the value equals the value of another field:

use Rougin\Valla\Valid;

$data = array('a' => 'hello', 'b' => 'world');

$valid = new Valid($data);

$valid->addRule('a', 'equals:b');

if (! $valid->passed())
{
    // "A must be equal to b"
    echo $valid->firstError();
}

integer

Checks that the value is an integer with an optional strict mode:

use Rougin\Valla\Valid;

$data = array('age' => 'abc');

$valid = new Valid($data);

$valid->addRule('age', 'integer');

if (! $valid->passed())
{
    // "Age must be an integer"
    echo $valid->firstError();
}

ip

Checks that the value is a valid IP address:

use Rougin\Valla\Valid;

$data = array('ip' => 'not-an-ip');

$valid = new Valid($data);

$valid->addRule('ip', 'ip');

if (! $valid->passed())
{
    // "Ip must be a valid IP address"
    echo $valid->firstError();
}

ipv4

Checks that the value is a valid IPv4 address:

use Rougin\Valla\Valid;

$data = array('ip' => '::1');

$valid = new Valid($data);

$valid->addRule('ip', 'ipv4');

if (! $valid->passed())
{
    // "Ip must be a valid IPv4 address"
    echo $valid->firstError();
}

ipv6

Checks that the value is a valid IPv6 address:

use Rougin\Valla\Valid;

$data = array('ip' => '192.168.1.1');

$valid = new Valid($data);

$valid->addRule('ip', 'ipv6');

if (! $valid->passed())
{
    // "Ip must be a valid IPv6 address"
    echo $valid->firstError();
}

length

Checks that the string length matches an exact value or falls between min and max:

use Rougin\Valla\Valid;

$data = array('name' => 'Jo');

$valid = new Valid($data);

$valid->addRule('name', 'length:5');

if (! $valid->passed())
{
    // "Name must be exactly 5 characters"
    echo $valid->firstError();
}

lengthBetween

Checks that the string length falls between the given min and max:

use Rougin\Valla\Valid;

$data = array('name' => 'too long');

$valid = new Valid($data);

$valid->addRule('name', 'lengthBetween:1,5');

if (! $valid->passed())
{
    // "Name must be between 1 and 5 characters"
    echo $valid->firstError();
}

listContains

Checks that the array value contains the specified item:

use Rougin\Valla\Valid;

$data = array('items' => array('a', 'b'));

$valid = new Valid($data);

$valid->addRule('items', 'listContains:c');

if (! $valid->passed())
{
    // "Items must contain the specified value"
    echo $valid->firstError();
}

max

Checks that the numeric value does not exceed the given maximum:

use Rougin\Valla\Valid;

$data = array('age' => 25);

$valid = new Valid($data);

$valid->addRule('age', 'max:20');

if (! $valid->passed())
{
    // "Age must not exceed 20"
    echo $valid->firstError();
}

min

Checks that the numeric value is at least the given minimum:

use Rougin\Valla\Valid;

$data = array('age' => 5);

$valid = new Valid($data);

$valid->addRule('age', 'min:10');

if (! $valid->passed())
{
    // "Age must be at least 10"
    echo $valid->firstError();
}

optional

Always passes regardless of the value, allowing a field to be optional:

use Rougin\Valla\Valid;

$data = array('field' => '');

$valid = new Valid($data);

$valid->addRule('field', 'optional');

// [NOTE] Optional rules always pass
$valid->passed();

regex

Checks that the value matches the given regular expression:

use Rougin\Valla\Valid;

$data = array('name' => 'hello');

$valid = new Valid($data);

$valid->addRule('name', 'regex:/^[0-9]+$/');

if (! $valid->passed())
{
    // "Name does not match the required pattern"
    echo $valid->firstError();
}

slug

Checks that the value is a valid slug (alpha-numeric, dashes, underscores):

use Rougin\Valla\Valid;

$data = array('slug' => 'not a slug!');

$valid = new Valid($data);

$valid->addRule('slug', 'slug');

if (! $valid->passed())
{
    // "Slug must be a valid slug"
    echo $valid->firstError();
}

url

Checks that the value is a valid URL starting with http, https, or ftp:

use Rougin\Valla\Valid;

$data = array('link' => 'not-a-url');

$valid = new Valid($data);

$valid->addRule('link', 'url');

if (! $valid->passed())
{
    // "Link must be a valid URL"
    echo $valid->firstError();
}

urlActive

Checks that the value is a valid URL with an active domain (DNS record):

use Rougin\Valla\Valid;

$data = array('link' => 'http://nonexistent-domain.invalid');

$valid = new Valid($data);

$valid->addRule('link', 'urlActive');

if (! $valid->passed())
{
    // "Link must be a valid URL with active domain"
    echo $valid->firstError();
}

Changelog

Please see CHANGELOG for more recent changes.

Contributing

See CONTRIBUTING on how to contribute to the project.

License

The MIT License (MIT). Please see LICENSE for more information.