A modern, extensible PHP library for validating primitive data types, files, nested objects, and arrays of objects with reusable and centralized validation rules.


README

Packagist Version Downloads CI PHP Version License

fynix is a modern, extensible PHP library for validating primitive data types, files, complex objects, and nested objects, including arrays of objects. Designed for high performance and flexibility, it enables developers to implement robust validation logic across modern PHP applications and frameworks.

logo

Table of Contents

  1. Overview Example

  2. Features

  3. Architecture Overview

    • Validator Classes
    • Validator Options
    • ValidationHandler
    • ValidationRegistry
    • ValidationError
  4. Validator Classes

    • ValidatorBase
    • StringValidator
    • NumberValidator
    • EmailValidator
    • PhoneNumberValidator
    • PasswordValidator
    • ImageValidator
    • ImagesValidator
    • ObjectValidator
    • ObjectArrayValidator
  5. Validator Options

    • ValidationOptionsBase
    • StringValidationOptions
    • NumberValidationOptions
    • EmailValidationOptions
    • PhoneNumberValidationOptions
    • PasswordValidationOptions
    • ImageValidationOptions
  6. Validation Matrix

    • Feature Comparison Table
    • Key Capabilities
      • Nullability
      • Min/Max Length
      • Min/Max Number
      • HTML Exclusion
      • Format/Pattern
      • Uniqueness/Existence
      • Nested Validation
      • Array Validation
  7. Basic Usage Examples

  8. Advanced Usage

  9. Core Classes and Their Roles

  10. Extending the Library

    • Creating Custom Validators
    • Creating Custom Options Classes
  11. Best Practices & Advanced Patterns

    • Centralize Validation Logic
    • Normalize Errors for UI
    • Custom Validators
    • Custom Options
    • Batch Validation
  12. Installation

  13. Testing

  14. Contributing

    • Forking & Branching
    • Committing
    • Running Tests
    • Pull Requests
    • Reporting Issues
  15. Funding & Sponsorship

  16. License

  17. Author

    • GitHub Profile
    • Repository
    • Website
    • Packagist Link

Overview Example

FreightDto

<?php 
namespace App\Dto\Quote;;

use App\Dto\Address\AddressDto;
use App\Traits\ArrayConvertible;
use DateTime;

class FreightDto {
    use ArrayConvertible;

    public ?int $id = null;

    /**@var PackageDto[] */
    public array $packages = [];
    public ?string $customerName;
    public ?DateTime $shippingDate;
    public bool $containsDangerousGoods = false;
    public AddressDto $pickupAddress;
    public AddressDto $deliveryAddress;
}

Nested DTO Structure

$freight = new FreightDto();
$freight->packages[] = (new PackageDto())->items[] = new ItemDto();
$freight->pickupAddress = new AddressDto();
$freight->deliveryAddress = new AddressDto();

Setting Up Example Validation

Validation rules are registered using the ValidationRegistry. You can organize rules by DTO type for better structure and maintainability.

<?php
class ValidationRuleServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        self::registerDimensionValidation();
        self::registerItemValidation();
        self::registerAddressValidation();
        self::registerShippingValidation();
        self::registerPackageValidation();
    }

    private static function registerDimensionValidation() : void {
        ValidationRegistry::register(DimensionDto::class, function(DimensionDto $dimension) {
            return [
                new NumberValidator('Length', 'lengthCm', new NumberValidationOptions(number: [1, 1800])),
                new NumberValidator('Width', 'widthCm', new NumberValidationOptions(number: [1, 1800])),
                new NumberValidator('Height', 'heightCm', new NumberValidationOptions(number: [1, 2000])),
                new NumberValidator('Weight', 'weightKg', new NumberValidationOptions(number: [1, 1000]))
            ];
        });
    }

    private static function registerItemValidation() : void
    {
       ValidationRegistry::register(ItemDto::class, function (ItemDto $dto) {
            return [
                new StringValidator('Description', 'description'),
                new ObjectValidator('dimension', DimensionDto::class),
           ];
       });
    }

    private static function registerAddressValidation() : void 
    {
        ValidationRegistry::register(AddressDto::class, function(AddressDto $dto) {
            return [
                new StringValidator('Suburb', 'suburb'),
                new NumberValidator('Postcode', 'postcode', new NumberValidationOptions(length: [2, 10])),
                new StringValidator('State', 'state', new StringValidationOptions(length: [2, 6])),
                new StringValidator('Country', 'countryCode', new StringValidationOptions(length: [2, 4]))
            ];
        });
    }

    private static function registerShippingValidation(): void 
    {
        ValidationRegistry::register(FreightDto::class, function(FreightDto $dto) {
            return[
                new ObjectArrayValidator('packages', PackageDto::class),
                new StringValidator('Customer name', 'customerName', new StringValidationOptions(length: [0, 50], isRequired: false)),
                new ObjectValidator('pickupAddress', AddressDto::class),
                new ObjectValidator('deliveryAddress', AddressDto::class),
            ];
        });
    }

    private static function registerPackageValidation(): void {
        ValidationRegistry::register(PackageDto::class, function(PackageDto $dto) {
            return [
                new StringValidator('Package Type', 'type'),
                new StringValidator('Description', 'description', new StringValidationOptions(length: [0, 50], isRequired: false)),
                new ObjectValidator('dimensions', DimensionDto::class),
                new ObjectArrayValidator('items', ItemDto::class)
            ];
        });
    }
}

