pfilsx/dto-param-converter-bundle
A Symfony bundle that converts request parameters into typed DTOs via a param converter, keeping controllers clean and making input handling more explicit, reusable, and testable.
Install the bundle and confirm what it actually supports in the installed version:
composer require pfilsx/dto-param-converter-bundle
If Flex does not auto-register it, add it to config/bundles.php:
return [
// ...
Pfilsx\DtoParamConverterBundle\DtoParamConverterBundle::class => ['all' => true],
];
Look first at:
README.md for the supported attribute/annotation syntax and converter options.src/Attribute or src/Annotation and src/Converter to see exactly how request data is read and how DTOs are constructed.composer.json for Symfony/Sensio/annotations constraints and whether the bundle depends on an abandoned ParamConverter stack.bin/console debug:container --tag=request.param_converter to confirm the converter is registered.The first useful use case is replacing manual query-string extraction with a typed filter DTO:
namespace App\Dto;
class UserFilterDto
{
public string $q = '';
public int $page = 1;
public int $limit = 20;
}
use Pfilsx\DtoParamConverterBundle\Attribute\DtoParamConverter; // verify namespace in the installed version
#[Route('/api/users', methods: ['GET'])]
public function list(#[DtoParamConverter] UserFilterDto $filter): JsonResponse
{
// $filter is populated from the request before the controller runs
return $this->json($this->userRepository->search($filter));
}
Treat that as the “hello world” pattern: one DTO per endpoint/use case, type-hint the DTO in the controller, and let the bundle populate it. Do not start by binding entities or complex nested graphs.
Use request DTOs as controller boundary objects. Keep DTOs under src/Dto/Request or similar, with scalar typed properties and safe defaults. This gives controllers a stable input object instead of scattered $request->query->get() calls.
class SearchOrdersDto
{
public string $status = 'open';
public ?string $customerId = null;
public int $page = 1;
public int $limit = 25;
}
Separate query filters from command/payload DTOs. A GET search filter and a POST create payload usually have different validation, security, and mapping needs. Do not reuse one DTO for both. For write endpoints, verify whether the bundle reads JSON body content or only request query/form data; if not, keep using Symfony’s native #[MapRequestPayload] for JSON bodies.
Validate explicitly unless the bundle proves otherwise. A ParamConverter-style bundle usually only constructs the DTO; it does not automatically run Symfony Validator constraints.
$errors = $this->validator->validate($filter);
if (count($errors) > 0) {
throw new ValidationFailedException($errors);
}
Put constraints on the DTO:
use Symfony\Component\Validator\Constraints as Assert;
class SearchOrdersDto
{
#[Assert\Choice(['open', 'paid', 'cancelled'])]
public string $status = 'open';
#[Assert\Range(min: 1, max: 100)]
public int $limit = 25;
}
Use one DTO per endpoint until a shared base object is clearly stable. Shared pagination/filter DTOs are fine, but avoid deep inheritance. Composition is easier to validate and document.
Map naming deliberately. If your API uses snake_case query parameters and your DTO uses camelCase, use the serializer name converter or explicit serialized-name metadata supported by the installed version. Do not rely on accidental property-name matching.
Integrate with testing at the controller boundary. Functional tests should send real query strings and assert the HTTP result; unit tests should validate DTO constraints directly.
$client->request('GET', '/api/users?page=2&limit=50');
self::assertResponseIsSuccessful();
Consider a custom converter or serializer normalizer only for repeated parsing problems. Examples: custom date parsing, enum conversion, comma-separated IDs, or nested filter objects. Prefer fixing this at the DTO/serializer level before decorating the bundle’s converter.
Document parameters for OpenAPI manually. A type-hinted DTO is not automatically visible to NelmioApiDocBundle/OpenAPI as query parameters. Add explicit query-parameter annotations or migrate to #[MapQueryString], which has better native documentation support in newer Symfony versions.
Plan an exit path. Keep DTOs independent of the bundle so you can replace #[DtoParamConverter] with Symfony’s native #[MapQueryString] or #[MapRequestPayload] later. For Symfony 6.3+, native attributes cover most of this package’s use cases.
sensio/framework-extra-bundle or Doctrine annotations, it may be a legacy bridge. Symfony 7 removed annotation support; prefer attribute-based usage and verify compatibility before upgrading.422/Problem Details response.isAdmin, ownership IDs, or entity relations directly.$request->query, $request->request, route attributes, or JSON content. If it does not decode JSON, use native MapRequestPayload for JSON APIs.?page=abc may fail during DTO construction before validation runs. Use nullable/string DTO properties with constraints if you need consistent 422 responses, or catch conversion exceptions.?filters[status]=open or ?ids[]=1&ids[]=2 may require serializer support, normalizers, or manual post-processing. Start with flat DTOs.bin/console debug:container --tag=request.param_converter, clear cache, and verify the attribute namespace and bundle registration.How can I help you explore Laravel packages today?