crtl/request-dto-resolver-bundle 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

crtl/request-dto-resolver-bundle

Composer 安装命令:

composer require crtl/request-dto-resolver-bundle

包简介

Deserializes and validates requests into objects

README 文档

README

codecov Latest Stable Version Total Downloads Latest Unstable Version License PHP Version Require

A Symfony bundle for predictable, type-safe instantiation and validation of request DTOs.

It removes boilerplate from controllers while staying close to Symfony’s native validation and argument resolving mechanisms.

Features

  • Automatic DTO Resolution
    DTOs type-hinted in controller actions are instantiated and validated automatically.

  • Native Symfony Validator Integration
    Uses Symfony’s ValidatorInterface without custom validation layers.

  • Nested DTO Support
    Supports complex request payloads with nested DTOs for query, body, header, file and route parameters.

  • Strict Typing Friendly
    DTO properties can be strictly typed for better IDE support and safer refactoring.

  • Flexible Query Parameter Transformation
    Query parameters can be transformed to scalar types or via custom callbacks.

Installation

composer require crtl/request-dto-resolver-bundle

Configuration

Register the bundle in your Symfony application:

// config/bundles.php
return [
    // ...
    Crtl\RequestDtoResolverBundle\CrtlRequestDtoResolverBundle::class => ["all" => true],
];

Bundle Options

The bundle exposes two options under the crtl_request_dto_resolver key:

# config/packages/crtl_request_dto_resolver.yaml
crtl_request_dto_resolver:
    default_strict: true   # default: true
    default_null: false     # default: false
Option Type Default Description
default_strict bool true Controls how property values are assigned during hydration. When true, values are assigned directly ($object->prop = $value), which enforces PHP's native type checks. When false, values are assigned via reflection, allowing implicit type coercion.
default_null bool false When true, properties that are missing from the request are treated as if null was sent. For non-nullable properties this produces a type constraint violation; for nullable properties the value is skipped as usual.

These serve as bundle-level defaults. Individual DTOs can override them via the #[RequestDto] attribute:

// Uses bundle defaults for both options
#[RequestDto]
class MyDto { /* ... */ }

// Overrides strict for this DTO only
#[RequestDto(strict: false)]
class NonStrictDto { /* ... */ }

// Overrides defaultNull for this DTO only
#[RequestDto(defaultNull: true)]
class RequireAllFieldsDto { /* ... */ }

When the attribute parameter is omitted (or explicitly set to null), the bundle-level default is used.

Usage

Step 1: Define a Request DTO

Create a DTO class and annotate it with #[RequestDto]. Use parameter attributes to map request data to properties.

The attribute is required to identify which controller arguments should be resolved and validated.

1.1 Strictly typed DTO

namespace App\DTO;

use Crtl\RequestDtoResolverBundle\Attribute\BodyParam;
use Crtl\RequestDtoResolverBundle\Attribute\FileParam;
use Crtl\RequestDtoResolverBundle\Attribute\HeaderParam;
use Crtl\RequestDtoResolverBundle\Attribute\QueryParam;
use Crtl\RequestDtoResolverBundle\Attribute\RouteParam;
use Crtl\RequestDtoResolverBundle\Attribute\RequestDto;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints as Assert;

#[RequestDto]
class ExampleDto
{
    #[BodyParam, Assert\NotBlank, Assert\Type("string")]
    public string $someParam;
    
     #[BodyParam, Assert\NotBlank, Assert\Type("string")]
    public string $withDefaultValue = "My default value";
    
    #[BodyParam]
    public ?string $nullable;

    #[FileParam, Assert\NotNull]
    public ?UploadedFile $file;

    #[HeaderParam("Content-Type"), Assert\NotBlank]
    public string $contentType;

    // Because query params are all strings by default
    // we can provide a type transformer to transform its type.
    // values are converted using filter_var with the corrosponding FILTER_VALIDATE_* option.
    #[QueryParam(transformType: "int"), Assert\GreaterThan(18)]
    public int $age;
    
