The Data Transfer Objects with validation for Hyperf applications. Forked from laravel-validated-dto
https://wendell-adriel.gitbook.io/laravel-validated-dto
composer require friendsofhyperf/validated-dto
You can create DTO
using the gen:dto
command:
php bin/hyperf.php gen:dto UserDTO
The DTO
are going to be created inside app/DTO
.
You can validate data in the same way you validate Request
data:
<?php
namespace App\DTO;
class UserDTO extends ValidatedDTO
{
protected function rules(): array
{
return [
'name' => ['required', 'string'],
'email' => ['required', 'email'],
'password' => ['required'],
];
}
}
You can create a DTO
instance on many ways:
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A'
]);
$dto = UserDTO::fromJson('{"name": "Deeka Wong", "email": "deeka@example.com", "password": "D3Crft!@1b2A"}');
public function store(RequestInterface $request): JsonResponse
{
$dto = UserDTO::fromRequest($request);
}
$user = new User([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A'
]);
$dto = UserDTO::fromModel($user);
Beware that the fields in the $hidden
property of the Model
won't be used for the DTO
.
You have three ways of creating a DTO
instance from an Artisan Command
:
<?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);
}
}
<?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);
}
}
<?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);
}
}
After you create your DTO
instance, you can access any properties 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 listed in the rules
method of your DTO
, this data will be ignored and won't be available in your DTO
:
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A',
'username' => 'john_doe',
]);
$dto->username; // THIS WON'T BE AVAILABLE IN YOUR DTO
Sometimes we can have properties that are optional and that can have default values. You can define the default values for
your DTO
properties in the defaults
function:
<?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 DTO
definition above you could run:
$dto = UserDTO::fromArray([
'name' => 'Deeka Wong',
'email' => 'deeka@example.com',
'password' => 'D3Crft!@1b2A'
]);
$dto->username; // 'deeka_wong'
You can convert your DTO to some formats:
$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",
// ]
$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"
// }
$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",
// }
You can define custom messages and attributes implementing the messages
and attributes
methods in your DTO
class:
/**
* Defines the custom messages for validator errors.
*/
protected function messages() {
return [];
}
/**
* Defines the custom attributes for validator errors.
*/
protected function attributes() {
return [];
}
You can easily cast your DTO properties by defining a casts method in your DTO:
/**
* Defines the type casting for the properties of the DTO.
*
* @return array
*/
protected function casts(): array
{
return [
'name' => new StringCast(),
'age' => new IntegerCast(),
'created_at' => new CarbonImmutableCast(),
];
}
For JSON strings, it will convert into an array, for other types, it will wrap them in an array.
protected function casts(): array
{
return [
'property' => new ArrayCast(),
];
}
For string values, this uses the filter_var
function with the FILTER_VALIDATE_BOOLEAN
flag.
protected function casts(): array
{
return [
'property' => new BooleanCast(),
];
}
This accepts any value accepted by the Carbon
constructor. 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 if you need that will be used when casting the value.
protected function casts(): array
{
return [
'property' => new CarbonCast('Europe/Lisbon'),
];
}
You can also pass a format when defining the cast to be used to cast the value. If the property has a different format than
the specified it will throw a \FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.
protected function casts(): array
{
return [
'property' => new CarbonCast('Europe/Lisbon', 'Y-m-d'),
];
}
This accepts any value accepted by the CarbonImmutable
constructor. If an invalid value is found it will throw a
\FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.
protected function casts(): array
{
return [
'property' => new CarbonImmutableCast(),
];
}
You can also pass a timezone when defining the cast if you need that will be used when casting the value.
protected function casts(): array
{
return [
'property' => new CarbonImmutableCast('Europe/Lisbon'),
];
}
You can also pass a format when defining the cast to be used to cast the value. If the property has a different format than
the specified it will throw a \FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.
protected function casts(): array
{
return [
'property' => new CarbonImmutableCast('Europe/Lisbon', 'Y-m-d'),
];
}
For JSON strings, it will convert into an array first, before wrapping it into a Collection
object.
protected function casts(): array
{
return [
'property' => new CollectionCast(),
];
}
If you want to cast all the elements inside the Collection
, you can pass a Castable
to the CollectionCast
constructor. Let's say that you want to convert all the items inside the Collection
into integers:
protected function casts(): array
{
return [
'property' => new CollectionCast(new IntegerCast()),
];
}
This works with all Castable
, including DTOCast
and ModelCast
for nested data.
This works with arrays and JSON strings. This will validate the data and also cast the data for the given DTO.
This will throw a Hyperf\Validation\ValidationException
exception if the data is not valid for the DTO.
This will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception if the property is not a valid
array or valid JSON string.
This will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastTargetException
exception if the class passed to the
DTOCast
constructor is not a ValidatedDTO
instance.
protected function casts(): array
{
return [
'property' => new DTOCast(UserDTO::class),
];
}
If a not numeric value is found, it will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.
protected function casts(): array
{
return [
'property' => new FloatCast(),
];
}
If a not numeric value is found, it will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.
protected function casts(): array
{
return [
'property' => new IntegerCast(),
];
}
This works with arrays and JSON strings.
This will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception if the property is not a valid
array or valid JSON string.
This will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastTargetException
exception if the class passed to the
ModelCast
constructor is not a Model
instance.
protected function casts(): array
{
return [
'property' => new ModelCast(User::class),
];
}
This works with arrays and JSON strings.
This will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception if the property is not a valid
array or valid JSON string.
protected function casts(): array
{
return [
'property' => new ObjectCast(),
];
}
If the data can't be converted into a string, this will throw a FriendsOfHyperf\ValidatedDTO\Exception\CastException
exception.
protected function casts(): array
{
return [
'property' => new StringCast(),
];
}
You can easily create new Castable
types for your project by implementing the FriendsOfHyperf\ValidatedDTO\Casting\Castable
interface. This interface has a single method that must be implemented:
/**
* Casts the given value.
*
* @param string $property
* @param mixed $value
* @return mixed
*/
public function cast(string $property, mixed $value): mixed;
Let's say that you have a URLWrapper
class in your project, and you want that when passing a URL into your
DTO
it will always return a URLWrapper
instance instead of a simple string:
class URLCast implements Castable
{
/**
* @param string $property
* @param mixed $value
* @return URLWrapper
*/
public function cast(string $property, mixed $value): URLWrapper
{
return new URLWrapper($value);
}
}
Then you could apply this to your DTO:
class CustomDTO extends ValidatedDTO
{
protected function rules(): array
{
return [
'url' => ['required', 'url'],
];
}
protected function defaults(): array
{
return [];
}
protected function casts(): array
{
return [
'url' => new URLCast(),
];
}
}