aneterial / laravel-data-validator
Laravel package for convenient validation and hydration of a request into DTO structures
Requires
- php: >=8.2
- laravel/framework: ^11
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.58
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^11.0
README
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 propertyrules
: validation rules based on component semantics Laravel Validation, accepts only string valuerequestDataType
: to indicate where a field is expected - in the request body (default)const RequestProperty::BODY_TYPE
or in query stringconst 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
- 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; ... }
- 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; }
- 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; }
- 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; ... }
- 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; ... }
- 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