Symfony MapQueryString doesn't allow optional parameters

1 day ago 2
ARTICLE AD BOX

I have a simple endpoint which has optional query parameters used for filtration. In my controller I use MapQueryString to map them to a DTO and validate inside. The problem is when I call the endpoint with no parameters at all I get 422 HTTP error by Symfony.

My controller endpoint:

#[SuccessfulPaginatedResponse(description: 'Returns paginated list of categories by given filters.')] #[ValidationErrorResponse] #[Route(name: 'categories_index', methods: ['GET'], format: 'json')] public function index( #[MapQueryString( validationGroups: ['default'], validationFailedStatusCode: Response::HTTP_UNPROCESSABLE_ENTITY )] IndexRequestQueryDto $queryParams, Request $request ): JSONResponse { $filter = CategoryFilterFactory::createFromIndexRequest($queryParams); $result = $this->categoryService->fetch($filter); $paginatedResponse = PaginatedResponse::create( $result['data'], $queryParams, $result['totalCount'], $request ); return $this->json($paginatedResponse->toArray()); }

My DTO:

class IndexRequestQueryDto extends PaginatedRequest { public function __construct( #[Assert\Positive] #[Assert\Type('integer')] public readonly ?int $businessUnit = null, #[Assert\Positive] #[Assert\Type('integer')] public readonly ?int $parentId = null, #[Assert\Type('string')] public readonly ?string $name = null, ?int $page = null, ?int $pageSize = null, ) { parent::__construct($page, $pageSize); } }

My DTO's extended class:

class PaginatedRequest implements DtoInterface { public const DEFAULT_PAGE = 1; public const DEFAULT_PAGE_SIZE = 25; public const MAX_PAGE_SIZE = 100; public function __construct( #[Assert\Type('integer')] #[Assert\Positive] public readonly ?int $page = null, #[Assert\Type('integer')] #[Assert\Positive] #[Assert\LessThanOrEqual(self::MAX_PAGE_SIZE)] public readonly ?int $pageSize = null, ) { }

Things I tried which didn't work out:
- Removed all assertions
- In IndexRequestQueryDto tried to define page & pageSize without passing them to parent constructor
- Tried using #[Assert\When()]

The only thing that worked out for me was to use ValidatorInterface in my controller but that solution requires extra code and I don't like it:

$queryParams = new IndexRequestQueryDto( $request->query->has('businessUnit') ? (int) $request->query->get('businessUnit') : null, $request->query->has('parentId') ? (int) $request->query->get('parentId') : null, $request->query->has('name') ? (string) $request->query->get('name') : null, $request->query->has('page') ? (int) $request->query->get('page') : null, $request->query->has('pageSize') ? (int) $request->query->get('pageSize') : null, ); $errors = $this->validator->validate($queryParams); if (count($errors) > 0) { return $this->json(['errors' => $errors], Response::HTTP_UNPROCESSABLE_ENTITY); }

Symfony version is 6.4, PHP is 8.2

Read Entire Article