laravel-validated-dto

repository·main·Indexed 20 days ago

https://github.com/wendelladriel/laravel-validated-dto

A Laravel package for creating type-safe Data Transfer Objects (DTOs) with built-in validation, default values, and property casting. It supports instantiating DTOs from arrays, JSON strings, Laravel Request objects, Eloquent models, and Artisan commands. Starting with v3, DTOs can be type-hinted directly in controller actions to act like Form Request classes for automatic resolution via the service container.

Tokens
17.4K
Snippets
70
Records
87
Agent score
72%

What's inside laravel-validated-dto

  1. What is Validated DTO for Laravel

    main
    Validated DTO is a package for Laravel that provides Data Transfer Objects (DTOs) with built-in validation. It allows you to define typed objects that validate incoming data using standard Laravel validation rules. This ensures that data moving between different layers of your application (such as from an HTTP request to a service class, or from a queued job) adheres to a single, consistent, and validated data contract.
  2. Key features of Validated DTO

    main

    The package offers several capabilities for managing data integrity in Laravel:

    • Laravel Integration: Seamlessly integrates into existing Laravel applications.
    • Standard Validation: Uses existing Laravel validation rules.
    • Typed Properties: Uses public PHP properties for type safety.
    • Advanced Casting: Supports casting for scalar values, objects, collections, models, enums, and nested DTOs.
    • Customization: Allows defining custom validation messages and attribute names.
    • Mapping: Supports mapping between incoming and outgoing property names.
    • API & Frontend: Enables building API response objects with resource DTOs and generating TypeScript definitions from DTO classes.
    • Livewire Support: Compatible with Laravel Livewire.
  3. Implement custom data transformation

    main

    To implement custom transformation logic (e.g., mapping keys or changing formats), override or use the buildDataForExport() method. This method returns the validated data with your custom mappings applied, which can then be used by other transformation methods or converted to other types.

    public function toObject(): object
    {
        return (object) $this->buildDataForExport();
    }
  4. How nested data transformation works

    main

    When transforming a DTO, the transformation logic is applied recursively to all properties. The behavior depends on the property type:

    Property TypeTransformation Behavior
    Scalar Data Type and ArrayPreserves original values
    stdClassTransformed into arrays via type casting
    UnitEnumTransformed into the case name
    BackedEnumTransformed into the case value
    Carbon and CarbonImmutableTransformed into strings via ->toISOString()
    Collection, Model, and DTORecursively transformed using the same rules above
  5. Understand property filtering in DTOs

    main

    DTOs automatically filter incoming data based on the rules method. If you pass keys in the constructor array that are not explicitly listed in your DTO's rules method, those properties will be ignored and will not be accessible on the resulting object.

    // If 'username' is NOT defined in UserDTO::rules()...
    $dto = new UserDTO([
        'name' => 'John Doe',
        'email' => 'john.doe@example.com',
        'password' => 's3CreT!@1a2B',
        'username' => 'john_doe', 
    ]);
    
    $dto->username; // This will not be available
  6. Understand the default DTO methods

    main

    Generated DTOs are designed to be simple and include only the most essential methods. The following methods are included by default:

    • rules(): Used specifically for Validated DTOs to define validation logic.
    • defaults(): Defines default values for properties.
    • casts(): Defines how properties should be cast.

    If your implementation requires more advanced functionality, you can manually add the following methods to your DTO class:

    • messages()
    • attributes()
    • mapData()
    • mapToTransform()
  7. Choose the right DTO type

    main

    The package provides three primary DTO types. Choose based on your specific use case:

    • ValidatedDTO: Use when incoming data must be validated before the DTO is considered ready. This is the recommended default.
    • SimpleDTO: Use when you need typed data, casts, mapping, and transforms, but do not require validation.
    • ResourceDTO: Use when the DTO is primarily an API response object intended to be returned directly from controllers.
  8. Map DTO properties to different formats during transformation

    main

    If you need the output of your DTO (when calling toArray(), toJson(), or toModel()) to have different keys than the DTO properties themselves, use these methods:

    1. mapToTransform() method: Override this protected method to return an array mapping DTO property paths to the desired output keys. This is useful for flattening nested DTO data into a flat Model or Array.
    2. #[Map(transform: '...')] attribute: Apply this to a property to specify its name in the transformed output.

    You can combine mapData() (for input) and mapToTransform() (for output) to create a complete pipeline: Input Key -> DTO Property -> Output Key.

    // Using mapToTransform() to flatten nested DTO data for a Model
    class UserDTO extends ValidatedDTO
    {
        public NameDTO $name;
    
        protected function mapToTransform(): array
        {
            return [
                'name.first_name' => 'first_name',
                'name.last_name' => 'last_name',
            ];
        }
    }
    
    // Using #[Map] attribute for transformation
    use WendellAdriel//ValidatedDTO//Attributes//Map;
    
    class UserDTO extends ValidatedDTO
    {
        #[Map(transform: 'username')]
        public string $name;
    }
  9. Create custom type casts using Callables

    main

    For simpler casting logic that doesn't require a dedicated class, you can use a callable (such as an anonymous function/closure) directly within the casts() method of your ValidatedDTO.

    The callable must accept two arguments: string $property and mixed $value, and should return the casted value.

    class CustomDTO extends ValidatedDTO
    {
        protected function rules(): array
        {
            return ['url' => ['required', 'url']];
        }
    
        protected function casts(): array
        {
            return [
                'url' => function (string $property, mixed $value) {
                    return new URLWrapper($value);
                },
            ];
        }
    }
  10. Enable Lazy Validation in a ValidatedDTO

    main

    By default, ValidatedDTO performs validation and attribute casting during instantiation. If you want to instantiate a DTO without immediate validation (e.g., to allow manual property assignment before validation), you can enable Lazy Validation.

    When lazy validation is enabled:

    1. You can instantiate the DTO without providing initial attributes.
    2. Setting properties on the DTO will not trigger automatic casting.
    3. Data is only validated and attributes are only cast when you explicitly call the validate() method.

    If validate() passes, the attributes are cast within the DTO object. If it fails, an Illuminate\Validation\ValidationException is thrown.

    class LazyDTO extends ValidatedDTO
    {
        public bool $lazyValidation = true;
    }
    
    $dto = new LazyDTO();
    $dto->name = 'John Doe';
    $dto->validate(); // Validation and casting happen here