Skip to content

Commit b1ff9db

Browse files
committed
Document toggleable listeners
1 parent 218b482 commit b1ff9db

File tree

5 files changed

+77
-104
lines changed

5 files changed

+77
-104
lines changed

core/errors.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ final class ProductManager implements EventSubscriberInterface
6363
```
6464

6565
If you use the standard distribution of API Platform, this event listener will be automatically registered. If you use a
66-
custom installation, [learn how to register listeners](events.md).
66+
custom installation, [learn how to register listeners](events.md#custom-event-listeners).
6767

6868
Then, configure the framework to catch `App\Exception\ProductNotFoundException` exceptions and convert them in `404`
6969
errors:

core/events.md

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,70 @@ of event listeners are executed which validate the data, persist it in database,
99
and create an HTTP response that will be sent to the client.
1010

1111
To do so, API Platform Core leverages [events triggered by the Symfony HTTP Kernel](https://symfony.com/doc/current/reference/events.html#kernel-events).
12-
You can also hook your own code to those events. They are handy and powerful extension points available at all points
12+
You can also hook your own code to those events. There are handy and powerful extension points available at all points
1313
of the request lifecycle.
1414

15+
If you are using Doctrine, lifecycle events ([ORM](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html#lifecycle-events), [MongoDB ODM](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/events.html#lifecycle-events))
16+
are also available if you want to hook into the persistence layer's object lifecycle.
17+
18+
## Built-in Event Listeners
19+
20+
These built-in event listeners are registered for routes managed by API Platform:
21+
22+
Name | Event | [Pre & Post hooks](#custom-event-listeners) | Priority | Description
23+
------------------------------|--------------------|---------------------------------------------|----------|-------------
24+
`AddFormatListener` | `kernel.request` | None | 7 | Guesses the best response format ([content negotiation](content-negotiation.md))
25+
`ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | Retrieves data from the persistence system using the [data providers](data-providers.md) (`GET`, `PUT`, `DELETE`)
26+
`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`)
27+
`DenyAccessListener` | `kernel.request` | None | 1 | Enforces [access control](security.md) using Security expressions
28+
`ValidateListener` | `kernel.view` | `PRE_VALIDATE`, `POST_VALIDATE` | 64 | [Validates data](validation.md) (`POST`, `PUT`)
29+
`WriteListener` | `kernel.view` | `PRE_WRITE`, `POST_WRITE` | 32 | Persists changes in the persistence system using the [data persisters](data-persisters.md) (`POST`, `PUT`, `DELETE`)
30+
`SerializeListener` | `kernel.view` | `PRE_SERIALIZE`, `POST_SERIALIZE` | 16 | Serializes the PHP entity in string [according to the request format](content-negotiation.md)
31+
`RespondListener` | `kernel.view` | `PRE_RESPOND`, `POST_RESPOND` | 8 | Transforms serialized to a `Symfony\Component\HttpFoundation\Response` instance
32+
`AddLinkHeaderListener` | `kernel.response` | None | 0 | Adds a `Link` HTTP header pointing to the Hydra documentation
33+
`ValidationExceptionListener` | `kernel.exception` | None | 0 | Serializes validation exceptions in the Hydra format
34+
`ExceptionListener` | `kernel.exception` | None | -96 | Serializes PHP exceptions in the Hydra format (including the stack trace in debug mode)
35+
36+
Some of these built-in listeners can be enabled/disabled by setting operation attributes:
37+
38+
Attribute | Type | Default | Description
39+
--------------|--------|---------|-------------
40+
`read` | `bool` | `true` | Enables or disables `ReadListener`
41+
`deserialize` | `bool` | `true` | Enables or disables `DeserializeListener`
42+
`validate` | `bool` | `true` | Enables or disables `ValidateListener`
43+
`write` | `bool` | `true` | Enables or disables `WriteListener`
44+
`serialize` | `bool` | `true` | Enables or disables `SerializeListener`
45+
46+
Some of these built-in listeners can be enabled/disabled by setting request attributes (for instance in the [`defaults`
47+
attribute of an operation](operations.md#recommended-method)):
48+
49+
Attribute | Type | Default | Description
50+
---------------|--------|---------|-------------
51+
`_api_receive` | `bool` | `true` | Enables or disables `ReadListener`, `DeserializeListener`, `ValidateListener`
52+
`_api_respond` | `bool` | `true` | Enables or disables `SerializeListener`, `RespondListener`
53+
`_api_persist` | `bool` | `true` | Enables or disables `WriteListener`
54+
55+
## Custom Event Listeners
56+
57+
Registering your own event listeners to add extra logic is convenient.
58+
59+
The [`ApiPlatform\Core\EventListener\EventPriorities`](https://github.com/api-platform/core/blob/master/src/EventListener/EventPriorities.php) class comes with a convenient set of class constants corresponding to commonly used priorities:
60+
61+
Constant | Event | Priority |
62+
-------------------|-------------------|----------|
63+
`PRE_READ` | `kernel.request` | 5 |
64+
`POST_READ` | `kernel.request` | 3 |
65+
`PRE_DESERIALIZE` | `kernel.request` | 3 |
66+
`POST_DESERIALIZE` | `kernel.request` | 1 |
67+
`PRE_VALIDATE` | `kernel.view` | 65 |
68+
`POST_VALIDATE` | `kernel.view` | 63 |
69+
`PRE_WRITE` | `kernel.view` | 33 |
70+
`POST_WRITE` | `kernel.view` | 31 |
71+
`PRE_SERIALIZE` | `kernel.view` | 17 |
72+
`POST_SERIALIZE` | `kernel.view` | 15 |
73+
`PRE_RESPOND` | `kernel.view` | 9 |
74+
`POST_RESPOND` | `kernel.response` | 0 |
75+
1576
In the following example, we will send a mail each time a new book is created using the API:
1677

1778
```php
@@ -62,55 +123,7 @@ final class BookMailSubscriber implements EventSubscriberInterface
62123
}
63124
```
64125

65-
If you use the official API Platform distribution, creating the previous class is enough. The Symfony Dependency Injection
66-
component will automatically register this subscriber as a service and will inject its dependencies thanks to the [autowiring
67-
feature](http://symfony.com/doc/current/components/dependency_injection/autowiring.html).
68-
69-
Alternatively, [the subscriber must be registered manually](http://symfony.com/doc/current/components/http_kernel/introduction.html#creating-an-event-listener).
70-
71-
Doctrine events ([ORM](https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/events.html), [MongoDB ODM](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/events.html#lifecycle-events))
72-
are also available (if you use it) if you want to hook the object's lifecycle events.
73-
74-
Built-in event listeners are:
75-
76-
Name | Event | Pre & Post hooks | Priority | Description
77-
------------------------------|--------------------|--------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------
78-
`AddFormatListener` | `kernel.request` | None | 7 | Guesses the best response format ([content negotiation](content-negotiation.md))
79-
`ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | Retrieves data from the persistence system using the [data providers](data-providers.md) (`GET`, `PUT`, `DELETE`)
80-
`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`)
81-
`ValidateListener` | `kernel.view` | `PRE_VALIDATE`, `POST_VALIDATE` | 64 | [Validates data](validation.md) (`POST`, `PUT`)
82-
`WriteListener` | `kernel.view` | `PRE_WRITE`, `POST_WRITE` | 32 | Persists changes in the persistence system using the [data persisters](data-persisters.md) (`POST`, `PUT`, `DELETE`)
83-
`SerializeListener` | `kernel.view` | `PRE_SERIALIZE`, `POST_SERIALIZE` | 16 | Serializes the PHP entity in string [according to the request format](content-negotiation.md)
84-
`RespondListener` | `kernel.view` | `PRE_RESPOND`, `POST_RESPOND` | 8 | Transforms serialized to a `Symfony\Component\HttpFoundation\Response` instance
85-
`AddLinkHeaderListener` | `kernel.response` | None | 0 | Adds a `Link` HTTP header pointing to the Hydra documentation
86-
`ValidationExceptionListener` | `kernel.exception` | None | 0 | Serializes validation exceptions in the Hydra format
87-
`ExceptionListener` | `kernel.exception` | None | -96 | Serializes PHP exceptions in the Hydra format (including the stack trace in debug mode)
88-
89-
Those built-in listeners are always executed for routes managed by API Platform. Registering your own event listeners to
90-
add extra logic is convenient.
91-
92-
The [`ApiPlatform\Core\EventListener\EventPriorities`](https://github.com/api-platform/core/blob/master/src/EventListener/EventPriorities.php) class comes with a convenient set of class constants corresponding to commonly used priorities:
93-
94-
Constant | Event | Priority |
95-
-------------------|-------------------|----------|
96-
`PRE_READ` | `kernel.request` | 5 |
97-
`POST_READ` | `kernel.request` | 3 |
98-
`PRE_DESERIALIZE` | `kernel.request` | 3 |
99-
`POST_DESERIALIZE` | `kernel.request` | 1 |
100-
`PRE_VALIDATE` | `kernel.view` | 65 |
101-
`POST_VALIDATE` | `kernel.view` | 63 |
102-
`PRE_WRITE` | `kernel.view` | 33 |
103-
`POST_WRITE` | `kernel.view` | 31 |
104-
`PRE_SERIALIZE` | `kernel.view` | 17 |
105-
`POST_SERIALIZE` | `kernel.view` | 15 |
106-
`PRE_RESPOND` | `kernel.view` | 9 |
107-
`POST_RESPOND` | `kernel.response` | 0 |
108-
109-
Some of those built-in listeners can be enabled/disabled by setting request attributes ([for instance in the `defaults`
110-
attribute of an operation](operations.md#recommended-method)):
126+
If you use the official API Platform distribution, creating the previous class is enough. The Symfony DependencyInjection
127+
component will automatically register this subscriber as a service and will inject its dependencies thanks to the [autowiring feature](https://symfony.com/doc/current/service_container/autowiring.html).
111128

112-
Attribute | Type | Default | Description |
113-
---------------|--------|---------|--------------------------------------------------------------------------------------|
114-
`_api_receive` | `bool` | `true` | Enables or disables the `ReadListener`, `DeserializeListener` and `ValidateListener` |
115-
`_api_respond` | `bool` | `true` | Enables or disables `SerializeListener`, `RespondListener` |
116-
`_api_persist` | `bool` | `true` | Enables or disables `WriteListener` |
129+
Alternatively, [the subscriber must be registered manually](https://symfony.com/doc/current/components/event_dispatcher.html#connecting-listeners).

core/file-upload.md

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ use Vich\UploaderBundle\Mapping\Annotation as Vich;
6363
* collectionOperations={
6464
* "post"={
6565
* "controller"=CreateMediaObjectAction::class,
66-
* "defaults"={
67-
* "_api_receive"=false,
68-
* },
66+
* "deserialize"=false,
6967
* "access_control"="is_granted('ROLE_USER')",
7068
* "validation_groups"={"Default", "media_object_create"},
7169
* "swagger_context"={
@@ -142,59 +140,24 @@ that handles the file upload.
142140
143141
namespace App\Controller;
144142
145-
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
146-
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
147-
use ApiPlatform\Core\Util\RequestAttributesExtractor;
148-
use ApiPlatform\Core\Validator\ValidatorInterface;
149143
use App\Entity\MediaObject;
150-
use Doctrine\Common\Persistence\ManagerRegistry;
151144
use Symfony\Component\HttpFoundation\Request;
152145
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
153146
154147
final class CreateMediaObjectAction
155148
{
156-
private $managerRegistry;
157-
private $validator;
158-
private $resourceMetadataFactory;
159-
160-
public function __construct(ManagerRegistry $managerRegistry, ValidatorInterface $validator, ResourceMetadataFactoryInterface $resourceMetadataFactory)
161-
{
162-
$this->managerRegistry = $managerRegistry;
163-
$this->validator = $validator;
164-
$this->resourceMetadataFactory = $resourceMetadataFactory;
165-
}
166-
167149
public function __invoke(Request $request): MediaObject
168150
{
169151
$uploadedFile = $request->files->get('file');
170-
171152
if (!$uploadedFile) {
172153
throw new BadRequestHttpException('"file" is required');
173154
}
174155
175156
$mediaObject = new MediaObject();
176157
$mediaObject->file = $uploadedFile;
177158
178-
$this->validate($mediaObject, $request);
179-
180-
$em = $this->managerRegistry->getManager();
181-
$em->persist($mediaObject);
182-
$em->flush();
183-
184159
return $mediaObject;
185160
}
186-
187-
/**
188-
* @throws ValidationException
189-
*/
190-
private function validate(MediaObject $mediaObject, Request $request): void
191-
{
192-
$attributes = RequestAttributesExtractor::extractAttributes($request);
193-
$resourceMetadata = $this->resourceMetadataFactory->create(MediaObject::class);
194-
$validationGroups = $resourceMetadata->getOperationAttribute($attributes, 'validation_groups', null, true);
195-
196-
$this->validator->validate($mediaObject, ['groups' => $validationGroups]);
197-
}
198161
}
199162
```
200163

@@ -203,7 +166,7 @@ final class CreateMediaObjectAction
203166
Returning the plain file path on the filesystem where the file is stored is not useful for the client, which needs a
204167
URL to work with.
205168

206-
An [event subscriber](events.md) could be used to set the `contentUrl` property:
169+
An [event subscriber](events.md#custom-event-listeners) could be used to set the `contentUrl` property:
207170

208171
```php
209172
<?php

core/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Here is the fully featured REST API you'll get in minutes:
3737
* Files and `\DateTime` and serialization and deserialization
3838
* [FOSUserBundle](fosuser-bundle.md) integration (user management)
3939

40-
Everything is fully customizable through a powerful event system and strong OOP.
40+
Everything is fully customizable through a powerful [event system](events.md) and strong OOP.
4141

4242
This bundle is extensively tested (unit and functional). The [`Fixtures/` directory](https://github.com/api-platform/core/tree/master/tests/Fixtures) contains a working app covering all features of the library.
4343

core/operations.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ you need and it will be autowired too.
593593
The `__invoke` method of the action is called when the matching route is hit. It can return either an instance of
594594
`Symfony\Component\HttpFoundation\Response` (that will be displayed to the client immediately by the Symfony kernel) or,
595595
like in this example, an instance of an entity mapped as a resource (or a collection of instances for collection operations).
596-
In this case, the entity will pass through [all built-in event listeners](events.md) of API Platform. It will be
596+
In this case, the entity will pass through [all built-in event listeners](events.md#built-in-event-listeners) of API Platform. It will be
597597
automatically validated, persisted and serialized in JSON-LD. Then the Symfony kernel will send the resulting document to
598598
the client.
599599

@@ -742,8 +742,8 @@ Or in XML:
742742

743743
#### Entity Retrieval
744744

745-
If you want to bypass the automatic retrieval of the entity in your custom operation, you can set the parameter
746-
`_api_receive` to `false` in the `defaults` attribute:
745+
If you want to bypass the automatic retrieval of the entity in your custom operation, you can set `"read"=false` in the
746+
operation attribute:
747747

748748
```php
749749
<?php
@@ -759,7 +759,7 @@ use App\Controller\CreateBookPublication;
759759
* "method"="POST",
760760
* "path"="/books/{id}/publication",
761761
* "controller"=CreateBookPublication::class,
762-
* "defaults"={"_api_receive"=false},
762+
* "read"=false,
763763
* }
764764
* })
765765
*/
@@ -780,8 +780,7 @@ App\Entity\Book:
780780
method: POST
781781
path: /books/{id}/publication
782782
controller: App\Controller\CreateBookPublication
783-
defaults:
784-
_api_receive: false
783+
read: false
785784
```
786785

787786
Or in XML:
@@ -801,17 +800,15 @@ Or in XML:
801800
<attribute name="method">POST</attribute>
802801
<attribute name="path">/books/{id}/publication</attribute>
803802
<attribute name="controller">App\Controller\CreateBookPublication</attribute>
804-
<attribute name="defaults">
805-
<attribute name="_api_receive">false</attribute>
806-
</attribute>
803+
<attribute name="read">false</attribute>
807804
</itemOperation>
808805
</itemOperations>
809806
</resource>
810807
</resources>
811808
```
812809

813-
This way, it will skip the `Read`, `Deserialize` and `Validate` listeners (see [the event system](events.md) for more
814-
information).
810+
This way, it will skip the `ReadListener`. You can do the same for some other built-in listeners. See [Built-in Event Listeners](events.md#built-in-event-listeners)
811+
for more information.
815812

816813
### Alternative Method
817814

0 commit comments

Comments
 (0)