    // Or provide a custom callable to tranform type
    #[
        QueryParam(
            name: "age", 
            transformType: fn(string $value) => strtolower($value)
        ), 
        Assert\GreaterThan(18)
    ]
    public mixed $customQueryParam;

    #[RouteParam, Assert\NotBlank]
    public string $id;

    // Nested DTOs are supported for BodyParam and QueryParam
    // Do NOT use Assert\Valid here
    #[BodyParam("nested")]
    public ?NestedRequestDTO $nestedBodyDto;

    // Optional constructor receiving the Request
    // It is recommended to make the request argument nullable to support creation of DTOs from
    // array data but not required when only used in HTTP contexts.
    public function __construct(?Request $request = null)
    {
    }
}

Any type mismatches will trigger a constraint violation and thus a RequestValidationException is thrown.

1.2 Mixed typed DTO

namespace App\DTO;

use Crtl\RequestDtoResolverBundle\Attribute\BodyParam;
use Crtl\RequestDtoResolverBundle\Attribute\FileParam;
use Crtl\RequestDtoResolverBundle\Attribute\HeaderParam;
use Crtl\RequestDtoResolverBundle\Attribute\QueryParam;
use Crtl\RequestDtoResolverBundle\Attribute\RouteParam;
use Crtl\RequestDtoResolverBundle\Attribute\RequestDto;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Constraints as Assert;

#[RequestDto]
class ExampleDto
{
    /**
     * @var string
     */
    #[BodyParam, Assert\NotBlank]
    public mixed $contentType;
}

1.3 Important notes about DTO hydration

  • Uninitialized properties
    If a request does not contain data for a property, that property will remain uninitialized.
    To guarantee initialization, either:

    • provide a default value, or
    • add appropriate validation constraints (e.g. NotNull, NotBlank).
  • Hydration from arrays
    When a DTO is manually hydrated from an array using RequestDtoFactory::fromArray(), the configured AbstractParam::$name is ignored.
    In this case, the DTO’s property names are always used as the source keys.

  • Validation still runs in strict mode
    Even if some properties cannot be assigned due to type mismatches in strict typing mode, the DTO is still fully validated using Symfony’s validator.
    Any resulting violations will lead to a RequestValidationException.

1.4 Validation group sequences

All Symfony validation group sequence variants are supported.

Because request data can never be trusted, DTO properties may be uninitialized regardless of whether strict typing is used or not.
Missing or invalid input can prevent a property from being assigned during hydration.

When using validation group sequences, you must therefore ensure that properties are accessed safely by:

  • checking initialization with isset(), or
  • using reflection-based checks when necessary.

Failing to do so may lead to runtime errors before later validation groups are evaluated.

Step 2: Use the DTO in a Controller

namespace App\Controller;

use App\DTO\ExampleDto;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class ExampleController extends AbstractController
{
    #[Route("/example", name: "example")]
    public function exampleAction(ExampleDto $data): Response
    {
        return new Response("DTO received and validated successfully!");
    }
}

Handling Validation Errors

On validation failure, a RequestValidationException is thrown.

The bundle registers a default exception subscriber (priority -32) that returns a 400 Bad Request JSON response.

You can override this with your own listener:

namespace App\EventListener;

use Crtl\RequestDtoResolverBundle\Exception\RequestValidationException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;

class RequestValidationExceptionListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            KernelEvents::EXCEPTION => ["onKernelException", 0],
        ];
    }

    public function onKernelException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();

        if ($exception instanceof RequestValidationException) {
            $event->setResponse(new JsonResponse([
                "error" => "Validation failed",
                "details" => $exception->getViolations(),
            ], JsonResponse::HTTP_BAD_REQUEST));
        }
    }
}

License

This bundle is licensed under the MIT License. See the LICENSE file for details.

crtl/request-dto-resolver-bundle 适用场景与选型建议

crtl/request-dto-resolver-bundle 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 404 次下载、GitHub Stars 达 1, 最近一次更新时间为 2024 年 06 月 03 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 crtl/request-dto-resolver-bundle 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 crtl/request-dto-resolver-bundle 我们能提供哪些服务?
定制开发 / 二次开发

基于 crtl/request-dto-resolver-bundle 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 404
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-06-03