Validated DTO
Official Documentation
Laravel Validated DTO Official Documentation
Installation
composer require friendsofhyperf/validated-dto
Creating DTOs
You can create a DTO
using the gen:dto
command:
php bin/hyperf.php gen:dto UserDTO
The DTO
will be created in the app/DTO
directory.
Defining Validation Rules
You can validate data just like validating Request
data:
<?php
namespace App\DTO;
class UserDTO extends ValidatedDTO
{
protected function rules(): array
{
return [
'name' => ['required', 'string'],
'email' => ['required', 'email'],
'password' => ['required'],
];
}
}
Creating DTO Instances
You can create DTO
instances in multiple ways:
From Array
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A'
]);
From JSON String
$dto = UserDTO::fromJson('{"name": "Deeka Wong", "email": "deeka@example.com", "password": "D3Crft!@1b2A"}');
From Request Object
public function store(RequestInterface $request): JsonResponse
{
$dto = UserDTO::fromRequest($request);
}
From Model
$user = new User([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A'
]);
$dto = UserDTO::fromModel($user);
Note that fields in the model's $hidden
property will not be used in the DTO
.
From Artisan Command
You have three ways to create a DTO
instance from an Artisan Command
:
From Command Arguments
<?php
use App\DTO\UserDTO;
use Hyperf\Command\Command;
class CreateUserCommand extends Command
{
protected ?string $signature = 'create:user {name} {email} {password}';
protected string $description = 'Create a new User';
/**
* Execute the console command.
*
* @return int
*
* @throws ValidationException
*/
public function handle()
{
$dto = UserDTO::fromCommandArguments($this);
}
}
From Command Options
<?php
use App\DTO\UserDTO;
use Hyperf\Command\Command;
class CreateUserCommand extends Command
{
protected ?string $signature = 'create:user { --name= : The user name } { --email= : The user email } { --password= : The user password }';
protected string $description = 'Create a new User';
/**
* Execute the console command.
*
* @return int
* @throws ValidationException
*/
public function handle()
{
$dto = UserDTO::fromCommandOptions($this);
}
}
From Command Arguments and Options
<?php
use App\DTO\UserDTO;
use Hyperf\Command\Command;
class CreateUserCommand extends Command
{
protected ?string $signature = 'create:user {name}
{ --email= : The user email }
{ --password= : The user password }';
protected string $description = 'Create a new User';
/**
* Execute the console command.
*
* @return int
*
* @throws ValidationException
*/
public function handle()
{
$dto = UserDTO::fromCommand($this);
}
}
Accessing DTO Data
After creating a DTO
instance, you can access any property like an object
:
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A'
]);
$dto->name; // 'Deeka Wong'
$dto->email; // 'deeka@example.com'
$dto->password; // 'D3Crft!@1b2A'
If you pass properties that are not in the rules
method of your DTO
, this data will be ignored and won't be available in the DTO
:
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A',
'username' => 'john_doe', // This property will not be available in the DTO
]);
$dto->username; // This property is not available in the DTO
Defining Default Values
Sometimes we might have optional properties that can have default values. You can define default values for DTO
properties in the defaults
method:
<?php
namespace App\DTO;
use Hyperf\Stringable\Str;
class UserDTO extends ValidatedDTO
{
protected function rules(): array
{
return [
'name' => ['required', 'string'],
'email' => ['required', 'email'],
'username' => ['sometimes', 'string'],
'password' => ['required'],
];
}
protected function defaults(): array
{
return [
'username' => Str::snake($this->name),
];
}
}
With the above DTO
definition, you can run:
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A'
]);
$dto->username; // 'deeka_wong'
Converting DTO Data
You can convert your DTO to several formats:
To Array
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A',
]);
$dto->toArray();
// [
// "name" => "Deeka Wong",
// "email" => "deeka@example.com",
// "password" => "D3Crft!@1b2A",
// ]
To JSON String
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A',
]);
$dto->toJson();
// '{"name":"Deeka Wong","email":"deeka@example.com","password":"D3Crft!@1b2A"}'
$dto->toJson(true); // You can call it like this to pretty print your JSON
// {
// "name": "Deeka Wong",
// "email": "deeka@example.com",
// "password": "D3Crft!@1b2A"
// }
To Eloquent Model
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A',
]);
$dto->toModel(\App\Model\User::class);
// App\Model\User {#3776
// name: "Deeka Wong",
// email: "deeka@example.com",
// password: "D3Crft!@1b2A",
// }
Custom Messages, Attributes, and Exceptions
You can define custom messages and attributes by implementing the messages
and attributes
methods in your DTO
class:
/**
* Define custom messages for validator errors.
*/
protected function messages() {
return [];
}
/**
* Define custom attributes for validator errors.
*/
protected function attributes() {
return [];
}
Type Casting
You can easily cast your DTO properties by defining the casts
method in your DTO:
/**
* Define the type casting for DTO properties.
*
* @return array
*/
protected function casts(): array
{
return [
'name' => new StringCast(),
'age' => new IntegerCast(),
'created_at' => new CarbonImmutableCast(),
];
}
Available Types
Array
For JSON strings, it will convert to array, for other types, it will wrap in an array.
protected function casts(): array
{
return [
'property' => new ArrayCast(),
];
}
Boolean
For string values, this uses the filter_var
function with the FILTER_VALIDATE_BOOLEAN
flag.
protected function casts(): array
{
return [
'property' => new BooleanCast(),
];
}
Carbon
This accepts any value that the Carbon
constructor accepts. If an invalid value is found, it will throw a \FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.
protected function casts(): array
{
return [
'property' => new CarbonCast(),
];
}
You can also pass a timezone when defining the cast and it will be used when casting the value if needed.
protected function casts(): array
{
return [
'property' => new CarbonCast('Europe/Lisbon'),
];
}
You can also pass a format to be used when casting the value. If the property format is different from the specified one, it will throw a \FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.