dualmedia/symfony-request-dto-bundle
Installation
composer require dualmedia/symfony-request-dto-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Dualmedia\SymfonyRequestDtoBundle\DualmediaSymfonyRequestDtoBundle::class => ['all' => true],
];
First DTO Class
Create a DTO class (e.g., src/Dto/CreateUserDto.php):
use Dualmedia\SymfonyRequestDtoBundle\Annotation\Bag;
use Dualmedia\SymfonyRequestDtoBundle\Annotation\FindOneBy;
use Doctrine\ORM\Mapping as ORM;
#[Bag("query")]
class CreateUserDto
{
#[FindOneBy(targetEntity: User::class, property: "email")]
public ?User $user;
public string $name;
public int $age;
}
First Controller Usage Inject the DTO directly into a controller method:
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserController
{
#[Route("/users", methods: ["POST"])]
public function create(CreateUserDto $dto): Response
{
// $dto->user, $dto->name, $dto->age are automatically populated, validated, and typecast
return new Response("User created!");
}
}
Verify Auto-Wiring The bundle hooks into Symfony’s dependency injection. No manual extraction or validation wiring is needed.
#[Bag("body")]
class LoginDto
{
public string $email;
public string $password;
}
POST /login with {"email": "...", "password": "..."}).class AddressDto
{
public string $street;
public string $city;
}
class UserProfileDto
{
public string $name;
public AddressDto $address; // Nested DTO
}
{
"name": "John",
"address": {
"street": "123 Main",
"city": "Metropolis"
}
}
use Dualmedia\SymfonyRequestDtoBundle\Annotation\Collection;
class BulkUserDto
{
#[Collection]
public array $users; // Array of UserDto objects
}
POST /users/bulk with [{...}, {...}]).use Dualmedia\SymfonyRequestDtoBundle\Annotation\AsRoot;
#[AsRoot]
class FlatUserDto
{
public string $name;
public int $age;
}
POST /users with {"name": "...", "age": 10} instead of nested under a key).use Dualmedia\SymfonyRequestDtoBundle\Annotation\FindOneBy;
use App\Entity\User;
class UserReferenceDto
{
#[FindOneBy(targetEntity: User::class, property: "id")]
public ?User $user;
}
User entities from request fields (e.g., POST /posts with {"userId": 1}).use Dualmedia\SymfonyRequestDtoBundle\Annotation\Action;
class CustomActionDto
{
public string $field;
#[Action("custom.action")]
public function onResolve(): void
{
// Custom logic (e.g., logging, transformations)
}
}
use Symfony\Component\Validator\Constraints as Assert;
class RegisterDto
{
#[Assert\NotBlank]
#[Assert\Email]
public string $email;
#[Assert\Length(min: 8)]
public string $password;
}
#[Bag("headers")]
class ApiKeyDto
{
public string $apiKey;
}
X-API-KEY), query params, or route attributes.Symfony Bridge
Use symfony/http-foundation-bundle and symfony/dependency-injection as Laravel dependencies:
composer require symfony/http-foundation-bundle symfony/dependency-injection
Configure Laravel to load Symfony bundles via config/app.php:
'extra' => [
'bundles' => [
Dualmedia\SymfonyRequestDtoBundle\DualmediaSymfonyRequestDtoBundle::class,
],
],
Request Conversion
Convert Laravel’s Illuminate\Http\Request to Symfony’s Request in controllers:
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
public function __construct(private SymfonyRequest $request)
{
$this->request = $request;
}
Doctrine Integration Ensure Doctrine ORM is installed and configured in Laravel:
composer require doctrine/orm
Configure config/packages/doctrine.php (Symfony-style) or use Laravel’s Doctrine bridge.
Nelmio API Docs Install Nelmio’s bundle for OpenAPI/Swagger support:
composer require nelmio/api-doc-bundle
The DTO bundle auto-generates API docs for DTOs.
Event Listeners
Register Symfony events in Laravel’s EventServiceProvider:
protected $listeners = [
'kernel.request' => [
\Dualmedia\SymfonyRequestDtoBundle\EventListener\DtoListener::class,
],
];
Circular References in DTOs
UserDto referencing PostDto which references UserDto). Use lazy loading or break cycles with IDs.Case Sensitivity in Enums
#[BackedEnum] or #[Enum] annotations for clarity:
use Dualmedia\SymfonyRequestDtoBundle\Annotation\Enum;
class StatusDto
{
#[Enum]
public string $status; // "ACTIVE", "INACTIVE" (case-sensitive)
}
Doctrine Entity Not Found
#[FindOneBy] fails to load an entity, the DTO property becomes null. Handle this gracefully:
if ($dto->user === null) {
throw new \RuntimeException("User not found");
}
Validator Overrides
#[Assert\Valid] sparingly on nested DTOs.Profiler Panel Missing
web_profiler bundle. Install it if debugging:
composer require symfony/web-profiler-bundle
File Uploads
UploadedFile $avatar) require #[Bag("files")] and proper Symfony Request configuration. Laravel’s file handling may need adaptation.Nested Collections
array<array<Dto>>) may require explicit type hints:
#[Collection(type: UserDto::class)]
public array $users;
Enable Verbose Logging
Configure the bundle in config/packages/dualmedia_symfony_request_dto.yaml:
dualmedia_symfony_request_dto:
debug: true
Logs resolution steps to var/log/dev.log.
Check Resolution Events
Listen to DtoResolveEvent for debugging:
use Dualmedia\SymfonyRequestDtoBundle\Event\DtoResolveEvent;
$eventDispatcher->addListener(DtoResolveEvent::class, function (DtoResolveEvent $event) {
\Log::debug("Resolving DTO:", [
'dtoClass' => $event->getDtoClass(),
'source' => $event->getSource(),
'errors' => $event->getErrors
How can I help you explore Laravel packages today?