Skip to content

Validated DTO

Official Documentation

Laravel Validated DTO Official Documentation

Installation

shell
composer require friendsofhyperf/validated-dto

Creating DTOs

You can create a DTO using the gen:dto command:

shell
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
<?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

php
$dto = UserDTO::fromArray([
    'name' => 'Deeka Wong',
    'email' => 'deeka@example.com',
    'password' => 'D3Crft!@1b2A'
]);

From JSON String

php
$dto = UserDTO::fromJson('{"name": "Deeka Wong", "email": "deeka@example.com", "password": "D3Crft!@1b2A"}');

From Request Object

php
public function store(RequestInterface $request): JsonResponse
{
    $dto = UserDTO::fromRequest($request);
}

From Model

php
$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
<?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
<?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
<?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:

php
$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:

php
$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
<?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:

php
$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

php
$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

php
$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

php
$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:

php
/**
 * 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:

php
/**
 * 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.

php
protected function casts(): array
{
    return [
        'property' => new ArrayCast(),
    ];
}

Boolean

For string values, this uses the filter_var function with the FILTER_VALIDATE_BOOLEAN flag.

php
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.

php
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.

php
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.