teonord/validator

Advanced validation library with multiple API styles including JSON templates, inline rules, builder pattern, and functional composition. Features 50+ built-in validators, dot notation for nested objects, field reference resolution, and internationalization support. Zero dependencies, fully synchron

Installs: 2

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Forks: 0

pkg:composer/teonord/validator

0.1.2 2025-10-15 22:26 UTC

This package is auto-updated.

Last update: 2025-12-16 02:46:06 UTC


README

Advanced validation library for PHP with multiple API styles including JSON templates, inline rules, builder pattern, and functional composition. Features 50+ built-in validators, dot notation for nested objects, field reference resolution, and internationalization support. Zero dependencies, fully synchronous, and designed for maximum developer experience with extensible custom rule system.

Features

  • 🎯 Multiple Validation APIs: JSON templates, inline rules, builder pattern, functional composition
  • 🏷️ PHP 8.2+: Modern PHP with strict types and full type safety
  • 🌍 Internationalization: Multi-language error messages
  • 🔧 Extensible: Custom rules and validators
  • 🧪 Comprehensive Rule Set: 50+ built-in validation rules
  • 🚀 Lightweight: Zero dependencies, only PHP extensions

📚 Table of Contents

Installation

composer require teonord/validator

Examples

Quick Start

use Teonord\Validator\Validator;
use Teonord\Validator\Parser\InlineParser;

$data = [
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'age' => 25
];

$validator = new Validator($data);
$result = $validator->validateRules([
    'name' => InlineParser::parse('required|minLength:2'),
    'email' => InlineParser::parse('required|email'),
    'age' => [['rule' => 'numeric'], ['rule' => 'min', 'value' => 18]]
]);

var_dump($result->isValid); // bool(true)
var_dump($result->errors); // []

JSON Template Validation

use Teonord\Validator\Validator;

$data = [
    'username' => 'john_doe'
];

$validator = new Validator($data);
$template = [
    [
        'name' => 'username',
        'validation' => [
            [
                'rule' => 'required',
                'message' => [
                    'en' => 'Username is required',
                    'fr' => 'Nom d\'utilisateur est requis'
                ]
            ],
            [
                'rule' => 'minLength',
                'value' => 3,
                'message' => [
                    'en' => 'Username must be at least 3 characters',
                    'fr' => 'Le nom d\'utilisateur doit contenir au moins 3 caractères'
                ]
            ]
        ]
    ]
];

$result = $validator->validateTemplate($template);

Validation APIs

JSON Template API

Structured validation with field definitions and custom messages:

use Teonord\Validator\Validator;

$data = [
    'email' => 'john@example.com',
    'password' => 'pass123'
];

$validator = new Validator($data);
$template = [
    [
        'name' => 'email',
        'validation' => [
            [
                'rule' => 'required',
                'message' => ['en' => 'Email is required', 'es' => 'El email es requerido']
            ],
            [
                'rule' => 'email',
                'message' => ['en' => 'Invalid email format', 'es' => 'Formato de email inválido']
            ]
        ]
    ],
    [
        'name' => 'password',
        'validation' => [
            [
                'rule' => 'required',
                'message' => ['en' => 'Password is required', 'es' => 'La contraseña es requerida']
            ],
            [
                'rule' => 'minLength',
                'value' => 8,
                'message' => ['en' => 'Password must be 8+ characters', 'es' => 'La contraseña debe tener 8+ caracteres']
            ]
        ]
    ]
];

$result = $validator->validateTemplate($template);

Inline Rules API

use Teonord\Validator\Validator;
use Teonord\Validator\Parser\InlineParser;

$data = [
    'username' => 'john',
    'email' => 'john@example.com',
    'age' => 25,
    'tags' => ['js', 'php'],
    'website' => 'https://example.com'
];

$validator = new Validator($data);
$result = $validator->validateRules([
    'username' => InlineParser::parse('required|minLength:3|maxLength:20|alphaDash'),
    'email' => InlineParser::parse('required|email'),
    'age' => InlineParser::parse('required|numeric|min:18|max:100'),
    'tags' => InlineParser::parse('array|minItems:1|maxItems:5'),
    'website' => InlineParser::parse('url')
]);

Builder Pattern API

Fluent interface for building validation rules:

use Teonord\Validator\ValidatorBuilder;

