DTO Generation
Introduction
DTOs (Data Transfer Objects) are classes used to transfer data between different layers of your application. The new version of the generator uses modern PHP 8.1+ features to create cleaner and safer DTOs.
New Features
1. Constructor Property Promotion
Instead of separate property and constructor definitions:
php
// Old ❌
class ProductDTO
{
public mixed $name;
public mixed $price;
public function __construct(mixed $name = null, mixed $price = null)
{
$this->name = $name;
$this->price = $price;
}
}
Now automatically:
php
// New ✅
class ProductDTO
{
public function __construct(
public readonly mixed $name = null,
public readonly mixed $price = null,
public readonly mixed $description = null
) {}
}
2. Readonly Properties
All properties are readonly, meaning they cannot be changed after initial assignment:
php
$dto = new ProductDTO(name: 'Laptop', price: 1500);
$dto->name = 'Phone'; // ❌ Error: Cannot modify readonly property
3. Named Arguments
Using named arguments for better readability:
php
$dto = new ProductDTO(
name: 'Laptop',
price: 1500,
description: 'A powerful laptop'
);
Usage
Automatic Generation
bash
php artisan make:module Product --api
DTO is automatically created:
php
<?php
namespace App\DTOs;
use Illuminate\Http\Request;
class ProductDTO
{
public function __construct(
public readonly mixed $name = null,
public readonly mixed $price = null,
public readonly mixed $description = null
) {}
public static function fromRequest(Request $request): self
{
return new self(
name: $request->input('name'),
price: $request->input('price'),
description: $request->input('description'),
);
}
public function toArray(): array
{
$out = [];
if ($this->name !== null) { $out['name'] = $this->name; }
if ($this->price !== null) { $out['price'] = $this->price; }
if ($this->description !== null) { $out['description'] = $this->description; }
return $out;
}
}
Using in Controllers
php
use App\DTOs\ProductDTO;
class ProductController extends Controller
{
public function store(StoreProductRequest $request)
{
$dto = ProductDTO::fromRequest($request);
$product = $this->service->store($dto);
return new ProductResource($product);
}
}
Advanced Patterns
1. Helper Methods
php
class ProductDTO
{
public function __construct(
public readonly mixed $name = null,
public readonly mixed $price = null
) {}
public function getPriceWithDiscount(float $discountPercent): float
{
return $this->price * (1 - $discountPercent / 100);
}
}
2. Multiple Sources
php
class ProductDTO
{
public static function fromRequest(Request $request): self { }
public static function fromModel(Product $product): self { }
public static function fromArray(array $data): self { }
}
Best Practices
1. Use Type Safety
php
// Better than mixed
class ProductDTO
{
public function __construct(
public readonly string $name,
public readonly float $price,
public readonly ?string $description = null
) {}
}
2. Validate in FormRequest
php
// Validation in FormRequest
class StoreProductRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
];
}
}
// DTO just transfers data
$dto = ProductDTO::fromRequest($request);