Skip to content

Commit 2482b4d

Browse files
authored
Merge pull request #1704 from alanpoulain/chore/merge-3.0
2 parents 00bc650 + 9adf88f commit 2482b4d

Some content is hidden

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

42 files changed

+1016
-254
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name: Lint
22

33
on:
4-
- push
5-
- pull_request
4+
push:
5+
pull_request:
66

77
jobs:
88
build:
@@ -16,7 +16,7 @@ jobs:
1616
fetch-depth: 0
1717

1818
- name: Lint
19-
uses: github/super-linter@v3.17.0
19+
uses: github/super-linter/slim@v4
2020
env:
2121
VALIDATE_ALL_CODEBASE: false
2222
VALIDATE_EDITORCONFIG: false

core/content-negotiation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Format | Format name |
2323
[JSON-LD](https://json-ld.org) | `jsonld` | `application/ld+json` | yes
2424
[GraphQL](graphql.md) | n/a | n/a | yes
2525
[JSON:API](http://jsonapi.org/) | `jsonapi` | `application/vnd.api+json` | yes
26-
[HAL](http://stateless.co/hal_specification.html) | `jsonhal` | `application/hal+json` | yes
26+
[HAL](https://stateless.group/hal_specification.html) | `jsonhal` | `application/hal+json` | yes
2727
[YAML](http://yaml.org/) | `yaml` | `application/x-yaml` | no
2828
[CSV](https://tools.ietf.org/html/rfc4180) | `csv` | `text/csv` | no
2929
[HTML](https://whatwg.org/) (API docs) | `html` | `text/html` | no

core/controllers.md

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ In the following examples, the built-in `GET` operation is registered as well as
2323

2424
By default, API Platform uses the first `Get` operation defined to generate the IRI of an item and the first `GetCollection` operation to generate the IRI of a collection.
2525

26+
If your resource does not have any `Get` operation, API Platform automatically adds an operation to help generating this IRI.
27+
If your resource has any identifier, this operation will look like `/books/{id}`. But if your resource doesn't have any identifier, API Platform will use the Skolem format `/.well-known/genid/{id}`.
28+
Those routes are not exposed from any documentation (for instance OpenAPI), but are anyway declared on the Symfony routing and always return a HTTP 404.
29+
2630
If you create a custom operation, you will probably want to properly document it.
2731
See the [OpenAPI](openapi.md) part of the documentation to do so.
2832

@@ -56,7 +60,7 @@ class CreateBookPublication extends AbstractController
5660
}
5761
```
5862

59-
This custom operation behaves exactly like the built-in operation: it returns a JSON-LD document corresponding to the id
63+
This custom operation behaves exactly like the built-in operation: it returns a JSON-LD document corresponding to the ID
6064
passed in the URL.
6165

6266
Here we consider that [autowiring](https://symfony.com/doc/current/service_container/autowiring.html) is enabled for
@@ -144,9 +148,77 @@ App\Entity\Book:
144148

145149
[/codeSelector]
146150

147-
It is mandatory to set the `method`, `path` and `controller` attributes. They allow API Platform to configure the routing path and
151+
It is mandatory to set the `method`, `uriTemplate` and `controller` attributes. They allow API Platform to configure the routing path and
148152
the associated controller respectively.
149153

154+
## Using the PlaceholderAction
155+
156+
Complex use cases may lead you to create multiple custom operations.
157+
158+
In such a case, you will probably create the same amount of custom controllers while you may not need to perform custom logic inside.
159+
160+
To avoid that, API Platform provides the `ApiPlatform\Action\PlaceholderAction` which behaves the same when using the [built-in operations](operations.md#operations).
161+
162+
You just need to set the `controller` attribute with this class. Here, the previous example updated:
163+
164+
[codeSelector]
165+
166+
```php
167+
// api/src/Entity/Book.php
168+
namespace App\Entity;
169+
170+
use ApiPlatform\Action\PlaceholderAction;
171+
use ApiPlatform\Metadata\ApiResource;
172+
use ApiPlatform\Metadata\Get;
173+
use ApiPlatform\Metadata\Post;
174+
175+
#[ApiResource(operations: [
176+
new Get(),
177+
new Post(
178+
name: 'publication',
179+
uriTemplate: '/books/{id}/publication',
180+
controller: PlaceholderAction::class
181+
)
182+
])]
183+
class Book
184+
{
185+
// ...
186+
}
187+
```
188+
189+
```yaml
190+
# api/config/api_platform/resources.yaml
191+
App\Entity\Book:
192+
operations:
193+
ApiPlatform\Metadata\Get: ~
194+
post_publication:
195+
class: ApiPlatform\Metadata\Post
196+
method: POST
197+
uriTemplate: /books/{id}/publication
198+
controller: ApiPlatform\Action\PlaceholderAction
199+
```
200+
201+
```xml
202+
<?xml version="1.0" encoding="UTF-8" ?>
203+
<!-- api/config/api_platform/resources.xml -->
204+
205+
<resources
206+
xmlns="https://api-platform.com/schema/metadata/resources-3.0"
207+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
208+
xsi:schemaLocation="https://api-platform.com/schema/metadata/resources-3.0
209+
https://api-platform.com/schema/metadata/resources-3.0.xsd">
210+
<resource class="App\Entity\Book">
211+
<operations>
212+
<operation class="ApiPlatform\Metadata\Get" />
213+
<operation class="ApiPlatform\Metadata\Post" name="post_publication" uriTemplate="/books/{id}/publication"
214+
controller="ApiPlatform\Action\PlaceholderAction" />
215+
</operations>
216+
</resource>
217+
</resources>
218+
```
219+
220+
[/codeSelector]
221+
150222
## Using Serialization Groups
151223

152224
You may want different serialization groups for your custom operations. Just configure the proper `normalizationContext` and/or `denormalizationContext` in your operation:

core/dto.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ And the processor:
4343
namespace App\State;
4444

4545
use App\Dto\UserResetPasswordDto;
46+
use ApiPlatform\Metadata\Operation;
4647
use ApiPlatform\State\ProcessorInterface;
4748
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
4849

@@ -107,6 +108,7 @@ namespace App\State;
107108

108109
use App\Dto\AnotherRepresentation;
109110
use App\Model\Book;
111+
use ApiPlatform\Metadata\Operation;
110112
use ApiPlatform\State\ProviderInterface;
111113

112114
final class BookRepresentationProvider implements ProviderInterface
@@ -117,3 +119,77 @@ final class BookRepresentationProvider implements ProviderInterface
117119
}
118120
}
119121
```
122+
123+
## Implementing a Write Operation With an Output Different From the Resource
124+
125+
For returning another representation of your data in a [State Processor](./state-processors.md), you should specify your processor class in the `processor` attribute and same for your `output`.
126+
127+
[codeSelector]
128+
129+
```php
130+
<?php
131+
132+
namespace App\Entity;
133+
134+
use ApiPlatform\Metadata\Post;
135+
use App\Dto\AnotherRepresentation;
136+
use App\State\BookRepresentationProcessor;
137+
138+
#[Post(output: AnotherRepresentation::class, processor: BookRepresentationProcessor::class)]
139+
class Book {}
140+
```
141+
```yaml
142+
# api/config/api_platform/resources.yaml
143+
App\Entity\Book:
144+
operations:
145+
ApiPlatform\Metadata\Post:
146+
output: App\Dto\AnotherRepresentation
147+
processor: App\State\BookRepresentationProcessor
148+
```
149+
```xml
150+
<?xml version="1.0" encoding="UTF-8" ?>
151+
<!-- api/config/api_platform/resources.xml -->
152+
153+
<resources xmlns="https://api-platform.com/schema/metadata/resources-3.0"
154+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
155+
xsi:schemaLocation="https://api-platform.com/schema/metadata/resources-3.0
156+
https://api-platform.com/schema/metadata/resources-3.0.xsd">
157+
<resource class="App\Entity\Book">
158+
<operations>
159+
<operation class="ApiPlatform\Metadata\Post"
160+
processor="App\State\BookRepresentationProcessor"
161+
output="App\Dto\AnotherRepresentation" />
162+
</operations>
163+
</resource>
164+
</resources>
165+
```
166+
167+
[/codeSelector]
168+
169+
Here the `$data` attribute represents an instance of your resource.
170+
171+
```php
172+
<?php
173+
174+
namespace App\State;
175+
176+
use ApiPlatform\Metadata\Operation;
177+
use ApiPlatform\State\ProcessorInterface;
178+
use App\Dto\AnotherRepresentation;
179+
use App\Model\Book;
180+
181+
final class BookRepresentationProcessor implements ProcessorInterface
182+
{
183+
/**
184+
* @param Book $data
185+
*/
186+
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
187+
{
188+
return new AnotherRepresentation(
189+
$data->getId(),
190+
$data->getTitle(),
191+
// etc.
192+
);
193+
}
194+
}
195+
```

core/elasticsearch.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ Keep in mind that it is your responsibility to populate your Elasticsearch index
199199
a custom [state processors](state-processors.md#creating-a-custom-state-processor) or any other mechanism that suits your
200200
project (such as an [ETL](https://en.wikipedia.org/wiki/Extract,_transform,_load)).
201201

202+
To disable elasticsearch index discovery for non-elasticsearch entities you can set `elasticsearch: false` in the `#[ApiResource]` attribute. If this property is absent, all entities will perform an index check during cache warmup to determine if they are on elasticsearch or not.
203+
202204
You're done! The API is now ready to use.
203205

204206
### Creating custom mapping

core/errors.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ use App\Exception\ProductWasRemovedException;
125125
use App\Exception\ProductNotFoundException;
126126
127127
#[ApiResource(
128-
exceptionToStatus: ['ProductNotFoundException::class' => 404]
128+
exceptionToStatus: [ProductNotFoundException::class => 404]
129129
operations: [
130-
new Get(exceptionToStatus: ['ProductWasRemovedException::class' => 410]),
130+
new Get(exceptionToStatus: [ProductWasRemovedException::class => 410]),
131131
new GetCollection(),
132132
new Post()
133133
]

core/extending.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ The following tables summarizes which extension point to use depending on what y
1515
| [State Processors](state-processors) | custom business logic and computations to trigger before or after persistence (ex: mail, call to an external API...) |
1616
| [Normalizers](serialization.md#decorating-a-serializer-and-adding-extra-data) | customize the resource sent to the client (add fields in JSON documents, encode codes, dates...) |
1717
| [Filters](filters.md) | create filters for collections and automatically document them (OpenAPI, GraphQL, Hydra) |
18-
| [Serializer Context Builders](serialization.md#changing-the-serialization-context-dynamically) | Changing the Serialization context (e.g. groups) dynamically |
18+
| [Serializer Context Builders](serialization.md#changing-the-serialization-context-dynamically) | change the Serialization context (e.g. groups) dynamically |
1919
| [Messenger Handlers](messenger.md) | create 100% custom, RPC, async, service-oriented endpoints (should be used in place of custom controllers because the messenger integration is compatible with both REST and GraphQL, while custom controllers only work with REST) |
20-
| [DTOs and Data Transformers](dto.md) | use a specific class to represent the input or output data structure related to an operation |
20+
| [DTOs](dto.md) | use a specific class to represent the input or output data structure related to an operation |
2121
| [Kernel Events](events.md) | customize the HTTP request or response (REST only, other extension points must be preferred when possible) |
2222

2323
## Doctrine Specific Extension Points

core/extensions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace App\Doctrine;
6060
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
6161
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
6262
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
63+
use ApiPlatform\Metadata\Operation;
6364
use App\Entity\Offer;
6465
use Doctrine\ORM\QueryBuilder;
6566
use Symfony\Component\Security\Core\Security;
@@ -73,12 +74,12 @@ final class CurrentUserExtension implements QueryCollectionExtensionInterface, Q
7374
$this->security = $security;
7475
}
7576

76-
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void
77+
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
7778
{
7879
$this->addWhere($queryBuilder, $resourceClass);
7980
}
8081

81-
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []): void
82+
public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
8283
{
8384
$this->addWhere($queryBuilder, $resourceClass);
8485
}

core/filters.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ App\Entity\Tweet:
951951

952952
[/codeSelector]
953953

954-
Given that the collection endpoint is `/tweets`, you can filter tweets by id and date in ascending or descending order:
954+
Given that the collection endpoint is `/tweets`, you can filter tweets by ID and date in ascending or descending order:
955955
`/tweets?order[id]=asc&order[date]=desc`.
956956

957957
By default, whenever the query does not specify the direction explicitly (e.g: `/tweets?order[id]&order[date]`), filters
@@ -1173,8 +1173,8 @@ to retrieve items, [extensions](extensions.md) are the way to go.
11731173
A Doctrine ORM filter is basically a class implementing the `ApiPlatform\Doctrine\Orm\Filter\FilterInterface`.
11741174
API Platform includes a convenient abstract class implementing this interface and providing utility methods: `ApiPlatform\Doctrine\Orm\Filter\AbstractFilter`.
11751175

1176-
In the following example, we create a class to filter a collection by applying a regexp to a property. The `REGEXP` DQL
1177-
function used in this example can be found in the [`DoctrineExtensions`](https://github.com/beberlei/DoctrineExtensions)
1176+
In the following example, we create a class to filter a collection by applying a regular expression to a property.
1177+
The `REGEXP` DQL function used in this example can be found in the [`DoctrineExtensions`](https://github.com/beberlei/DoctrineExtensions)
11781178
library. This library must be properly installed and registered to use this example (works only with MySQL).
11791179

11801180
```php
@@ -1191,7 +1191,7 @@ use Symfony\Component\PropertyInfo\Type;
11911191
11921192
final class RegexpFilter extends AbstractFilter
11931193
{
1194-
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = [])
1194+
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
11951195
{
11961196
// otherwise filter is applied to order and page as well
11971197
if (
@@ -1518,7 +1518,7 @@ final class UserFilter extends SQLFilter
15181518
// Don't worry, getParameter automatically escapes parameters
15191519
$userId = $this->getParameter('id');
15201520
} catch (\InvalidArgumentException $e) {
1521-
// No user id has been defined
1521+
// No user ID has been defined
15221522
return '';
15231523
}
15241524

core/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ resources:
183183
<?xml version="1.0" encoding="UTF-8" ?>
184184
<!-- api/config/api_platform/resources.xml -->
185185

186-
<resource xmlns="https://api-platform.com/schema/metadata/resources-3.0"
186+
<resources xmlns="https://api-platform.com/schema/metadata/resources-3.0"
187187
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
188188
xsi:schemaLocation="https://api-platform.com/schema/metadata/resources-3.0
189189
https://api-platform.com/schema/metadata/resources-3.0.xsd">

0 commit comments

Comments
 (0)