1
1
# Handling Data Transfer Objects (DTOs)
2
2
3
- ## How to use a DTO for Writing
3
+ ## How to Use a DTO for Writing
4
4
5
5
Sometimes it's easier to use a DTO than an Entity when performing simple
6
6
operation. For example, the application should be able to send an email when
@@ -9,6 +9,7 @@ someone has lost its password.
9
9
So let's create a basic DTO for this request:
10
10
11
11
``` php
12
+ <?php
12
13
// api/src/Api/Dto/ForgotPasswordRequest.php
13
14
14
15
namespace App\Api\Dto;
@@ -122,3 +123,230 @@ services:
122
123
# Uncomment the following line only if you don't use autoconfiguration
123
124
# tags: [ 'kernel.event_subscriber' ]
124
125
```
126
+
127
+ ## How to Use a DTO for Reading
128
+
129
+ Sometimes, you need to retrieve data not related to an entity.
130
+ For example, the application can send the
131
+ [ list of supported locales] ( https://github.com/symfony/demo/blob/master/config/services.yaml#L6 )
132
+ and the default locale.
133
+
134
+ So let's create a basic DTO for this datas:
135
+
136
+ ``` php
137
+ <?php
138
+ // api/src/Dto/LocalesList.php
139
+
140
+ namespace App\Dto;
141
+
142
+ final class LocalesList
143
+ {
144
+ /**
145
+ * @var array
146
+ */
147
+ public $locales;
148
+
149
+ /**
150
+ * @var string
151
+ */
152
+ public $defaultLocale;
153
+ }
154
+ ```
155
+
156
+ And create a controller to send them:
157
+
158
+ ``` php
159
+ <?php
160
+ // api/src/Controller/LocaleController.php
161
+
162
+ namespace App\Controller;
163
+
164
+ use App\DTO\LocalesList;
165
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
166
+ use Symfony\Component\Routing\Annotation\Route;
167
+
168
+ class LocaleController extends AbstractController
169
+ {
170
+ /**
171
+ * @Route(
172
+ * path="/api/locales",
173
+ * name="api_get_locales",
174
+ * methods={"GET"},
175
+ * defaults={
176
+ * "_api_respond"=true,
177
+ * "_api_normalization_context"={"api_sub_level"=true}
178
+ * }
179
+ * )
180
+ */
181
+ public function __invoke(): LocalesDTO
182
+ {
183
+ $response = new LocalesList();
184
+ $response->locales = explode('|', $this->getParameter('app_locales'));
185
+ $response->defaultLocale = $this->getParameter('locale');
186
+
187
+ return $response;
188
+ }
189
+ }
190
+ ```
191
+
192
+ As you can see, the controller doesn't return a ` Response ` , but the data object directly.
193
+ Behind the scene, the ` SerializeListener ` catch the response, and thanks to the ` _api_respond `
194
+ flag, it serializes the object correctly.
195
+
196
+ To deal with arrays, we have to set the ` api_sub_level ` context option to ` true ` .
197
+ It prevents API Platform's normalizers to look for a non-existing class marked as an API resource.
198
+
199
+ ### Adding this Custom DTO reading in Swagger Documentation.
200
+
201
+ By default, ApiPlatform Swagger UI integration will display documentation only
202
+ for ApiResource operations.
203
+ In this case, our DTO is not declared as ApiResource, so no documentation will
204
+ be displayed.
205
+
206
+ There is two solutions to achieve that:
207
+
208
+ #### Use Swagger Decorator
209
+
210
+ By following the doc about [ Override the Swagger Documentation] ( swagger.md##overriding-the-swagger-documentation )
211
+ and adding the ability to retrieve a ` _api_swagger_context ` in route
212
+ parameters, you should be able to display your custom endpoint.
213
+
214
+ ``` php
215
+ <?php
216
+ // src/App/Swagger/ControllerSwaggerDecorator
217
+
218
+ namespace App\Swagger;
219
+
220
+ use Symfony\Component\Routing\RouterInterface;
221
+ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
222
+
223
+ final class ControllerSwaggerDecorator implements NormalizerInterface
224
+ {
225
+ private $decorated;
226
+
227
+ private $router;
228
+
229
+ public function __construct(
230
+ NormalizerInterface $decorated,
231
+ RouterInterface $router
232
+ ) {
233
+ $this->decorated = $decorated;
234
+ $this->router = $router;
235
+ }
236
+
237
+ public function normalize($object, $format = null, array $context = [])
238
+ {
239
+ $docs = $this->decorated->normalize($object, $format, $context);
240
+ $mimeTypes = $object->getMimeTypes();
241
+ foreach ($this->router->getRouteCollection()->all() as $routeName => $route) {
242
+ $swaggerContext = $route->getDefault('_api_swagger_context');
243
+ if (!$swaggerContext) {
244
+ // No swagger_context set, continue
245
+ continue;
246
+ }
247
+
248
+ $methods = $route->getMethods();
249
+ $uri = $route->getPath();
250
+
251
+ foreach ($methods as $method) {
252
+ // Add available mimesTypes
253
+ $swaggerContext['produces'] ?? $swaggerContext['produces'] = $mimeTypes;
254
+
255
+ $docs['paths'][$uri][\strtolower($method)] = $swaggerContext;
256
+ }
257
+ }
258
+
259
+ return $docs;
260
+ }
261
+
262
+ public function supportsNormalization($data, $format = null)
263
+ {
264
+ return $this->decorated->supportsNormalization($data, $format);
265
+ }
266
+ }
267
+ ```
268
+
269
+ Register it as a service:
270
+
271
+ ``` yaml
272
+ # config/services.yaml
273
+ # ...
274
+ ' App\Swagger\ControllerSwaggerDecorator ' :
275
+ decorates : ' api_platform.swagger.normalizer.documentation'
276
+ arguments : [ '@App\Swagger\ControllerSwaggerDecorator.inner']
277
+ autoconfigure : false
278
+ ` ` `
279
+
280
+ And finally, complete the Route annotation of your controller like this:
281
+
282
+ ` ` ` php
283
+ <?php
284
+ // api/src/Controller/LocaleController.php
285
+
286
+ use Nelmio\ApiDocBundle\Annotation\Model;
287
+ use Swagger\Annotations as SWG;
288
+
289
+ //...
290
+
291
+ /**
292
+ * @Route(
293
+ * path="/api/locales",
294
+ * name="api_get_locales",
295
+ * methods={"GET"},
296
+ * defaults={
297
+ * "_api_respond"=true,
298
+ * "_api_normalization_context"={"api_sub_level"=true},
299
+ * "_api_swagger_context"={
300
+ * "tags"={"Locales"},
301
+ * "summary"="Retrieve locales availables",
302
+ * "parameters"={},
303
+ * "responses"={
304
+ * "200"={
305
+ * "description"="List of available locales and the default locale",
306
+ * "schema"={
307
+ * "type"="object",
308
+ * "properties"={
309
+ * "defaultLocale"={"type"="string"},
310
+ * }
311
+ * }
312
+ * }
313
+ * }
314
+ * }
315
+ * }
316
+ * )
317
+ */
318
+ public function __invoke()
319
+ ```
320
+
321
+ #### Use [ NelmioApiDoc] ( nelmio-api-doc.md )
322
+
323
+ With NelmioApiDoc, you should be able to add annotations to your controllers :
324
+
325
+ ``` php
326
+ <?php
327
+ // api/src/Controller/LocaleController.php
328
+
329
+ use Nelmio\ApiDocBundle\Annotation\Model;
330
+ use Swagger\Annotations as SWG;
331
+
332
+ //...
333
+
334
+ /**
335
+ * @Route(
336
+ * path="/api/locales",
337
+ * name="api_get_locales",
338
+ * methods={"GET"},
339
+ * defaults={
340
+ * "_api_respond"=true,
341
+ * "_api_normalization_context"={"api_sub_level"=true}
342
+ * }
343
+ * )
344
+ * @SWG\Tag(name="Locales")
345
+ * @SWG\Response(
346
+ * response=200,
347
+ * description="List of available locales and the default locale",
348
+ * @SWG\Schema(ref=@Model(type=LocalesList::class)),
349
+ * )
350
+ */
351
+ public function __invoke()
352
+ ```
0 commit comments