monkeyscloud / monkeyslegion-validation
Attribute-driven validation & DTO binding for the MonkeysLegion PHP 8.4 framework — self-validating constraints, property hooks, PSR-15 middleware.
Package info
github.com/MonkeysCloud/MonkeysLegion-Validation
pkg:composer/monkeyscloud/monkeyslegion-validation
Requires
- php: ^8.4
- ext-json: *
- ext-mbstring: *
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- guzzlehttp/psr7: ^2.7
- http-interop/http-factory-guzzle: ^1.2
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
Suggests
- monkeyscloud/monkeyslegion-core: For framework integration and service providers
- monkeyscloud/monkeyslegion-di: For auto-wired DI container support
This package is auto-updated.
Last update: 2026-04-16 04:06:05 UTC
README
Attribute‑driven DTO binding & validation for the MonkeysLegion PHP 8.4 framework — self-validating constraints, property hooks, PSR-15 middleware.
✨ Features
- Self-validating constraints — each
#[Email],#[NotBlank], etc. validates itself (no monolithic if/else chain) - PHP 8.4 property hooks —
$result->isValid,$result->errors,$result->errorCount - 24 built-in constraints — strings, numbers, dates, collections, networking, cross-field, and more
- Automatic DTO binding — JSON body + query parameters → strongly‑typed DTO
- PSR‑15 middleware — validates, returns
422 Unprocessable Entityon failure - ValidationResult — structured result object with field-level error queries
- ValidationException — throwable with full result for catch-and-inspect
- Zero magic — no doctrine proxies, only native PHP reflection & attributes
- Extensible — implement
ConstraintInterfacein a single class to add constraints
🛠 Requirements
| Minimum | |
|---|---|
| PHP | 8.4 |
| Extensions | ext-json, ext-mbstring |
| PSR | psr/http-message ^2.0, psr/http-server-handler ^1.0, psr/http-server-middleware ^1.0 |
🚀 Installation
composer require monkeyscloud/monkeyslegion-validation:^2.0
⚡ Quick Start
1. Define a DTO with constraints
<?php declare(strict_types=1); namespace App\Dto; use MonkeysLegion\Validation\Attributes as Assert; final readonly class CreateUserRequest { public function __construct( #[Assert\NotBlank] #[Assert\Email] public string $email, #[Assert\NotBlank] #[Assert\Length(min: 8, max: 64)] public string $password, #[Assert\SameAs(otherField: 'password')] public string $confirmPassword, ) {} }
2. Validate directly
use MonkeysLegion\Validation\Validator; $validator = new Validator(); $result = $validator->validate($dto); if (!$result->isValid) { foreach ($result->errors as $error) { echo "{$error->field}: {$error->message}\n"; } } // Or throw on failure: $result->throwIfInvalid();
3. Use the helper function
$result = validate($dto); if ($result->hasErrorsFor('email')) { echo $result->firstError('email'); }
4. PSR-15 middleware integration
use MonkeysLegion\Validation\DtoBinder; use MonkeysLegion\Validation\Validator; use MonkeysLegion\Validation\Middleware\ValidationMiddleware; $middleware = new ValidationMiddleware( binder: new DtoBinder(new Validator()), responseFactory: $responseFactory, // PSR-17 ResponseFactoryInterface streamFactory: $streamFactory, // PSR-17 StreamFactoryInterface dtoMap: [ 'user.create' => \App\Dto\CreateUserRequest::class, ], );
When validation fails the client receives:
HTTP/1.1 422 Unprocessable Entity Content-Type: application/json { "errors": [ { "field": "email", "message": "Value must be a valid e-mail." }, { "field": "password", "message": "Length constraint violated." } ] }
5. Handler with validated DTO
public function createUser(ServerRequestInterface $request): ResponseInterface { /** @var CreateUserRequest $dto */ $dto = $request->getAttribute('dto'); $this->userService->register($dto->email, $dto->password); return $this->responseFactory->createResponse(201); }
📦 Built-in Constraints
Strings
| Attribute | Description |
|---|---|
#[NotBlank] |
Value must not be null, empty string, or empty array |
#[Email] |
Valid e-mail address |
#[Length(min, max)] |
String length within bounds (UTF-8) |
#[Pattern(regex)] |
Matches regular expression |
#[Alpha] |
Letters only |
#[Alnum] |
Letters and digits only |
#[Json] |
Valid JSON string |
Numbers
| Attribute | Description |
|---|---|
#[Numeric] |
Value is numeric (is_numeric) |
#[Min(value)] |
Minimum numeric value |
#[Max(value)] |
Maximum numeric value |
#[Range(min, max)] |
Numeric range |
#[Decimal(scale)] |
Decimal with max scale digits |
Collections
| Attribute | Description |
|---|---|
#[Count(min, max)] |
Array/iterable size within bounds |
#[Choice(choices)] |
Value in allowed list |
Dates
| Attribute | Description |
|---|---|
#[Date(format)] |
Valid date (default: Y-m-d) |
#[After(otherField)] |
Date after another field |
#[Before(otherField)] |
Date before another field |
Networking
| Attribute | Description |
|---|---|
#[Url] |
Valid URL |
#[Ip(allowV6)] |
Valid IP address |
#[UuidV4] |
Valid UUID v4 |
Cross-field & Type
| Attribute | Description |
|---|---|
#[SameAs(otherField)] |
Must match another field |
#[Type(type)] |
PHP type check (string, int, float, bool, array, or class) |
#[Unique] |
Marker for uniqueness (consumer provides checker) |
#[Callback(fn)] |
Custom callable validation |
🪄 Creating Custom Constraints
Implement ConstraintInterface — the constraint is the validator:
<?php declare(strict_types=1); namespace App\Validation; use MonkeysLegion\Validation\Contracts\ConstraintInterface; use MonkeysLegion\Validation\ValidationError; use Attribute; #[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)] final readonly class Slug implements ConstraintInterface { public function __construct( public string $message = 'Value must be a valid URL slug.', ) {} public function validate(mixed $value, string $field, object $dto): ?ValidationError { if ($value === null || $value === '') { return null; } if (!preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', (string) $value)) { return new ValidationError($field, $this->message); } return null; } }
Now #[Slug] is usable on any DTO property — no registration needed.
🧪 Testing
composer test # or vendor/bin/phpunit
57 tests, 71 assertions covering all 24 constraints.
🗺 Roadmap
- 🌐 I18n-aware validation messages via
monkeyslegion-i18n - 📚 Constraint composition (
#[Each(new Email())]style) - 🔄 Async validation for remote checks (uniqueness, etc.)
- 🧰 CLI generator for DTO scaffolding
🙌 Contributing
- Fork & create a feature branch.
- Follow MonkeysLegion v2 code standards.
- Add unit tests (
vendor/bin/phpunit). - Open a PR.
📄 License
Released under the MIT License © 2026 MonkeysCloud.