infocyph/reqshield

Fast, modern PHP request validation and sanitization. Schema-based rules, fail-fast execution, typed input, PSR-7 friendly.

Maintainers

Package info

github.com/infocyph/ReqShield

pkg:composer/infocyph/reqshield

Transparency log

Statistics

Installs: 6 263

Dependents: 1

Suggesters: 1

Stars: 1

Open Issues: 0

3.1 2026-08-25 03:26 UTC

This package is auto-updated.

Last update: 2026-08-25 03:31:52 UTC


README

Security & Standards Packagist Downloads License: MIT Packagist Version Packagist PHP Version GitHub Code Size Documentation

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() and when() 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

View Complete Rule Reference

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 community
MIT Licensed
DocumentationSecurityCode of ConductContributing
🗂️ BugFeatureDocumentationQuestionCI failure
🔀 GeneralBug fixFeatureRefactorPerformanceSecurity & reliabilityDocumentationMaintenance