Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Validated Dto Laravel Package

wendelladriel/laravel-validated-dto

Create Data Transfer Objects that validate input on instantiation. Define rules once and reuse them across controllers, services, jobs, and CLI commands—reducing duplication and keeping validation decoupled from HTTP requests. Compatible with Laravel 11–13.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:
    composer require wendelladriel/laravel-validated-dto
    
  2. Publish the config (optional):
    php artisan vendor:publish --provider="WendellAdriel\ValidatedDTO\ValidatedDTOServiceProvider"
    
  3. Generate your first DTO:
    php artisan make:dto UserCreateDto
    
    This creates a UserCreateDto class in app/Dtos/UserCreateDto.php with a basic structure.

First Use Case: Validating Request Data

use App\Dtos\UserCreateDto;
use Illuminate\Http\Request;

public function store(Request $request)
{
    $dto = UserCreateDto::fromRequest($request);

    // Validation errors are automatically thrown if invalid
    $user = User::create($dto->toArray());

    return response()->json($user, 201);
}

Key Files to Review

  • DTO Base Class: vendor/wendelladriel/laravel-validated-dto/src/ValidatedDTO.php
  • Attributes: vendor/wendelladriel/laravel-validated-dto/src/Attributes/
  • Artisan Commands: vendor/wendelladriel/laravel-validated-dto/src/Console/

Implementation Patterns

Core Workflow: Validation and Transformation

  1. Define Validation Rules:

    use WendellAdriel\ValidatedDTO\Attributes\Validated;
    
    #[Validated]
    class UserCreateDto
    {
        #[Validated('required|string|max:255')]
        public string $name;
    
        #[Validated('required|email')]
        public string $email;
    }
    
  2. Instantiate and Validate:

    $dto = UserCreateDto::fromArray($request->all());
    // or
    $dto = UserCreateDto::fromRequest($request);
    
  3. Access Validated Data:

    $name = $dto->name; // Automatically validated
    $data = $dto->toArray(); // Transforms nested DTOs, models, and collections
    

Common Patterns

1. Nested DTOs

#[Validated]
class AddressDto
{
    #[Validated('required|string')]
    public string $street;
}

#[Validated]
class UserCreateDto
{
    public AddressDto $address;
}

2. Custom Casting

use WendellAdriel\ValidatedDTO\Attributes\Cast;

#[Validated]
class UserDto
{
    #[Cast('date:Y-m-d')]
    public string $birthdate;
}

3. Lazy Validation

#[Validated(lazy: true)]
class UserDto
{
    // Validation runs only when accessing properties
    public function __get($key)
    {
        $this->validate();
        return parent::__get($key);
    }
}

4. Reusing Validation in CLI Commands

use App\Dtos\UserCreateDto;
use Illuminate\Console\Command;

class UserCreateCommand extends Command
{
    protected $signature = 'user:create {--name= : User Name} {--email= : User Email}';

    public function handle()
    {
        $dto = UserCreateDto::fromArray([
            'name'  => $this->option('name'),
            'email' => $this->option('email'),
        ]);

        // Proceed with validated data
    }
}

5. Transforming to API Responses

use WendellAdriel\ValidatedDTO\Attributes\Resource;

#[Resource]
#[Validated]
class UserResourceDto
{
    public string $name;
    public string $email;

    public function toArray(): array
    {
        return [
            'full_name' => $this->name,
            'email'     => $this->email,
        ];
    }
}

6. Handling Collections

use WendellAdriel\ValidatedDTO\Attributes\CollectionOf;

#[Validated]
class UserListDto
{
    #[CollectionOf(UserDto::class)]
    public array $users;
}

Gotchas and Tips

Pitfalls

  1. Validation Timing:

    • By default, validation runs on instantiation. Use lazy: true to defer validation.
    • Fix: Explicitly call $dto->validate() when needed.
  2. Nested DTO Transformations:

    • Nested DTOs must implement Arrayable or Jsonable for proper toArray()/toJson() behavior.
    • Fix: Ensure nested DTOs extend ValidatedDTO or implement the required interfaces.
  3. Circular References:

    • DTOs with circular references (e.g., User has Address, Address has User) may cause infinite loops.
    • Fix: Use #[SkipOnTransform] on problematic properties or break cycles manually.
  4. Type Safety:

    • PHP 8.0+ type hints are enforced, but runtime type mismatches (e.g., passing a string to a DateTime property) may not throw errors until validation.
    • Fix: Use #[Cast] attributes for explicit type conversion.
  5. Validation Error Handling:

    • Validation errors throw ValidationException by default. Catch them globally or per-use-case.
    • Fix:
      try {
          $dto = UserCreateDto::fromArray($data);
      } catch (ValidationException $e) {
          return response()->json($e->errors(), 422);
      }
      

Debugging Tips

  1. Inspect Validation Rules:

    $dto = new UserCreateDto();
    dd($dto->getValidationRules()); // Dump raw validation rules
    
  2. Enable Debug Mode:

    config(['validated-dto.debug' => true]);
    

    Logs validation steps and transformations to storage/logs/laravel.log.

  3. Check for Missing Properties:

    • If toArray() skips properties, verify:
      • The property has a #[Validated] attribute.
      • The property is not marked with #[SkipOnTransform].
  4. Custom Error Messages:

    #[Validated('required|string', message: 'Name must be a string')]
    public string $name;
    

Extension Points

  1. Custom Validation Logic:

    use WendellAdriel\ValidatedDTO\Contracts\ValidatedDTO;
    
    class CustomDto extends ValidatedDTO
    {
        public function validate(): void
        {
            parent::validate();
            // Add custom validation
            if ($this->email === 'test@example.com') {
                $this->addError('email', 'Test email is not allowed.');
            }
        }
    }
    
  2. Override Transformation:

    use WendellAdriel\ValidatedDTO\Contracts\Arrayable;
    
    #[Validated]
    class UserDto implements Arrayable
    {
        public function toArray(): array
        {
            return [
                'id'   => $this->id,
                'name' => strtoupper($this->name), // Custom logic
            ];
        }
    }
    
  3. Dynamic Validation:

    #[Validated]
    class DynamicDto
    {
        public function getValidationRules(): array
        {
            $rules = parent::getValidationRules();
            if ($this->isAdmin) {
                $rules['email'] = 'required|email|verified';
            }
            return $rules;
        }
    }
    
  4. Integration with Laravel Policies:

    use App\Dtos\UserUpdateDto;
    use Illuminate\Auth\Access\HandlesAuthorization;
    
    class UserPolicy
    {
        use HandlesAuthorization;
    
        public function update(User $user, UserUpdateDto $dto): bool
        {
            return $user->id === auth()->id();
        }
    }
    

Configuration Quirks

  1. Default Validation Behavior:

    • Set in config/validated-dto.php:
      'validation' => [
          'throw_exceptions' => true, // Default: true
          'lazy'             => false, // Default: false
      ],
      
  2. Stub Customization:

    • Override default DTO stubs by publishing and modifying:
      php artisan vendor:publish --tag="validated-dto-stubs"
      
    • Edit files in resources/stubs/dtos/.
  3. Performance:

    • For large datasets, use #[Lazy] to avoid validating unused properties:
      #[Lazy]
      public function getFullName(): string
      {
          return "{$this->firstName} {$this->lastName}";
      }
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4