infocyph / reqshield
Fast, modern PHP request validation and sanitization. Schema-based rules, fail-fast execution, typed input, PSR-7 friendly.
Requires
- php: >=8.4
Requires (Dev)
- captainhook/captainhook: ^5.28
- laravel/pint: ^1.29
- pestphp/pest: ^4.4
- rector/rector: ^2.3
- symfony/var-dumper: ^7.4 || ^8.0
README
Fast, modern PHP request validation and sanitization. Schema-based rules, fail-fast execution, intelligent batching, and 103 built-in validation rules.
$validator = Validator::make([ 'email' => 'required|email|max:255', 'age' => 'required|integer|min:18', ])->setSanitizers([ 'email' => ['trim', 'lowercase'], ])->setCasts([ 'age' => 'integer', ]); $result = $validator->validate($data); if ($result->passes()) { $clean = $result->typed(); // โ All good! }
โจ Features
- ๐ 103 Built-in Rules - Basic types, conditional rules, files, database checks, and more
- ๐งน 50+ Sanitizers - Manual sanitization or built-in sanitize+validate pipeline
- โก Intelligent Batching - Expensive DB checks are batched automatically
- ๐ฏ Fail-Fast + Full Collection Modes - Per-field fail-fast with configurable behavior
- ๐ Nested + Wildcard Validation - Dot notation with wildcard expansion
- ๐งพ Custom Messages + Placeholders -
:field,:rule,:min, and more - ๐ Locale Packs - Per-rule localized message templates with fallback
- ๐ง Failure Metadata - Structured failures (
field,rule,message,value) - ๐ฆ Schema Fragments + Composition - Reuse validation contracts across endpoints
- ๐ Conditional Closures -
sometimes()andwhen()for dynamic rule activation - ๐งฑ Schema Export - JSON Schema, OpenAPI shape, and introspection metadata
- ๐งฐ Typed Output + DTO Mapping - Cast map +
toDTO()support - ๐ค Uploaded File Object Support - Array-style uploads and PSR-7 style objects
- ๐ ๏ธ PHP 8.4+ - Built with modern PHP features
๐ฆ Installation
composer require infocyph/reqshield
Requirements: PHP 8.4+ and ext-hash with xxh3 support
๐ Quick Start
Basic Validation
use Infocyph\ReqShield\Validator; $validator = Validator::make([ 'email' => [ 'rules' => 'required|email|max:255', 'sanitize' => ['trim', 'lowercase'], 'alias' => 'Email Address', ], 'password' => 'required|string|min:8|confirmed', 'age' => 'required|integer|min:18', ])->setCasts([ 'age' => 'integer', ])->setCustomMessages([ 'email.required' => ':field is required.', '*.min' => ':field must be at least :min.', ]); $result = $validator->validate($data); if ($result->passes()) { $validated = $result->typed(); // Process your data... } else { $errors = $result->errors(); $failures = $result->failures(); // Handle validation errors... }
Sanitization
Manual sanitization:
use Infocyph\ReqShield\Sanitizer; $clean = [ 'email' => Sanitizer::email($input['email']), // 'john@example.com' 'username' => Sanitizer::alphaDash($input['username']), // 'john_doe' 'age' => Sanitizer::integer($input['age']), // 25 'bio' => Sanitizer::string($input['bio']), // Strips HTML tags ]; $result = $validator->validate($clean);
Or built-in sanitize+validate pipeline:
$validator = Validator::make([ 'email' => 'required|email', 'contacts.*.email' => 'required|email', ])->setSanitizers([ 'email' => ['trim', 'lowercase'], 'contacts.*.email' => ['trim', 'lowercase'], ]);
Or use the helper:
$clean = sanitize(' TEST@ex.com ', 'email'); // 'TEST@ex.com' $clean = sanitize('<b>TEXT</b>', ['string', 'lowercase']); // 'text'
๐ Available Rules (103)
ReqShield includes 103 validation rules covering several common scenarios:
- Basic Types
- Formats
- Strings
- Numbers
- Dates
- Conditionals
- Database
- Files
- Arrays
- Comparison
- Patterns
- Additional
๐ View Complete Rule Reference
๐งน Available Sanitizers (50+)
ReqShield includes 50+ sanitizers covering several common scenarios:
- Basic Types
- Case Conversions
- Text Processing
- Special Formats
- Alphanumeric Filters
- Security & HTML
- Encoding
- Array Operations
๐ View Complete Sanitizer Reference
๐ฏ Advanced Features
Nested Validation
Validate deeply nested arrays using dot notation:
$validator = Validator::make([ 'user.email' => 'required|email', 'user.name' => 'required|min:3', 'user.profile.age' => 'required|integer|min:18', 'user.profile.bio' => 'string|max:500', ])->enableNestedValidation(); $data = [ 'user' => [ 'email' => 'john@example.com', 'name' => 'John Doe', 'profile' => [ 'age' => 25, 'bio' => 'Software developer', ], ], ]; $result = $validator->validate($data);
Use enableNestedValidation(false) to flatten only required paths for large nested payloads.
Custom Field Names
Make error messages user-friendly:
$validator->setFieldAliases([ 'user_email' => 'Email Address', 'contacts.*.email' => 'Contact Email', ]);
Custom Messages + Locale Packs
$validator ->setCustomMessages([ 'email.required' => ':field is required.', '*.min' => ':field must be at least :min.', 'contacts.*.email.email' => 'Each :field must be valid.', ]) ->addLocalePack('es', [ 'required' => 'El campo :field es obligatorio.', '*' => 'El campo :field no es valido.', ]) ->setLocale('es');
Throw Exceptions on Failure
use Infocyph\ReqShield\Exceptions\ValidationException; $validator = Validator::make($rules)->throwOnFailure(); try { $result = $validator->validate($data); $validated = $result->validated(); } catch (ValidationException $e) { echo $e->getMessage(); // "Validation failed" print_r($e->getErrors()); // All errors echo $e->getErrorCount(); // Number of failed fields echo $e->getFirstFieldError('email'); // First error for specific field echo $e->getCode(); // 422 }
Failure Metadata for APIs
$result = $validator->validate($data); if ($result->fails()) { return [ 'errors' => $result->errors(), 'failures' => $result->failures(), // field, rule, message, value ]; }
Conditional Rules
$validator ->sometimes('vat', 'required', fn(array $data) => ($data['type'] ?? null) === 'business') ->when( fn(array $data) => ($data['country'] ?? null) === 'US', fn() => ['state' => 'required|string'], );
Schema Fragments
Validator::defineFragment('address', [ 'line1' => 'required|string|max:120', 'zip' => 'required|digits:5', ]); $validator = Validator::make([ 'name' => 'required|string', ])->useFragment('address', 'billing');
Typed Output + DTO
$validator = Validator::make([ 'age' => 'required|integer', 'active' => 'required|boolean', ])->setCasts([ 'age' => 'integer', 'active' => 'boolean', ])->setDtoClass(App\DTO\UserInput::class); $result = $validator->validate($data); $typed = $result->typed(); $dto = $result->toDTO();
Custom Rules (Simple)
Use callbacks for quick custom validation:
use Infocyph\ReqShield\Rules\Callback; $validator = Validator::make([ 'code' => [ 'required', new Callback( callback: fn($value, $field, $data) => $value % 2 === 0, message: 'The code must be an even number' ), ], ]);
Custom Rules (Advanced)
Create reusable rule classes:
use Infocyph\ReqShield\Contracts\Rule; class StrongPassword implements Rule { public function passes(mixed $value, string $field, array $data): bool { return strlen($value) >= 12 && preg_match('/[A-Z]/', $value) && preg_match('/[a-z]/', $value) && preg_match('/[0-9]/', $value) && preg_match('/[^A-Za-z0-9]/', $value); } public function message(string $field): string { return "The {$field} must be at least 12 characters with uppercase, lowercase, number, and special character."; } public function cost(): int { return 20; } public function isBatchable(): bool { return false; } } // Usage $validator = Validator::make([ 'password' => ['required', new StrongPassword()], ]);
Database Validation
Validate against your database:
use Infocyph\ReqShield\Validator; use Infocyph\ReqShield\Contracts\DatabaseProvider; // Implement your database provider class MyDatabaseProvider implements DatabaseProvider { // Implement required methods... } $db = new MyDatabaseProvider(); $validator = Validator::make([ 'email' => 'required|email|unique:users,email', 'category_id' => 'required|exists:categories,id', ], $db);
Benefits:
- ๐ Automatic batching - Multiple checks become one query
- ๐ฏ Update support -
unique:users,email,5ignores ID 5 - ๐งพ Soft-delete aware unique -
unique:users,email,,id,false,deleted_at
Schema Export / Introspection
$jsonSchema = $validator->exportSchema('json_schema'); $openApiShape = $validator->exportSchema('openapi'); $introspection = $validator->exportSchema('introspection');
Stop on First Error
For maximum performance, stop all validation on first error:
$validator = Validator::make($rules) ->setStopOnFirstError(true); // Stops immediately when any field fails $result = $validator->validate($data);
โก Performance
ReqShield is built for speed:
1. Cost-Based Rule Sorting
Rules automatically execute in order of complexity:
- Cheap (< 50): Type checks, empty checks
- Medium (50-99): String operations, regex
- Expensive (100+): Database queries, API calls
2. Intelligent Batching
Database rules are automatically batched:
// 3 separate rules... 'user_id' => 'exists:users,id', 'email' => 'unique:users,email', 'category_id' => 'exists:categories,id', // ...become just 2 queries (50x faster!) // - One batch for exists checks // - One batch for unique checks
3. Fail-Fast Execution
Stops validating a field on first rule failure:
'email' => 'required|email|max:255' // If empty โ fails on 'required', skips 'email' and 'max:255'
4. Zero Overhead for Simple Cases
Nested validation only activates if you use dot notation. No performance cost for simple flat arrays.
๐ License
ReqShield is open-sourced software licensed under the MIT license.
๐ Show Your Support
If you find ReqShield helpful, please consider giving it a โญ๏ธ on GitHub!
Made with โค๏ธ for the PHP community
Documentation โข Report Bug โข Request Feature