infocyph / reqshield
Fast, modern PHP request validation and sanitization. Schema-based rules, fail-fast execution, typed input, PSR-7 friendly.
Requires
- php: >=8.4
- ext-fileinfo: *
- ext-hash: *
- ext-mbstring: *
Requires (Dev)
- infocyph/dblayer: ^5.0
- infocyph/phpforge: dev-main@dev
README
Fast, modern PHP request validation and sanitization. Schema-based rules, fail-fast execution, intelligent batching, and 108 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
- 108 Built-in Rules - Basic types, conditional rules, files, database checks, enums, and more
- 46 Built-in 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
- ️ Upload Hardening Rules -
safe_filename,upload_meta,upload_id,secure_file - ️ PHP 8.4+ - Built with modern PHP features
Installation
composer require infocyph/reqshield
Requirements: PHP 8.4+, ext-hash with xxh3 support, ext-mbstring, and ext-fileinfo
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 import the namespaced helper:
use function Infocyph\ReqShield\sanitize; $clean = sanitize(' TEST@ex.com ', 'email'); // 'TEST@ex.com' $clean = sanitize('<b>TEXT</b>', ['string', 'lowercase']); // 'text'
Available Rules (108)
ReqShield includes 108 validation rules covering several common scenarios:
- Basic Types
- Formats
- Strings
- Numbers
- Dates
- Conditionals
- Database
- Files
- Arrays
- Comparison
- Patterns
- Additional
Upload Hardening Rules
Use upload-focused rules for request metadata and filename safety:
$validator = Validator::make([ 'upload' => 'required|secure_file', 'filename' => 'required|safe_filename', 'upload_id' => 'required|upload_id', ]);
secure_file combines file and upload_meta so you can enforce
payload validity and safe upload metadata in one rule.
Available Sanitizers (46 Built-in)
ReqShield includes 46 built-in sanitizers covering several common scenarios:
- Basic Types
- Case Conversions
- Text Processing
- Special Formats
- Alphanumeric Filters
- HTML and escaping utilities
- Encoding
- Array Operations
View Complete Sanitizer Reference
Advanced Features
Request Input Helpers
$result = Validator::fromArray($rules, $data); $result = Validator::fromQuery($rules, $_GET); $result = Validator::fromBody($rules, $body); $result = Validator::fromFiles($rules, $_FILES);
PSR-style request objects are supported through fromServerRequest() when compatible accessors are available.
$result = Validator::fromServerRequest($rules, $request);
Strict Unknown Field Handling
$validator = Validator::make($rules) ->strict(); // same as allowUnknown(false) $validator = Validator::make($rules) ->stripUnknown(); // remove unknown fields instead of failing
Enum Validation and Casting
'status' => 'required|enum:App\\Enums\\OrderStatus'
'status' => [ 'rules' => 'required|enum:App\\Enums\\OrderStatus', 'cast' => App\\Enums\\OrderStatus::class, ]
After Validation Hooks
use Infocyph\ReqShield\Support\ValidationContext; $validator->after(function (ValidationContext $ctx): void { if ((string) $ctx->get('start_date') > (string) $ctx->get('end_date')) { $ctx->addError('end_date', 'End date must be after start date.'); } });
API Error Formatters and Input Bag
$result->toProblemJson(); $result->toJsonApiErrors(); $result->toApiErrors(); $result->toFlatErrors();
$input = $result->input(); $input->string('email'); $input->int('age'); $input->only(['email', 'age']);
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', ]); $data = [ 'user' => [ 'email' => 'john@example.com', 'name' => 'John Doe', 'profile' => [ 'age' => 25, 'bio' => 'Software developer', ], ], ]; $result = $validator->validate($data);
Nested paths are detected automatically and optimized targeted traversal is the default.
Use setNestedFlattenMode('all') only when full flattening is required.
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();
Compiled Validator Wrapper
Validator::compile() returns a reusable validator wrapper.
$compiled = Validator::compile([ 'email' => 'required|email', 'age' => 'required|integer|min:18', ]); $result = $compiled->validate($data);
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; } } // 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);
Database schemas require a provider and throw DatabaseProviderRequiredException
when it is absent. The contract contains only batchExists() and batchUnique();
ReqShield owns logical validation batching, while providers own query construction
and driver-safe physical chunking. ReqShield is database-library agnostic: a
provider may use PDO, DBLayer, Laravel, Doctrine, or another database layer.
DBLayer 5 is used only as the development-suite reference integration and is not
a runtime dependency for normal consumers.
Benefits:
- Automatic batching - Multiple checks become one query
- Update support -
Rule::unique('users', 'email')->ignore(5)ignores ID 5 - Explicit object syntax -
Rule::unique('users', 'email')->ignore($id)->withoutTrashed()
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
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.
Security
Do not disclose suspected vulnerabilities in a public issue, discussion or pull request. Follow SECURITY.md and use GitHub private vulnerability reporting.
ReqShield is protected by PHPForge, which provides automated tests, static and taint analysis, dependency auditing, architecture checks and release-readiness gates. Automated controls do not replace responsible disclosure or manual review.
Made with ❤️ for the PHP communityMIT Licensed
Documentation • Security • Code of Conduct • Contributing
🗂️ Bug • Feature • Documentation • Question • CI failure
🔀 General • Bug fix • Feature • Refactor • Performance • Security & reliability • Documentation • Maintenance