Skip to content

Commit c815dc8

Browse files
committed
Merge branch '2.6' into merge-2.6
2 parents 1df6938 + 28fd89d commit c815dc8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+741
-531
lines changed

.proselintrc renamed to .proselintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"typography.symbols": false,
44
"typography.exclamation": false,
55
"hyperbole.misc": false,
6-
"cliches.misc": false
6+
"cliches.misc": false,
7+
"lexical_illusions.misc": false
78
}
89
}

admin/file-upload.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ export default () => (
3333
```
3434

3535
And that's it!
36-
You don't need to decorate the data provider if you are using the Hydra one: it detects that you have used a `FileInput` and uses a `multipart/form-data` request instead of a JSON-LD one.
36+
The guessers are able to detect that you have used a `FileInput` and are passing this information to the data provider, through a `hasFileField` field in the `extraInformation` object, itself in the data.
37+
If you are using the Hydra data provider, it uses a `multipart/form-data` request instead of a JSON-LD one.
38+
In the case of the `EditGuesser`, the HTTP method used also becomes a `POST` instead of a `PUT`, to prevent a [PHP bug](https://bugs.php.net/bug.php?id=55815).

admin/performance.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use Doctrine\ORM\Mapping as ORM;
2727
/**
2828
* @ORM\Entity
2929
*/
30-
#[ApiResource]
30+
#[ApiResource]
3131
class Author
3232
{
3333
/**

admin/validation.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ use Symfony\Component\Validator\Constraints as Assert;
2020
#[ApiResource]
2121
class Book
2222
{
23-
/**
24-
* @Assert\NotBlank
25-
*/
23+
#[Assert\NotBlank]
2624
public ?string $title = null;
2725
}
2826
```
@@ -54,9 +52,7 @@ use Symfony\Component\Validator\Constraints as Assert;
5452
#[ApiResource]
5553
class Book
5654
{
57-
/**
58-
* @Assert\Isbn
59-
*/
55+
#[Assert\Isbn]
6056
public ?string $isbn = null;
6157
}
6258
```

core/configuration.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ api_platform:
1515
# The version of the API.
1616
version: '0.0.0'
1717

18-
# Return IRIs as Absolute URLs.
19-
# ex: http://example.com/authors/1
20-
absolute_url: false
21-
2218
# Set this to false if you want Webby to disappear.
2319
show_webby: true
2420

core/content-negotiation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ api_platform:
9797
Support for specific formats can also be configured at resource and operation level using the `input_formats` and `output_formats` attributes.
9898
`input_formats` controls the formats accepted in request bodies while `output_formats` controls formats available for responses.
9999

100-
The `format` attribute can be used as a shortcut, it sets both the `input_formats` and `output_formats` in one time.
100+
The `formats` attribute can be used as a shortcut, it sets both the `input_formats` and `output_formats` in one time.
101101

102102
```php
103103
<?php

core/controllers.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,12 +378,9 @@ use Symfony\Component\Routing\Annotation\Route;
378378
#[AsController]
379379
class CreateBookPublication extends AbstractController
380380
{
381-
private $bookPublishingHandler;
382-
383-
public function __construct(BookPublishingHandler $bookPublishingHandler)
384-
{
385-
$this->bookPublishingHandler = $bookPublishingHandler;
386-
}
381+
public function __construct(
382+
private BookPublishingHandler $bookPublishingHandler
383+
) {}
387384

388385
#[Route(
389386
name: 'book_post_publication',

core/data-persisters.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ final class BlogPostDataPersister implements ContextAwareDataPersisterInterface
5252

5353
public function persist($data, array $context = [])
5454
{
55-
// call your persistence layer to save $data
56-
return $data;
55+
// call your persistence layer to save $data
56+
return $data;
5757
}
5858

5959
public function remove($data, array $context = [])
6060
{
61-
// call your persistence layer to delete $data
61+
// call your persistence layer to delete $data
6262
}
6363
}
6464
```
@@ -173,13 +173,13 @@ final class BlogPostDataPersister implements ContextAwareDataPersisterInterface,
173173

174174
public function persist($data, array $context = [])
175175
{
176-
// call your persistence layer to save $data
177-
return $data;
176+
// call your persistence layer to save $data
177+
return $data;
178178
}
179179

180180
public function remove($data, array $context = [])
181181
{
182-
// call your persistence layer to delete $data
182+
// call your persistence layer to delete $data
183183
}
184184

185185
// Once called this data persister will resume to the next one
@@ -190,7 +190,7 @@ final class BlogPostDataPersister implements ContextAwareDataPersisterInterface,
190190
}
191191
```
192192

193-
This is very useful when using [`Messenger` with API Platform](messenger.md) as you may want to do something asynchronously with the data but still call the default Doctrine data persister, for example:
193+
This is useful when using [`Messenger` with API Platform](messenger.md) as you may want to do something asynchronously with the data but still call the default Doctrine data persister, for example:
194194

195195
```php
196196
namespace App\DataPersister;

core/data-providers.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ retrieve data for a given resource will be used.
1313

1414
For a given resource, you can implement two kinds of interface:
1515

16-
* the [`CollectionDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/DataProvider/CollectionDataProviderInterface.php)
16+
* the [`CollectionDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/Core/DataProvider/CollectionDataProviderInterface.php)
1717
is used when fetching a collection.
18-
* the [`ItemDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/DataProvider/ItemDataProviderInterface.php)
18+
* the [`ItemDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/Core/DataProvider/ItemDataProviderInterface.php)
1919
is used when fetching items.
2020

2121
Both implementations can also implement a third, optional, interface called
22-
['RestrictedDataProviderInterface'](https://github.com/api-platform/core/blob/main/src/DataProvider/RestrictedDataProviderInterface.php)
22+
['RestrictedDataProviderInterface'](https://github.com/api-platform/core/blob/main/src/Core/DataProvider/RestrictedDataProviderInterface.php)
2323
if you want to limit their effects to a single resource or operation.
2424

2525
In the following examples we will create custom data providers for an entity class called `App\Entity\BlogPost`.
26-
Note, that if your entity is not Doctrine-related, you need to flag the identifier property by using `#[ApiProperty(identifier: true)]` for things to work properly (see also [Entity Identifier Case](serialization.md#entity-identifier-case)).
26+
Note, that if your entity is not Doctrine-related, you need to flag the identifier property by using `#[ApiProperty(identifier: true)` for things to work properly (see also [Entity Identifier Case](serialization.md#entity-identifier-case)).
2727

2828
## Custom Collection Data Provider
2929

@@ -33,9 +33,9 @@ If the [Symfony MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMake
3333
bin/console make:data-provider --collection-only
3434
```
3535

36-
First, your `BlogPostCollectionDataProvider` has to implement the [`CollectionDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/DataProvider/CollectionDataProviderInterface.php):
36+
First, your `BlogPostCollectionDataProvider` has to implement the [`CollectionDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/Core/DataProvider/CollectionDataProviderInterface.php):
3737

38-
The `getCollection` method must return an `array`, a `Traversable` or a [`ApiPlatform\Core\DataProvider\PaginatorInterface`](https://github.com/api-platform/core/blob/main/src/DataProvider/PaginatorInterface.php) instance.
38+
The `getCollection` method must return an `array`, a `Traversable` or a [`ApiPlatform\Core\DataProvider\PaginatorInterface`](https://github.com/api-platform/core/blob/main/src/Core/DataProvider/PaginatorInterface.php) instance.
3939
If no data is available, you should return an empty array.
4040

4141
```php
@@ -91,7 +91,7 @@ If the [Symfony MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMake
9191
bin/console make:data-provider --item-only
9292
```
9393

94-
The process is similar for item data providers. Create a `BlogPostItemDataProvider` implementing the [`ItemDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/DataProvider/ItemDataProviderInterface.php)
94+
The process is similar for item data providers. Create a `BlogPostItemDataProvider` implementing the [`ItemDataProviderInterface`](https://github.com/api-platform/core/blob/main/src/Core/DataProvider/ItemDataProviderInterface.php)
9595
interface:
9696

9797
The `getItem` method can return `null` if no result has been found.

core/default-order.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ namespace App\Entity;
5050
5151
use ApiPlatform\Metadata\ApiResource;
5252
53-
#[ApiResource(order: ['foo' => 'bar'])]
53+
#[ApiResource(order: ['foo', 'bar'])]
5454
class Book
5555
{
5656
// ...
@@ -126,8 +126,8 @@ use ApiPlatform\Metadata\ApiResource;
126126
127127
#[ApiResource]
128128
#[GetCollection]
129-
#[GetCollection(name: 'desc_custom', uriTemplate: 'custom_collection_desc_foos', order: ['name' => 'DESC'])]
130-
#[GetCollection(name: 'asc_custom', uriTemplate: 'custom_collection_asc_foos', order: ['name' => 'ASC'])]
129+
#[GetCollection(name: 'get_desc_custom', uriTemplate: 'custom_collection_desc_foos', order: ['name' => 'DESC'])]
130+
#[GetCollection(name: 'get_asc_custom', uriTemplate: 'custom_collection_asc_foos', order: ['name' => 'ASC'])]
131131
class Book
132132
{
133133
// ...

core/deprecations.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,10 @@ namespace App\Entity;
121121
122122
use ApiPlatform\Metadata\ApiResource;
123123
124-
#[ApiResource(deprecationReason:"Create a Book instead", sunset: "01/01/2020")]
124+
#[ApiResource(
125+
deprecationReason: 'Create a Book instead',
126+
sunset: '01/01/2020'
127+
)]
125128
class Parchment
126129
{
127130
// ...
@@ -142,7 +145,10 @@ use ApiPlatform\Metadata\ApiResource;
142145
use ApiPlatform\Metadata\Get;
143146
144147
#[ApiResource]
145-
#[Get(sunset: '01/01/2020', deprecationReason: 'Retrieve a Book instead')]
148+
#[Get(
149+
deprecationReason: 'Retrieve a Book instead',
150+
sunset: '01/01/2020'
151+
)]
146152
class Parchment
147153
{
148154
// ...

core/dto.md

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

3-
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.
3+
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.
44

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

@@ -75,7 +75,8 @@ We have the following `BookInput`:
7575

7676
namespace App\Dto;
7777

78-
final class BookInput {
78+
final class BookInput
79+
{
7980
/**
8081
* @var string
8182
*/
@@ -104,6 +105,7 @@ final class BookInputDataTransformer implements DataTransformerInterface
104105
{
105106
$book = new Book();
106107
$book->isbn = $data->isbn;
108+
107109
return $book;
108110
}
109111

@@ -142,7 +144,8 @@ To manage the output, it's exactly the same process. For example, we have the fo
142144

143145
namespace App\Dto;
144146

145-
final class BookOutput {
147+
final class BookOutput
148+
{
146149
/**
147150
* @var string
148151
*/
@@ -171,6 +174,7 @@ final class BookOutputDataTransformer implements DataTransformerInterface
171174
{
172175
$output = new BookOutput();
173176
$output->name = $data->name;
177+
174178
return $output;
175179
}
176180

@@ -213,7 +217,8 @@ With the following `BookInput`:
213217

214218
namespace App\Dto;
215219

216-
final class BookInput {
220+
final class BookInput
221+
{
217222
/**
218223
* @var \App\Entity\Author
219224
*/
@@ -242,6 +247,7 @@ final class BookInputDataTransformer implements DataTransformerInterface
242247
{
243248
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE];
244249
$existingBook->author = $data->author;
250+
245251
return $existingBook;
246252
}
247253

@@ -268,6 +274,79 @@ services:
268274
#tags: [ 'api_platform.data_transformer' ]
269275
```
270276

277+
## Initialize the Input DTO For Partial Update
278+
279+
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.
280+
281+
This way, the input DTO will be correctly validated with its old data and partial new data.
282+
283+
Create a class implementing the `DataTransformerInitializerInterface` instead of the `DataTransformerInterface`:
284+
285+
```php
286+
<?php
287+
// src/DataTransformer/BookInputDataTransformerInitializer.php
288+
289+
namespace App\DataTransformer;
290+
291+
use ApiPlatform\Core\DataTransformer\DataTransformerInitializerInterface;
292+
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
293+
use App\Entity\Book;
294+
use App\Dto\BookInput;
295+
296+
final class BookInputDataTransformerInitializer implements DataTransformerInitializerInterface
297+
{
298+
/**
299+
* {@inheritdoc}
300+
*/
301+
public function transform($data, string $to, array $context = [])
302+
{
303+
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE];
304+
$existingBook->author = $data->author;
305+
306+
return $existingBook;
307+
}
308+
309+
/**
310+
* {@inheritdoc}
311+
*/
312+
public function initialize(string $inputClass, array $context = [])
313+
{
314+
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE] ?? null;
315+
if (!$existingBook) {
316+
return new BookInput();
317+
}
318+
319+
$bookInput = new BookInput();
320+
$bookInput->author = $existingBook->author;
321+
322+
return $bookInput;
323+
}
324+
325+
/**
326+
* {@inheritdoc}
327+
*/
328+
public function supportsTransformation($data, string $to, array $context = []): bool
329+
{
330+
if ($data instanceof Book) {
331+
return false;
332+
}
333+
334+
return Book::class === $to && null !== ($context['input']['class'] ?? null);
335+
}
336+
}
337+
```
338+
339+
Register it:
340+
341+
```yaml
342+
# api/config/services.yaml
343+
services:
344+
# ...
345+
'App\DataTransformer\BookInputDataTransformerInitializer': ~
346+
# Uncomment only if autoconfiguration is disabled
347+
#tags: [ 'api_platform.data_transformer' ]
348+
```
349+
271350
## Disabling the Input or the Output
272351

273352
Both the `input` and the `output` attributes can be set to `false`. If `input` is `false`, the deserialization process
@@ -285,6 +364,8 @@ will be skipped. If `output` is `false`, the serialization process will be skipp
285364
namespace App\Entity;
286365

287366
use ApiPlatform\Metadata\ApiResource;
367+
use ApiPlatform\Metadata\Post;
368+
use ApiPlatform\Metadata\Put;
288369
use App\Dto\BookOutput;
289370
use App\Dto\CreateBook;
290371
use App\Dto\UpdateBook;

core/events.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Name | Event | [Pre & Post hooks](#custom-
2727
`AddFormatListener` | `kernel.request` | None | 7 | Guesses the best response format ([content negotiation](content-negotiation.md))
2828
`QueryParameterValidateListener` | `kernel.request` | None | 16 | Validates query parameters
2929
`ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | Retrieves data from the persistence system using the [data providers](data-providers.md) (`GET`, `PUT`, `PATCH`, `DELETE`)
30-
`DeserializeListener` | `kernel.request` | `PRE_DESERIALIZE`, `POST_DESERIALIZE` | 2 | Deserializes data into a PHP entity (`GET`, `POST`, `DELETE`); updates the entity retrieved using the data provider (`PUT`, `PATCH`)
30+
`DeserializeListener` | `kernel.request` | `PRE_DESERIALIZE`, `POST_DESERIALIZE` | 2 | Deserializes data into a PHP entity (`POST`); updates the entity retrieved using the data provider (`PUT`, `PATCH`)
3131
`DenyAccessListener` | `kernel.request` | None | 1 | Enforces [access control](security.md) using Security expressions
3232
`ValidateListener` | `kernel.view` | `PRE_VALIDATE`, `POST_VALIDATE` | 64 | [Validates data](validation.md) (`POST`, `PUT`, `PATCH`)
3333
`WriteListener` | `kernel.view` | `PRE_WRITE`, `POST_WRITE` | 32 | Persists changes in the persistence system using the [data persisters](data-persisters.md) (`POST`, `PUT`, `PATCH`, `DELETE`)
@@ -61,7 +61,7 @@ Attribute | Type | Default | Description
6161

6262
Registering your own event listeners to add extra logic is convenient.
6363

64-
The [`ApiPlatform\Core\EventListener\EventPriorities`](https://github.com/api-platform/core/blob/main/src/EventListener/EventPriorities.php) class comes with a convenient set of class constants corresponding to commonly used priorities:
64+
The [`ApiPlatform\Core\EventListener\EventPriorities`](https://github.com/api-platform/core/blob/main/src/Core/EventListener/EventPriorities.php) class comes with a convenient set of class constants corresponding to commonly used priorities:
6565

6666
Constant | Event | Priority |
6767
-------------------|-------------------|----------|

0 commit comments

Comments
 (0)