You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: core/data-persisters.md
+2-1Lines changed: 2 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -7,10 +7,11 @@ process](serialization.md).
7
7
8
8
A data persister using [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html) is included with the library and
9
9
is enabled by default. It is able to persist and delete objects that are also mapped as [Doctrine entities](https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/basic-mapping.html).
10
+
A [Doctrine MongoDB ODM](https://www.doctrine-project.org/projects/mongodb-odm.html) data persister is also included and can be enabled by following the [MongoDB documentation](mongodb.md).
10
11
11
12
However, you may want to:
12
13
13
-
* store data to other persistence layers (ElasticSearch, MongoDB, external web services...)
14
+
* store data to other persistence layers (ElasticSearch, external web services...)
14
15
* not publicly expose the internal model mapped with the database through the API
15
16
* use a separate model for [read operations](data-providers.md) and for updates by implementing patterns such as [CQRS](https://martinfowler.com/bliki/CQRS.html)
## Specifying an Input or an Output data representation
4
4
5
5
For a given resource class, you may want to have a different representation of this class as input (write) or output (read).
6
6
To do so, a resource can take an input and/or an output class:
@@ -17,100 +17,126 @@ use App\Dto\BookOutput;
17
17
18
18
/**
19
19
* @ApiResource(
20
-
* inputClass=BookInput::class,
21
-
* outputClass=BookOutput::class
20
+
* input=BookInput::class,
21
+
* output=BookOutput::class
22
22
* )
23
23
*/
24
24
final class Book
25
25
{
26
26
}
27
27
```
28
28
29
-
The `input_class` attribute is used during [the deserialization process](serialization.md), when transforming the user provided data to a resource instance.
30
-
Similarly, the `output_class` attribute is used during the serialization process, this class represents how the `Book` resource will be represented in the `Response`.
29
+
The `input` attribute is used during [the deserialization process](serialization.md), when transforming the user provided data to a resource instance.
30
+
Similarly, the `output` attribute is used during [the serialization process](serialization.md), this class represents how the `Book` resource will be represented in the `Response`.
31
31
32
-
To create a `Book`, we `POST` a data structure corresponding to the `BookInput` class and get back in the response a data structure corresponding to the `BookOuput` class.
32
+
The `input` and `output` attributes are taken into account by all the documentation generators (GraphQL and OpenAPI, Hydra).
33
33
34
-
To persist the input object, a custom [data persister](data-persisters.md) handling `BookInput` instances must be written.
35
-
To retrieve an instance of the output class, a custom [data provider](data-providers.md) returning a `BookOutput` instance must be written.
34
+
To create a `Book`, we `POST` a data structure corresponding to the `BookInput` class and get back in the response a data structure corresponding to the `BookOuput` class:
36
35
37
-
The `input_class` and `output_class` attributes are taken into account by all the documentation generators (GraphQL and OpenAPI, Hydra).
36
+

38
37
39
-
## Disabling the Input or the Output
38
+
To simplify object transformations we have to implement a Data Transformer that will convert the input into a resource or a resource into an output.
40
39
41
-
Both the `input_class` and the `output_class` attributes can be set to `false`.
42
-
If `input_class` is `false`, the deserialization process will be skipped, and no data persisters will be called.
43
-
If `output_class` is `false`, the serialization process will be skipped, and no data providers will be called.
40
+
With the following `BookInput`:
44
41
45
-
## Creating a Service-Oriented endpoint
42
+
```php
43
+
<?php
44
+
// src/Dto/BookInput.php
46
45
47
-
Sometimes it's convenient to create [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call)-like endpoints.
48
-
For example, the application should be able to send an email when someone has lost his password.
46
+
namespace App\Dto;
49
47
50
-
So let's create a basic DTO for this request:
48
+
final class BookInput {
49
+
public $isbn;
50
+
}
51
+
```
52
+
53
+
We can transform the `BookInput` to a `Book` resource instance:
In this case, we disable all operations except `POST`. We also set the `output_class` attribute to `false` to hint API Platform that no data will be returned by this endpoint.
86
+
And register it:
84
87
85
-
Then, thanks to [a custom data persister](data-persisters.md), it's possible to trigger some custom logic when the request is received.
88
+
```yaml
89
+
# api/config/services.yaml
90
+
services:
91
+
# ...
92
+
'App\DataTransformer\BookInputDataTransformer': ~
93
+
# Uncomment only if autoconfiguration is disabled
94
+
#tags: [ 'api_platform.data_transformer' ]
95
+
```
86
96
87
-
Create the data persister:
97
+
To manage the output, it's exactly the same process. For example with `BookOutput` being:
When performing an update (e.g. `PUT` operation), the resource to be updated is read by ApiPlatform before the deserialization phase. To do so, it uses a [data provider](data-providers.md) with the `:id` parameter given in the URL. The *body* of the request is the JSON object sent by the client, it is deserialized and is used to update the previously found resource.
158
+
159
+

160
+
161
+
Now, we will update our resource by using a different input representation.
162
+
163
+
With the following `BookInput`:
164
+
165
+
```
166
+
<?php
167
+
// src/Dto/BookInput.php
168
+
169
+
namespace App\Dto;
170
+
171
+
final class BookInput {
172
+
/**
173
+
* @var \App\Entity\Author
174
+
*/
175
+
public $author;
176
+
}
177
+
```
178
+
179
+
We will implement a `BookInputDataTransformer` that transforms the `BookInput` to our `Book` resource instance. In this case, the `Book` (`/books/1`) already exists, so we will just update it.
Both the `input` and the `output` attributes can be set to `false`.
225
+
If `input` is `false`, the deserialization process will be skipped, and no data persisters will be called.
226
+
If `output` is `false`, the serialization process will be skipped, and no data providers will be called.
227
+
228
+
## Input/Output metadata
229
+
230
+
When specified, `input` and `output` attributes support:
231
+
- a string representing the class to use
232
+
- a falsy boolean to disable them
233
+
- an array to specify more metadata for example `['class' => BookInput::class, 'name' => 'BookInput', 'iri' => '/book_input']`
234
+
235
+
236
+
## Using DTO objects inside resources
237
+
238
+
Because ApiPlatform can (de)normalize anything in the supported formats (`jsonld`, `jsonapi`, `hal`, etc.), you can use any object you want inside resources. For example, let's say that the `Book` has an `attribute` property that can't be represented by a resource, we can do the following:
239
+
240
+
```php
241
+
<?php
242
+
// api/src/Entity/Book.php
243
+
244
+
namespace App\Entity;
245
+
246
+
use ApiPlatform\Core\Annotation\ApiResource;
247
+
use App\Dto\Attribute;
248
+
249
+
/**
250
+
* @ApiResource(
251
+
* input=BookInput::class,
252
+
* output=BookOutput::class
253
+
* )
254
+
*/
255
+
final class Book
256
+
{
257
+
/**
258
+
* @var Attribute
259
+
**/
260
+
public $attribute;
261
+
262
+
public $isbn;
263
+
}
127
264
```
128
265
129
-
Instead of a custom data persister, you'll probably want to leverage [the Symfony Messenger Component integration](messenger.md).
266
+
The `Book``attribute` property will now be an instance of `Attribute` after the (de)normalization phase.
Alternatively, [the subscriber must be registered manually](http://symfony.com/doc/current/components/http_kernel/introduction.html#creating-an-event-listener).
Creating custom extensions is the same as Doctrine ORM.
158
+
159
+
The interfaces are:
160
+
* `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface` and `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface` to add stages to the [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/aggregation-builder.html).
161
+
* `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface` and `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface` to return a result.
162
+
163
+
The tags are `api_platform.doctrine.mongodb.aggregation_extension.item` and `api_platform.doctrine.mongodb.aggregation_extension.collection`.
164
+
165
+
The custom extensions receive the [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/aggregation-builder.html),
166
+
used to execute [complex operations on data](https://docs.mongodb.com/manual/aggregation/).
167
+
155
168
## Custom Elasticsearch Extension
156
169
157
170
Currently only extensions querying for a collection of items through a [search request](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html)
0 commit comments