$result = (new ValidatorBuilder())
    ->setData(['username' => 'john_doe', 'email' => 'john@example.com'])
    ->addField('username', [
        ['rule' => 'required'],
        ['rule' => 'minLength', 'value' => 3],
        ['rule' => 'alphaDash']
    ])
    ->addField('email', [
        ['rule' => 'required'],
        ['rule' => 'email']
    ])
    ->setLanguage('en')
    ->validate();

Functional Composition API

Composable validation pipelines:

use Teonord\Validator\Functional\Composition;
use Teonord\Validator\Functional\ValidatorPipes;
use Teonord\Validator\Types\ValidationError;

// Individual validation pipes
$userValidation = ValidatorPipes::createPipe([
    'name' => [['rule' => 'required'], ['rule' => 'minLength', 'value' => 2]],
    'email' => [['rule' => 'required'], ['rule' => 'email']]
]);

$addressValidation = ValidatorPipes::createPipe([
    'street' => [['rule' => 'required']],
    'city' => [['rule' => 'required']],
    'country' => [['rule' => 'required'], ['rule' => 'in', 'value' => ['US', 'CA', 'UK']]]
]);

$paymentValidation = ValidatorPipes::createPipe([
    'payment_method' => [['rule' => 'required']],
    'amount' => [['rule' => 'required'], ['rule' => 'numeric'], ['rule' => 'min', 'value' => 1]]
]);

// Data transformation
$sanitizeData = function(array $data): array {
    return [
        ...$data,
        'name' => isset($data['name']) ? strtolower(trim($data['name'])) : null,
        'email' => isset($data['email']) ? strtolower(trim($data['email'])) : null,
        'street' => isset($data['street']) ? trim($data['street']) : null
    ];
};

// Error formatting
$formatErrors = function(array $errors): array {
    return array_map(function(ValidationError $error) {
        return new ValidationError(
            $error->field,
            $error->rule,
            "{$error->field}: {$error->message}",
            $error->value,
            [...$error->params, 'code' => "VALIDATION_" . strtoupper($error->rule)]
        );
    }, $errors);
};

// Complete validation pipeline
$completeValidation = Composition::pipe(
    Composition::transformData($userValidation, $sanitizeData),
    $addressValidation,
    Composition::conditional(
        fn(array $data) => $data['requires_payment'] ?? false,
        $paymentValidation
    )
);

$validationPipe = Composition::mapErrors($completeValidation, $formatErrors);

// Usage
$userData = [
    'name' => '  JOHN DOE  ',
    'email' => 'JOHN@EXAMPLE.COM',
    'street' => '123 Main St',
    'city' => 'New York',
    'country' => 'US',
    'requires_payment' => true,
    'payment_method' => 'credit_card',
    'amount' => 50
];

$result = $validationPipe($userData);

Rule Reference

String Rules

  • required - Field is required
  • minLength:value - Minimum string length
  • maxLength:value - Maximum string length
  • length:value - Exact string length
  • email - Valid email format
  • url - Valid URL format
  • ip - Valid IP address (IPv4, IPv6 compressed/uncompressed, IPv4-mapped)
  • uuid - Valid UUID format
  • alpha - Only letters
  • alphaNumeric - Letters and numbers only
  • alphaDash - Letters, numbers, dashes and underscores
  • regex:pattern - Matches regex pattern
  • startsWith:value - Starts with value
  • endsWith:value - Ends with value
  • contains:value - Contains value
  • notContains:value - Does not contain value
  • in:value1,value2,... - Value is in list
  • notIn:value1,value2,... - Value is not in list

Number Rules

  • numeric - Is a number
  • integer - Is an integer
  • float - Is a float
  • min:value - Minimum value
  • max:value - Maximum value
  • between:min,max - Between min and max
  • positive - Positive number
  • negative - Negative number
  • multipleOf:value - Multiple of value

Array Rules

  • array - Is an array
  • minItems:value - Minimum array length
  • maxItems:value - Maximum array length
  • lengthItems:value - Exact array length
  • includes:value - Array includes value
  • excludes:value - Array excludes value
  • unique - Array has unique values

Date Rules

  • date - Valid date
  • before:date - Date is before
  • after:date - Date is after
  • betweenDates:start,end - Date is between

File Rules

  • file - Is a file object
  • mime:type1,type2,... - File MIME type
  • maxSize:bytes - Maximum file size
  • minSize:bytes - Minimum file size

Boolean Rules

  • boolean - Is boolean
  • accepted - Is accepted (true, 'true', 1, '1', 'yes', 'on')

