Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Laravel Validated DTO
https://github.com/wendelladriel/laravel-validated-dto
Admin
A Laravel package that provides validated Data Transfer Objects (DTOs) for transferring and
...
Tokens:
11,667
Snippets:
58
Trust Score:
10
Update:
4 weeks ago
Context
Skills
Chat
Benchmark
94.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Laravel Validated DTO Laravel Validated DTO is a powerful package that provides Data Transfer Objects with built-in validation for Laravel applications. DTOs are objects used to transfer data between systems, providing a consistent format for data exchange between different parts of an application such as controllers, services, CLI commands, and APIs. This package solves the problem of validation duplication by allowing you to define validation rules once and reuse them across your entire application. The core functionality centers around three base classes: `ValidatedDTO` for DTOs with validation rules, `SimpleDTO` for DTOs without validation (casting only), and `ResourceDTO` for API response transformations. The package supports PHP 8 attributes for a cleaner syntax, multiple data source resolvers (Request, JSON, Array, Model, Command), type casting with built-in casters, property mapping for input/output transformations, and Livewire integration through the Wireable trait. ## Installation Install the package via Composer. ```bash composer require wendelladriel/laravel-validated-dto ``` ## Creating DTOs with Artisan Command The `make:dto` command generates new DTO classes with three variants: standard ValidatedDTO, SimpleDTO (without validation), and ResourceDTO (for API responses). ```bash # Create a ValidatedDTO (with validation rules) php artisan make:dto UserDTO # Create a SimpleDTO (without validation) php artisan make:dto UserDTO --simple # Create a ResourceDTO (for API responses) php artisan make:dto UserResourceDTO --resource # Force overwrite existing DTO php artisan make:dto UserDTO --force ``` ## ValidatedDTO - Creating DTOs with Validation ValidatedDTO requires implementing three abstract methods: `rules()` for Laravel validation rules, `defaults()` for default values, and `casts()` for type casting. Validation is performed automatically on instantiation. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Casting\IntegerCast; use WendellAdriel\ValidatedDTO\Casting\StringCast; class CreateUserDTO extends ValidatedDTO { public string $name; public string $email; public ?int $age; public bool $active; protected function rules(): array { return [ 'name' => ['required', 'string', 'min:3', 'max:255'], 'email' => ['required', 'email', 'max:255'], 'age' => ['nullable', 'integer', 'min:0'], 'active' => ['sometimes', 'boolean'], ]; } protected function defaults(): array { return [ 'active' => true, ]; } protected function casts(): array { return [ 'name' => new StringCast(), 'email' => new StringCast(), 'age' => new IntegerCast(), ]; } // Optional: Custom validation error messages public function messages(): array { return [ 'email.email' => 'Please provide a valid email address.', ]; } // Optional: Custom attribute names for error messages public function attributes(): array { return [ 'email' => 'email address', ]; } } // Usage - ValidationException thrown if validation fails try { $dto = new CreateUserDTO([ 'name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30, ]); echo $dto->name; // "John Doe" echo $dto->email; // "john@example.com" echo $dto->age; // 30 echo $dto->active; // true (default value) } catch (\Illuminate\Validation\ValidationException $e) { $errors = $e->errors(); // Handle validation errors } ``` ## SimpleDTO - DTOs Without Validation SimpleDTO provides data casting and transformation without validation rules. Useful when you trust the data source or want simpler DTOs. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\SimpleDTO; use WendellAdriel\ValidatedDTO\Casting\IntegerCast; use WendellAdriel\ValidatedDTO\Casting\StringCast; use WendellAdriel\ValidatedDTO\Casting\CollectionCast; use WendellAdriel\ValidatedDTO\Casting\DTOCast; use Illuminate\Support\Collection; class ProfileDTO extends SimpleDTO { public string $username; public ?int $followers; public ?Collection $posts; protected function defaults(): array { return [ 'followers' => 0, ]; } protected function casts(): array { return [ 'username' => new StringCast(), 'followers' => new IntegerCast(), 'posts' => new CollectionCast(new DTOCast(PostDTO::class)), ]; } } // Usage - No validation, just casting $profile = new ProfileDTO([ 'username' => 'johndoe', 'followers' => '1500', // Will be cast to integer 'posts' => [ ['title' => 'First Post', 'content' => 'Hello World'], ['title' => 'Second Post', 'content' => 'Another post'], ], ]); echo $profile->username; // "johndoe" echo $profile->followers; // 1500 (integer) echo $profile->posts->count(); // 2 echo $profile->posts->first()->title; // "First Post" ``` ## ResourceDTO - API Response DTOs ResourceDTO extends SimpleDTO and implements Laravel's `Responsable` interface, making it perfect for API responses. It can be returned directly from controllers and supports collections. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ResourceDTO; use WendellAdriel\ValidatedDTO\Casting\IntegerCast; use WendellAdriel\ValidatedDTO\Casting\StringCast; class UserResourceDTO extends ResourceDTO { public string $name; public string $email; public int $age; protected function defaults(): array { return []; } protected function casts(): array { return [ 'name' => new StringCast(), 'email' => new StringCast(), 'age' => new IntegerCast(), ]; } } // In a Controller - Single resource class UserController extends Controller { public function show(User $user) { // Returns JSON response automatically return new UserResourceDTO([ 'name' => $user->name, 'email' => $user->email, 'age' => $user->age, ]); // Response: {"name":"John Doe","email":"john@example.com","age":30} } // With custom status and headers public function created(Request $request) { $user = User::create($request->validated()); return new UserResourceDTO( ['name' => $user->name, 'email' => $user->email, 'age' => $user->age], 201, // HTTP status code ['X-Custom-Header' => 'value'] // Custom headers ); } // Collection of resources public function index() { $users = User::all()->map(fn($u) => [ 'name' => $u->name, 'email' => $u->email, 'age' => $u->age, ])->toArray(); return UserResourceDTO::collection($users); // Response: [{"name":"John","email":"john@example.com","age":30},...] } } ``` ## Data Source Resolvers - fromRequest, fromArray, fromJson, fromModel, fromCommand DTOs provide multiple static factory methods to create instances from different data sources. All factory methods are available on ValidatedDTO, SimpleDTO, and ResourceDTO. ```php <?php use App\DTOs\CreateUserDTO; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Console\Command; // From HTTP Request class UserController extends Controller { public function store(Request $request) { $dto = CreateUserDTO::fromRequest($request); // Creates DTO from $request->all() $user = User::create($dto->toArray()); return response()->json($user); } } // From Array $dto = CreateUserDTO::fromArray([ 'name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30, ]); // From JSON string $json = '{"name":"Jane Doe","email":"jane@example.com","age":25}'; $dto = CreateUserDTO::fromJson($json); // From Eloquent Model $user = User::find(1); $dto = CreateUserDTO::fromModel($user); // Creates DTO from $user->toArray() // From Artisan Command (arguments and options) class CreateUserCommand extends Command { protected $signature = 'user:create {name} {email} {--age=}'; public function handle() { // From command arguments only $dto = CreateUserDTO::fromCommandArguments($this); // From command options only $dto = CreateUserDTO::fromCommandOptions($this); // From both arguments and options (merged) $dto = CreateUserDTO::fromCommand($this); User::create($dto->toArray()); $this->info("User {$dto->name} created!"); } } ``` ## Data Output Transformers - toArray, toJson, toModel DTOs provide methods to transform data for output to various formats. The transformation respects property mappings defined in `mapToTransform()`. ```php <?php use App\DTOs\CreateUserDTO; use App\Models\User; $dto = CreateUserDTO::fromArray([ 'name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30, 'active' => true, ]); // Convert to array $array = $dto->toArray(); // ['name' => 'John Doe', 'email' => 'john@example.com', 'age' => 30, 'active' => true] // Convert to JSON $json = $dto->toJson(); // '{"name":"John Doe","email":"john@example.com","age":30,"active":true}' // Convert to pretty JSON $prettyJson = $dto->toPrettyJson(); // { // "name": "John Doe", // "email": "john@example.com", // "age": 30, // "active": true // } // Convert to Eloquent Model instance $user = $dto->toModel(User::class); // Returns new User instance (not persisted) with DTO data $user->save(); // Now persisted ``` ## Type Casting - Built-in Casters The package provides built-in casters for common types. All casters implement the `Castable` interface and are used in the `casts()` method. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Casting\ArrayCast; use WendellAdriel\ValidatedDTO\Casting\BooleanCast; use WendellAdriel\ValidatedDTO\Casting\CarbonCast; use WendellAdriel\ValidatedDTO\Casting\CarbonImmutableCast; use WendellAdriel\ValidatedDTO\Casting\CollectionCast; use WendellAdriel\ValidatedDTO\Casting\DTOCast; use WendellAdriel\ValidatedDTO\Casting\EnumCast; use WendellAdriel\ValidatedDTO\Casting\FloatCast; use WendellAdriel\ValidatedDTO\Casting\IntegerCast; use WendellAdriel\ValidatedDTO\Casting\ModelCast; use WendellAdriel\ValidatedDTO\Casting\ObjectCast; use WendellAdriel\ValidatedDTO\Casting\StringCast; use Carbon\Carbon; use Carbon\CarbonImmutable; use Illuminate\Support\Collection; use App\Enums\UserStatus; use App\Models\User; class FullExampleDTO extends ValidatedDTO { public string $name; public int $count; public float $price; public bool $isActive; public array $tags; public object $metadata; public Collection $items; public Carbon $publishedAt; public CarbonImmutable $createdAt; public UserStatus $status; public AddressDTO $address; public User $author; protected function rules(): array { return [ 'name' => ['required', 'string'], 'count' => ['required'], 'price' => ['required'], 'isActive' => ['required'], 'tags' => ['required', 'array'], 'metadata' => ['required'], 'items' => ['required'], 'publishedAt' => ['required'], 'createdAt' => ['required'], 'status' => ['required'], 'address' => ['required'], 'author' => ['required'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return [ // Basic type casts 'name' => new StringCast(), 'count' => new IntegerCast(), 'price' => new FloatCast(), 'isActive' => new BooleanCast(), // Array cast (JSON string -> array, or keeps array) 'tags' => new ArrayCast(), // Object cast (array -> stdClass object) 'metadata' => new ObjectCast(), // Collection cast (array -> Laravel Collection) 'items' => new CollectionCast(), // Carbon cast with optional timezone and format 'publishedAt' => new CarbonCast('UTC'), 'createdAt' => new CarbonImmutableCast('America/New_York', 'Y-m-d H:i:s'), // Enum cast (string/int -> PHP Enum) 'status' => new EnumCast(UserStatus::class), // DTO cast (array -> nested DTO) 'address' => new DTOCast(AddressDTO::class), // Model cast (array -> Eloquent Model) 'author' => new ModelCast(User::class), ]; } } // Usage $dto = new FullExampleDTO([ 'name' => 'Product', 'count' => '100', // Cast to int: 100 'price' => '29.99', // Cast to float: 29.99 'isActive' => 'true', // Cast to bool: true 'tags' => '["php","laravel"]', // Cast from JSON string to array 'metadata' => ['key' => 'value'], // Cast to object 'items' => [1, 2, 3], // Cast to Collection 'publishedAt' => '2024-01-15', // Cast to Carbon 'createdAt' => '2024-01-15 10:30:00', // Cast to CarbonImmutable 'status' => 'active', // Cast to UserStatus enum 'address' => ['street' => '123 Main St', 'city' => 'NYC'], 'author' => ['id' => 1, 'name' => 'Author'], ]); ``` ## Nested DTOs and Collections Cast nested objects to DTOs and collections of DTOs using `DTOCast`, `ArrayCast`, and `CollectionCast` with nested casters. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Casting\ArrayCast; use WendellAdriel\ValidatedDTO\Casting\CollectionCast; use WendellAdriel\ValidatedDTO\Casting\DTOCast; use Illuminate\Support\Collection; class AddressDTO extends ValidatedDTO { public string $street; public string $city; public string $country; protected function rules(): array { return [ 'street' => ['required', 'string'], 'city' => ['required', 'string'], 'country' => ['required', 'string'], ]; } protected function defaults(): array { return ['country' => 'USA']; } protected function casts(): array { return []; } } class OrderItemDTO extends ValidatedDTO { public string $product; public int $quantity; public float $price; protected function rules(): array { return [ 'product' => ['required', 'string'], 'quantity' => ['required', 'integer', 'min:1'], 'price' => ['required', 'numeric', 'min:0'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return []; } } class OrderDTO extends ValidatedDTO { public string $orderNumber; public AddressDTO $shippingAddress; public AddressDTO $billingAddress; public array $itemsArray; public Collection $itemsCollection; protected function rules(): array { return [ 'orderNumber' => ['required', 'string'], 'shippingAddress' => ['required', 'array'], 'billingAddress' => ['required', 'array'], 'itemsArray' => ['required', 'array'], 'itemsCollection' => ['required', 'array'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return [ // Single nested DTO 'shippingAddress' => new DTOCast(AddressDTO::class), 'billingAddress' => new DTOCast(AddressDTO::class), // Array of DTOs 'itemsArray' => new ArrayCast(new DTOCast(OrderItemDTO::class)), // Collection of DTOs 'itemsCollection' => new CollectionCast(new DTOCast(OrderItemDTO::class)), ]; } } // Usage $order = new OrderDTO([ 'orderNumber' => 'ORD-001', 'shippingAddress' => [ 'street' => '123 Main St', 'city' => 'New York', 'country' => 'USA', ], 'billingAddress' => [ 'street' => '456 Oak Ave', 'city' => 'Boston', 'country' => 'USA', ], 'itemsArray' => [ ['product' => 'Widget', 'quantity' => 2, 'price' => 19.99], ['product' => 'Gadget', 'quantity' => 1, 'price' => 49.99], ], 'itemsCollection' => [ ['product' => 'Widget', 'quantity' => 2, 'price' => 19.99], ['product' => 'Gadget', 'quantity' => 1, 'price' => 49.99], ], ]); // Access nested DTOs echo $order->shippingAddress->city; // "New York" echo $order->itemsArray[0]->product; // "Widget" echo $order->itemsCollection->first()->quantity; // 2 ``` ## Custom Callable Casters You can use callable functions for custom casting logic instead of Castable classes. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; class ProductDTO extends ValidatedDTO { public string $name; public string $sku; public float $price; protected function rules(): array { return [ 'name' => ['required', 'string'], 'sku' => ['required', 'string'], 'price' => ['required', 'numeric'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return [ // Callable cast - receives property name and value 'name' => fn(string $property, mixed $value) => ucwords(strtolower($value)), // Transform SKU to uppercase 'sku' => fn(string $property, mixed $value) => strtoupper($value), // Round price to 2 decimals 'price' => fn(string $property, mixed $value) => round((float) $value, 2), ]; } } // Usage $dto = new ProductDTO([ 'name' => 'WIRELESS MOUSE', // Cast to: "Wireless Mouse" 'sku' => 'wm-001', // Cast to: "WM-001" 'price' => '29.999', // Cast to: 29.99 ]); ``` ## Creating Custom Casters Implement the `Castable` interface to create reusable custom casters with complex logic. ```php <?php namespace App\Casters; use WendellAdriel\ValidatedDTO\Casting\Castable; use WendellAdriel\ValidatedDTO\Exceptions\CastException; use Brick\Money\Money; class MoneyCast implements Castable { public function __construct( private string $currency = 'USD' ) {} public function cast(string $property, mixed $value): Money { try { if ($value instanceof Money) { return $value; } if (is_numeric($value)) { return Money::of($value, $this->currency); } if (is_string($value) && preg_match('/^[\d.,]+$/', $value)) { $cleaned = str_replace(',', '', $value); return Money::of($cleaned, $this->currency); } throw new CastException($property); } catch (\Throwable $e) { throw new CastException($property); } } } // Usage in DTO namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use App\Casters\MoneyCast; use Brick\Money\Money; class InvoiceDTO extends ValidatedDTO { public string $invoiceNumber; public Money $amount; public Money $tax; public Money $total; protected function rules(): array { return [ 'invoiceNumber' => ['required', 'string'], 'amount' => ['required'], 'tax' => ['required'], 'total' => ['required'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return [ 'amount' => new MoneyCast('USD'), 'tax' => new MoneyCast('USD'), 'total' => new MoneyCast('EUR'), // Different currency ]; } } $invoice = new InvoiceDTO([ 'invoiceNumber' => 'INV-001', 'amount' => '1,234.56', 'tax' => 123.46, 'total' => '1,358.02', ]); echo $invoice->amount->getAmount(); // 1234.56 echo $invoice->total->getCurrency()->getCurrencyCode(); // EUR ``` ## PHP 8 Attributes - Rules, Cast, DefaultValue, Map Use PHP 8 attributes for a cleaner, more declarative syntax. Combine with `EmptyRules`, `EmptyCasts`, and `EmptyDefaults` traits. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Attributes\Cast; use WendellAdriel\ValidatedDTO\Attributes\DefaultValue; use WendellAdriel\ValidatedDTO\Attributes\Map; use WendellAdriel\ValidatedDTO\Attributes\Rules; use WendellAdriel\ValidatedDTO\Casting\ArrayCast; use WendellAdriel\ValidatedDTO\Casting\BooleanCast; use WendellAdriel\ValidatedDTO\Casting\FloatCast; use WendellAdriel\ValidatedDTO\Casting\IntegerCast; use WendellAdriel\ValidatedDTO\Casting\StringCast; use WendellAdriel\ValidatedDTO\Concerns\EmptyCasts; use WendellAdriel\ValidatedDTO\Concerns\EmptyDefaults; use WendellAdriel\ValidatedDTO\Concerns\EmptyRules; class ProductDTO extends ValidatedDTO { // Use traits to avoid implementing empty methods use EmptyCasts, EmptyDefaults, EmptyRules; // Validation rules via attribute #[Rules(['required', 'string', 'min:3', 'max:255'])] public string $name; // Rules with custom error messages #[Rules( rules: ['required', 'email', 'max:255'], messages: ['email.email' => 'Invalid email format'] )] public string $contactEmail; // Default value via attribute #[Rules(['sometimes', 'boolean'])] #[DefaultValue(true)] #[Cast(BooleanCast::class)] public bool $isActive; // Type casting via attribute #[Rules(['sometimes', 'integer', 'min:0'])] #[Cast(IntegerCast::class)] public ?int $stock; // Casting with parameters (ArrayCast with FloatCast for items) #[Rules(['sometimes', 'array'])] #[Cast(type: ArrayCast::class, param: FloatCast::class)] public ?array $prices; // Property mapping - transform input/output names #[Rules(['required', 'string'])] #[Map(data: 'product_code', transform: 'sku')] public string $code; } // Usage $dto = new ProductDTO([ 'name' => 'Wireless Mouse', 'contactEmail' => 'support@example.com', 'isActive' => 'yes', // Cast to true 'stock' => '50', // Cast to 50 'prices' => ['19.99', '24.99'], // Cast to [19.99, 24.99] 'product_code' => 'WM-001', // Mapped from 'product_code' to 'code' ]); echo $dto->code; // "WM-001" $array = $dto->toArray(); // 'sku' => 'WM-001' (transformed on export via Map attribute) ``` ## Property Mapping - mapData and mapToTransform Map property names when receiving data and when transforming/exporting data. Useful for API integration with different naming conventions. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Casting\DTOCast; class NameDTO extends ValidatedDTO { public string $firstName; public string $lastName; protected function rules(): array { return [ 'firstName' => ['required', 'string'], 'lastName' => ['required', 'string'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return []; } } class UserDTO extends ValidatedDTO { public string $email; public NameDTO $name; protected function rules(): array { return [ 'email' => ['required', 'email'], 'name' => ['required', 'array'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return [ 'name' => new DTOCast(NameDTO::class), ]; } // Map incoming data property names to DTO properties protected function mapData(): array { return [ 'email_address' => 'email', // Map 'email_address' -> 'email' 'user_name' => 'name', // Map 'user_name' -> 'name' ]; } // Map DTO properties to export names protected function mapToTransform(): array { return [ 'email' => 'contact_email', // Export as 'contact_email' // Flatten nested DTO properties 'name.firstName' => 'first_name', // Export nested as flat keys 'name.lastName' => 'last_name', ]; } } // Input uses mapped names $dto = UserDTO::fromArray([ 'email_address' => 'john@example.com', // Mapped to 'email' 'user_name' => [ // Mapped to 'name' 'firstName' => 'John', 'lastName' => 'Doe', ], ]); echo $dto->email; // "john@example.com" echo $dto->name->firstName; // "John" // Output uses transformed names $output = $dto->toArray(); // [ // 'contact_email' => 'john@example.com', // 'first_name' => 'John', // 'last_name' => 'Doe', // ] ``` ## Receive and Provide Attributes - Automatic Case Conversion Use `#[Receive]` and `#[Provide]` class attributes to automatically convert property cases when receiving and providing data. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Attributes\Provide; use WendellAdriel\ValidatedDTO\Attributes\Receive; use WendellAdriel\ValidatedDTO\Attributes\Rules; use WendellAdriel\ValidatedDTO\Concerns\EmptyCasts; use WendellAdriel\ValidatedDTO\Concerns\EmptyDefaults; use WendellAdriel\ValidatedDTO\Concerns\EmptyRules; use WendellAdriel\ValidatedDTO\Enums\PropertyCase; // Receive snake_case input, provide PascalCase output #[Receive(PropertyCase::SnakeCase)] #[Provide(PropertyCase::PascalCase)] class ApiUserDTO extends ValidatedDTO { use EmptyCasts, EmptyDefaults, EmptyRules; #[Rules(['required', 'string'])] public string $firstName; #[Rules(['required', 'string'])] public string $lastName; #[Rules(['required', 'email'])] public string $emailAddress; #[Rules(['required', 'integer'])] public int $accountId; } // Input: snake_case (from external API) $dto = new ApiUserDTO([ 'first_name' => 'John', // Mapped to firstName 'last_name' => 'Doe', // Mapped to lastName 'email_address' => 'john@example.com', // Mapped to emailAddress 'account_id' => 123, // Mapped to accountId ]); echo $dto->firstName; // "John" echo $dto->emailAddress; // "john@example.com" // Output: PascalCase $output = $dto->toArray(); // [ // 'FirstName' => 'John', // 'LastName' => 'Doe', // 'EmailAddress' => 'john@example.com', // 'AccountId' => 123, // ] // Available PropertyCase options: // PropertyCase::SnakeCase - converts to/from snake_case // PropertyCase::PascalCase - converts to/from PascalCase ``` ## SkipOnTransform Attribute Exclude specific properties from the output transformation using the `#[SkipOnTransform]` attribute. Useful for sensitive data. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Attributes\Rules; use WendellAdriel\ValidatedDTO\Attributes\SkipOnTransform; use WendellAdriel\ValidatedDTO\Concerns\EmptyCasts; use WendellAdriel\ValidatedDTO\Concerns\EmptyDefaults; use WendellAdriel\ValidatedDTO\Concerns\EmptyRules; class UserCredentialsDTO extends ValidatedDTO { use EmptyCasts, EmptyDefaults, EmptyRules; #[Rules(['required', 'string'])] public string $username; #[Rules(['required', 'email'])] public string $email; // This property will NOT be included in toArray()/toJson() output #[Rules(['required', 'string', 'min:8'])] #[SkipOnTransform] public string $password; // Sensitive data excluded from exports #[Rules(['sometimes', 'string'])] #[SkipOnTransform] public ?string $apiSecret; } $dto = new UserCredentialsDTO([ 'username' => 'johndoe', 'email' => 'john@example.com', 'password' => 'secureP@ss123', 'apiSecret' => 'secret-key-123', ]); // Properties are accessible echo $dto->password; // "secureP@ss123" echo $dto->apiSecret; // "secret-key-123" // But excluded from exports $array = $dto->toArray(); // [ // 'username' => 'johndoe', // 'email' => 'john@example.com', // // 'password' and 'apiSecret' are NOT included // ] ``` ## Lazy Validation Enable lazy validation to defer casting until the `validate()` method is explicitly called. Useful for building DTOs incrementally. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Attributes\Lazy; use WendellAdriel\ValidatedDTO\Attributes\Rules; use WendellAdriel\ValidatedDTO\Casting\IntegerCast; use WendellAdriel\ValidatedDTO\Casting\StringCast; use WendellAdriel\ValidatedDTO\Concerns\EmptyCasts; use WendellAdriel\ValidatedDTO\Concerns\EmptyDefaults; use WendellAdriel\ValidatedDTO\Concerns\EmptyRules; // Method 1: Using #[Lazy] attribute #[Lazy] class LazyUserDTO extends ValidatedDTO { use EmptyCasts, EmptyDefaults, EmptyRules; #[Rules(['required', 'string'])] public string $name; #[Rules(['required', 'integer'])] public int $age; } // Method 2: Using property class LazyProductDTO extends ValidatedDTO { public bool $lazyValidation = true; public ?string $name; public ?int $quantity; protected function rules(): array { return [ 'name' => ['required', 'string'], 'quantity' => ['required', 'integer', 'min:0'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return [ 'name' => new StringCast(), 'quantity' => new IntegerCast(), ]; } } // Usage - No validation on construction $dto = new LazyProductDTO([ 'name' => 'Widget', 'quantity' => '100', ]); // Values stored but NOT cast yet echo $dto->quantity; // "100" (string, not cast) // Explicitly validate and cast $dto->validate(); echo $dto->quantity; // 100 (integer, now cast) // Validation errors are thrown on validate() call $invalidDto = new LazyProductDTO([ 'name' => '', // Invalid - required ]); try { $invalidDto->validate(); } catch (\Illuminate\Validation\ValidationException $e) { $errors = $e->errors(); // ['name' => ['The name field is required.']] } ``` ## Custom Validation with after() Hook Add custom validation logic that runs after standard validation using the `after()` method. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use Illuminate\Validation\Validator; class PasswordResetDTO extends ValidatedDTO { public string $password; public string $passwordConfirmation; public string $currentPassword; protected function rules(): array { return [ 'password' => ['required', 'string', 'min:8'], 'passwordConfirmation' => ['required', 'string'], 'currentPassword' => ['required', 'string'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return []; } // Custom validation logic protected function after(Validator $validator): void { // Check password confirmation matches if ($this->password !== $this->passwordConfirmation) { $validator->errors()->add( 'passwordConfirmation', 'The password confirmation does not match.' ); } // Ensure new password is different from current if ($this->password === $this->currentPassword) { $validator->errors()->add( 'password', 'The new password must be different from your current password.' ); } // Complex business rule validation if (!$this->isPasswordStrong($this->password)) { $validator->errors()->add( 'password', 'Password must contain uppercase, lowercase, number, and special character.' ); } } private function isPasswordStrong(string $password): bool { return preg_match('/[A-Z]/', $password) && preg_match('/[a-z]/', $password) && preg_match('/[0-9]/', $password) && preg_match('/[^A-Za-z0-9]/', $password); } } try { $dto = new PasswordResetDTO([ 'password' => 'NewP@ss123', 'passwordConfirmation' => 'NewP@ss123', 'currentPassword' => 'OldPass123', ]); } catch (\Illuminate\Validation\ValidationException $e) { $errors = $e->errors(); } ``` ## Livewire Integration with Wireable Trait Use the `Wireable` trait to make DTOs compatible with Livewire components for seamless data binding. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\SimpleDTO; use WendellAdriel\ValidatedDTO\Concerns\Wireable; use WendellAdriel\ValidatedDTO\Casting\IntegerCast; use WendellAdriel\ValidatedDTO\Casting\StringCast; use Livewire\Wireable as LivewireWireable; class ContactFormDTO extends SimpleDTO implements LivewireWireable { use Wireable; public ?string $name; public ?string $email; public ?string $message; public ?int $priority; protected function defaults(): array { return [ 'priority' => 1, ]; } protected function casts(): array { return [ 'name' => new StringCast(), 'email' => new StringCast(), 'message' => new StringCast(), 'priority' => new IntegerCast(), ]; } } // In a Livewire Component namespace App\Livewire; use Livewire\Component; use App\DTOs\ContactFormDTO; class ContactForm extends Component { public ContactFormDTO $form; public function mount() { $this->form = new ContactFormDTO([]); } public function submit() { // The DTO is automatically hydrated/dehydrated by Livewire $data = $this->form->toArray(); // Process the form data Contact::create($data); $this->reset('form'); } public function render() { return view('livewire.contact-form'); } } // In Blade template (livewire/contact-form.blade.php) // <input type="text" wire:model="form.name"> // <input type="email" wire:model="form.email"> // <textarea wire:model="form.message"></textarea> // <select wire:model="form.priority">...</select> // <button wire:click="submit">Send</button> ``` ## Eloquent Model Casting with DTOs Use DTOs as Eloquent model attribute casts to automatically transform JSON columns to/from DTOs. ```php <?php namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Casting\StringCast; use WendellAdriel\ValidatedDTO\Casting\FloatCast; class AddressDTO extends ValidatedDTO { public string $street; public string $city; public string $state; public string $zipCode; public string $country; protected function rules(): array { return [ 'street' => ['required', 'string'], 'city' => ['required', 'string'], 'state' => ['required', 'string'], 'zipCode' => ['required', 'string'], 'country' => ['required', 'string'], ]; } protected function defaults(): array { return ['country' => 'USA']; } protected function casts(): array { return []; } } class SettingsDTO extends ValidatedDTO { public string $theme; public string $language; public bool $notifications; protected function rules(): array { return [ 'theme' => ['required', 'string'], 'language' => ['required', 'string'], 'notifications' => ['required', 'boolean'], ]; } protected function defaults(): array { return [ 'theme' => 'light', 'language' => 'en', 'notifications' => true, ]; } protected function casts(): array { return []; } } // In Eloquent Model namespace App\Models; use Illuminate\Database\Eloquent\Model; use App\DTOs\AddressDTO; use App\DTOs\SettingsDTO; class User extends Model { protected $fillable = ['name', 'email', 'address', 'settings']; // Cast JSON columns to DTOs protected $casts = [ 'address' => AddressDTO::class, 'settings' => SettingsDTO::class, ]; } // Usage $user = User::create([ 'name' => 'John Doe', 'email' => 'john@example.com', 'address' => [ 'street' => '123 Main St', 'city' => 'New York', 'state' => 'NY', 'zipCode' => '10001', ], 'settings' => [ 'theme' => 'dark', 'language' => 'en', 'notifications' => false, ], ]); // Access as DTOs echo $user->address->city; // "New York" echo $user->settings->theme; // "dark" // Modify and save $user->address = new AddressDTO([ 'street' => '456 Oak Ave', 'city' => 'Boston', 'state' => 'MA', 'zipCode' => '02101', ]); $user->save(); // Retrieve from database - automatically cast back to DTOs $user = User::find(1); echo $user->address instanceof AddressDTO; // true ``` ## Configuration Publish and customize the package configuration for DTO namespace and required casting behavior. ```php <?php // Publish configuration // php artisan vendor:publish --tag=dto-config // config/dto.php return [ /* |-------------------------------------------------------------------------- | NAMESPACE |-------------------------------------------------------------------------- | | The namespace where your DTOs will be created by make:dto command. | */ 'namespace' => 'App\\DTOs', /* |-------------------------------------------------------------------------- | REQUIRE CASTING |-------------------------------------------------------------------------- | | If true, ALL DTO properties must have a cast type defined. | Missing cast types will throw MissingCastTypeException. | */ 'require_casting' => false, ]; // When require_casting is true: namespace App\DTOs; use WendellAdriel\ValidatedDTO\ValidatedDTO; use WendellAdriel\ValidatedDTO\Casting\StringCast; class StrictDTO extends ValidatedDTO { public string $name; public string $email; // If 'email' doesn't have a cast and require_casting is true, // MissingCastTypeException will be thrown protected function rules(): array { return [ 'name' => ['required'], 'email' => ['required'], ]; } protected function defaults(): array { return []; } protected function casts(): array { return [ 'name' => new StringCast(), // Missing 'email' cast will throw exception if require_casting is true ]; } } ``` ## Summary Laravel Validated DTO is ideal for applications that need consistent data validation across multiple entry points (web controllers, API endpoints, CLI commands, queue jobs). The primary use cases include API request validation where DTOs replace Form Requests for reusable validation, service layer inputs where DTOs ensure validated data passes between controllers and services, and API responses where ResourceDTO provides structured JSON responses. The package excels in domain-driven design by serving as value objects that represent domain concepts with built-in validation. Integration patterns typically involve creating DTOs from incoming requests in controllers, passing validated DTOs to service classes, and returning ResourceDTOs from API endpoints. For complex applications, nested DTOs model hierarchical data structures, while the attribute-based syntax provides a clean, declarative approach. The package integrates seamlessly with Laravel's ecosystem through Eloquent model casting, Livewire support via the Wireable trait, and full compatibility with Laravel's validation system including custom rules, messages, and the after() hook for complex validation scenarios.