monkeyscloud/monkeyslegion-validation

Attribute-driven DTO binding and validation layer for the MonkeysLegion framework.

dev-main / 1.0.x-dev 2025-05-21 23:58 UTC

This package is auto-updated.

Last update: 2025-05-22 00:00:16 UTC


README

Attribute‑driven DTO binding & validation layer for the MonkeysLegion PHP framework.

✨ Features

  • Attribute‑based constraints – ship with #[NotBlank], #[Email], #[Length] (extend in minutes)
  • Automatic binding – JSON body and query parameters → strongly‑typed DTO
  • PSR‑15 middleware – intercepts the request, validates, and returns a 400 JSON error payload if needed
  • Fail‑fast & zero‑magic – no doctrine/metadata proxies, only native PHP reflection
  • Extensible – write a new constraint attribute in <10 LOC
  • Lean footprint – depends only on PSR interfaces + Laminas Diactoros for JsonResponse

🛠 Requirements

Minimum
PHP 8.4
Extensions ext-json, ext-mbstring
Composer deps psr/http-message, psr/http-server-handler, psr/http-server-middleware, laminas/laminas-diactoros

All other MonkeysLegion packages (core, di, …) are pulled in transitively.

🚀 Installation

composer require monkeyscloud/monkeyslegion-validation:^1.0@dev

Ensure your root composer.json allows dev stability while we are pre‑1.0:

{
  "minimum-stability": "dev",
  "prefer-stable": true
}

⚡ Quick Start

1. Define a DTO

<?php
namespace App\Http\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\NotBlank]
        #[Assert\Pattern('/^[A-Z0-9_-]{3,10}$/')]
        public string $sku,

        #[Assert\Range(min: 0.01, max: 9999.99)]
        public float $price,

        #[Assert\Url]
        public string $productPage,

        #[Assert\UuidV4]
        public string $categoryId,
    ) {}
}

2. Register services in your DI container

$container->register(\MonkeysLegion\Validation\ValidatorInterface::class,
                     \MonkeysLegion\Validation\AttributeValidator::class);

$container->register(\MonkeysLegion\Validation\DtoBinder::class)
          ->addArgument($container->get(\MonkeysLegion\Validation\ValidatorInterface::class));

3. Add the middleware to your PSR‑15 pipeline

use MonkeysLegion\Validation\Middleware\ValidationMiddleware;

$pipeline->pipe(new ValidationMiddleware(
    $container->get(\MonkeysLegion\Validation\DtoBinder::class),
    [
        // router‑name => DTO class
        'user_create' => \App\Http\Dto\CreateUserRequest::class,
    ]
));

4. Use the validated DTO in your handler

public function createUser(ServerRequestInterface $request): ResponseInterface
{
    /** @var \App\Http\Dto\CreateUserRequest $dto */
    $dto = $request->getAttribute('dto');

    $this->userService->register($dto->email, $dto->password);

    return new JsonResponse(['status' => 'created'], 201);
}

When validation fails the client receives:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "errors": [
    { "field": "email", "message": "Value must be a valid e-mail." },
    { "field": "password", "message": "Length constraint violated." }
  ]
}

🪄 Adding Custom Constraints

  1. Create an attribute class implementing ConstraintInterface (optional but recommended):
namespace MonkeysLegion\Validation\Attributes;

use MonkeysLegion\Validation\ConstraintInterface;

#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class UuidV4 implements ConstraintInterface
{
    public function __construct(
        public string $message = 'Value must be a valid UUIDv4.'
    ) {}
}
  1. Add a check inside AttributeValidator::validate():
if ($instance instanceof Assert\UuidV4 &&
    !preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value)) {
    $errors[] = new ValidationError($prop->getName(), $instance->message);
}

That’s it—#[UuidV4] is now usable on any DTO property.

🗺 Roadmap

  • 🌐 Localisable validation messages
  • 🔌 Symfony Validation bridge
  • 📚 Constraint composition (#[Assert\All(new Assert\Email())]‑style)
  • 🧰 CLI generator for DTO & constraint scaffolding

🙌 Contributing

  1. Fork & create a feature branch.
  2. Follow PSR‑12 coding standards.
  3. Add unit tests (vendor/bin/phpunit).
  4. Open a PR.

📄 License

Released under the MIT License © 2025 MonkeysCloud.