Nhảy tới nội dung

ValidatedDTO

ValidatedDTO cho phép định nghĩa và validate dữ liệu đầu vào một cách rõ ràng và type-safe bằng PHP Attributes.

Tạo DTO cơ bản

<?php

namespace App\DTOs;

use Vietiso\Core\ValidatedDTO\ValidatedDTO;
use Vietiso\Core\ValidatedDTO\Rules\Required;
use Vietiso\Core\ValidatedDTO\Rules\Email;
use Vietiso\Core\ValidatedDTO\Rules\Min;
use Vietiso\Core\ValidatedDTO\Rules\Max;

class CreateUserDTO extends ValidatedDTO
{
#[Required]
#[Min(2)]
#[Max(100)]
public string $name;

#[Required]
#[Email]
public string $email;

#[Required]
#[Min(8)]
public string $password;
}

Sử dụng trong Controller

use App\DTOs\CreateUserDTO;
use Vietiso\Core\Route\Attributes\Post;

#[Post('/users')]
public function store(CreateUserDTO $dto)
{
// $dto đã được validate tự động
$user = User::create([
'name' => $dto->name,
'email' => $dto->email,
'password' => $dto->password,
]);

return $user;
}

Các Validation Rules

Required & Nullable

#[Required]
public string $name;

#[Nullable]
public ?string $bio;

// Required với điều kiện
#[RequiredIf('type', 'business')]
public ?string $company_name;

Type Rules

#[TypeString]
public string $name;

#[TypeInteger]
public int $age;

#[TypeFloat]
public float $price;

#[TypeBoolean]
public bool $is_active;

#[TypeList] // Mảng tuần tự [1, 2, 3]
public array $tags;

#[TypeAssociativeArray] // Mảng kết hợp ['key' => 'value']
public array $metadata;

#[TypeEnum(UserStatus::class)]
public UserStatus $status;

String Rules

#[Alpha]          // Chỉ chữ cái
public string $code;

#[AlphaNum] // Chữ cái và số
public string $username;

#[AlphaDash] // Chữ cái, số, dấu gạch ngang, gạch dưới
public string $slug;

#[AlphaSpaces] // Chữ cái và khoảng trắng
public string $full_name;

#[Lowercase]
public string $code;

#[Uppercase]
public string $country_code;

#[StartWith('VN')]
public string $phone;

#[Regex('/^[A-Z]{2}\d{6}$/')]
public string $passport;

#[NotRegex('/[<>]/')]
public string $content;

Size Rules

#[Min(2)]
#[Max(100)]
public string $name; // String length

#[Min(0)]
#[Max(100)]
public int $quantity; // Numeric value

#[Min(1)]
#[Max(10)]
public array $items; // Array count

#[Size(10)] // Exactly 10
public string $phone;

// So sánh với field khác
#[Gt('min_price')] // Greater than
public float $max_price;

#[Gte('min_value')] // Greater than or equal
public int $value;

Format Rules

#[Email]
public string $email;

#[Url]
public string $website;

#[Ip]
public string $ip_address;

#[Ipv4]
public string $ipv4;

#[Ipv6]
public string $ipv6;

#[MacAddress]
public string $mac;

#[Phone] // Sử dụng libphonenumber
public string $phone;

#[Date]
public string $birthday;

#[Json]
public string $json_data;

#[CssColor]
public string $theme_color;

#[Password(min: 8, letters: true, numbers: true, symbols: true)]
public string $password;

In / Not In

#[In(['admin', 'user', 'guest'])]
public string $role;

#[NotIn(['banned', 'deleted'])]
public string $status;

File Rules

#[File]
public UploadedFile $document;

#[Image] // jpeg, png, gif, webp, svg
public UploadedFile $avatar;

#[UploadedFile(maxSize: 5120)] // Max 5MB
public UploadedFile $file;

#[MimeType(['image/jpeg', 'image/png'])]
public UploadedFile $photo;

#[Extension(['pdf', 'doc', 'docx'])]
public UploadedFile $document;

Database Rules

// Kiểm tra tồn tại trong database
#[Exists('users', 'id')]
public int $user_id;

// Kiểm tra unique
#[Unique('users', 'email')]
public string $email;

// Unique với ignore (cho update)
#[Unique('users', 'email', ignore: 'id')]
public string $email;

Other Rules

#[Accepted]  // true, 'yes', 'on', 1, '1'
public mixed $terms;

#[Distinct] // Không có giá trị trùng lặp trong array
public array $emails;

#[Before('end_date')]
public string $start_date;

#[BeforeOrEqual('2024-12-31')]
public string $deadline;

#[Equal('password')] // Phải bằng field password
public string $password_confirmation;

Bail - Dừng validation khi có lỗi

#[Bail]
#[Required]
#[Email]
#[Unique('users', 'email')]
public string $email;
// Nếu Required fail -> không chạy Email và Unique

Custom Validation

use Vietiso\Core\ValidatedDTO\Rules\Custom;

#[Custom(callback: 'validateSlug')]
public string $slug;

public function validateSlug(string $value): bool|string
{
if (str_contains($value, ' ')) {
return 'Slug cannot contain spaces';
}
return true;
}

Làm việc với DTO

Chuyển đổi dữ liệu

// Lấy tất cả data
$data = $dto->all();
$data = $dto->toArray();

// Lấy một số fields
$data = $dto->only(['name', 'email']);

// Loại trừ một số fields
$data = $dto->except(['password']);

// Chuyển sang JSON
$json = $dto->toJson();

Tạo DTO từ nhiều nguồn

// Từ array
$dto = CreateUserDTO::fromArray([
'name' => 'John',
'email' => 'john@example.com',
'password' => 'secret123',
]);

// Từ JSON
$dto = CreateUserDTO::fromJson('{"name": "John", ...}');

Xử lý lỗi Validation

Khi validation fail, ValidationException sẽ được throw tự động. Response mặc định:

{
"message": "The given data was invalid.",
"errors": {
"email": ["The email field is required."],
"password": ["The password must be at least 8 characters."]
}
}

Custom Error Handling

Override phương thức failedValidation trong DTO:

<?php

namespace App\DTOs;

use Vietiso\Core\ValidatedDTO\ValidatedDTO;
use Vietiso\Core\ValidatedDTO\ValidationException;
use Vietiso\Core\Http\Response;
use Vietiso\Core\Http\Request;

class CreateUserDTO extends ValidatedDTO
{
// ...properties

public static function failedValidation(Request $request, ValidationException $th): Response
{
return Response::json([
'success' => false,
'message' => 'Validation failed',
'errors' => $th->getErrors()
])->setStatusCode(422);
}
}

Nested DTO

<?php

namespace App\DTOs;

use Vietiso\Core\ValidatedDTO\ValidatedDTO;
use Vietiso\Core\ValidatedDTO\Rules\Required;

class AddressDTO extends ValidatedDTO
{
#[Required]
public string $street;

#[Required]
public string $city;

public ?string $zip;
}

class CreateOrderDTO extends ValidatedDTO
{
#[Required]
public int $user_id;

#[Required]
public AddressDTO $shipping_address;

#[Required]
public AddressDTO $billing_address;
}