aneterial/laravel-data-validator

Laravel package for convenient validation and hydration of a request into DTO structures

1.0.1 2024-06-04 22:00 UTC

This package is auto-updated.

Last update: 2025-06-05 00:05:55 UTC


README

Build Status License Packagist PHP Laravel

Contents

Prerequisites

  • PHP 8.2 or higher
  • Laravel 11

for dev

  • PHPUnit &11
  • phpstan ^1.11
  • php-cs-fixer ^3.58

Installation

Install package via composer

composer require aneterial/laravel-data-validator

Usage

You now have a class attribute DataValidator\Attributes\RequestProperty at your disposal. Add it to the properties of your DTO and set the necessary configuration fields

use DataValidator\Attributes\RequestProperty;

final readonly class ExampleDTO {
  #[RequestProperty(property: 'id', rules: 'required|integer|min:0')]
  public int $id;

  #[RequestProperty(property: 'email', rules: 'required|string|email')]
  public string $email;
}

Description of fields:

  • property: name of the request key that matches the property
  • rules: validation rules based on component semantics Laravel Validation, accepts only string value
  • requestDataType: to indicate where a field is expected - in the request body (default) const RequestProperty::BODY_TYPE or in query string const RequestProperty::QUERY_TYPE
  • listRules: if the value is an array (list) - set the validation rules for each element according to the semantics of Laravel Validation

Next, you need to get a DataValidator\DataManager instance in your controller from app DI container and pass the request essence to it, indicating the DTO class that you expect to receive after validation and filling with data.

$dataManager = app(\DataValidator\DataManager::class);

Next, the Laravel validator will check the request entity (instanse of \Illuminate\Http\Request), and if the data is incorrect, it will throw an \Illuminate\Validation\ValidationException. If the data is correct, the Manager will create and fill the DTO object with data, which you can use in your application

/** @var ExampleDTO $dto */
$dto = $dataManager->validateAndConvert(from: $request, to: ExampleDTO::class);

If your endpoint involves passing array of objects [{...}, {...}, {...}], you can use a method that will validate the request and return an array of DTOs

/** @var ExampleDTO[] $dtos */
$dtos = $dataManager->validateAndConvertList(from: $request, to: ExampleDTO::class);

Examples

Here are some examples of using validation by attributes

  1. DTO with lists
final readonly class ExampleDTO {
...

  /** @var string[] $emails */
  #[RequestProperty(property: 'emails', rules: 'required|list', listRules: 'string|email')]
  public array $emails;
  
  /** @var int[] $ids */
  #[RequestProperty(property: 'ids', rules: 'required|list', listRules: 'int|min:0')]
  public array $ids;

...
}
  1. DTO with non required properties, if it not required - it should be nullable, except array - it can be empty array
final readonly class ExampleDTO {
  #[RequestProperty(property: 'id', rules: 'integer|min:0')]
  public ?int $id;

  #[RequestProperty(property: 'email', rules: 'string|email')]
  public ?string $email;

  /** @var int[] $ids */
  #[RequestProperty(property: 'ids', rules: 'list', listRules: 'int|min:0')]
  public array $ids;
}
  1. DTO with nested object
final readonly class ExampleDTO {
...

  #[RequestProperty(property: 'child', rules: 'required|array')]
  public NestedDTO $child;

...
}

// NestedDTO should contain properties with attributes
final readonly class NestedDTO {
  #[RequestProperty(property: 'id', rules: 'required|integer|min:0')]
  public int $id;

  #[RequestProperty(property: 'email', rules: 'required|string|email')]
  public string $email;
}
  1. DTO with list of nested object
final readonly class ExampleDTO {
...

  /** @var NestedDTO[] $children */
  #[RequestProperty(property: 'children', rules: 'required|list', listRules: NestedDTO::class)]
  public array $children;

...
}
  1. DTO with enum of BackedEnum property, you should set enum type to property and no more rules are required except, if you want - indicate type of enum
final readonly class ExampleDTO {
...

  #[RequestProperty(property: 'enum', rules: 'required|string')]
  public AnApplicationEnum $enum;

...
}
  1. DTO with enums of BackedEnum property
final readonly class ExampleDTO {
...
  /** @var AnApplicationEnum[] $enums */
  #[RequestProperty(property: 'enums', rules: 'required|list', listRules: AnApplicationEnum::class)]
  public array $enums;

...
}

Restrictions

- Important note: for different requestDataType

if DTO has nested objects or arrays of nested objects, the requestDataType of these entities is ignored and taken from the parrent entity

So if you use

final readonly class ExampleDTO {
...

  #[RequestProperty(property: 'child', rules: 'required|array', requestDataType: RequestProperty::BODY_TYPE)]
  public NestedDTO $child;

...
}

// NestedDTO should contain properties with attributes
final readonly class NestedDTO {
  #[RequestProperty(property: 'id', rules: 'required|integer|min:0', requestDataType: RequestProperty::QUERY_TYPE)]
  public int $id;

  #[RequestProperty(property: 'email', rules: 'required|string|email', requestDataType: RequestProperty::QUERY_TYPE)]
  public string $email;
}

In nested object requestDataType will not work and it be RequestProperty::BODY_TYPE like in parrent entity

- Another one: for list validation

Method DataManager::validateAndConvertList can only work with data from request body, so all properties of entity will be force casted to type RequestProperty::BODY_TYPE in this usage