PHPackages                             hydrakit/validation - 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. hydrakit/validation

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

hydrakit/validation
===================

Validation library for Hydra PHP framework

v0.2.0(yesterday)014—0%1MITPHP &gt;=8.2

Since Jul 6Compare

[ Source](https://github.com/hydra-foundation/validation)[ Packagist](https://packagist.org/packages/hydrakit/validation)[ RSS](/packages/hydrakit-validation/feed)WikiDiscussions Synced today

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

Hydra Validation
================

[](#hydra-validation)

Validates a set of input values against a per-field list of rules. Stateless — the rules travel with each call, so one `Validator` can be shared and autowired. Ships **no** `ServiceProvider`; the validator has no dependencies to bind.

Usage
-----

[](#usage)

For each field the rules run in order and the validator short-circuits on the first failure — there's no point computing messages a single-message-per-field UI (an htmx form) will never show.

```
$result = $validator->validate($data, [
    'email' => [new Required, new Pattern('/\A.+@.+\z/')],
    'name'  => [new Required, new MaxLength(120)],
]);

if ($result->fails()) {
    $errors = $result->errors();      // ['email' => 'first message', ...]
    $first  = $result->first('email'); // or null if it passed
}
```

`Result` carries at most one message per field (the first rule that failed), which is the shape view models consume.

Consuming validated input
-------------------------

[](#consuming-validated-input)

On a passing result, `validated()` returns the vetted subset of the input, so a controller consumes that instead of reaching back into the raw request:

```
$result = $validator->validate($input->all(), [
    'username' => [new Required, new MaxLength(64)],
    'bio'      => [new MaxLength(280)], // optional: no Required
]);

if ($result->fails()) {
    return $this->render('form', ['errors' => $result->errors()], Status::UnprocessableEntity);
}

$valid = $result->validated();          // ONLY the ruled, present, passing fields
$this->users->create($valid['username'], $valid['bio'] ?? '');
```

The semantics are deliberate and strict:

- **Failed result → `LogicException`.** Asking a failed result for validated data is a programming error — there is no safe subset to hand out — so it fails loud instead of returning partial data. Check `passes()` first.
- **Unruled input keys never appear.** Whatever extra fields the client posts, only what was vetted comes out — that's the point.
- **Absent stays absent.** A field listed in the rules but missing from the input is omitted, not invented as `null`; use `?? $default` for optional fields. (Listing a field with an empty rule list is still an explicit opt-in, so such a field appears when present.)

Note the `\A`/`\z` anchors in the pattern: with `^`/`$`, PCRE's `$` tolerates a trailing newline (so `"a@b\n"` would pass), whereas `\z` anchors at the true end of the string.

Custom rules
------------

[](#custom-rules)

`RuleInterface` is the package's one extension point: a rule inspects one value and returns an error message when invalid, or `null` when it passes. The message is the rule's own — there's no separate message subsystem. App-specific rules (e.g. "unique in the database") are application policy and live in the app.

```
final class Unique implements RuleInterface
{
    public function __construct(private UserRepository $users) {}

    public function validate(mixed $value): ?string
    {
        return $this->users->existsByEmail((string) $value) ? 'Already taken.' : null;
    }
}
```

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance100

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

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 ~1 days

Total

3

Last Release

1d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a947272da0bf1d7d6326b4fbf6ddb8049977cbdbe333d0d1282dbb6f5927dfdf?d=identicon)[whleucka](/maintainers/whleucka)

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/hydrakit-validation/health.svg)

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

PHPackages © 2026

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