julio-cavallari / laravel-dto
Laravel package to automatically generate DTOs from Form Request classes
Requires
- php: ^8.2|^8.3|^8.4
- illuminate/console: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- laravel/framework: ^11.0|^12.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^10.0
- rector/rector: ^1.0
README
A Laravel package that automatically generates Data Transfer Objects (DTOs) from Form Request classes, keeping them in sync with your validation rules.
Features
- 🚀 Automatic Generation: Generate DTOs from existing Form Request classes
- 🔄 Stay in Sync: Keep DTOs updated with Form Request changes
- 🛡️ Type Safe: Generate strongly typed DTOs with proper type hints
- 🎯 Immutable: Generate readonly DTOs for better data integrity
- 🏷️ Smart Enums: Automatically generate PHP 8.1+ enums from "in" validation rules
- ⚡ Fast: Efficient parsing and generation
- 🔧 Configurable: Customize namespaces, paths, and generation options
Installation
You can install the package via composer:
composer require julio-cavallari/laravel-dto
Optionally, you can publish the config file:
php artisan vendor:publish --tag=laravel-dto-config
Usage
Basic Usage
Generate DTOs for all Form Requests:
php artisan dto:generate
Generate DTO for a specific Form Request:
php artisan dto:generate CreateArticleRequest
Checking DTO Status
Check which Form Requests have corresponding DTOs:
# Check all Form Requests php artisan dto:check # Show only Form Requests without DTOs php artisan dto:check --missing # Show only Form Requests with DTOs php artisan dto:check --existing # Show detailed information php artisan dto:check --details
Command Options
--force
: Force regenerate existing DTOs--dry-run
: Preview changes without writing files--enhance-requests
: Add toDto() method to Form Requests using trait
# Force regenerate all DTOs php artisan dto:generate --force # Preview what would be generated php artisan dto:generate --dry-run
Example
Given this Form Request:
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class CreateArticleRequest extends FormRequest { public function rules(): array { return [ 'title' => 'required|string|max:255', 'content' => 'required|string', 'excerpt' => 'nullable|string|max:500', 'status' => 'required|string|in:draft,published,archived', 'category' => 'required|string|in:news,sports,technology', 'tags' => 'array', 'published' => 'boolean', ]; } }
The package will generate this DTO:
<?php declare(strict_types=1); namespace App\DTOs; use App\Http\Requests\CreateArticleRequest; use App\Enums\StatusEnum; use App\Enums\CategoryEnum; /** * Generated DTO class * * This class was automatically generated from a Form Request. * * @generated 2025-08-01 22:30:00 */ final readonly class CreateArticleData { public function __construct( public string $title, public string $content, public ?string $excerpt = null, public StatusEnum $status, public CategoryEnum $category, public array $tags = [], public bool $published = false, ) { } public static function fromRequest(CreateArticleRequest $request): self { return new self( title: $request->validated('title'), content: $request->validated('content'), excerpt: $request->validated('excerpt', null), status: $request->enum('status', StatusEnum::class), category: $request->enum('category', CategoryEnum::class), tags: $request->validated('tags', []), published: $request->validated('published', false), ); } }
And automatically generate these enums:
<?php declare(strict_types=1); namespace App\Enums; enum StatusEnum: string { case DRAFT = 'draft'; case PUBLISHED = 'published'; case ARCHIVED = 'archived'; }
<?php declare(strict_types=1); namespace App\Enums; enum CategoryEnum: string { case NEWS = 'news'; case SPORTS = 'sports'; case TECHNOLOGY = 'technology'; }
Automatic Enum Generation
The package intelligently detects in
validation rules and automatically generates PHP 8.1+ backed enums:
Smart Detection
Enums are created when validation rules meet these criteria:
- Contains an
in
rule (e.g.,in:draft,published,archived
) - Has between 2-10 values (configurable)
- Values are not large numeric IDs (to avoid enum pollution)
Enum Types
- String values: Creates
string
backed enums - Integer values: Creates
int
backed enums - Mixed values: Creates
string
backed enums (casts integers to strings)
Examples
// Form Request 'status' => 'required|in:draft,published,archived', 'priority' => 'nullable|integer|in:1,2,3,4,5', 'language' => 'nullable|in:en,fr,es,de', // Generated Enums enum StatusEnum: string { case DRAFT = 'draft'; case PUBLISHED = 'published'; case ARCHIVED = 'archived'; } enum PriorityEnum: int { case _1 = 1; case _2 = 2; case _3 = 3; case _4 = 4; case _5 = 5; } enum LanguageEnum: string { case EN = 'en'; case FR = 'fr'; case ES = 'es'; case DE = 'de'; }
Enum Configuration
Configure enum generation in the config file:
return [ // Enum namespace 'enum_namespace' => 'App\\Enums', // Enum output directory 'enum_output_path' => 'app/Enums', // Other configurations... ];
Usage in Controllers
<?php namespace App\Http\Controllers; use App\Http\Requests\CreateArticleRequest; use App\DTOs\CreateArticleData; use App\Services\ArticleService; class ArticleController extends Controller { public function store(CreateArticleRequest $request, ArticleService $articleService) { $articleData = CreateArticleData::fromRequest($request); $article = $articleService->create($articleData); return response()->json($article, 201); } }
Enhanced Form Requests with toDto() Method
You can enhance your Form Requests to include a convenient toDto()
method using the provided trait and interface:
# Generate DTOs and enhance Form Requests with toDto() method
php artisan dto:generate --enhance-requests
This will automatically add the ConvertsToDto
trait and HasDto
interface to your Form Requests:
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use JulioCavallari\LaravelDto\Contracts\HasDto; use JulioCavallari\LaravelDto\Traits\ConvertsToDto; class CreateArticleRequest extends FormRequest implements HasDto { use ConvertsToDto; // Your existing validation rules... }
Now you can easily convert requests to DTOs:
// In your controller public function store(CreateArticleRequest $request) { $articleData = $request->toDto(); // ✨ Clean and simple! // Use the strongly typed DTO $article = Article::create([ 'title' => $articleData->title, 'content' => $articleData->content, 'excerpt' => $articleData->excerpt, 'tags' => $articleData->tags, ]); }
The trait automatically:
- Resolves the correct DTO class name based on conventions
- Validates that the DTO class exists
- Calls the
fromRequest()
method to create the DTO - Provides helpful error messages if something goes wrong
Custom DTO Class with UseDto Attribute
By default, the package uses naming conventions to determine the DTO class name (e.g., CreateArticleRequest
→ CreateArticleData
). You can customize this using the UseDto
attribute:
<?php namespace App\Http\Requests; use App\DTOs\ArticleCreationData; use Illuminate\Foundation\Http\FormRequest; use JulioCavallari\LaravelDto\Attributes\UseDto; use JulioCavallari\LaravelDto\Contracts\HasDto; use JulioCavallari\LaravelDto\Traits\ConvertsToDto; #[UseDto(ArticleCreationData::class)] class CreateArticleRequest extends FormRequest implements HasDto { use ConvertsToDto; public function rules(): array { return [ 'title' => 'required|string|max:255', 'content' => 'required|string', 'excerpt' => 'nullable|string', ]; } }
Now when you call $request->toDto()
, it will return an instance of ArticleCreationData
instead of the default CreateArticleData
.
You can also use fully qualified class names:
#[UseDto('App\DTOs\Custom\MyCustomDto')] class CreateArticleRequest extends FormRequest implements HasDto { // ... }
Benefits of using UseDto
:
- ✅ Custom naming conventions
- ✅ Reuse existing DTO classes
- ✅ Better organization of DTOs
- ✅ Type safety with class-string parameter
Configuration
You can customize the package behavior by editing the config file:
return [ // DTO namespace 'namespace' => 'App\\DTOs', // Output directory 'output_path' => 'app/DTOs', // Enum namespace 'enum_namespace' => 'App\\Enums', // Enum output directory 'enum_output_path' => 'app/Enums', // Form Request directory 'form_request_path' => 'app/Http/Requests', // Form Request namespace 'form_request_namespace' => 'App\\Http\\Requests', // Class name suffixes 'dto_suffix' => 'Data', 'form_request_suffix' => 'Request', // Exclude specific Form Requests 'excluded_requests' => [ // 'LoginRequest', ], // Generate readonly DTOs 'readonly' => true, // Generate fromRequest method 'generate_from_request_method' => true, // Type mapping for validation rules 'type_mapping' => [ 'string' => 'string', 'integer' => 'int', 'numeric' => 'float', 'boolean' => 'bool', 'array' => 'array', 'file' => 'Illuminate\\Http\\UploadedFile', 'image' => 'Illuminate\\Http\\UploadedFile', 'date' => 'Carbon\\Carbon', 'email' => 'string', 'url' => 'string', 'uuid' => 'string', 'json' => 'array', 'in' => 'string', // Value must be in array, generates enum when suitable ], ];
Type Mapping
The package automatically infers PHP types from Laravel validation rules:
Validation Rule | PHP Type | Notes |
---|---|---|
string |
string |
|
integer |
int |
|
numeric |
float |
|
boolean |
bool |
|
array |
array |
|
file |
Illuminate\Http\UploadedFile |
|
date |
Carbon\Carbon |
|
in:val1,val2 |
App\Enums\FieldEnum |
Auto-generates enum for suitable values |
nullable |
Adds ? prefix |
Makes type nullable |
Requirements
- PHP 8.2+
- Laravel 11.0+
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.