mb4it / validation
Framework-agnostic validation with flexible rules and translation
Requires
- php: >=8.2
- brick/math: ^0.14.8
- egulias/email-validator: ^4.0
- mb4it/collections: ^1
- mb4it/messages: ^1.0
- mb4it/stringable: ^1
- psr/container: ^2.0
- symfony/mime: ^7.2
- symfony/polyfill-php83: ^1.33
Requires (Dev)
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-03-30 09:54:09 UTC
README
Framework-agnostic validation library inspired by Laravel-style rules, without coupling to a framework.
Features
- String rules (
required|string|min:3) - Object and closure rules
- Localized messages (
ru/en) - Wildcard array rules (
items.*.name) - Strict handling of unknown string rules (fail-fast)
- Optional file-like validation without requiring
symfony/http-foundation
Installation
composer require mb4it/validation
symfony/http-foundation is optional.
File-related rules work with file-like objects that expose methods such as:
getSize()getPath()orgetRealPath()guessExtension()getMimeType()
Quick Start
use MB\Validation\Factory; $factory = Factory::create(lang: 'en'); // or 'ru' $validator = $factory->make( ['name' => 'John', 'age' => 30], ['name' => 'required|string|min:3', 'age' => 'required|integer|between:18,99'] ); if ($validator->fails()) { $errors = $validator->errors()->toArray(); } else { $validated = $validator->validated(); }
Factory API
Create Factory
use MB\Validation\Factory; $factory = Factory::create(lang: 'ru');
Signature:
Factory::create( ?MessagesInterface $message = null, ?ContainerInterface $container = null, string $lang = 'ru' )
If MessagesInterface is not provided, factory uses DefaultMessages::create($lang).
One-shot Validation
$validated = $factory->validate($data, $rules, $messages = [], $attributes = []);
validate() throws MB\Validation\ValidationException if validation fails.
Strict Mode
Unknown string rules throw InvalidArgumentException by default.
Use backward-compatible mode if needed:
$factory = Factory::create()->allowUnknownRules();
Supported Rule Groups
- Presence / structure:
required,required_if,required_unless,required_array_keys,filled,array,list,boolean - Type:
string,numeric,integer,decimal - Size:
min,max,between,size,digits,digits_between - String:
alpha,alpha_num,alpha_dash,contains,doesnt_contain,starts_with,ends_with,doesnt_start_with,doesnt_end_with,uppercase,lowercase,ascii - Format / network:
email,url,ip,ipv4,ipv6,mac_address,hex_color,uuid,ulid,json - Date / time:
date,date_format,timezone - Set / comparison:
in,not_in,same,different,any_of - Database:
exists,unique(requiresPresenceVerifierInterface)
Messages and Localization
Default files:
lang/en/validation.phplang/ru/validation.php
Message resolution order:
- Inline messages passed to
make()/validate() - Validator fallback custom messages
- Translator (
MessagesInterface)
Inline message example:
$validator = $factory->make( ['email' => null], ['email' => 'required|email'], ['email.required' => 'We need your email address.'] );
Validate Object Properties via Trait
Use MB\Validation\Concerns\ValidatesProperties for DTO/value object validation.
Trait behavior:
- collects initialized public/protected/private properties via reflection
- supports wildcard keys (
items.*.name,profile.tags.*) - validates with
Factory::create(lang: $this->validatorLang)
Example
use MB\Validation\Concerns\ValidatesProperties; final class ProductDto { use ValidatesProperties; protected string $validatorLang = 'ru'; // optional public function __construct( public string $title, protected array $items, private array $profile, ) {} protected function rules(): array { return [ 'title' => 'required|string|min:3', 'items' => 'required|array|min:1', 'items.*.name' => 'required|string', 'profile.tags.*' => 'required|string', ]; } } $dto = new ProductDto( title: 'Notebook', items: [['name' => 'A4'], ['name' => 'A5']], profile: ['tags' => ['office', 'paper']] ); $validated = $dto->validate();
Get Errors with Trait
validate() throws ValidationException:
use MB\Validation\ValidationException; try { $validated = $dto->validate(); } catch (ValidationException $e) { $errors = $e->errors(); // ['field' => ['message1', ...]] }
For non-throwing flow, expose a validator helper in your class:
use MB\Validation\Factory; use MB\Validation\Validator; public function validator(): Validator { return Factory::create(lang: $this->validatorLang)->make( $this->validationData(), $this->rules() ); }
exists / unique and Presence Verifier
use MB\Validation\Factory; use MB\Validation\PresenceVerifierInterface; final class MyPresenceVerifier implements PresenceVerifierInterface { public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = []) { return 0; } public function getMultiCount($collection, $column, array $values, array $extra = []) { return 0; } } $factory = Factory::create(); $factory->setPresenceVerifier(new MyPresenceVerifier());
Custom Rules
Closure Rule
use MB\Validation\Validator; $validator = $factory->make( ['field' => 'bad'], [ 'field' => [ function (string $attribute, mixed $value, callable $fail, Validator $validator): void { if ($value === 'bad') { $fail($attribute, 'Custom closure rule failed'); } }, ], ] );
Class-based Rule
Implement MB\Validation\Contracts\ValidationRule and register your alias in rule registry.
Testing
composer test
or
vendor/bin/phpunit -c phpunit.xml.dist
License
MIT (see LICENSE).
MB Validation
Framework-agnostic библиотека валидации в стиле Laravel rules, но без зависимости от фреймворка.
- строковые правила (
required|string|min:3); - object/closure-правила;
- локализация сообщений (
ru/en); - wildcard-правила для массивов (
items.*.name); - строгий режим неизвестных правил (fail-fast).
Установка
composer require mb4it/validation
symfony/http-foundation is optional.
File rules work with any file-like object that provides methods such as getSize() and path/mime helpers (getPath/getRealPath, guessExtension, getMimeType), so the package does not require HttpFoundation classes at runtime.
Быстрый старт
use MB\Validation\Factory; $factory = Factory::create(lang: 'ru'); // или 'en' $validator = $factory->make( ['name' => 'John', 'age' => 30], ['name' => 'required|string|min:3', 'age' => 'required|integer|between:18,99'] ); if ($validator->fails()) { $errors = $validator->errors()->toArray(); } else { $validated = $validator->validated(); }
API фабрики (подробно)
Создание
use MB\Validation\Factory; $factory = Factory::create(lang: 'ru');
Сигнатура:
Factory::create( ?MessagesInterface $message = null, ?ContainerInterface $container = null, string $lang = 'ru' )
Если MessagesInterface не передан, используется DefaultMessages::create($lang).
Одноразовая проверка
$validated = $factory->validate($data, $rules, $messages = [], $attributes = []);
validate() бросает MB\Validation\ValidationException, если данные невалидны.
Режим строгих правил
По умолчанию пакет работает в strict-режиме:
- неизвестное строковое правило ->
InvalidArgumentException.
Для backward-compatible поведения:
$factory = Factory::create()->allowUnknownRules();
Поддерживаемые правила (основные группы)
- Presence/structure:
required,required_if,required_unless,required_array_keys,filled,array,list,boolean - Type:
string,numeric,integer,decimal - Size:
min,max,between,size,digits,digits_between - String:
alpha,alpha_num,alpha_dash,contains,doesnt_contain,starts_with,ends_with,doesnt_start_with,doesnt_end_with,uppercase,lowercase,ascii - Format/network:
email,url,ip,ipv4,ipv6,mac_address,hex_color,uuid,ulid,json - Date/time:
date,date_format,timezone - Sets/comparison:
in,not_in,same,different,any_of - Database:
exists,unique(нуженPresenceVerifierInterface)
Сообщения и локализация
Стандартные файлы:
lang/ru/validation.phplang/en/validation.php
При ошибке поиск сообщения происходит в таком порядке:
- inline custom messages (
$messagesвmake()/validate()); - fallback/custom messages валидатора;
- перевод из
MessagesInterface.
Пример inline сообщений:
$validator = $factory->make( ['email' => null], ['email' => 'required|email'], ['email.required' => 'Нужно указать email'] );
Trait для валидации свойств объекта
Для DTO/ValueObject/Command-классов можно использовать:
MB\Validation\Concerns\ValidatesProperties.
Trait:
- собирает инициализированные public/protected/private свойства через reflection;
- поддерживает wildcard-правила (
items.*.name,profile.tags.*); - валидирует через
Factory::create(lang: $this->validatorLang).
Полный пример
use MB\Validation\Concerns\ValidatesProperties; final class ProductDto { use ValidatesProperties; protected string $validatorLang = 'ru'; // optional public function __construct( public string $title, protected array $items, private array $profile, ) {} protected function rules(): array { return [ 'title' => 'required|string|min:3', 'items' => 'required|array|min:1', 'items.*.name' => 'required|string', 'profile.tags.*' => 'required|string', ]; } } $dto = new ProductDto( title: 'Notebook', items: [['name' => 'A4'], ['name' => 'A5']], profile: ['tags' => ['office', 'paper']] ); $validated = $dto->validate();
Как получить ошибки через trait
validate() бросает ValidationException:
use MB\Validation\ValidationException; try { $validated = $dto->validate(); } catch (ValidationException $e) { $errors = $e->errors(); // ['field' => ['message1', ...]] }
Non-throwing flow (через Validator)
Так как validationData() и rules() в trait защищенные, обычно добавляют метод-обертку в вашем классе:
use MB\Validation\Factory; use MB\Validation\Validator; final class ProductDto { // ... trait + rules + свойства public function validator(): Validator { return Factory::create(lang: $this->validatorLang)->make( $this->validationData(), $this->rules() ); } } $validator = $dto->validator(); if ($validator->fails()) { $errors = $validator->errors()->toArray(); }
exists / unique: подключение PresenceVerifier
Для exists и unique нужен verifier:
use MB\Validation\Factory; use MB\Validation\PresenceVerifierInterface; final class MyPresenceVerifier implements PresenceVerifierInterface { public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = []) { // Реализация под ваш storage (DB/ORM/API) return 0; } public function getMultiCount($collection, $column, array $values, array $extra = []) { return 0; } } $factory = Factory::create(); $factory->setPresenceVerifier(new MyPresenceVerifier()); $validator = $factory->make( ['email' => 'john@example.com'], ['email' => 'unique:users,email'] );
Кастомные правила
1) Closure rule
use MB\Validation\Validator; $validator = $factory->make( ['field' => 'bad'], [ 'field' => [ function (string $attribute, mixed $value, callable $fail, Validator $validator): void { if ($value === 'bad') { $fail($attribute, 'Custom closure rule failed'); } }, ], ] );
2) Class-based rule
Реализуйте MB\Validation\Contracts\ValidationRule и зарегистрируйте правило в реестре (alias).
Рекомендации для production
- Используйте strict режим правил (default).
- Передавайте локаль явно (
Factory::create(lang: 'ru'|'en')) в точках входа. - Для object-validation через trait держите
rules()близко к классу DTO. - Для
exists/uniqueдобавляйте verifier на уровне composition root. - В CI гоняйте:
composer testcomposer audit
Тестирование
Запуск тестов:
composer test
или:
vendor/bin/phpunit -c phpunit.xml.dist
Лицензия
MIT. См. файл LICENSE.
MB Validation
Framework‑agnostic, rule‑based validation library inspired by Laravel’s validator, but decoupled from the framework and from illuminate/translation.
It uses mb4it/messages for message lookup and supports multiple locales out of the box.
Installation
composer require mb4it/validation
Basic usage
use MB\Validation\Factory; $factory = Factory::create(lang: 'en'); // or 'ru' $validator = $factory->make( ['name' => 'John', 'age' => 30], ['name' => 'required|string|min:3', 'age' => 'required|integer|between:18,99'] ); if ($validator->fails()) { // Array of errors grouped by attribute $errors = $validator->errors()->toArray(); } else { // Validated data $data = $validator->validated(); }
Factory and messages
Factory::create(?MessagesInterface $message = null, ?ContainerInterface $container = null, string $lang = 'ru')- If you do not pass a
MessagesInterface, the factory internally usesDefaultMessages::create($lang)which is aFileMessagesinstance pointing to this package’slangdirectory. - By default
lang = 'ru'; pass'en'to use English messages.
- If you do not pass a
- You can also construct the factory manually:
use MB\Messages\FileMessages; use MB\Validation\Factory; $messages = new FileMessages(__DIR__.'/vendor/mb4it/validation/lang', 'en'); $factory = new Factory($messages);
new Factory() without explicit messages also uses Russian locale (ru) by default.
Rules
Most Laravel‑style rules are supported, e.g.:
- Presence / structure:
required,required_if,required_unless,required_array_keys,filled,array,list,boolean - Type:
string,numeric,integer,decimal - Size:
min,max,between,size,digits,digits_between - Strings:
alpha,alpha_num,alpha_dash,contains,doesnt_contain,starts_with,ends_with,doesnt_start_with,doesnt_end_with,uppercase,lowercase,ascii - Dates / time:
date,date_format,timezone - Network / IDs:
email,ip,ipv4,ipv6,mac_address,hex_color,url,uuid,ulid - Database:
exists,unique(requires presence verifier implementation) - Sets / comparison:
in,not_in,same,different,any_of
Rules can be defined as strings ('required|string|min:3'), as rule objects, or as closures (inline rules).
Unknown string rules are strict by default and throw InvalidArgumentException.
If you need backward-compatible behavior, call Factory::create()->allowUnknownRules().
Default messages and locales
All default validation messages are stored in:
lang/en/validation.php— Englishlang/ru/validation.php— Russian
Keys follow the familiar convention:
- Simple rules:
validation.required,validation.string,validation.boolean, etc. - Size rules by type:
validation.min.string,validation.min.numeric,validation.min.array,validation.min.file, etc.
When validation fails, the validator:
- Looks for inline custom messages you passed to
Factory::make(...). - Looks for custom arrays (
fallbackMessages, custom messages in the factory/validator). - Falls back to the translator (your
MessagesInterfaceimplementation — by defaultFileMessages+ this package’s lang files).
If no translation exists for a key, the key itself is returned (e.g. validation.unknown_rule), so you can easily spot missing entries.
Overriding messages
You can override or replace messages by:
-
Passing your own
MessagesInterfacetoFactory:$factory = new Factory($customMessages);
-
Adding additional translation files / locales in your own project and pointing
FileMessagesto your path. -
Providing per‑call custom messages when creating a validator:
$validator = $factory->make( ['email' => null], ['email' => 'required|email'], ['email.required' => 'We need your email address.'] );
Custom rules
You can register your own rules via the rule registry and use them in string notation, or pass them as rule objects / closures:
-
Closure rule:
use MB\Validation\Validator; $validator = $factory->make( ['field' => 'bad'], [ 'field' => [ function (string $attribute, mixed $value, callable $fail, Validator $validator): void { if ($value === 'bad') { $fail($attribute, 'Custom closure rule failed'); } }, ], ] );
The closure receives $attribute, $value, a $fail callback, and the current Validator.
Calling $fail($attribute, '...') adds a failure with the given message; omitting the message delegates to the normal translation lookup (validation.<rule>).
For more advanced scenarios you can implement MB\Validation\Contracts\ValidationRule and register the rule in the rule registry so it can be used by alias in rule strings.
Trait for object properties
You can validate any object with a reusable trait:
use MB\Validation\Concerns\ValidatesProperties; final class ProductDto { use ValidatesProperties; protected string $validatorLang = 'ru'; // optional, default is 'ru' public function __construct( public string $title, private array $items, ) {} protected function rules(): array { return [ 'title' => 'required|string|min:3', 'items' => 'required|array|min:1', 'items.*.name' => 'required|string', ]; } } $dto = new ProductDto('My title', [['name' => 'item-1']]); $validated = $dto->validate();
The trait collects initialized public/protected/private properties via reflection, supports wildcard paths like items.*.name, and validates through the package Factory.
Getting errors with trait
validate() throws ValidationException when validation fails:
use MB\Validation\ValidationException; try { $validated = $dto->validate(); } catch (ValidationException $e) { $errors = $e->errors(); // ['field' => ['message 1', ...]] }
If you need non-throwing flow, create a validator manually using the same trait data/rules and check fails() / errors():
$validator = Factory::create(lang: $this->validatorLang)->make( $this->validationData(), $this->rules() ); if ($validator->fails()) { $errors = $validator->errors()->toArray(); }
Testing
The library ships with an extensive PHPUnit test suite under tests/, including:
- Rule‑focused tests under
tests/Rules/*(numeric rules, string rules, required/conditional rules, format/date rules, etc.). - Message tests that assert that default messages from
lang/en|ru/validation.phpare correctly used after placeholder substitution.
You can run the tests with:
composer test
or
vendor/bin/phpunit