PHPackages                             wscore/leanvalidator - 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. wscore/leanvalidator

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

wscore/leanvalidator
====================

AI-friendly &amp; simple validator

0.3.1(2mo ago)018↓93.3%[1 issues](https://github.com/asaokamei/LeanValidator/issues)1MITPHPPHP &gt;=8.1CI passing

Since Feb 14Pushed 2mo agoCompare

[ Source](https://github.com/asaokamei/LeanValidator)[ Packagist](https://packagist.org/packages/wscore/leanvalidator)[ RSS](/packages/wscore-leanvalidator/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (4)Versions (17)Used By (1)

LeanValidator
=============

[](#leanvalidator)

[![PhpUnit](https://github.com/asaokamei/LeanValidator/actions/workflows/phpunit.yml/badge.svg)](https://github.com/asaokamei/LeanValidator/actions/workflows/phpunit.yml)

An AI-friendly and simple validator for PHP.

Features
--------

[](#features)

- **Fluent Interface**: Easy to read and write validation rules.
- **Whitelist Validation**: `getValidatedData()` returns only the data that has been validated.
- **Nested Structures**: Supports validation of arrays and nested objects using `asList()` and `asObject()`.
- **Flexible Rules**: Use built-in rules, closures, or any PHP callable.
- **AI-Friendly**: Simple and consistent API that is easy for AI to understand and generate.

### Sanitizer:

[](#sanitizer)

This package also provides a sanitization utility that can be used to clean and transform input data.

- **Sanitization**: Automatic UTF-8 conversion and trimming of strings. Supports custom rules and nested data using dot-notation and wildcards.

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

[](#installation)

Use composer to install the package.

```
composer require wscore/leanvalidator
```

Basic Usage
-----------

[](#basic-usage)

### Initialization

[](#initialization)

You can create a validator instance using the `make` method.

```
use Wscore\LeanValidator\Validator;

$data = [
    'name' => 'John Doe',
    'age' => 25,
    'email' => 'john@example.com'
];

$v = Validator::make($data);
```

### Defining Rules

[](#defining-rules)

Use `field` to specify the field and chain validation rules.

```
$v->field('name')->required()->string();
$v->field('age')->int()->between(18, 99);
$v->field('email')->email();
```

### Checking Results

[](#checking-results)

```
if ($v->isValid()) {
    // Get only the validated data
    $validated = $v->getValidatedData();
} else {
    // Get error messages as a flat array [field_path => with]
    $errors = $v->getErrorsFlat();
}
```

Rules and Messages
------------------

[](#rules-and-messages)

### Default Error Messages

[](#default-error-messages)

By default, the validator provides two error messages for common validation rules:

- **Default error**: "Please check the input value." (used for rules like `int`, `email`, etc.)
- **Required error**: "This field is required." (used for `required`)

```
$v->field('age')
  ->required()
  ->int()
  ->min(18);
```

- If the `age` field is missing, the error message will be "This field is required."
- If the value is not an integer or is less than 18, the error message will be "Please check the input value."

You can change these default messages for the entire validator instance:

```
$v->defaultMessage = 'Invalid input!';
$v->defaultMessageRequired = 'Cannot skip this field!';
```

### Custom Error Messages

[](#custom-error-messages)

You can set custom messages at three levels:

1. **Rule level**: Use `with()` to set a custom message for the **next** rule in the chain. This message is used only for that rule and is cleared immediately after.
2. **Field level**: Use the second argument of `field()` to set a default error message for all rules in that field.
3. **Manual level**: Use `addError(string $field, string $message)` to manually add an error to a specific field.
4. **Required level**: Pass a custom message directly to `required()`.

```
$v->addError('general', 'Something went wrong!');             // Manual level
$v->field('age', 'Please enter your age (must be above 18)') // Field level default
  ->required('You must be at least 18 years old.')           // Required level
  ->with('Age must be an integer')->int()                 // Rule level (applied to next rule)
  ->with('You must be at least 18')->min(18);             // Rule level (applied to next rule)
```

The `message()` call only affects the rule that immediately follows it. If you want to use custom messages for multiple rules, call `message()` before each one.

The validator searches for messages in the following order:

1. Message passed directly to the rule (via `message()` or `required($msg)`).
2. Message passed to `field($key, $msg)`.
3. Global default message (`defaultMessage` or `defaultMessageRequired`).

### Getting Error Messages

[](#getting-error-messages)

If `isValid()` returns `false`, you can retrieve error messages from the validator:

```
// Get all errors as a flat array [field_path => first_message]
$errors = $v->getErrorsFlat();

// Example:
// [
//   'email' => 'Please enter a valid email.',
//   'address.city' => 'This field is required.'
// ]

// For more control, get the MessageBag object
$bag = $v->getErrors();
$allMessages = $bag->toArray(); // [field_path => [message1, message2, ...]]
```

The `MessageBag` also supports retrieving messages using HTML form field names:

```
// If field is 'address[city]', it will look up 'address.city'
$error = $bag->firstFromFormName('address[city]');
```

Validation Flow
---------------

[](#validation-flow)

### Optional Fields

[](#optional-fields)

```
$v->field('extra_info')->optional()->string();

// With default value
$v->field('status')->optional('active')->string();
```

### Required Fields

[](#required-fields)

```
$v->field('name')->required()->string();

// With a custom with
$v->field('title')->required('Title is required')->string();
```

### Conditional Required Fields (`requiredIf`, `requiredWith`, `requiredWithout`)

[](#conditional-required-fields-requiredif-requiredwith-requiredwithout)

Use these methods to make a field required based on another field.

#### `requiredIf(string $otherKey, mixed $expect, ?string $msg = null, mixed $elseOverwrite = null)`

[](#requiredifstring-otherkey-mixed-expect-string-msg--null-mixed-elseoverwrite--null)

Required if `$otherKey`'s value matches `$expect`.

```
// 'state' is required only if 'country' is 'US'
$v->field('state')->requiredIf('country', 'US')->string();
```

#### `requiredUnless(string $otherKey, mixed $expect, ?string $msg = null, mixed $elseOverwrite = null)`

[](#requiredunlessstring-otherkey-mixed-expect-string-msg--null-mixed-elseoverwrite--null)

Required unless `$otherKey`'s value matches `$expect`.

```
// 'state' is required only if 'country' is 'US'
$v->field('state')->requiredUnless('country', 'US')->string();
```

#### `requiredWith(string $otherKey, ?string $msg = null, mixed $elseOverwrite = null)`

[](#requiredwithstring-otherkey-string-msg--null-mixed-elseoverwrite--null)

Required if `$otherKey` exists in the input data (even if it's null).

```
// 'confirm_password' is required if 'password' exists
$v->field('confirm_password')->requiredWith('password')->string();
```

#### `requiredWithout(string $otherKey, ?string $msg = null, mixed $elseOverwrite = null)`

[](#requiredwithoutstring-otherkey-string-msg--null-mixed-elseoverwrite--null)

Required if `$otherKey` does not exist in the input data.

```
// 'guest_email' is required if 'user_id' is missing
$v->field('guest_email')->requiredWithout('user_id')->email();
```

#### `requiredWhen(callable $call, ?string $msg = null, mixed $elseOverwrite = null)`

[](#requiredwhencallable-call-string-msg--null-mixed-elseoverwrite--null)

Required if the callback `$call($data)` returns true. The `$data` contains all input data.

```
// 'name' is required only if 'type' is 'personal'
$v->field('name')->requiredWhen(function($data) {
    return ($data['type'] ?? '') === 'personal';
})->string();
```

#### `elseOverwrite`

[](#elseoverwrite)

For all these methods, if the condition is not met, you can provide an `elseOverwrite` value. The field will be set to this value and further validation rules in the chain will be skipped.

```
$v->field('type')->requiredIf('category', 'special', 'Required', 'default-type')->string();
```

### Built-in Rules

[](#built-in-rules)

- `string()`: Checks if value is a string.
- `int()`: Checks that the value is a PHP integer (`is_int`).
- `min(int|float $min)`: Value must be numeric and `>= $min`.
- `max(int|float $max)`: Value must be numeric and ` $max` the bounds are swapped).
- `float()`: Validates if the value is a float.
- `email()`: Validates email format.
- `url()`: Validates URL format.
- `regex(string $pattern)`: Validates against a regular expression.
- `alnum()`: Validates alphanumeric characters.
- `alpha()`: Validates alphabetic characters.
- `digit()`: Validates that the value is a string containing only ASCII digits (`0`–`9`), one or more characters.
- `numeric()`: Validates that the value is numeric in the PHP sense (`is_numeric()`), including int/float and numeric strings (e.g. `"123"`, `"1.5"`).
- `in(array $choices)`: Validates if the value is within the given choices. Supports `Enum::cases()`.
- `enum(string $enumClass)`: Validates if the value matches the given BackedEnum (value) or UnitEnum (name).
- `contains(string $needle)`: Validates if the value contains the needle.
- `startsWith(string $prefix)`: Validates if the string value starts with the prefix.
- `endsWith(string $suffix)`: Validates if the string value ends with the suffix.
- `json()`: Validates that the value is a string containing syntactically valid JSON (`json_decode` with no JSON error).
- `bool()`: Validates that the value is strictly a PHP boolean (`is_bool`), i.e. `true` or `false` (not `1`, `"true"`, etc.).
- `equalTo(mixed $expect)`: Validates if the value is equal to the expected value.
- `sameAs(string $otherKey)`: Validates that the value strictly matches (`===`) the value at another top-level input key (e.g. password confirmation against `password`).
- `length(?int $min = null, ?int $max = null)`: Checks the length of a string.
- `filterVar(int $filter)`: Uses PHP's `filter_var`.

Advanced Validation
-------------------

[](#advanced-validation)

### Validating Enums

[](#validating-enums)

You can validate against PHP Enums using `enum()` or `in()`.

```
// BackedEnum (string or int)
$v->field('status')->enum(StatusEnum::class);

// UnitEnum
$v->field('type')->enum(TypeEnum::class);

// Using in() with Enum::cases()
$v->field('role')->in(RoleEnum::cases());
```

When using `enum()` with a `BackedEnum: int`, the validator automatically casts numeric strings to integers before validation.

### Validating Arrays of Scalars

[](#validating-arrays-of-scalars)

Use `asList()` to apply a rule to every element in an array.

```
$v->field('tags')->asList('string');
// Integer elements between 0 and 100 (each element must be a PHP int)
$v->field('scores')->asList('between', 0, 100);
```

### Validating Array Size

[](#validating-array-size)

Use `arrayCount()` to validate the number of elements in an array.

```
$v->field('tags')->arrayCount(1, 5, 'Please provide 1 to 5 tags');
```

### Validating Nested Objects (Associative Arrays)

[](#validating-nested-objects-associative-arrays)

Use `asObject()` to validate a nested associative array without using dot-notation in `field`. Only the validated fields will be included in `getValidatedData()` (whitelist).

```
$data = ['address' => [
    'post_code' => '123-1234',
    'town' => 'TOKYO',
    'city' => 'Meguro',
]];
$v = Validator::make($data);

$v->field('address')->required()->asObject(function (Validator $child) {
    $child->field('post_code')->required()->regex('/^\d{3}-\d{4}$/');
    $child->field('town')->required()->string();
    $child->field('city')->required()->string();
});
if ($v->isValid()) {
    $validated = $v->getValidatedData();
} // ['address' => ['post_code' => '123-1234', 'town' => 'TOKYO', 'city' => 'Meguro']] }
```

### Validating Arrays of Objects (Nested Data)

[](#validating-arrays-of-objects-nested-data)

Use `asListObject()` to validate complex nested structures.

```
$data = [
    'users' => [
        ['name' => 'John', 'email' => 'john@example.com'],
        ['name' => 'Jane', 'email' => 'jane@example.com'],
    ]
];

$v = Validator::make($data);
$v->field('users')->asListObject(function(Validator $child) {
    $child->field('name')->required()->string();
    $child->field('email')->required()->email();
});
```

### Custom Validators

[](#custom-validators)

You can use `apply()` to use any callable, closure, or rule class as a validation rule.

```
// Using a closure
$v->field('username')->apply(fn($value) => !in_array($value, ['admin', 'root']));

// Using a closure that operates on the validator instance
$v->field('zip')->apply(function() {
    $this->regex('/^\d{3}-\d{4}$/');
});

// Using external functions
$v->field('count')->apply('is_numeric');

// Using first-class callables
$v->field('price')->apply($myValidator->checkPrice(...));

// Using external rule classes (instantiated automatically)
$v->field('token')->apply(MyCustomRule::class, $options);

// Using invokable objects
$v->field('price')->apply(new MyInvokableRule(), $minPrice);
```

### Extending: Custom Rules and IDE Completion

[](#extending-custom-rules-and-ide-completion)

You can add project-specific rules **with IDE code completion** by extending `ValidatorRules` and overriding `createRules()` on your Validator (or ValidatorData) subclass.

1. **Extend ValidatorRules** and add your methods (or register names in `$rules` and use `@method` in the docblock).
2. **Override `createRules()`** in your Validator subclass to return your rules instance.
3. **Override `field()` with `@return YourValidatorRules`** so that `$v->field('x')` is inferred as your class and your custom methods appear in autocomplete.

```
use Wscore\LeanValidator\Validator;
use Wscore\LeanValidator\ValidatorData;
use Wscore\LeanValidator\ValidatorRules;

class MyValidatorRules extends ValidatorRules
{
    public function postCode(): static
    {
        return $this->regex('/^\d{3}-\d{4}$/');
    }

    public function hiragana(): static
    {
        return $this->regex('/^[\x{3040}-\x{309F}\s]+$/u');
    }
}

class MyValidator extends Validator
{
    protected function createRules(): ValidatorRules
    {
        return new MyValidatorRules($this);
    }

    /** @return MyValidatorRules */
    public function field(string $key, ?string $errorMsg = null): ValidatorRules
    {
        return parent::field($key, $errorMsg);
    }
}

// Usage: IDE will suggest postCode() and hiragana()
$v = MyValidator::make($data);
$v->field('zip')->required()->postCode();
$v->field('name_kana')->required()->hiragana();
```

### Language- or context-specific Rules (Japanese, etc.)

[](#language--or-context-specific-rules-japanese-etc)

For Japanese-specific validations, use the `Wscore\LeanValidator\Rule\Ja` class.

```
use Wscore\LeanValidator\Rule\Ja;

$v = Validator::make($data);
$v->field('name_kana')->required()->apply(Ja::kana());
$v->field('zip')->required()->apply(Ja::zip());
```

Available rules in `Ja` class:

- `hiragana()`: Hiragana only.
- `katakana()`: Katakana only.
- `kana()`: Hiragana and Katakana.
- `hankakuKana()`: Hankaku-Katakana.
- `kanji()`: Kanji only.
- `zenkaku()`: Zenkaku characters (non-ASCII).
- `zip()`: Japanese Zip code (000-0000).
- `tel()`: Japanese Phone number.

#### Network-specific Rules (IP, UUID, etc.)

[](#network-specific-rules-ip-uuid-etc)

For network-related validations, use the `Wscore\LeanValidator\Rule\Net` class.

```
use Wscore\LeanValidator\Rule\Net;
use Wscore\LeanValidator\Rule\Date;

$v = Validator::make($data);
$v->field('ip_address')->required()->apply(Net::ip());
$v->field('uuid')->required()->apply(Net::uuid());
$v->field('start_date')->required()->apply(Date::htmlDate());
$v->field('start_date')->apply(Date::notFutureDate());
```

Available rules in `Net` class:

- `ip(int $flags = 0)`: IP address (v4 or v6).
- `ipv4()`: IPv4 address.
- `ipv6()`: IPv6 address.
- `mac()`: MAC address.
- `uuid()`: UUID.
- `domain()`: Domain name.

#### Date-specific Rules (HTML date/time, etc.)

[](#date-specific-rules-html-datetime-etc)

For HTML date/time formats and date constraints, use the `Wscore\LeanValidator\Rule\Date` class.

```
use Wscore\LeanValidator\Rule\Date;

$v = Validator::make($data);
$v->field('birthday')->required()->apply(Date::htmlDate());
$v->field('appointment_at')->apply(Date::htmlDateTimeLocal());
$v->field('work_time')->apply(Date::htmlTime());
$v->field('billing_month')->apply(Date::htmlMonth());
$v->field('reporting_week')->apply(Date::htmlWeek());

// Disallow future dates (including today is allowed)
$v->field('birthday')->apply(Date::notFutureDate());
```

Available rules in `Date` class:

- `htmlDate()`: HTML5 `` (`Y-m-d`).
- `htmlMonth()`: HTML5 `` (`Y-m`).
- `htmlTime()`: HTML5 `` (`H:i` or `H:i:s`).
- `htmlDateTimeLocal()`: HTML5 `` (`Y-m-d\TH:i` or `Y-m-d\TH:i:s`).
- `htmlWeek()`: HTML5 `` (`YYYY-Www`).
- `notFutureDate(?string $format = 'Y-m-d')`: Rejects dates strictly in the future, using the given format.

You can also create a Rules subclass for better IDE support:

```
class ValidatorRulesJa extends ValidatorRules
{
    public function hiragana(): static { return $this->apply(Ja::hiragana()); }
    public function zip(): static { return $this->apply(Ja::zip()); }
}
```

Rules added with `addRule()` or by merging callables into `$this->rules` in the constructor (same shape as `Sanitizer::$rules`: `fn ($value) => bool`, e.g. `'ip' => fn ($v) => is_string($v) && filter_var($v, FILTER_VALIDATE_IP) !== false`) work with `apply('name')` and `__call`, but will not show in IDE completion unless you add a corresponding `@method` on your Rules subclass.

API Reference
-------------

[](#api-reference)

### `Validator::make(array|string|numeric $data): static`

[](#validatormakearraystringnumeric-data-static)

Creates a validator. If a string or number is passed, it treats it as a single item to be validated.

### `field(string $key, ?string $errorMsg = null): static`

[](#fieldstring-key-string-errormsg--null-static)

Specifies the field to validate. Optionally sets a default error message for any rule in the chain.

### `required(?string $msg = null): static`

[](#requiredstring-msg--null-static)

Marks the field as required.

### `optional(mixed $default = null): static`

[](#optionalmixed-default--null-static)

Marks the field as optional. If the field is missing, it will be set to the default value.

### `requiredIf(string $otherKey, mixed $expect, ?string $msg = null, mixed $elseOverwrite = null): static`

[](#requiredifstring-otherkey-mixed-expect-string-msg--null-mixed-elseoverwrite--null-static)

Marks the field as required if the value of `$otherKey` matches `$expect`.

- If matched: acts like `required($msg)`.
- If not matched:
    - If `$elseOverwrite` is provided, sets the field to this value and skips further validation.
    - Otherwise, acts like `optional()`.

### `requiredWith(string $otherKey, ?string $msg = null, mixed $elseOverwrite = null): static`

[](#requiredwithstring-otherkey-string-msg--null-mixed-elseoverwrite--null-static)

Marks the field as required if `$otherKey` exists.

- If exists: acts like `required($msg)`.
- Otherwise: acts like `optional()` or overwrites if `$elseOverwrite` is provided.

### `requiredWithout(string $otherKey, ?string $msg = null, mixed $elseOverwrite = null): static`

[](#requiredwithoutstring-otherkey-string-msg--null-mixed-elseoverwrite--null-static)

Marks the field as required if `$otherKey` does not exist.

- If not exists: acts like `required($msg)`.
- Otherwise: acts like `optional()` or overwrites if `$elseOverwrite` is provided.

### `requiredWhen(callable $call, ?string $msg = null, mixed $elseOverwrite = null): static`

[](#requiredwhencallable-call-string-msg--null-mixed-elseoverwrite--null-static)

Marks the field as required if `$call($data)` returns true.

- If true: acts like `required($msg)`.
- Otherwise: acts like `optional()` or overwrites if `$elseOverwrite` is provided.

### `isValid(): bool`

[](#isvalid-bool)

Returns true if there are no validation errors.

### `getValidatedData(): array`

[](#getvalidateddata-array)

Returns the validated data. Throws `RuntimeException` if validation failed.

### `getErrorsFlat(): array`

[](#geterrorsflat-array)

Returns a flat array of error messages where keys are the field names.

### `addError(string $fieldName, string $errorMessage): static`

[](#adderrorstring-fieldname-string-errormessage-static)

Manually adds an error message to a specific field. Useful for errors that cannot be expressed with validation rules.

Sanitizer Features
------------------

[](#sanitizer-features)

The `Sanitizer` class provides various methods to clean and transform input data.

### Default Sanitization

[](#default-sanitization)

- **UTF-8 Conversion**: Ensures strings are valid UTF-8.
- **Trimming**: Removes surrounding whitespace using Unicode-aware regex.

### Built-in Rules

[](#built-in-rules-1)

Use these methods to apply specific transformations:

- `toUtf8(...$fields)`: Ensures valid UTF-8.
- `toTrim(...$fields)`: Trims whitespace.
- `toDigits(...$fields)`: Removes all non-digit characters.
- `toInt(...$fields)`: If the string is a valid decimal integer (`FILTER_VALIDATE_INT`), converts it to PHP `int`; otherwise leaves the string unchanged so validation can reject it.
- `toFloat(...$fields)`: If the string is a valid float (`FILTER_VALIDATE_FLOAT`), converts it to PHP `float`; otherwise leaves the string unchanged.
- `toBool(...$fields)`: Uses `FILTER_VALIDATE_BOOLEAN` with `FILTER_NULL_ON_FAILURE`. Recognized **true** values include (case-insensitive) `1`, `true`, `on`, `yes`; **false** values include `0`, `false`, `off`, `no`, and empty string. Anything else (e.g. `maybe`, `2`) is left as the original string so validation can reject it. Output is real PHP `bool`; map to `0`/`1` or DB types at persistence if your schema requires it.
- `toLower(...$fields)`: Converts to lowercase.
- `toUpper(...$fields)`: Converts to uppercase.
- `toKana(...$fields)`: Converts to Zenkaku-Kana (needs `mbstring`).
- `toHankaku(...$fields)`: Converts to Hankaku-Kana (needs `mbstring`).
- `toZenkaku(...$fields)`: Converts to Zenkaku-Kana/Alphanumeric (needs `mbstring`).

### Skipping Sanitization

[](#skipping-sanitization)

- `skip(...$fields)`: Skips all sanitization (useful for passwords).
- `skipTrim(...$fields)`: Skips only the trimming process.

### Dot-Notation and Wildcards

[](#dot-notation-and-wildcards)

You can target nested data using dot-notation and wildcards.

```
$s->toDigits('user.tel');           // Target 'tel' inside 'user'
$s->toDigits('items.*.code');       // Target 'code' in all elements of 'items'
$s->skipTrim('user.*');             // Skip trim for all direct children of 'user'
```

### Adding Custom Rules

[](#adding-custom-rules)

You can add your own sanitization rules using `addRule()`.

```
$s = new Sanitizer();
$s->addRule('stars', function($value) {
    return str_repeat('*', strlen($value));
});

$s->apply('stars', 'password');
```

### Data Sanitization

[](#data-sanitization)

By default, the `Validator` can sanitize input data using the `Sanitizer` class. To apply sanitization, you must explicitly call the `sanitize()` method before validation.

```
use Wscore\LeanValidator\Sanitizer;

$data = [
    'name' => '  John Doe  ',
    'tel' => '03-1234-5678',
    'password' => '  secret  ',
    'items' => [
        ['code' => ' A-123 '],
        ['code' => ' B-456 '],
    ]
];

$s = new Sanitizer();

// Configure sanitization
$s->skip('password')           // Do not trim or clean password
    ->toDigits('tel')             // Remove non-digit characters
    ->toDigits('items.*.code');   // Use dot-notation and wildcards for nested data
// Apply sanitization
$cleanData = $s->clean($data);
// $cleanData['name'] => 'John Doe'
// $cleanData['tel'] => '0312345678'
// $cleanData['password'] => '  secret  '
// $cleanData['items'][0]['code'] => '123'
```

By default, the `Sanitizer` converts strings to UTF-8 and trims surrounding whitespace. If you don't call `sanitize()`, the validation will be performed on the raw input data.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance85

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity42

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

Every ~4 days

Recently: every ~11 days

Total

15

Last Release

80d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8ed783829e6fa0bd4b0def8c04ccfdfb2fc99f9e61e4a9470acad9e5abc5fcac?d=identicon)[asaokamei](/maintainers/asaokamei)

---

Top Contributors

[![asaokamei](https://avatars.githubusercontent.com/u/747030?v=4)](https://github.com/asaokamei "asaokamei (81 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/wscore-leanvalidator/health.svg)

```
[![Health](https://phpackages.com/badges/wscore-leanvalidator/health.svg)](https://phpackages.com/packages/wscore-leanvalidator)
```

###  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)
