Data

/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:

  1. DTO name - Name of the DTO class (e.g., CreateUserData)
  2. DTO purpose
    • Request validation (form input)
    • API response (resource)
    • Internal data transfer
    • All of the above
  3. 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.)
  4. Include validation rules - Add validation attributes
  5. From existing model - Generate from model structure

Best Practices

  1. Naming convention - Suffix DTOs with "Data" (CreateUserData, ProductData)
  2. Single responsibility - Create separate DTOs for different operations (Create vs Update)
  3. Type everything - Use strict types and declare(strict_types=1)
  4. Immutability - Mark DTO classes as final and use readonly where possible
  5. Validation - Use attributes for simple rules, static methods for complex validation
  6. Lazy loading - Use Lazy for expensive relationships in API resources
  7. Documentation - Add PHPDoc for array collections and complex types
  8. Testing - Test DTO validation, casting, and transformation logic

Common Validation Attributes

Attribute Example Description
Required #[Required] Field must be present
Email #[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