Conditional Rules

  • requiredIf:field,value - Required if field equals value
  • requiredUnless:field,value - Required unless field equals value
  • requiredWith:field - Required when field is present
  • requiredWithAll:field1,field2,... - Required when all fields present
  • requiredWithout:field - Required when field is not present
  • requiredWithoutAll:field1,field2,... - Required when none of fields present
  • same:field - Same as another field
  • different:field - Different from another field
  • gt:field - Greater than another field
  • gte:field - Greater than or equal to another field
  • lt:field - Less than another field
  • lte:field - Less than or equal to another field
  • when:condition - Apply rules when condition is true
  • requiredIfAny:conditions - Required if any condition is met
  • requiredIfAll:conditions - Required if all conditions are met

Error Handling

use Teonord\Validator\Validator;

$validator = new Validator($data);
$result = $validator->validateRules([
    'email' => [['rule' => 'required'], ['rule' => 'email']]
]);

if (!$result->isValid) {
    foreach ($result->errors as $error) {
        echo "Field: " . $error->field . "\n";
        echo "Rule: " . $error->rule . "\n";
        echo "Message: " . $error->message . "\n";
        echo "Value: " . ($error->value ?? 'null') . "\n";
    }

    $apiErrors = array_map(function($error) {
        return [
            'field' => $error->field,
            'message' => $error->message,
            'code' => $error->rule
        ];
    }, $result->errors);
}

Internationalization

use Teonord\Validator\Validator;
use Teonord\Validator\Types\ValidatorOptions;

$validator = new Validator($data, new ValidatorOptions(
    'fr', // language
    [
        'email.required' => [
            'en' => 'Email address is required',
            'fr' => 'L\'adresse email est requise',
            'es' => 'La dirección de correo electrónico es obligatoria'
        ],
        'email.email' => [
            'en' => 'Must be a valid email address',
            'fr' => 'Doit être une adresse email valide',
            'es' => 'Debe ser una dirección de correo electrónico válida'
        ]
    ]
));

Custom Rules

use Teonord\Validator\Validator;

$validator = new Validator($data);

$customRule = function(mixed $value, array $params, array $data): bool|string {
    if ($value === 'custom') {
        return true;
    }
    return false; // or return error message string
};

$validator->addCustomRule('customRule', $customRule);

$validator->addCustomRule('even', fn($value) => is_numeric($value) && $value % 2 === 0);

$result = $validator->validateRules([
    'field' => [['rule' => 'customRule']],
    'number' => [['rule' => 'even']]
]);

API Reference

Main Classes and Functions

  • Validator - Main validation class
  • ValidatorBuilder - Fluent builder for validation rules
  • InlineParser::parse() - Parse inline rule string

Validator Class

Constructor

new Validator(array $data, ?ValidatorOptions $options = null)

Methods

  • validateTemplate(array $template): ValidationResult
  • validateRules(array $rules): ValidationResult
  • addCustomRule(string $name, callable $rule): void

ValidatorBuilder Class

Methods

  • setData(array $data): self
  • addField(string $field, array $rules): self
  • setOptions(ValidatorOptions $options): self
  • setLanguage(string $language): self
  • build(): Validator
  • validate(): ValidationResult

Functional API

  • Composition::pipe(callable ...$pipes): callable
  • Composition::mapErrors(callable $pipe, callable $mapper): callable
  • Composition::transformData(callable $pipe, callable $transformer): callable
  • Composition::conditional(callable $condition, callable $truePipe, ?callable $falsePipe = null): callable
  • Composition::mergePipes(callable ...$pipes): callable
  • ValidatorPipes::createPipe(array $rules, ?ValidatorOptions $options = null): callable
  • ValidatorPipes::createTemplatePipe(array $template, ?ValidatorOptions $options = null): callable
  • ValidatorPipes::validateField(string $field, array $rules, ?ValidatorOptions $options = null): callable
  • Composition::tap(callable $pipe, callable $callback): callable
  • Composition::catchError(callable $pipe, callable $handler): callable
  • Composition::withDefault(callable $pipe, array $defaultData): callable

Types

class ValidationResult {
    public bool $isValid;
    /** @var ValidationError[] */
    public array $errors;
    public array $data;
}

class ValidationError {
    public string $field;
    public string $rule;
    public string $message;
    public mixed $value;
    public array $params;
}

class ValidatorOptions {
    public string $language = 'en';
    /** @var array<string, array<string, string>> */
    public array $customMessages = [];
    /** @var array<string, callable> */
    public array $customRules = [];
}

License

MIT