Skip to content

Fix messenger documentation #1268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions core/data-persisters.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,41 @@ services:
#arguments: ['@App\DataPersister\UserDataPersister.inner']
#tags: [ 'api_platform.data_persister' ]
```

## Calling multiple DataPersisters

Our DataPersisters are called in chain, once a data persister is supported the chain breaks and API Platform assumes your data is persisted. You can call mutliple data persisters by implementing the `ResumableDataPersisterInterface`:

```php
namespace App\DataPersister;

use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\Entity\BlogPost;

final class BlogPostDataPersister implements ContextAwareDataPersisterInterface, ResumableDataPersisterInterface
{
public function supports($data, array $context = []): bool
{
return $data instanceof BlogPost;
}

public function persist($data, array $context = [])
{
// call your persistence layer to save $data
return $data;
}

public function remove($data, array $context = [])
{
// call your persistence layer to delete $data
}

// Once called this data persister will resume to the next one
public function resumable(array $context = []): bool
{
return true;
}
}
```

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.
103 changes: 37 additions & 66 deletions core/messenger.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,99 +18,81 @@ docker-compose exec php \

## Dispatching a Resource through the Message Bus

Set the `messenger` attribute to `true`, and API Platform will automatically dispatch the API Resource instance as a message using the message bus provided by the Messenger Component:
Set the `messenger` attribute to `true`, and API Platform will automatically dispatch the API Resource instance as a message using the message bus provided by the Messenger Component. The following example allows you to create a new `Person` in an asynchronous manner:

[codeSelector]

```php
<?php
// api/src/Entity/ResetPasswordRequest.php
// api/src/Entity/Person.php

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;

/**
* @ApiResource(
* messenger=true,
* collectionOperations={
* "post"={"status"=202}
* },
* itemOperations={},
* output=false
* )
*/
final class ResetPasswordRequest
use ApiPlatform\Core\Action\NotFoundAction;

#[ApiResource(collectionOperations: [
"post" => ["messenger" => true, "output" => false, "status" => 202]
], itemOperations: [
"get" => ["controller" => NotFoundAction::class, "read" => false, "status" => 404]
]
)]
final class Person
{
#[ApiProperty(identifier: true)]
public string $id;

/**
* @var string
*
* @Assert\NotBlank
*/
public $username;
public string $name;
}
```

```yaml
# api/config/api_platform/resources.yaml
resources:
App\Entity\ResetPasswordRequest:
App\Entity\Person:
collectionOperations:
post:
status: 202
itemOperations: []
attributes:
messenger: true
output: false
itemOperations:
get:
status: 404
controller: ApiPlatform\Core\Action\NotFoundAction
read: false
```

[/codeSelector]

Because the `messenger` attribute is `true`, when a `POST` is handled by API Platform, the corresponding instance of the `ResetPasswordRequest` will be dispatched.
Because the `messenger` attribute is `true`, when a `POST` is handled by API Platform, the corresponding instance of the `Person` will be dispatched.

For this example, only the `POST` operation is enabled.
For this example, only the `POST` operation is enabled. We disabled the item operation using the `NotFoundAction`. A resource must have at least one item operation as it must be identified by an IRI, here the route `/people/1` exists, eventhough it returns a 404 status code.
We use the `status` attribute to configure API Platform to return a [202 Accepted HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202).
It indicates that the request has been received and will be treated later, without giving an immediate return to the client.
Finally, the `output` attribute is set to `false`, so the HTTP response that will be generated by API Platform will be empty, and the [serialization process](serialization.md) will be skipped.

**Note:** when using `messenger=true` ApiResource attribute in a Doctrine entity, the Doctrine DataPersister is not called. You must use the `messenger="persist"` ApiResource attribute.

**Note:** when using `messenger="input"` ApiResource attribute in a Doctrine entity, the Doctrine DataPersister is not called. You must use an array containing `persist` and `input` if you want it to be called, for example:

[codeSelector]

```php
/**
* @ApiResource(messenger={"persist", "input"})
*/
```
Finally, the `output` attribute is set to `false`, so the HTTP response that will be generated by API Platform will be empty, and the [serialization process](serialization.md) will be skipped.

```yaml
resources:
# ...
attributes:
messenger: ['persist', 'input']
```

[/codeSelector]
**Note:** when using `messenger=true` ApiResource attribute in a Doctrine entity, the Doctrine DataPersister is not called. If you want the Doctrine DataPersister to be called, you should implement a `ResumableDataPersisterInterface` [documented here](data-persisters.md).

## Registering a Message Handler

To process the message that will be dispatched, [a handler](https://symfony.com/doc/current/messenger.html#registering-handlers) must be created:

```php
<?php
// api/src/Handler/ResetPasswordRequestHandler.php
// api/src/Handler/PersonHandler.php

namespace App\Handler;

use App\Entity\ResetPasswordRequest;
use App\Entity\Person;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;

final class ResetPasswordRequestHandler implements MessageHandlerInterface
final class PersonHandler implements MessageHandlerInterface
{
public function __invoke(ResetPasswordRequest $forgotPassword)
public function __invoke(PersonHandler $person)
{
// do something with the resource
}
Expand All @@ -137,7 +119,7 @@ To differentiate typical persists calls (create and update) and removal calls, c

## Using Messenger with an Input Object

Set the `messenger` attribute to `input`, and API Platform will automatically dispatch the given Input as a message instead of the Resource. Indeed, it'll add a default `DataTransformer` ([see input/output documentation](./dto.md)) that handles the given `input`.
Set the `messenger` attribute to `input`, and API Platform will automatically dispatch the given Input as a message instead of the Resource. Indeed, it'll add a default `DataTransformer` ([see input/output documentation](./dto.md)) that handles the given `input`. In this example, we'll handle a `ResetPasswordRequest` on a custom operation on our `User` resource:

```php
<?php
Expand All @@ -148,17 +130,11 @@ namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Dto\ResetPasswordRequest;

/**
* @ApiResource(
* collectionOperations={
* "post"={"status"=202}
* },
* itemOperations={},
* messenger="input",
* input=ResetPasswordRequest::class,
* output=false
* )
*/
#[ApiResource(collectionOperations: [
"post", "get", "delete"
"reset_password" => ["status" => 202, "messenger" => "input", "input" => SomeInput::class, "output" => false, "method" => "POST", "path" => "/users/reset_password"]
]
)]
final class User
{
}
Expand All @@ -175,20 +151,15 @@ use Symfony\Component\Validator\Constraints as Assert;

final class ResetPasswordRequest
{
/**
* @var string
* @Assert\NotBlank
*/
public $var;
public string $username;
}
```

For this example, only the `POST` operation is enabled on `/users`.
As above, we use the `status` attribute to configure API Platform to return a [202 Accepted HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202).
It indicates that the request has been received and will be treated later, without giving an immediate return to the client.
Finally, the `output` attribute is set to `false`, so the HTTP response that will be generated by API Platform will be empty, and the [serialization process](serialization.md) will be skipped.

In this case, when a `POST` request is issued on `/users` the message handler will receive an `App\Dto\ResetPasswordRequest` object instead a `User` because we specified it as `input` and set `messenger=input`:
In this case, when a `POST` request is issued on `/users/reset_password` the message handler will receive an `App\Dto\ResetPasswordRequest` object instead a `User` because we specified it as `input` and set `messenger=input`:

```php
<?php
Expand Down