/laravel-agent:dto:make
Create type-safe Data Transfer Objects using spatie/laravel-data
Overview
The /dto:make command generates type-safe Data Transfer Objects using the spatie/laravel-data package. DTOs provide a clean, validated way to transfer data between application layers, combining request validation, API responses, and data transformation in a single, reusable class.
Key features:
- Type safety - Full PHP 8.2+ type hints with strict validation
- Attribute-based validation - Validation rules defined directly on properties
- Automatic casting - Convert between arrays, models, requests, and DTOs
- Lazy loading - Include expensive relationships only when needed
- API resources - Use DTOs as API response transformers
Usage
/laravel-agent:dto:make <DTOName> [--from=request|model|array] [--resource]
Examples
# Create a basic DTO for user creation
/laravel-agent:dto:make CreateUserData
# Create a DTO from an existing model as an API resource
/laravel-agent:dto:make ProductData --from=model --resource
# Create a DTO that integrates with request validation
/laravel-agent:dto:make OrderData --from=request
# Interactive mode (will prompt for configuration)
/laravel-agent:dto:make
What Gets Created
The command generates a complete DTO class in the app/Data/ directory with:
| Component | Description |
|---|---|
| Constructor Properties | Type-hinted properties with optional defaults |
| Validation Attributes | Built-in validation rules as PHP attributes |
| Custom Validation | Static rules() and messages() methods |
| Casting Rules | Automatic type casting for dates, enums, nested DTOs |
| Transformation Methods | fromModel() and custom factory methods |
Generated Code Examples
Basic DTO
A simple DTO for user creation with type-safe properties:
<?php
declare(strict_types=1);
namespace App\Data;
use Spatie\LaravelData\Data;
final class CreateUserData extends Data
{
public function __construct(
public string $name,
public string $email,
public string $password,
public ?string $phone = null,
) {}
}
DTO with Validation Attributes
Enhanced DTO with built-in validation using PHP attributes:
<?php
declare(strict_types=1);
namespace App\Data;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\Validation\Email;
use Spatie\LaravelData\Attributes\Validation\Max;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Attributes\Validation\Required;
use Spatie\LaravelData\Attributes\Validation\Unique;
final class CreateUserData extends Data
{
public function __construct(
#[Required, Max(255)]
public string $name,
#[Required, Email, Unique('users', 'email')]
public string $email,
#[Required, Min(8)]
public string $password,
#[Max(20)]
public ?string $phone = null,
) {}
public static function rules(): array
{
return [
'password' => ['confirmed'],
];
}
public static function messages(): array
{
return [
'email.unique' => 'This email is already registered.',
];
}
}
DTO from Model (API Resource)
Transform Eloquent models into API responses with lazy-loaded relationships:
<?php
declare(strict_types=1);
namespace App\Data;
use App\Models\Product;
use Carbon\Carbon;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Lazy;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Attributes\WithCast;
use Spatie\LaravelData\Casts\DateTimeInterfaceCast;
final class ProductData extends Data
{
public function __construct(
public int $id,
public string $name,
public string $slug,
public ?string $description,
#[MapOutputName('price')]
public int $price_cents,
public string $formatted_price,
#[WithCast(DateTimeInterfaceCast::class, format: 'Y-m-d')]
public Carbon $created_at,
// Lazy loaded relationships
public Lazy|CategoryData $category,
/** @var ProductImageData[] */
public Lazy|array $images,
) {}
public static function fromModel(Product $product): self
{
return new self(
id: $product->id,
name: $product->name,
slug: $product->slug,
description: $product->description,
price_cents: $product->price_cents,
formatted_price: $product->formatted_price,
created_at: $product->created_at,
category: Lazy::whenLoaded('category', $product, fn () =>
CategoryData::from($product->category)
),
images: Lazy::whenLoaded('images', $product, fn () =>
ProductImageData::collect($product->images)
),
);
}
public function withFullIncludes(): self
{
return $this->include('category', 'images');
}
}
Nested DTOs
Complex data structures with nested DTOs and enums:
<?php
declare(strict_types=1);
namespace App\Data;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\DataCollectionOf;
final class OrderData extends Data
{
public function __construct(
public int $id,
public string $number,
public OrderStatus $status,
public CustomerData $customer,
public AddressData $shipping_address,
public AddressData $billing_address,
#[DataCollectionOf(OrderItemData::class)]
public array $items,
public MoneyData $subtotal,
public MoneyData $tax,
public MoneyData $total,
) {}
}
final class MoneyData extends Data
{
public function __construct(
public int $amount_cents,
public string $currency,
public string $formatted,
) {}
public static function fromCents(int $cents, string $currency = 'USD'): self
{
return new self(
amount_cents: $cents,
currency: $currency,
formatted: number_format($cents / 100, 2) . ' ' . $currency,
);
}
}
DTO with Enum
Leverage PHP 8.1+ enums for type-safe value objects:
<?php
declare(strict_types=1);
namespace App\Data;
use App\Enums\OrderStatus;
use Spatie\LaravelData\Data;
final class UpdateOrderData extends Data
{
public function __construct(
public OrderStatus $status,
public ?string $notes = null,
public ?string $tracking_number = null,
) {}
}
Usage Patterns
In Controllers (Request Validation)
Use DTOs directly in controller method signatures for automatic validation:
public function store(CreateUserData $data): JsonResponse
{
// $data is already validated
$user = User::create([
'name' => $data->name,
'email' => $data->email,
'password' => Hash::make($data->password),
'phone' => $data->phone,
]);
return response()->json(UserData::from($user), 201);
}
As API Resources
Transform Eloquent models and collections into JSON responses:
public function index(): JsonResponse
{
$products = Product::with(['category', 'images'])->paginate();
return ProductData::collect($products)->toResponse(request());
}
public function show(Product $product): ProductData
{
return ProductData::from($product->load(['category', 'images']))
->include('category', 'images');
}
Manual Creation
Create and validate DTOs from arrays or requests:
// From array
$data = CreateUserData::from([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'secret123',
]);
// From request
$data = CreateUserData::from($request);
// Validate and create
$data = CreateUserData::validateAndCreate($request->all());
Options
Customize DTO generation with these flags:
- --from=request - Generate DTO optimized for request validation
- --from=model - Generate DTO with fromModel() method for API resources
- --from=array - Basic DTO for internal data transfer
- --resource - Add API resource transformation methods
Interactive Prompts
When run without arguments, the command guides you through configuration:
- DTO name - Name of the DTO class (e.g., CreateUserData)
- DTO purpose
- Request validation (form input)
- API response (resource)
- Internal data transfer
- All of the above
- Properties - Interactive builder for each property:
- Name (text input)
- Type (string, int, float, bool, array, Carbon, Enum, DTO)
- Nullable (yes/no)
- Validation (required, email, max, min, unique, etc.)
- Include validation rules - Add validation attributes
- From existing model - Generate from model structure
Best Practices
- Naming convention - Suffix DTOs with "Data" (CreateUserData, ProductData)
- Single responsibility - Create separate DTOs for different operations (Create vs Update)
- Type everything - Use strict types and declare(strict_types=1)
- Immutability - Mark DTO classes as final and use readonly where possible
- Validation - Use attributes for simple rules, static methods for complex validation
- Lazy loading - Use Lazy for expensive relationships in API resources
- Documentation - Add PHPDoc for array collections and complex types
- Testing - Test DTO validation, casting, and transformation logic
Common Validation Attributes
| Attribute | Example | Description |
|---|---|---|
| Required | #[Required] |
Field must be present |
#[Email] |
Must be valid email format | |
| Min | #[Min(8)] |
Minimum length or value |
| Max | #[Max(255)] |
Maximum length or value |
| Unique | #[Unique('users', 'email')] |
Must be unique in database |
| Between | #[Between(1, 100)] |
Value must be within range |
| In | #[In(['draft', 'published'])] |
Must be one of specified values |
| Url | #[Url] |
Must be valid URL |
Package Installation
The command automatically installs spatie/laravel-data if not present:
composer require spatie/laravel-data
See Also
- /laravel-agent:api:make - Create versioned API resources
- /laravel-agent:validation:make - Generate form request validation
- /laravel-agent:resource:make - Create API resources
- Spatie Laravel Data Documentation - Official package docs