Skip to content

Commit 0865332

Browse files
authored
Merge pull request #739 from api-platform/2.4
merging
2 parents 6139498 + 63cef67 commit 0865332

22 files changed

+515
-110
lines changed

client-generator/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Client Generator works especially well with APIs built with the [API Platform](h
2020
* list view (with pagination)
2121
* detail view
2222
* creation form
23-
* edition form
23+
* editation form
2424
* delete button
2525
* Supports to-one and to-many relations
2626
* Uses the appropriate input type (`number`, `date`...)

core/configuration.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ api_platform:
2424
# Specify a path name generator to use.
2525
path_segment_name_generator: 'api_platform.path_segment_name_generator.underscore'
2626

27+
# Allow using plain IDs for JSON format
28+
allow_plain_identifiers: false
29+
30+
doctrine:
31+
# To enable or disable Doctrine ORM support.
32+
enabled: true
33+
34+
doctrine_mongodb_odm:
35+
# To enable or disable Doctrine MongoDB ODM support.
36+
enabled: false
37+
2738
eager_loading:
2839
# To enable or disable eager loading.
2940
enabled: true

core/data-persisters.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ process](serialization.md).
77

88
A data persister using [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html) is included with the library and
99
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).
1011

1112
However, you may want to:
1213

13-
* store data to other persistence layers (ElasticSearch, MongoDB, external web services...)
14+
* store data to other persistence layers (ElasticSearch, external web services...)
1415
* not publicly expose the internal model mapped with the database through the API
1516
* 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)
1617

core/dto.md

Lines changed: 197 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Using Data Transfer Objects (DTOs)
22

3-
## Specifying an Input or an Output Class
3+
## Specifying an Input or an Output data representation
44

55
For a given resource class, you may want to have a different representation of this class as input (write) or output (read).
66
To do so, a resource can take an input and/or an output class:
@@ -17,100 +17,126 @@ use App\Dto\BookOutput;
1717

1818
/**
1919
* @ApiResource(
20-
* inputClass=BookInput::class,
21-
* outputClass=BookOutput::class
20+
* input=BookInput::class,
21+
* output=BookOutput::class
2222
* )
2323
*/
2424
final class Book
2525
{
2626
}
2727
```
2828

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`.
3131

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).
3333

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:
3635

37-
The `input_class` and `output_class` attributes are taken into account by all the documentation generators (GraphQL and OpenAPI, Hydra).
36+
![Diagram post input output](images/diagrams/api-platform-post-i-o.png)
3837

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.
4039

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`:
4441

45-
## Creating a Service-Oriented endpoint
42+
```php
43+
<?php
44+
// src/Dto/BookInput.php
4645

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;
4947

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:
5154

5255
```php
5356
<?php
54-
// api/src/Entity/ResetPasswordRequest.php
57+
// src/DataTransformer/BookInputDataTransformer.php
5558

56-
namespace App\Entity;
59+
namespace App\DataTransformer;
5760

58-
use ApiPlatform\Core\Annotation\ApiResource;
59-
use Symfony\Component\Validator\Constraints as Assert;
61+
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
62+
use App\Dto\BookInput;
6063

61-
/**
62-
* @ApiResource(
63-
* collectionOperations={
64-
* "post"={
65-
* "path"="/users/forgot-password-request"
66-
* },
67-
* },
68-
* itemOperations={},
69-
* outputClass=false
70-
* )
71-
*/
72-
final class ResetPasswordRequest
64+
final class BookInputDataTransformer implements DataTransformerInterface
7365
{
7466
/**
75-
* @var string
76-
*
77-
* @Assert\NotBlank
67+
* {@inheritdoc}
68+
*/
69+
public function transform($data, string $to, array $context = [])
70+
{
71+
$book = new Book();
72+
$book->isbn = $data->isbn;
73+
return $book;
74+
}
75+
76+
/**
77+
* {@inheritdoc}
7878
*/
79-
public $username;
79+
public function supportsTransformation($data, string $to, array $context = []): bool
80+
{
81+
return Book::class === $to && $data instanceof BookInput;
82+
}
8083
}
8184
```
8285

83-
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:
8487

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+
```
8696

87-
Create the data persister:
97+
To manage the output, it's exactly the same process. For example with `BookOutput` being:
8898

8999
```php
90100
<?php
91-
// api/src/DataPersister/ResetPasswordRequestDataPersister.php
92-
****
93-
namespace App\DataPersister;
101+
// src/Dto/BookOutput.php
94102

95-
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
96-
use App\Entity\ResetPasswordRequest;
103+
namespace App\Dto;
97104

