sajad torkamani

In a nutshell

The Messenger component gives you a message bus with the ability to send messages and then either handle them immediately or send them through transports (e.g., queues) to be handled later.

Glossary

  • Message: Any serializable PHP object.
  • Message bus: The orchestrator that receives messages from your application code, applies middleware, and either dispatches to handlers (for sync tasks) or routes messages to the appropriate transports (for async tasks).
  • Message handler: The piece of code (typically a PHP callable) that executes your business logic when the message arrives.
  • Transport: The underlying service that handles the actual delivery, storage, and retrieval of messages once dispatched. Symfony provides many transports that are based on Redis, Doctrine, AWS SQS, and more. If using the Doctrine transport for example, your messages will be stored in your database.
  • Worker: The process that consumes messages from transports.

Quickstart: How to dispatch a message and handle it

Install the Messenger component:

composer require symfony/messenger

Using the Messenger component centres around two different classes:

  1. Message classes that hold data about each message.
  2. Handler classes that are invoked when particular messages are dispatched.

There are no specific requirements about a message class except that it should be serializable:

// src/Message/SmsNotification.php
namespace App\Message;

class SmsNotification
{
    public function __construct(
        private string $content,
    ) {
    }

    public function getContent(): string
    {
        return $this->content;
    }
}

Your message handler is a PHP callable. The recommended way is to create a class with the AsMessageHandler attribute and an __invoke method that’s type-hinted with the message class or interface:

// src/MessageHandler/SmsNotificationHandler.php
namespace App\MessageHandler;

use App\Message\SmsNotification;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler]
class SmsNotificationHandler
{
    public function __invoke(SmsNotification $message)
    {
        // ... do some work - like sending an SMS message!
    }
}

How to handle messages asynchronously

By default, messages are handled as soon as they are dispatched which may not be ideal if the message handling takes a long time but doesn’t need to be completed immediately (e.g., generating a long report in the background and sending an email once done when the user presses some button in your app).

To handle a message asynchronously, you can configure a transport. The transport can send your messages to a queuing system and receive them via a worker.

You can register a transport from your config/packages/messenger.yaml file:

# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async: "%env(MESSENGER_TRANSPORT_DSN)%"

            # or expanded to configure more options
            #async:
            #    dsn: "%env(MESSENGER_TRANSPORT_DSN)%"
            #    options: []

If you installed the Messenger component via Symfony Flex, you should have some example transport DSNs in your .env file. Uncomment one of these to use them or see here for additional transport configuration options.

You can then route specific messages to specific transports.

You can route individual messages:

// src/Message/SmsNotification.php
namespace App\Message;

use Symfony\Component\Messenger\Attribute\AsMessage;

#[AsMessage('async')]
class SmsNotification
{
    // ...
}

Or you can route all messages within a PHP namespace like App\Message\* to particular transports. See here for details.

Consuming messages

To actually consume messages, you need to start worker processes:

php bin/console messenger:consume <transports>

For example, to consume messages from a transport named async, you can do:

php bin/console messenge:consume async

You can pass the -vv flag to get more verbose output:

php bin/console messenge:consume async -vv

To consume messages from all transports, run:

php bin/console messenge:consume --all

Deploying to production

  • Use a process manager like Supervisor to ensure your workers are always running.
  • Don’t let workers run forever.
    • Some services like Doctrine’s EntityManager consume more memory over time. So instead of allowing your worker processes to run forever, use a flag like messenger:consume --limit=10 to tell your worker to only handle 10 messages before exiting. A process manager like Supervisor should then take care of spawning new processes.
  • Restart your workers on each deploy so that they use the newly deployed code.
    • Run messenger:stop-workers as part of your deploy script to gracefully shut down workers and then ensure your process manager like Supervisor spawns new processes.
  • See here for more.

Recipes

List all configured handlers

php bin/console debug:messenger

Links

    Tagged: Symfony