wendelladriel/laravel-validated-dto
Build typed Data Transfer Objects for Laravel that validate incoming data using familiar validation rules, defaults, and casting. Create DTOs by extending ValidatedDTO, define rules(), and get safe, validated, ready-to-use properties for your app.
Installation:
composer require wendelladriel/laravel-validated-dto
Create your first DTO:
php artisan make:dto User
This generates a stub with ValidatedDTO base class, rules(), defaults(), and casts() methods.
Define validation rules (e.g., in app/Dtos/UserDTO.php):
protected function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email'],
];
}
Instantiate and use:
$userDTO = UserDTO::fromRequest(); // Automatically validates request data
$userDTO->name; // Access validated data
Replace manual validation in controllers with DTOs:
// Before (Controller)
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string',
'email' => 'required|email',
]);
// ...
}
// After (Controller)
public function store(UserDTO $dto) // Automatically injects and validates
{
// $dto->name and $dto->email are guaranteed to be valid
}
Workflow:
fromRequest() to bind to incoming requests (web/API).Example:
// Controller
public function update(UserDTO $dto)
{
$user = User::find($dto->id);
$user->update($dto->toArray());
}
Pattern:
Receive attribute to nest DTOs (e.g., AddressDTO inside UserDTO).Example:
use WendellAdriel\ValidatedDTO\Attributes\Receive;
final class UserDTO extends ValidatedDTO
{
#[Receive]
public AddressDTO $address;
// ...
}
Use Case:
Implementation:
$dto = UserDTO::fromArray($data, lazy: true);
$dto->name; // Validates on first access
Pattern:
toArray() or toJson() for output.Example:
// Service
public function process(UserDTO $dto)
{
$this->userRepository->create($dto->toArray());
}
Extend Default Casts:
use WendellAdriel\ValidatedDTO\Casting\Cast;
final class UserDTO extends ValidatedDTO
{
#[Cast('WendellAdriel\ValidatedDTO\Casting\DateTimeCast')]
public Carbon $createdAt;
}
Pattern:
fromArray() with command arguments.Example:
// Command
protected $signature = 'user:create {--name= : User name} {--email= : User email}';
public function handle()
{
$dto = UserDTO::fromArray([
'name' => $this->option('name'),
'email' => $this->option('email'),
]);
// ...
}
Bidirectional Transformation:
// Convert Model to DTO
$userDTO = UserDTO::fromModel($user);
// Convert DTO to Model
$user->fill($dto->toArray());
Pattern:
Example:
$response = $this->post('/users', $data);
$response->assertValid();
$dto = UserDTO::fromJson($response->content());
$this->assertEquals('John', $dto->name);
Validation Timing:
lazy: true to defer validation.$dto = UserDTO::fromArray($data, lazy: true);Circular References:
UserDTO containing AddressDTO which contains UserDTO).#[SkipOnTransform] to exclude properties during serialization.Default Values Override:
defaults() are applied after validation. Use sometimes rules for optional fields.protected function rules(): array
{
return ['active' => ['sometimes', 'boolean']];
}
protected function defaults(): array
{
return ['active' => true]; // Only applied if 'active' is not in input
}
Mass Assignment:
toArray() explicitly:
$user->fill($dto->toArray()); // Correct
$user->fill($dto); // Fails (DTO is not an array)
Custom Casts:
Casting\Cast interface.cast() method.
public function cast($value): ?string
{
return $value ? 'yes' : 'no'; // Must return casted value
}
Nested DTO Validation:
#[Receive] for top-level nesting.rules() defined.Type Safety:
#[Assert\Type] for runtime type validation (e.g., #[Assert\Type('array')]).Performance:
Validation Errors:
$dto->errors() or $dto->failed().if ($dto->failed()) {
return response()->json($dto->errors(), 422);
}
Data Inspection:
$dto->getData() to inspect raw input before validation.$dto->toArray() to see validated output.Stub Customization:
php artisan vendor:publish --tag="validated-dto-stubs"
stubs/dto.stub for project-specific defaults.Lazy Validation:
if ($dto->isLazy()) {
$dto->validate(); // Force validation
}
Custom Transformers:
WendellAdriel\ValidatedDTO\Transformers\DataTransformer to modify output.transform() method in your DTO:
protected function transform(array $data): array
{
$data['full_name'] = "{$data['first_name']} {$data['last_name']}";
return $data;
}
Custom Validation Hooks:
afterValidation() for post-validation logic:
protected function afterValidation(): void
{
$this->name = strtoupper($this->name);
}
Custom Casts:
app/Casts/:
namespace App\Casts;
use WendellAdriel\ValidatedDTO\Casting\Cast;
class CustomCast implements Cast
{
public function cast($value): string
{
return strtoupper($value);
}
}
#[Cast('App\Casts\CustomCast')]
public string $name;
DTO Events:
dto.validated and dto.failed events:
event(new ValidatedDTOEvent($dto));
Published Config:
config/validated-dto.php).strict_mode: Throw exceptions on validation failure (default: false).casts: Global cast mappings.Strict Mode:
'strict_mode
How can I help you explore Laravel packages today?