PHPackages                             randomx98/input-guard - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. randomx98/input-guard

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

randomx98/input-guard
=====================

Sanitization &amp; validation with severity levels

v1.0.0(5mo ago)064MITPHPPHP &gt;=8.1CI passing

Since Jan 13Pushed 5mo ago1 watchersCompare

[ Source](https://github.com/RandomX89/input-guard)[ Packagist](https://packagist.org/packages/randomx98/input-guard)[ RSS](/packages/randomx98-input-guard/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (1)Versions (2)Used By (0)

InputGuard
==========

[](#inputguard)

A PHP library for input sanitization and validation with configurable severity levels.

Features
--------

[](#features)

- **Sanitize-then-validate pipeline** — sanitization runs before validation, in a predictable order
- **Severity levels** — `BASE`, `STRICT`, `PARANOID`, `PSYCHOTIC` to adjust strictness per context
- **Nested paths** — dot notation (`user.email`) and wildcards (`items.*.name`)
- **Composable presets** — `Type` and `RuleSet` for reusable validation logic
- **Structured errors** — stable error codes with metadata, messages resolved via translator
- **Security validators** — optional protection against XSS, SQL injection, path traversal, shell injection

Table of contents
-----------------

[](#table-of-contents)

- [Installation](#installation)
- [Core concepts](#core-concepts)
- [Levels](#levels)
- [Quick start](#quick-start)
- [Types](#types)
- [RuleSet presets](#ruleset-presets)
- [Security presets](#security-presets)
- [Sanitizers reference](#sanitizers-reference)
- [Validators reference](#validators-reference)
- [Optional and stopOnFirstError](#optional-and-stoponfirsterror)
- [Nested paths and wildcards](#nested-paths-and-wildcards)
- [Arrays: each, arrayOf, minItems/maxItems](#arrays-each-arrayof-minitemsmaxitems)
- [Objects: object and eachObject](#objects-object-and-eachobject)
- [Reject unknown fields](#reject-unknown-fields)
- [Schema-level validators](#schema-level-validators)
- [Policy versioning](#policy-versioning)
- [Errors and translations](#errors-and-translations)
- [Extending](#extending)
- [Testing](#testing)

Installation
------------

[](#installation)

```
composer require randomx98/input-guard
```

**Requirements:**

- PHP &gt;= 8.1
- ext-mbstring
- ext-intl (optional, for Unicode normalization)

Core concepts
-------------

[](#core-concepts)

- **Schema**Describes the whole input structure (request body, form, JSON payload).
- **Field**A pipeline of sanitizers and validators, configurable by `Level`.
- **Type**A reusable `Field` preset for a data shape (string, email, int, array, object).
- **RuleSet**A reusable set of rules you can apply to a `Field` (e.g. username, slug, email).
- **Error**Structured validation issue: `path`, `code`, `message` (optional), `meta` (array).
- **Translator**Converts an `Error` into a localized message (`MessageCatalog` / `DefaultCatalog`).

Levels
------

[](#levels)

Levels are cumulative: requesting `PSYCHOTIC` applies `BASE`, `STRICT`, `PARANOID`, and `PSYCHOTIC`.

- `Level::BASE`Safe normalization and type guards.
- `Level::STRICT`Typical business constraints.
- `Level::PARANOID`Defensive hardening.
- `Level::PSYCHOTIC`Extreme restrictions.

Quick start
-----------

[](#quick-start)

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$schema = Schema::make()
  ->field('email', Type::email()->addValidate(Level::STRICT, [Val::required()]))
  ->field('age', Type::int()->addValidate(Level::STRICT, [Val::min(18)]));

$input = [
  'email' => 'user@example.com',
  'age' => 21,
];

$result = $schema->process($input, Level::STRICT);

if (!$result->ok()) {
  // $result->errors() contains Error objects
}

$clean = $result->values();
```

Types
-----

[](#types)

Types return a `Field` preset.

Available:

- `Type::string()`
- `Type::email()`
- `Type::int()`
- `Type::array()`
- `Type::object()`
- `Type::arrayOf(Field $elementField)` (returns an `ArrayOf` helper)

Example:

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Type;

$field = Type::string()
  ->addValidate(Level::STRICT, [Val::minLen(3)]);
```

RuleSet presets
---------------

[](#ruleset-presets)

RuleSets are composable rule bundles.

Built-in presets:

- `RuleSet::username(int $min = 3, int $max = 30)`
- `RuleSet::slug(int $max = 80)`
- `RuleSet::email()`
- `RuleSet::paranoidString(int $maxLen = 1000, int $maxBytes = 4000)` - Maximum security for untrusted input
- `RuleSet::paranoidUrl(array $allowedSchemes = ['http', 'https'])` - Secure URL validation
- `RuleSet::paranoidFilename(int $maxLen = 255)` - Secure filename validation
- `RuleSet::antiSpam(int $minWords = 3, int $maxWords = 500)` - Anti-bot/spam heuristics
- `RuleSet::antiSpamStrict()` - Anti-spam + security validators combined
- `RuleSet::honeypot()` - Hidden field trap for bots

Apply a RuleSet to a Field:

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\RuleSet;
use InputGuard\Schema\Type;

$field = Type::string()
  ->use(RuleSet::username())
  ->addValidate(Level::STRICT, [Val::required()]);
```

Merge RuleSets (order matters: A then B):

```
use InputGuard\Schema\RuleSet;

$rules = RuleSet::username()->merge(RuleSet::slug());
```

Security presets
----------------

[](#security-presets)

These presets add extra validation rules to block common attack patterns. They are opt-in and may be too strict for some use cases — test with your actual data.

### ParanoidString

[](#paranoidstring)

Blocks HTML tags, control characters, path traversal patterns, and shell metacharacters:

```
use InputGuard\Core\Level;
use InputGuard\Schema\RuleSet;
use InputGuard\Schema\Schema;

$schema = Schema::make()
    ->field('comment', RuleSet::paranoidString()->toField());

$result = $schema->process(['comment' => 'alert(1)'], Level::PARANOID);
// Fails with 'no_html_tags' error
```

**Rules by level:**

LevelSanitizersValidatorsBASE`trim`, `normalizeNfkc``typeString`STRICT`nullIfEmpty``maxLen`PARANOID—`noControlChars`, `noZeroWidthChars`, `noHtmlTags`, `noPathTraversal`, `noShellChars`PSYCHOTIC—`noSqlPatterns`, `printableOnly`, `maxBytes`> **Note:** `paranoidString` blocks characters like `!`, `$`, which appear in normal text. For user comments or messages, consider `antiSpam` instead.

### ParanoidUrl

[](#paranoidurl)

Blocks `javascript:`, `data:`, `vbscript:`, `file:` URL schemes:

```
$schema = Schema::make()
    ->field('website', RuleSet::paranoidUrl()->toField());

$result = $schema->process(['website' => 'javascript:alert(1)'], Level::PARANOID);
// Fails with 'safe_url' error
```

### ParanoidFilename

[](#paranoidfilename)

Validates filenames: alphanumeric characters, blocks dangerous extensions (`.php`, `.exe`, etc.):

```
$schema = Schema::make()
    ->field('upload', RuleSet::paranoidFilename()->toField());

$result = $schema->process(['upload' => 'shell.php'], Level::PARANOID);
// Fails with 'safe_filename' error
```

Sanitizers reference
--------------------

[](#sanitizers-reference)

All sanitizers are available via `San::*` factory methods.

MethodDescription`San::trim()`Trims whitespace from both ends`San::nullIfEmpty()`Converts empty strings to `null``San::lowercase()`Converts to lowercase`San::toInt()`Casts value to integer`San::normalizeNfkc()`NFKC Unicode normalization (requires ext-intl, safe no-op otherwise)`San::stripTags(array $allowed = [])`Removes HTML tags (optionally allow specific tags)`San::htmlEntities()`Encodes HTML special charactersValidators reference
--------------------

[](#validators-reference)

All validators are available via `Val::*` factory methods.

### Type validators

[](#type-validators)

MethodError codeDescription`Val::typeString()``string`Value must be a string`Val::typeInt()``int`Value must be an integer`Val::typeArray()``array`Value must be an array`Val::typeObject()``object`Value must be an associative array### String validators

[](#string-validators)

MethodError codeDescription`Val::required()``required`Value is required (not null/empty)`Val::minLen(int $min)``min_len`Minimum string length`Val::maxLen(int $max)``max_len`Maximum string length`Val::email()``email`Valid email format`Val::regex(string $pattern)``regex`Matches regex pattern`Val::inSet(array $allowed)``in_set`Value in allowed set### Numeric validators

[](#numeric-validators)

MethodError codeDescription`Val::min(int $min)``min`Minimum value`Val::max(int $max)``max`Maximum value### Array validators

[](#array-validators)

MethodError codeDescription`Val::minItems(int $min)``min_items`Minimum array items`Val::maxItems(int $max)``max_items`Maximum array items### Security validators

[](#security-validators)

MethodError codeDescription`Val::noControlChars()``no_control_chars`Blocks control characters (0x00-0x1F, 0x7F)`Val::noZeroWidthChars()``no_zero_width_chars`Blocks zero-width Unicode characters`Val::noHtmlTags()``no_html_tags`Blocks HTML tags`Val::noSqlPatterns()``no_sql_patterns`Blocks SQL injection patterns`Val::noPathTraversal()``no_path_traversal`Blocks `../`, absolute paths, null bytes`Val::noShellChars()``no_shell_chars`Blocks shell metacharacters (`;`, ``Val::safeUrl(array $schemes)``safe_url`Blocks dangerous URL schemes`Val::safeFilename()``safe_filename`Validates safe filename characters and extensions`Val::printableOnly()``printable_only`Only printable ASCII characters`Val::maxBytes(int $max)``max_bytes`Maximum byte size (not character count)### Anti-spam/bot validators

[](#anti-spambot-validators)

MethodError codeDescription`Val::noGibberish()``gibberish`Detects keyboard mashing, excessive consonants, low vowel ratio`Val::noExcessiveUrls(int $max)``excessive_urls`Blocks text with too many URLs`Val::noRepeatedChars(int $max)``repeated_chars`Blocks excessive character repetition (aaaa, !!!!)`Val::noSpamKeywords()``spam_keywords`Blocks common spam phrases`Val::minWords(int $min)``min_words`Minimum word count`Val::maxWords(int $max)``max_words`Maximum word count`Val::noAllCaps()``all_caps`Blocks ALL CAPS text (shouting)`Val::honeypot()``honeypot`Field must be empty (bot trap)`Val::noSuspiciousPattern()``suspicious_pattern`Detects excessive punctuation, digits, etc.Anti-spam presets
-----------------

[](#anti-spam-presets)

Heuristic validators for public forms. These use pattern matching and may produce false positives — adjust thresholds as needed.

### AntiSpam

[](#antispam)

Basic checks for gibberish, repeated characters, and excessive URLs:

```
use InputGuard\Core\Level;
use InputGuard\Schema\RuleSet;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$schema = Schema::make()
    ->field('message', RuleSet::antiSpam()->toField())
    ->field('email', Type::email());

$result = $schema->process([
    'message' => 'asdfghjkl qwerty',  // Gibberish!
    'email' => 'user@example.com',
], Level::PARANOID);

// Fails with 'gibberish' error
```

**Rules by level:**

LevelValidatorsSTRICT`minWords`, `maxWords`, `noRepeatedChars`, `noExcessiveUrls`PARANOID`noGibberish`, `noAllCaps`, `noSuspiciousPattern`PSYCHOTIC`noSpamKeywords`### AntiSpamStrict

[](#antispamstrict)

AntiSpam + security validators combined:

```
$schema = Schema::make()
    ->field('message', RuleSet::antiSpamStrict()->toField());
```

### Honeypot

[](#honeypot)

Hidden field that should remain empty. Bots often fill all fields:

```
$schema = Schema::make()
    ->field('email', Type::email())
    ->field('message', RuleSet::antiSpam()->toField())
    ->field('website', RuleSet::honeypot()->toField());  // Hidden field

// In HTML:
// Bots fill all fields, humans leave it empty
```

Optional and stopOnFirstError
-----------------------------

[](#optional-and-stoponfirsterror)

`optional()` and `stopOnFirstError()` are `Field` flags.

- `optional(true)`Skips validation when the value is `null` or an empty string (after sanitization).
- `stopOnFirstError(true)`Stops validation on the first failing rule for that field.

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Type;

$field = Type::string()
  ->optional()
  ->stopOnFirstError()
  ->addValidate(Level::STRICT, [Val::minLen(3)]);
```

Nested paths and wildcards
--------------------------

[](#nested-paths-and-wildcards)

Schema paths use dot notation:

```
$schema = Schema::make()
  ->field('user.email', Type::email());
```

Wildcards let you target collections:

```
$schema = Schema::make()
  ->field('items.*.name', Type::string());
```

When a wildcard matches multiple items, errors always contain a **concrete path**, e.g. `items.2.name`.

Arrays: each, arrayOf, minItems/maxItems
----------------------------------------

[](#arrays-each-arrayof-minitemsmaxitems)

### Validate an array itself

[](#validate-an-array-itself)

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$schema = Schema::make()
  ->field('tags', Type::array()->addValidate(Level::STRICT, [Val::minItems(2)]));
```

### Apply a Field to each element: `Schema::each()`

[](#apply-a-field-to-each-element-schemaeach)

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$schema = Schema::make()
  ->each('tags', Type::string()->addValidate(Level::STRICT, [Val::minLen(2)]));
```

### Array of elements: `Type::arrayOf()`

[](#array-of-elements-typearrayof)

`Type::arrayOf()` returns an `ArrayOf` helper that can be applied to a `Schema`.

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$schema = Schema::make();

$schema = Type::arrayOf(Type::string()->addValidate(Level::STRICT, [Val::minLen(2)]))
  ->minItems(2)
  ->applyTo($schema, 'tags');
```

This applies:

- validation to the array at `tags`
- validation/sanitization to each element at `tags.*`

Objects: object and eachObject
------------------------------

[](#objects-object-and-eachobject)

### Nested object schema: `Schema::object()`

[](#nested-object-schema-schemaobject)

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$userSchema = Schema::make()
  ->field('email', Type::email()->addValidate(Level::STRICT, [Val::required()]))
  ->field('name', Type::string()->addValidate(Level::STRICT, [Val::minLen(2)]));

$schema = Schema::make()
  ->field('user', Type::object())
  ->object('user', $userSchema);
```

Child schema paths are relative. Errors and values are automatically prefixed, e.g. `user.name`.

**Field overrides**: If you define a field with a path that overlaps with an object schema, the field wins:

```
$schema = Schema::make()
    ->object('user', $childSchema)
    ->field('user.email', Type::email()->addValidate(Level::STRICT, [Val::required()]));
// The field 'user.email' overrides the one from $childSchema
```

To prevent accidental overlaps, use `disallowOverlaps(true)`:

```
$schema = Schema::make()
    ->disallowOverlaps(true)
    ->object('user', $childSchema)
    ->field('user.email', Type::email()); // Throws LogicException
```

### Collection of objects: `Schema::eachObject()`

[](#collection-of-objects-schemaeachobject)

```
use InputGuard\Core\Level;
use InputGuard\Rules\Val\Val;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$itemSchema = Schema::make()
  ->field('name', Type::string()->addValidate(Level::STRICT, [Val::required()]))
  ->field('qty', Type::int()->addValidate(Level::STRICT, [Val::min(1)]));

$schema = Schema::make()
  ->eachObject('items', $itemSchema);
```

Errors are prefixed with the concrete index, e.g. `items.1.name`.

Reject unknown fields
---------------------

[](#reject-unknown-fields)

Enable strict mode to reject any input fields not explicitly declared in the schema:

```
use InputGuard\Core\Level;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

$schema = Schema::make()
    ->field('email', Type::email())
    ->field('name', Type::string())
    ->rejectUnknownFields();

$result = $schema->process([
    'email' => 'user@example.com',
    'name' => 'John',
    'extra' => 'malicious'  // Unknown field!
], Level::STRICT);

$result->ok(); // false
$result->errors()[0]->code; // 'unknown_field'
$result->errors()[0]->path; // 'extra'
```

This protects against **mass assignment** attacks and ensures only expected data is processed.

Works with nested objects and wildcards:

```
$schema = Schema::make()
    ->field('items.*.name', Type::string())
    ->rejectUnknownFields();

// Input with 'items.0.extra' will produce error 'unknown_field' at path 'items.0.extra'
```

Schema-level validators
-----------------------

[](#schema-level-validators)

For cross-field validation rules (like password confirmation), use schema-level validators:

```
use InputGuard\Contract\SchemaValidator;
use InputGuard\Core\Error;
use InputGuard\Core\ErrorCode;
use InputGuard\Core\Level;
use InputGuard\Schema\Schema;
use InputGuard\Schema\Type;

class PasswordConfirmValidator implements SchemaValidator {
    public function validate(array $values, array $context = []): array {
        $password = $values['password'] ?? null;
        $confirm = $values['password_confirm'] ?? null;

        if ($password !== $confirm) {
            return [new Error('password_confirm', ErrorCode::INVALID, null, ['rule' => 'password_mismatch'])];
        }
        return [];
    }
}

$schema = Schema::make()
    ->field('password', Type::string())
    ->field('password_confirm', Type::string())
    ->rule(new PasswordConfirmValidator());

$result = $schema->process([
    'password' => 'secret',
    'password_confirm' => 'different'
], Level::STRICT);

$result->ok(); // false
```

Schema validators receive **sanitized values**, so you can rely on clean data.

Policy versioning
-----------------

[](#policy-versioning)

Track which validation policy version was used to process input:

```
$schema = Schema::make()
    ->policyVersion('1.2.3')
    ->field('email', Type::email());

$result = $schema->process(['email' => 'user@example.com'], Level::STRICT);

$result->meta()['policyVersion']; // '1.2.3'
```

Useful for auditing and debugging when validation rules change over time.

Errors and translations
-----------------------

[](#errors-and-translations)

Validators should emit structured errors:

- `code` (stable)
- `meta` (technical details)
- `message` is usually `null` and resolved by a `Translator`

### Default catalog

[](#default-catalog)

```
use InputGuard\Support\DefaultCatalog;
use InputGuard\Support\PresentableErrors;

$translator = DefaultCatalog::build();

$presentable = PresentableErrors::format(
  $result->errors(),
  $translator,
  'it'
);
```

### Custom catalog

[](#custom-catalog)

```
use InputGuard\Support\MessageCatalog;

$translator = new MessageCatalog([
  'en' => [
    'required' => fn($e) => "The field {$e->path} is required",
    'min_len' => fn($e) => "Minimum length is {$e->meta['min']}"
  ]
]);
```

Extending
---------

[](#extending)

### Add a validator

[](#add-a-validator)

- Implement `InputGuard\Contract\Validator`
- Return an array of `InputGuard\Core\Error`
- Emit `ErrorCode + meta` and keep `message` as `null`
- Expose it via `InputGuard\Rules\Val\Val` (factory methods)

### Add a sanitizer

[](#add-a-sanitizer)

- Implement `InputGuard\Contract\Sanitizer`
- Expose it via `InputGuard\Rules\San\San`

### Add a RuleSet

[](#add-a-ruleset)

```
use InputGuard\Core\Level;
use InputGuard\Schema\RuleSet;

$set = RuleSet::make()
  ->sanitize(Level::BASE, [/* ... */])
  ->validate(Level::STRICT, [/* ... */]);
```

### Add a Type

[](#add-a-type)

A Type is simply a method that returns a `Field` preset.

Testing
-------

[](#testing)

```
./vendor/bin/phpunit
```

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE) for details.

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance70

Regular maintenance activity

Popularity10

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

171d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/118442710?v=4)[RandomX](/maintainers/RandomX89)[@RandomX89](https://github.com/RandomX89)

---

Top Contributors

[![RandomX89](https://avatars.githubusercontent.com/u/118442710?v=4)](https://github.com/RandomX89 "RandomX89 (23 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/randomx98-input-guard/health.svg)

```
[![Health](https://phpackages.com/badges/randomx98-input-guard/health.svg)](https://phpackages.com/packages/randomx98-input-guard)
```

###  Alternatives

[marcosh/php-validation-dsl

A DSL for validating data in a functional fashion

483.9k](/packages/marcosh-php-validation-dsl)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
