45
Introduction to RabbitMQ and Symfony
One day I was trying to learn the deep concepts of RabbitMQ, its use cases and why it's different from other message brokers. I've started by reading the cool documentation and then I was eager to try it out in a demo application.
Turns out it wasn't so easy to setup a Symfony application and connect to RabbitMQ. Google displayed different solutions and I also needed StackOverflow to install some additional dependencies.
Hopefully I could condense all the information and display it here in a simple and fun way.
But why Symfony, right? It's a popular PHP framework and I really liked its software architecture and integration with RabbitMQ.
First, I installed Symfony CLI and create a a traditional web application:
symfony new --full php-symfony-rabbitmq
My application now can be started by running the following command on the new project directory:
symfony serve
composer require symfony/messenger
Following the good documentation, I created a simple class to encapsulate the message to be published:
final class SampleMessage
{
public function __construct(private string $content)
{
}
public function getContent(): string
{
return $this->content;
}
}
And its respective handler:
final class SampleMessangeHandler implements MessageHandlerInterface
{
public function __invoke(SampleMessage $message)
{
// magically invoked when an instance of SampleMessage is dispatched
print_r('Handler handled the message!');
}
}
Now to see if everything is working so far, I added a simple endpoint to dispatch a message:
final class SampleController extends AbstractController
{
#[Route('/sample', name: 'sample')]
public function sample(MessageBusInterface $bus): Response
{
$message = new SampleMessage('content');
$bus->dispatch($message);
return new Response(sprintf('Message with content %s was published', $message->getContent()));
}
}
Thankfully when I hit the endpoint, I can see the output from the handler and the http response from the controller:
curl http://localhost:8000/sample
Handler handled the message!Message with content content was published
I'll use docker to spawn a RabbitMQ instance, because it's much easier. Just install Docker Compose and then edit the .docker-compose.yml
file on the project root directory to add a new service:
version: '3'
services:
rabbitmq:
image: rabbitmq:3.9-management
ports:
- '5672:5672'
- '15672:15672'
By running docker-compose up
on project root directory, I can see everything is working.
The AMQP (Advanced Message Queuing Protocol, the protocol which RabbitMQ uses) extension is needed to be installed using PECL (PHP Extension Community Language). This was a bit tricky, at least on MacOS:
First, installed HomeBrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Then installed rabbitmq-c
:
brew install rabbitmq-c
Which enabled the installation of amqp extension:
pecl install amqp
When prompted to enter the path to librabbitmq
, you need to check which version is installed inside the folder /usr/local/Cellar/rabbitmq-c/
. Mine was 0.11.0
:
Set the path to librabbitmq install prefix [autodetect] : /usr/local/Cellar/rabbitmq-c/0.11.0
Finally, the last dependency:
composer require symfony/amqp-messenger
What a relief! Now I can go back to proper coding.
By default the username and password created in the docker image are guest
, which is coincidentally the exact line I need to uncomment on .env
file to expose the RabbitMQ connection as an environment variable:
###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=doctrine://default
MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
###< symfony/messenger ###
Now with this new known value, I need to tell the application which messages should be handled by this new transport.
Then on file config/packages/messanger.yaml
, I defined a new transport and the message type that will use it:
framework:
messenger:
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Route your messages to the transports
'App\Message\SampleMessage': async
Now I can hit the previous endpoint again:
curl http://localhost:8000/sample
And in another terminal I can check the message is still being handled:
php bin/console messenger:consume async -vv
This outputs a verbose log message but the important parts are:
[messenger] Received message App\Message\SampleMessage
[messenger] App\Message\SampleMessage was handled successfully (acknowledging to transport).
[messenger] Message App\Message\SampleMessage handled by App\MessageHandler\SampleMessangeHandler
To be sure the application is really using RabbitMQ, I can access the admin on http://localhost:15672 and see this cool chart:
45