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

Request Dto Resolver Bundle Laravel Package

crtl/request-dto-resolver-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:
    composer require crtl/request-dto-resolver-bundle
    
  2. Enable the bundle in config/bundles.php:
    Crtl\RequestDtoResolverBundle\CrtlRequestDtoResolverBundle::class => ["all" => true],
    
  3. Define a DTO with #[RequestDto] and parameter attributes (e.g., #[BodyParam], #[QueryParam]):
    #[RequestDto]
    class CreateUserDto {
        #[BodyParam, Assert\NotBlank]
        public string $name;
    }
    
  4. Use the DTO in a controller as a type-hinted argument:
    #[Route('/users', methods: ['POST'])]
    public function createUser(CreateUserDto $dto): Response { ... }
    

First Use Case

Replace a manual request validation workflow (e.g., Request::get() + ValidatorInterface) with a single DTO argument. For example:

// Before (manual)
public function createUser(Request $request, ValidatorInterface $validator): Response {
    $data = $request->request->all();
    $errors = $validator->validate($data, new CreateUserConstraints());
    if (count($errors)) { ... }
    // Process $data
}

// After (DTO)
public function createUser(CreateUserDto $dto): Response {
    // $dto is already validated and hydrated
    return new Response("User created: {$dto->name}");
}

Implementation Patterns

1. DTO Design Patterns

  • Strict vs. Loose Typing: Use #[RequestDto(strict: false)] for DTOs where implicit type coercion is needed (e.g., legacy APIs). Default is true (bundle/config level).

    #[RequestDto(strict: false)]
    class LegacyDto {
        #[BodyParam]
        public mixed $age; // Accepts "25" as int
    }
    
  • Nested DTOs: Support complex payloads with nested structures. Avoid Assert\Valid—use the bundle’s native resolution:

    #[RequestDto]
    class UserProfileDto {
        #[BodyParam("address")]
        public AddressDto $address;
    }
    
    #[RequestDto]
    class AddressDto {
        #[BodyParam]
        public string $street;
    }
    
  • Query Parameter Transformation: Convert query strings to typed values using transformType or custom callbacks:

    #[QueryParam(transformType: "int")] // Uses FILTER_VALIDATE_INT
    public int $page;
    
    #[QueryParam(transformType: fn(string $v) => (int) $v * 2)]
    public int $multiplier;
    

2. Controller Workflows

  • Single DTO per Action: Ideal for simple APIs where one DTO maps to the entire request payload.

    public function updateUser(UpdateUserDto $dto): Response { ... }
    
  • Multiple DTOs: Combine DTOs for different parts of the request (e.g., query + body):

    public function searchUsers(
        SearchQueryDto $query,
        FilterDto $filter
    ): Response { ... }
    
  • Conditional Validation: Use Symfony’s validation groups to validate DTOs differently based on context:

    #[RequestDto]
    class UserDto {
        #[BodyParam, Assert\NotBlank(groups: ["create"])]
        public string $password;
    }
    
    // In controller:
    $validator->validate($dto, null, ["create"]);
    

3. Integration with Symfony Ecosystem

  • Event Listeners: Override the default RequestValidationException handler to customize error responses:

    // config/services.yaml
    App\EventListener\RequestValidationListener:
        tags:
            - { name: kernel.event_subscriber }
    
  • Dependency Injection: Inject RequestDtoFactory for manual DTO creation (e.g., in services):

    public function __construct(
        private RequestDtoFactory $dtoFactory
    ) {}
    
    public function process(array $data): void {
        $dto = $this->dtoFactory->fromArray(MyDto::class, $data);
    }
    
  • API Platform Integration: Use DTOs alongside API Platform’s #[ApiResource] for hybrid validation:

    #[ApiResource, RequestDto]
    class Book {
        #[BodyParam, Assert\NotBlank]
        public string $title;
    }
    