Validating DTOs

Once your validation rules are registered, you can validate DTO instances anywhere in your application:

<?php
    $dto = DtoMapper::toFreightDto($args);
    $errors = ValidationHandler::validate($dto);
    $flattenedErrors = ValidationHandler::flattenValidationErrors($errors);
    
    if(count($errors) > 0) 
        return;

Validation Example Image Flattened Validation Example Image

Features

  • Comprehensive Validation: Strings, numbers, emails, phone numbers, passwords, images, arrays of images, nested objects, and arrays of objects.
  • Extensible Architecture: Easily add custom validation rules or extend built-in validators.
  • Validator Options: Fine-grained control over required fields, length, numeric ranges, file types, and more.
  • Nested & Array Validation: Validate nested objects and arrays of objects using registered rules.
  • Error Normalization: Flatten nested error structures for easy form binding.
  • Centralized Registry: Register and retrieve validation rules for any class.
  • Open Source: MIT-licensed and open for contributions.

Architecture Overview

The library is organized into several core components:

  • Validator Classes: Each validator encapsulates logic for a specific data type or structure.
  • Validator Options: Option classes provide configuration for validators, such as length, required status, and custom constraints.
  • ValidationHandler: Orchestrates validation, supports batch and associative validation, and error normalization.
  • ValidationRegistry: Central registry for registering and retrieving validation rules for custom classes.
  • ValidationError: Standardized error object for all validators.

Validator Classes

Validator Description Options Class Key Options
StringValidator Validates string type, length, nullability, and excludes HTML tags. StringValidationOptions length, isRequired, includeGenericValidation
NumberValidator Validates numeric type and enforces min/max value constraints. NumberValidationOptions length, number (min/max), isRequired, includeGenericValidation
EmailValidator Validates email format, DNS, uniqueness, and structure. EmailValidationOptions length, isUsername, isRequired, includeGenericValidation
PhoneNumberValidator Validates phone number format, allowed symbols, and length. PhoneNumberValidationOptions length, isRequired, includeGenericValidation
PasswordValidator Enforces password strength: uppercase, lowercase, number, special character, length. PasswordValidationOptions length, isRequired, includeGenericValidation
ImageValidator Validates a single image file: size, extension, and actual image content. ImageValidationOptions numImage, maxFileSizeMB, isRequired, includeGenericValidation
ImagesValidator Validates an array of image files, each using ImageValidator. ImageValidationOptions numImage, maxFileSizeMB, isRequired, includeGenericValidation
ObjectValidator Validates a nested object property using registered rules for its class. N/A N/A
ObjectArrayValidator Validates an array of objects, each using registered rules for its class. N/A N/A

ValidatorBase

Abstract base for all validators. Implements generic validation (nullability, length, HTML exclusion) and requires child classes to implement validate($fieldValue) for specific logic.

StringValidator

Validates string type, length, nullability, and excludes HTML tags. Usage:

$options = new StringValidationOptions(['min' => 2, 'max' => 50], true);
$validator = new StringValidator('First Name', 'firstName', $options);

NumberValidator

Validates numeric type and enforces min/max value constraints. Usage:

$options = new NumberValidationOptions(['min' => 1, 'max' => 30], true, true, ['min' => 18, 'max' => 99]);
$validator = new NumberValidator('Age', 'age', $options);

EmailValidator

Validates email format, DNS, uniqueness, and structure. Usage:

$options = new EmailValidationOptions(['min' => 6, 'max' => 100], true, true, true);
$validator = new EmailValidator('Email', 'email', $options);

