# Handling Data Transfer Objects (DTOs) ## How to Use a DTO for Writing Sometimes it's easier to use a DTO than an Entity when performing simple operation. For example, the application should be able to send an email when someone has lost its password. So let's create a basic DTO for this request: ```php userManager = $userManager; } public static function getSubscribedEvents() { return [ KernelEvents::VIEW => ['sendPasswordReset', EventPriorities::POST_VALIDATE], ]; } public function sendPasswordReset(GetResponseForControllerResultEvent $event) { $request = $event->getRequest(); if ('api_forgot_password_requests_post_collection' !== $request->attributes->get('_route')) { return; } $forgotPasswordRequest = $event->getControllerResult(); $user = $this->userManager->findOneByEmail($forgotPasswordRequest->email); // We do nothing if the user does not exist in the database if ($user) { $this->userManager->requestPasswordReset($user); } $event->setResponse(new JsonResponse(null, 204)); } } ``` Then this class should be registered as a service, then tagged. If service autowiring and autoconfiguration are enabled (it's the case by default), you are done! Otherwise, the following configuration is needed: ```yaml # api/config/services.yaml services: # ... 'App\Api\EventSubscriber\UserSubscriber': arguments: - '@app.manager.user' # Uncomment the following line only if you don't use autoconfiguration #tags: [ 'kernel.event_subscriber' ] ``` ## How to Use a DTO for Reading Sometimes, you need to retrieve data not related to an entity. For example, the application can send the [list of supported locales](https://github.com/symfony/demo/blob/master/config/services.yaml#L6) and the default locale. So let's create a basic DTO for this datas: ```php locales = explode('|', $this->getParameter('app_locales')); $response->defaultLocale = $this->getParameter('locale'); return $response; } } ``` As you can see, the controller doesn't return a `Response`, but the data object directly. Behind the scene, the `SerializeListener` catch the response, and thanks to the `_api_respond` flag, it serializes the object correctly. To deal with arrays, we have to set the `api_sub_level` context option to `true`. It prevents API Platform's normalizers to look for a non-existing class marked as an API resource. ### Adding this Custom DTO reading in Swagger Documentation. By default, ApiPlatform Swagger UI integration will display documentation only for ApiResource operations. In this case, our DTO is not declared as ApiResource, so no documentation will be displayed. There is two solutions to achieve that: #### Use Swagger Decorator By following the doc about [Override the Swagger Documentation](swagger.md#overriding-the-swagger-documentation) and adding the ability to retrieve a `_api_swagger_context` in route parameters, you should be able to display your custom endpoint. ```php decorated = $decorated; $this->router = $router; } public function normalize($object, $format = null, array $context = []) { $docs = $this->decorated->normalize($object, $format, $context); $mimeTypes = $object->getMimeTypes(); foreach ($this->router->getRouteCollection()->all() as $routeName => $route) { $swaggerContext = $route->getDefault('_api_swagger_context'); if (!$swaggerContext) { // No swagger_context set, continue continue; } $methods = $route->getMethods(); $uri = $route->getPath(); foreach ($methods as $method) { // Add available mimesTypes $swaggerContext['produces'] ?? $swaggerContext['produces'] = $mimeTypes; $docs['paths'][$uri][\strtolower($method)] = $swaggerContext; } } return $docs; } public function supportsNormalization($data, $format = null) { return $this->decorated->supportsNormalization($data, $format); } } ``` Register it as a service: ```yaml #config/services.yaml # ... 'App\Swagger\ControllerSwaggerDecorator': decorates: 'api_platform.swagger.normalizer.documentation' arguments: [ '@App\Swagger\ControllerSwaggerDecorator.inner'] autoconfigure: false ``` And finally, complete the Route annotation of your controller like this: ```php