4. Testing Patterns

  • Unit Testing DTOs: Test hydration and validation in isolation:

    public function testDtoHydration(): void {
        $dto = $this->dtoFactory->fromArray(
            CreateUserDto::class,
            ["name" => "John"]
        );
        $this->assertEquals("John", $dto->name);
    }
    
  • Controller Testing: Use RequestDtoFactory to create DTOs for controller tests:

    public function testCreateUser(): void {
        $dto = $this->dtoFactory->fromArray(CreateUserDto::class, ["name" => "Jane"]);
        $response = $this->client->postJson("/users", $dto->toArray());
        $this->assertResponseIsSuccessful();
    }
    

Gotchas and Tips

Pitfalls

  1. Missing #[RequestDto] Attribute:

    • Issue: DTOs without the attribute are ignored by the resolver.
    • Fix: Always annotate DTOs with #[RequestDto].
  2. Strict Mode Type Mismatches:

    • Issue: In strict mode (strict: true), type mismatches (e.g., string → int) throw RequestValidationException during hydration.
    • Fix:
      • Use strict: false for loose typing.
      • Ensure query/body data matches DTO property types (e.g., send 25 as "25" for int fields).
  3. Uninitialized Properties:

    • Issue: Properties without defaults or validation constraints remain null if missing in the request, causing UndefinedPropertyException in validation groups.
    • Fix:
      • Add defaults: public ?string $optional = null;.
      • Use Assert\NotNull or Assert\NotBlank to enforce presence.
      • Access properties safely: isset($dto->property) ? $dto->property : ....
  4. Circular References in Nested DTOs:

    • Issue: Nested DTOs with circular references (e.g., A contains B, B contains A) may cause infinite loops.
    • Fix: The bundle detects circular references, but ensure your DTOs are acyclic.
  5. Query Parameter Name Conflicts:

    • Issue: If two #[QueryParam] fields map to the same query parameter name, the last one wins.
    • Fix: Explicitly name query parameters:
      #[QueryParam(name: "user_id")]
      public int $id;
      
  6. File Uploads:

    • Issue: UploadedFile properties require #[FileParam] and may fail if the file key doesn’t match the property name.
    • Fix: Use name in #[FileParam] to specify the form field name:
      #[FileParam(name: "avatar")]
      public ?UploadedFile $image;
      

Debugging Tips

  1. Enable Debug Mode:

    • Symfony’s debug toolbar shows resolved DTOs and validation errors.
  2. Inspect Violations:

    • Catch RequestValidationException to log or inspect violations:
      try {
          $this->controller->action($dto);
      } catch (RequestValidationException $e) {
          error_log($e->getViolations());
      }
      
  3. Check Hydration Order:

    • Properties are hydrated in the order they appear in the request (e.g., query params before body params). Use defaultNull: true to skip missing properties.
  4. Validate DTOs Manually:

    • Use ValidatorInterface to validate DTOs outside controllers:
      $validator->validate($dto);
      

Configuration Quirks

  1. Bundle-Level Defaults:

    • default_strict: false in config overrides all DTOs unless explicitly set.
    • default_null: true treats missing properties as null (useful for optional fields).
  2. Attribute Precedence:

    • DTO-level attributes (e.g., #[RequestDto(strict: false)]) override bundle/config defaults.
  3. Performance:

    • The bundle caches DTO metadata. Clear the cache after major DTO changes:
      php bin/console cache:clear
      

Extension Points

  1. Custom Param Attributes:

    • Extend the bundle by creating your own attributes (e.g., #[CookieParam]) by implementing ParamAttributeInterface.
  2. Custom Hydrators:

    • Replace the default hydrator by
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.
iio/libmergepdf
redaxo/project
zatona-eg/zatona-eg-api
patrickbussmann/oauth2-apple
3brs/enterprise-security-bundle
ardenexal/fhir-models
ardenexal/fhir-validation
dpfx/laravel-livewire-wizards
dmstr/symfony-system-resources-bundle
dmstr/symfony-job-queue-bundle
dmstr/openapi-json-schema-bundle
dmstr/keycloak-security-bundle
dmstr/doctrine-audit-log-bundle
dmstr/api-platform-utils-bundle
dmstr/api-configuration-bundle
chrisdev/ux-components
crudly/encrypted
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony