# 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
['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
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
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
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
'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
['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
['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
['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
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
'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
['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
'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
'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
['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
['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
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)
//
//
//
//
//
```
## Eloquent Model Casting with DTOs
Use DTOs as Eloquent model attribute casts to automatically transform JSON columns to/from DTOs.
```php
['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
'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.