Skip to content

feat: add a documentation about the DataTransformerInitializer #1440

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 83 additions & 4 deletions core/dto.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Using Data Transfer Objects (DTOs)

As stated in [the general design considerations](design.md), in most cases [the DTO pattern](https://en.wikipedia.org/wiki/Data_transfer_object) should be implemented using an API Resource class representing the public data model exposed through the API and [a custom data provider](data-providers.md). In such cases, the class marked with `@ApiResource` will act as a DTO.
As stated in [the general design considerations](design.md), in most cases [the DTO pattern](https://en.wikipedia.org/wiki/Data_transfer_object) should be implemented using an API Resource class representing the public data model exposed through the API and [a custom data provider](data-providers.md). In such cases, the class marked with `@ApiResource` will act as a DTO.

However, it's sometimes useful to use a specific class to represent the input or output data structure related to an operation.

Expand Down Expand Up @@ -81,7 +81,8 @@ We have the following `BookInput`:

namespace App\Dto;

final class BookInput {
final class BookInput
{
/**
* @var string
*/
Expand Down Expand Up @@ -110,6 +111,7 @@ final class BookInputDataTransformer implements DataTransformerInterface
{
$book = new Book();
$book->isbn = $data->isbn;

return $book;
}

Expand Down Expand Up @@ -148,7 +150,8 @@ To manage the output, it's exactly the same process. For example, we have the fo

namespace App\Dto;

final class BookOutput {
final class BookOutput
{
/**
* @var string
*/
Expand Down Expand Up @@ -177,6 +180,7 @@ final class BookOutputDataTransformer implements DataTransformerInterface
{
$output = new BookOutput();
$output->name = $data->name;

return $output;
}

Expand Down Expand Up @@ -219,7 +223,8 @@ With the following `BookInput`:

namespace App\Dto;

final class BookInput {
final class BookInput
{
/**
* @var \App\Entity\Author
*/
Expand Down Expand Up @@ -248,6 +253,7 @@ final class BookInputDataTransformer implements DataTransformerInterface
{
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE];
$existingBook->author = $data->author;

return $existingBook;
}

Expand All @@ -274,6 +280,79 @@ services:
#tags: [ 'api_platform.data_transformer' ]
```

## Initialize the Input DTO For Partial Update

In order to be able to do a partial update (`PATCH`), it is needed to initialize the input DTO with the existing data before the deserialization process.

This way, the input DTO will be correctly validated with its old data and partial new data.

Create a class implementing the `DataTransformerInitializerInterface` instead of the `DataTransformerInterface`:

```php
<?php
// src/DataTransformer/BookInputDataTransformerInitializer.php

namespace App\DataTransformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface;
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
use App\Entity\Book;
use App\Dto\BookInput;

final class BookInputDataTransformerInitializer implements DataTransformerInitializerInterface
{
/**
* {@inheritdoc}
*/
public function transform($data, string $to, array $context = [])
{
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE];
$existingBook->author = $data->author;

return $existingBook;
}

/**
* {@inheritdoc}
*/
public function initialize(string $inputClass, array $context = [])
{
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE] ?? null;
if (!$existingBook) {
return new BookInput();
}

$bookInput = new BookInput();
$bookInput->author = $existingBook->author;

return $bookInput;
}

/**
* {@inheritdoc}
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
if ($data instanceof Book) {
return false;
}

return Book::class === $to && null !== ($context['input']['class'] ?? null);
}
}
```

Register it:

```yaml
# api/config/services.yaml
services:
# ...
'App\DataTransformer\BookInputDataTransformerInitializer': ~
# Uncomment only if autoconfiguration is disabled
#tags: [ 'api_platform.data_transformer' ]
```

## Disabling the Input or the Output

Both the `input` and the `output` attributes can be set to `false`. If `input` is `false`, the deserialization process
Expand Down