API Platform provides an integration with the Symfony Messenger Component.
This feature allows to implement the Command Query Responsibility Segregation (CQRS) pattern in a convenient way. It also makes it easy to send messages through the web API that will be consumed asynchronously.
Many transports are supported to dispatch messages to async consumers, including RabbitMQ, Apache Kafka, Amazon SQS and Google Pub/Sub.
To enable the support of Messenger, install the library:
docker-compose exec php \
composer require messenger
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:
<?php
// api/src/Entity/Person.php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;
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;
#[Assert\NotBlank]
public string $name;
}
# api/config/api_platform/resources.yaml
resources:
App\Entity\Person:
collectionOperations:
post:
status: 202
messenger: true
output: false
itemOperations:
get:
status: 404
controller: ApiPlatform\Core\Action\NotFoundAction
read: false
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. 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.
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 will be skipped.
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.
To process the message that will be dispatched, a handler must be created:
<?php
// api/src/Handler/PersonHandler.php
namespace App\Handler;
use App\Entity\Person;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
final class PersonHandler implements MessageHandlerInterface
{
public function __invoke(Person $person)
{
// do something with the resource
}
}
That’s all!
By default, the handler will process your message synchronously. If you want it to be consumed asynchronously (e.g. by a worker machine), configure a transport and the consumer.
API Platform automatically uses the Symfony\Component\Messenger\Stamp\HandledStamp
when set.
It means that if you use a synchronous handler, the data returned by the __invoke
method replaces the original data.
In cases where multiple handlers are registered, the last handler return value will be used as output. If none are returned, ensure resource configuration defines no output with output=false
.
Handler ordering can be configured using messenger priority tag.
When a DELETE
operation occurs, API Platform automatically adds a ApiPlatform\Core\Bridge\Symfony\Messenger\RemoveStamp
“stamp” instance to the “envelope”.
To differentiate typical persists calls (create and update) and removal calls, check for the presence of this stamp using a custom “middleware”.
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) that handles the given input
. In this example, we’ll handle a ResetPasswordRequest
on a custom operation on our User
resource:
<?php
// api/src/Entity/User.php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Dto\ResetPasswordRequest;
#[ApiResource(collectionOperations: [
"post", "get",
"reset_password" => [
"status" => 202,
"messenger" => "input",
"input" => ResetPasswordRequest::class,
"output" => false,
"method" => "POST",
"path" => "/users/reset_password"
]
]
)]
final class User
{
}
Where ResetPasswordRequest
would be:
<?php
// api/src/Dto/ResetPasswordRequest.php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
final class ResetPasswordRequest
{
public string $username;
}
As above, we use the status
attribute to configure API Platform to return a 202 Accepted HTTP status code.
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 will be skipped.
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
// api/src/Handler/ResetPasswordRequestHandler.php
namespace App\Handler;
use App\Dto\ResetPasswordRequest;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
final class ResetPasswordRequestHandler implements MessageHandlerInterface
{
public function __invoke(ResetPasswordRequest $forgotPassword)
{
// do something with the resource
}
}
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more