PhoneNumberValidator

Validates phone number format, allowed symbols, and length. Usage:

$options = new PhoneNumberValidationOptions(['min' => 10, 'max' => 12], true);
$validator = new PhoneNumberValidator('Phone', 'phoneNumber', $options);

PasswordValidator

Enforces password strength: uppercase, lowercase, number, special character, length. Usage:

$options = new PasswordValidationOptions(['min' => 8, 'max' => 30], true);
$validator = new PasswordValidator('Password', 'password', $options);

ImageValidator

Validates a single image file: size, extension, and actual image content. Usage:

$options = new ImageValidationOptions(['min' => 1, 'max' => 1], true, false, 5);
$validator = new ImageValidator('Profile Picture', 'profilePic', $options);

ImagesValidator

Validates an array of image files, each using ImageValidator. Usage:

$options = new ImageValidationOptions(['min' => 1, 'max' => 5], true, false, 5);
$validator = new ImagesValidator('Gallery', 'galleryImages', $options);

ObjectValidator

Validates a nested object property using registered rules for its class. Usage:

$validator = new ObjectValidator('address', UserAddress::class);

ObjectArrayValidator

Validates an array of objects, each using registered rules for its class. Usage:

$validator = new ObjectArrayValidator('items', FreightItemDto::class);

Validator Options

All validators accept an options object to configure their behavior. These options classes allow fine-tuning of validation logic for each field type.

Class Name Description Key Properties / Options
ValidationOptionsBase Common base for all options classes includeGenericValidation, fieldType, isRequired, length (min/max array)
StringValidationOptions Controls string validation length (min/max, default: 2-50), isRequired (default: true), includeGenericValidation (default: true)
NumberValidationOptions Controls number validation length (string length), number (min/max numeric value), isRequired, includeGenericValidation
EmailValidationOptions Controls email validation length (min/max), isUsername (check username uniqueness), isRequired, includeGenericValidation
PhoneNumberValidationOptions Controls phone number validation length (min/max), isRequired, includeGenericValidation
PasswordValidationOptions Controls password validation length (min/max), isRequired, includeGenericValidation
ImageValidationOptions Controls image validation numImage (min/max), maxFileSizeMB, isRequired, includeGenericValidation
ObjectValidationOptions Controls nested object validation validateOnNull (whether to validate when the object is null)

Validation Matrix

Feature String Number Email Phone Password Image Images Object ObjectArray
Nullability
Min/Max Length
Min/Max Number
HTML Exclusion
Format/Pattern
Uniqueness/Existence
Nested Validation
Array Validation

Basic Usage Examples

String Validation

use Fynix\Validators\StringValidator;
use Fynix\ValidationOptions\StringValidationOptions;

$options = new StringValidationOptions(['min' => 2, 'max' => 50], true);
$stringValidator = new StringValidator('First Name', 'firstName', $options);

Email Validation

use Fynix\Validators\EmailValidator;
use Fynix\ValidationOptions\EmailValidationOptions;

$emailOptions = new EmailValidationOptions(['min' => 6, 'max' => 100], true, true, true);
$emailValidator = new EmailValidator('Email', 'email', $emailOptions);

Number Validation

use Fynix\Validators\NumberValidator;
use Fynix\ValidationOptions\NumberValidationOptions;

$numberOptions = new NumberValidationOptions(['min' => 1, 'max' => 30], true, true, ['min' => 18, 'max' => 99]);
$numberValidator = new NumberValidator('Age', 'age', $numberOptions);

Password Validation

use Fynix\Validators\PasswordValidator;
use Fynix\ValidationOptions\PasswordValidationOptions;

$passwordOptions = new PasswordValidationOptions(['min' => 8, 'max' => 30], true);
$passwordValidator = new PasswordValidator('Password', 'password', $passwordOptions);

Image Validation

use Fynix\Validators\ImageValidator;
use Fynix\ValidationOptions\ImageValidationOptions;

$imageOptions = new ImageValidationOptions(['min' => 1, 'max' => 1], true, false, 5);
$imageValidator = new ImageValidator('Profile Picture', 'profilePic', $imageOptions);

Nested Object Validation

use Fynix\Validators\ObjectValidator;

$objectValidator = new ObjectValidator('address', UserAddress::class);

Array of Objects Validation

use Fynix\Validators\ObjectArrayValidator;

$objectArrayValidator = new ObjectArrayValidator('items', FreightItemDto::class);