98-
final class ResetPasswordRequestDataPersister implements DataPersisterInterface
105+
final class BookOutput {
106+
public $name;
107+
}
108+
```
109+
110+
We can transform the `Book` to a `BookOutput` object:
111+
112+
```php
113+
<?php
114+
// src/DataTransformer/BookOutputDataTransformer.php
115+
116+
namespace App\DataTransformer;
117+
118+
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
119+
use App\Dto\BookOutput;
120+
use App\Entity\Book;
121+
122+
final class BookOutputDataTransformer implements DataTransformerInterface
99123
{
100-
public function supports($data): bool
101-
{
102-
return $data instanceof ResetPasswordRequest;
103-
}
104-
105-
public function persist($data)
124+
/**
125+
* {@inheritdoc}
126+
*/
127+
public function transform($data, string $to, array $context = [])
106128
{
107-
// Trigger your custom logic here
108-
return $data;
129+
$output = new BookOutput();
130+
$output->name = $data->name;
131+
return $output;
109132
}
110-
111-
public function remove($data)
133+
134+
/**
135+
* {@inheritdoc}
136+
*/
137+
public function supportsTransformation($data, string $to, array $context = []): bool
112138
{
113-
throw new \RuntimeException('"remove" is not supported');
139+
return BookOutput::class === $to && $data instanceof Book;
114140
}
115141
}
116142
```
@@ -121,9 +147,120 @@ And register it:
121147
# api/config/services.yaml
122148
services:
123149
# ...
124-
'App\DataPersister\ResetPasswordRequestDataPersister': ~
150+
'App\DataTransformer\BookOutputDataTransformer': ~
125151
# Uncomment only if autoconfiguration is disabled
126-
#tags: [ 'api_platform.data_persister' ]
152+
#tags: [ 'api_platform.data_transformer' ]
153+
```
154+
155+
## Updating a resource with a custom input
156+
157+
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+
![Diagram put input output](images/diagrams/api-platform-put-i-o.png)
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.
180+
181+
```
182+
<?php
183+
// src/DataTransformer/BookInputDataTransformer.php
184+
185+
namespace App\DataTransformer;
186+
187+
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
188+
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
189+
use App\Dto\BookInput;
190+
191+
final class BookInputDataTransformer implements DataTransformerInterface
192+
{
193+
/**
194+
* {@inheritdoc}
195+
*/
196+
public function transform($data, string $to, array $context = [])
197+
{
198+
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE];
199+
$existingBook->author = $data->author;
200+
return $existingBook;
201+
}
202+
203+
/**
204+
* {@inheritdoc}
205+
*/
206+
public function supportsTransformation($data, string $to, array $context = []): bool
207+
{
208+
return Book::class === $to && $data instanceof BookInput;
209+
}
210+
}
211+
```
212+
213+
```yaml
214+
# api/config/services.yaml
215+
services:
216+
# ...
217+
'App\DataTransformer\BookInputDataTransformer': ~
218+
# Uncomment only if autoconfiguration is disabled
219+
#tags: [ 'api_platform.data_transformer' ]
220+
```
221+
222+
## Disabling the Input or the Output
223+
224+
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+
}
127264
```
128265

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.

core/events.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ feature](http://symfony.com/doc/current/components/dependency_injection/autowiri
6868

6969
Alternatively, [the subscriber must be registered manually](http://symfony.com/doc/current/components/http_kernel/introduction.html#creating-an-event-listener).
7070

71-
[Doctrine events](http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-lifecycle-events)
71+
Doctrine events ([ORM](http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-lifecycle-events), [MongoDB ODM](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/events.html#lifecycle-events))
7272
are also available (if you use it) if you want to hook at the object lifecycle events.
7373

7474
Built-in event listeners are:

core/extensions.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
API Platform Core provides a system to extend queries on items and collections.
44

5-
Extensions are specific to Doctrine and Elasticsearch-PHP, and therefore, the Doctrine ORM support or the Elasticsearch
5+
Extensions are specific to Doctrine and Elasticsearch-PHP, and therefore, the Doctrine ORM / MongoDB ODM support or the Elasticsearch
66
reading support must be enabled to use this feature. If you use custom providers it's up to you to implement your own
77
extension system or not.
88

@@ -152,6 +152,19 @@ security:
152152
- { path: ^/users, roles: IS_AUTHENTICATED_FULLY }
153153
```
154154

155+
## Custom Doctrine MongoDB ODM Extension
156+
157+
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+
155168
## Custom Elasticsearch Extension
156169

157170
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

Comments
 (0)