How to Implement a Simple Queue in Symfony
Tags: symfony, php, queue, messenger
Published: 2023-12-03 15:38:37

Symfony is a popular PHP framework that provides many features and components to create web applications. One of these components is the Messenger component, which allows you to send and receive messages between different parts of your application or between different applications. In this article, we will see how to use the Messenger component to implement a simple queue system using Redis as the transport layer.

## Installing the Required Packages

The first step is to install the Messenger component and the Redis adapter for it. You can do this by running the following commands in your project directory:

```bash
composer require symfony/messenger
composer require symfony/redis-messenger
```

This will add the necessary dependencies to your composer.json file and install them.

## Configuring the Transport

The next step is to configure the transport that will be used to send and receive messages. In this case, we will use Redis, which is a fast and reliable in-memory data store that can act as a message broker. To use Redis, you need to have a Redis server running and accessible from your application. You can use the official Redis Docker image for this purpose.

To configure the Redis transport, you need to edit the .env file in your project root and uncomment the line that defines the MESSENGER_TRANSPORT_DSN environment variable. This variable holds the connection string to the Redis server and the name of the queue that will be used. For example, if your Redis server is running on localhost on port 6379 and you want to use a queue named messages, you can set the variable as follows:

```env
MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
```

You can also use a different queue name or a different Redis server if you want.

## Creating the Message and the Handler

The next step is to create the message and the handler classes that will be used by the Messenger component. A message is a simple PHP object that represents the data that will be sent or received by the transport. A handler is a PHP class that implements the logic that will be executed when a message is received. You can create these classes using the make:message command provided by the Symfony console:

```bash
php bin/console make:message
```

This command will ask you to enter the name of the message class and the name of the handler class. For this example, we will use SmsNotification as the message class and SmsNotificationHandler as the handler class. The command will generate two files in the src/Message and src/MessageHandler directories respectively. You can edit these files to add the properties and methods that you need for your message and handler.

For example, the SmsNotification class could have a property that holds the phone number and a property that holds the text of the SMS:

```php
<?php

namespace App\Message;

class SmsNotification
{
    private $phoneNumber;
    private $text;

    public function __construct(string $phoneNumber, string $text)
    {
        $this->phoneNumber = $phoneNumber;
        $this->text = $text;
    }

    public function getPhoneNumber(): string
    {
        return $this->phoneNumber;
    }

    public function getText(): string
    {
        return $this->text;
    }
}
```

The SmsNotificationHandler class could have a method that uses a third-party service to send the SMS:

```php
<?php

namespace App\MessageHandler;

use App\Message\SmsNotification;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;

class SmsNotificationHandler implements MessageHandlerInterface
{
    public function __invoke(SmsNotification $smsNotification)
    {
        // Use a third-party service to send the SMS
        // For example, using Twilio:
        // $twilio = new \Twilio\Rest\Client($sid, $token);
        // $twilio->messages->create($smsNotification->getPhoneNumber(), [
        //     'from' => $from,
        //     'body' => $smsNotification->getText(),
        // ]);
    }
}
```

## Routing the Message to the Transport

The next step is to tell the Messenger component which transport to use for each message. You can do this by editing the config/packages/messenger.yaml file and adding a routing section. In this section, you can map each message class to a transport name. For example, to use the Redis transport that we configured earlier for the SmsNotification message, you can add the following lines:

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

        routing:
            # async is whatever name you gave your transport above
            'App\Message\SmsNotification': async
```

You can also use different transports for different messages if you want.

## Dispatching the Message

The next step is to dispatch the message to the transport. You can do this by using the MessageBusInterface service that is provided by the Messenger component. You can inject this service into any class that needs to send messages, such as a controller, a service, or a command. For example, in a controller, you can dispatch a message like this:

```php
<?php

namespace App\Controller;

use App\Message\SmsNotification;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    #[Route('/home', name: 'app_home')]
    public function index(MessageBusInterface $messageBus): JsonResponse
    {
        $messageBus->dispatch(new SmsNotification('1234567890', 'Hello from Symfony!'));

        return $this->json([
            'message' => 'Message has been dispatched successfully!',
        ]);
    }
}
```

This will send the message to the Redis transport, where it will be stored until it is consumed by a worker.

## Consuming the Message

The final step is to consume the message from the transport and execute the handler. You can do this by using the messenger:consume command provided by the Symfony console:

```bash
php bin/console messenger:consume async -vv
```

This command will start a worker process that will listen to the async transport (or any other transport that you specify) and handle the messages that are received. The -vv option will enable verbose output that will show you the details of each message and handler. You can also use other options to control the behavior of the worker, such as the number of messages to handle, the memory limit, the signal handling, etc. You can see the full list of options by running:

```bash
php bin/console messenger:consume --help
```

You can also run multiple workers in parallel to increase the throughput of your queue system. You can use tools like Supervisor or Systemd to manage your workers and ensure that they are always running.

## Conclusion

In this article, we have seen how to use the Messenger component to implement a simple queue system in Symfony using Redis as the transport layer. We have learned how to install the required packages, configure the transport, create the message and the handler, route the message to the transport, dispatch the message, and consume the message. The Messenger component provides a powerful and flexible way to send and receive messages between different parts of your application or between different applications. You can use it to implement various patterns, such as command bus, event bus, query bus, etc. You can also use different transports, such as AMQP, Doctrine, Amazon SQS, Google Pub/Sub, etc. You can find more information and examples in the [official documentation](https://symfony.com/doc/current/components/messenger.html).