Example: Full User Registration Validation

Below is a practical example showing how to use the library for a user registration form with multiple fields and nested validation:

use Fynix\Validators\StringValidator;
use Fynix\Validators\EmailValidator;
use Fynix\Validators\PasswordValidator;
use Fynix\Validators\ObjectValidator;
use Fynix\ValidationRegistry;
use Fynix\ValidationHandler;

class UserAddress {
    public ?string $street = null;
    public ?string $city = null;
    public ?string $postcode = null;
}

class User {
    public ?string $firstName = null;
    public ?string $email = null;
    public ?string $password = null;
    public ?UserAddress $address = null;

    public static function getValidationRules($instance) {
        return [
            new StringValidator('First Name', 'firstName'),
            new EmailValidator('Email', 'email'),
            new PasswordValidator('Password', 'password'),
            new ObjectValidator('address', UserAddress::class),
        ];
    }
}

ValidationRegistry::register(User::class, [User::class, 'getValidationRules']);
ValidationRegistry::register(UserAddress::class, function($instance) {
    return [
        new StringValidator('Street', 'street'),
        new StringValidator('City', 'city'),
        new StringValidator('Postcode', 'postcode'),
    ];
});

$user = new User();
$user->firstName = 'John';
$user->email = 'john@example.com';
$user->password = 'Password123!';
$user->address = new UserAddress();
$user->address->street = '123 Main St';
$user->address->city = 'Metropolis';
$user->address->postcode = '12345';

$errors = ValidationHandler::validate($user);
if (!empty($errors)) {
    // Handle errors
}

Advanced Usage

Batch Validation

use Fynix\ValidationHandler;

$errors = ValidationHandler::validateMany($user, $profile, $settings);

Associative Validation

$instances = ['user' => $user, 'profile' => $profile];
$errors = ValidationHandler::validateManyAssoc($instances);

Error Normalization

$errors = ValidationHandler::validate($user);
$flatErrors = ValidationHandler::flattenValidationErrors($errors);

Validation Example Flattened Validation Example

Registering Custom Validation Rules

use Fynix\ValidationRegistry;

ValidationRegistry::register(User::class, function($instance) {
    return [
        new StringValidator('First Name', 'firstName'),
        new EmailValidator('Email', 'email'),
        // ... other rules
    ];
});

Reusable Validation Instances - More modern way

fynix allows you to define reusable validation rules for your data transfer objects (DTOs) using the ValidationRegistry. The ValidationRuleServiceProvider demonstrates how to register validation rules for multiple DTOs in a structured and type-safe way.

<?php

namespace App\Providers;

use App\Dto\Address\AddressDto;
use App\Dto\DimensionDto;
use App\Dto\Quote\FreightDto;
use App\Dto\Quote\ItemDto;
use App\Dto\Quote\PackageDto;
use Illuminate\Support\ServiceProvider;
use Fynix\ValidationOptions\NumberValidationOptions;
use Fynix\ValidationOptions\StringValidationOptions;
use Fynix\ValidationRegistry;
use Fynix\Validators\NumberValidator;
use Fynix\Validators\ObjectArrayValidator;
use Fynix\Validators\ObjectValidator;
use Fynix\Validators\StringValidator;

class ValidationRuleServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        self::registerDimensionValidation();
        self::registerItemValidation();
        self::registerAddressValidation();
        self::registerShippingValidation();
        self::registerPackageValidation();
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        //
    }

    private static function registerDimensionValidation() : void {
        ValidationRegistry::register(DimensionDto::class, function(DimensionDto $dimension) {
            return [
                new NumberValidator('Length', 'lengthCm', new NumberValidationOptions(number: [1, 1800])),
                new NumberValidator('Width', 'widthCm', new NumberValidationOptions(number: [1, 1800])),
                new NumberValidator('Height', 'heightCm', new NumberValidationOptions(number: [1, 2000])),
                new NumberValidator('Weight', 'weightKg', new NumberValidationOptions(number: [1, 1000]))
            ];
        });
    }

    private static function registerItemValidation() : void
    {
       ValidationRegistry::register(ItemDto::class, function (ItemDto $dto) {
            return [
                new StringValidator('Description', 'description'),
                new ObjectValidator('dimension', DimensionDto::class),
           ];
       });
    }

    private static function registerAddressValidation() : void 
    {
        ValidationRegistry::register(AddressDto::class, function(AddressDto $dto) {
            return [
                new StringValidator('Suburb', 'suburb'),
                new NumberValidator('Postcode', 'postcode', new NumberValidationOptions(length: [2, 10])),
                new StringValidator('State', 'state', new StringValidationOptions(length: [2, 6])),
                new StringValidator('Country', 'countryCode', new StringValidationOptions(length: [2, 4]))
            ];
        });
    }

    private static function registerShippingValidation(): void 
    {
        ValidationRegistry::register(FreightDto::class, function(FreightDto $dto) {
            return[
                new ObjectArrayValidator('packages', PackageDto::class),
                new StringValidator('Customer name', 'customerName', new StringValidationOptions(length: [0, 50], isRequired: false)),
                new ObjectValidator('pickupAddress', AddressDto::class),
                new ObjectValidator('deliveryAddress', AddressDto::class),
            ];
        });
    }

    private static function registerPackageValidation(): void {
        ValidationRegistry::register(PackageDto::class, function(PackageDto $dto) {
            return [
                new StringValidator('Package Type', 'type'),
                new StringValidator('Description', 'description', new StringValidationOptions(length: [0, 50], isRequired: false)),
                new ObjectValidator('dimensions', DimensionDto::class),
                new ObjectArrayValidator('items', ItemDto::class)
            ];
        });
    }
}
  • and wherever needed just call with the dto class instance
<?php
    $dto = DtoMapper::toFreightDto($args);
    $errors = ValidationHandler::validate($dto);
    $flattenedErrors = ValidationHandler::flattenValidationErrors($errors);
    
    if(count($errors) > 0) 
        return;

Core Classes and Their Roles

Validator

The central utility for validating data objects against rules. It provides static methods to retrieve validation errors for a given object and set of rules. Handles nested and array validation, returning errors as either strings or ValidationError objects.

ValidationHandler

Orchestrates the validation process for single objects, arrays, or associative arrays. Supports error normalization (flattening nested error arrays to dot notation for easy form binding). Example:

$errors = ValidationHandler::validate($user);
$flatErrors = ValidationHandler::flattenValidationErrors($errors);

ValidationRegistry

Implements a registry pattern for associating classes with their validation rules. Register rules for a class and retrieve them dynamically during validation. Example:

ValidationRegistry::register(User::class, function($instance) {
    return [
        new StringValidator('First Name', 'firstName'),
        new EmailValidator('Email', 'email'),
        // ...
    ];
});

ValidationError

Represents a validation error, including the rule, error message, and field name. Used for structured error reporting and debugging.

Extending the Library

You can create your own custom validators by extending ValidatorBase and implementing the validate($fieldValue) method. Custom option classes can also be created by extending ValidationOptionsBase.

class CustomValidator extends ValidatorBase {
    public function validate($fieldValue) : ?ValidationError {
        // Custom validation logic
    }
}

Best Practices & Advanced Patterns

  • Centralize Validation Logic: Use ValidationRegistry to keep validation rules organized and reusable for each class.
  • Normalize Errors for UI: Use ValidationHandler::flattenValidationErrors() to flatten errors for form binding and display.
  • Custom Validators: Extend ValidatorBase for domain-specific validation needs.
  • Custom Options: Extend ValidationOptionsBase to add new configuration parameters for your validators.
  • Batch Validation: Validate multiple objects at once with ValidationHandler::validateMany() or associative arrays with validateManyAssoc().

Installation

composer require bishalshrestha/fynix

Testing

Unit tests are provided using PHPUnit:

vendor/bin/phpunit tests/

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository.
  2. Create a feature branch (git checkout -b feature-name).
  3. Make your changes and commit them with clear messages.
  4. Run tests to ensure nothing is broken.
  5. Submit a pull request explaining your changes.

For bug reports or feature requests, please open an issue on GitHub.

Funding & Sponsorship

fynix is an open-source project maintained with care to deliver a reliable and extensible validation engine for PHP developers.
If you or your organization find this project valuable, please consider supporting its development. Your sponsorship helps sustain long-term maintenance, improve features and documentation, and keep the library freely available to the community.

As a token of appreciation, sponsors may have their logo and link featured in the project README and documentation site.
Priority support and early access to planned features may also be offered where appropriate.

Support Options

GitHub Sponsors
Buy Me a Coffee
Thanks.dev

License

This project is licensed under the MIT License.

Author

Bishal Shrestha

GitHub
Repo
Website (coming soon)

© 2025 Bishal Shrestha, All rights reserved

Packagist