Разворачиваем RabbitMQ в Docker контейнере

Начнем с установки RabbitMQ, развернем его в отдельном Docker контейнере. Для этого добавим в ваш docker-compose.yaml строчки:

  rabbitmq:
    image: rabbitmq:4.3.2-management-alpine
    env_file:
      - app/.env.local
    ports:
      - "8083:15672"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    restart: always

в моем случае, пользователь RABBITMQ_DEFAULT_USER и пароль RABBITMQ_DEFAULT_PASS указаны в локальном файле .env.local поэтому они автоматически подтянутся оттуда.

- "8083:15672" Пробрасываем только порт для вебморды самого RabbitMQ

Также указал виртуальный хост в файле env.local - RABBITMQ_DEFAULT_VHOST

Устанавливаем нужные пакеты в Symfony для работы с брокером

Для работы нам потребуется установить два пакета:

composer require symfony/messenger symfony/amqp-messenger

При установке может возникнуть ошибка, потому что на php не установлено расширение для работы с протоколом AMQP

После установки пакетов приписываем DSN для подключения к RabbitMQ в моем случае это:

MESSENGER_TRANSPORT_DSN=amqp://guest:guest@rabbitmq:5672/myblog

Настраиваем первый обмен с очередью

Для примера я сделаю событие на авторизацию пользователя на сайте, а внутри обработчика буду просто записывать в лог сообщение, что событие прочитано.

И так для начала сконфигурируем messenger.yaml:

framework:
    messenger:
        # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
        # failure_transport: failed

        transports:
            # https://symfony.com/doc/current/messenger.html#transport-configuration
            # async: '%env(MESSENGER_TRANSPORT_DSN)%'
            # failed: 'doctrine://default?queue_name=failed'
            sync: 'sync://'
            external_events:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                options:
                    auto_setup: true
                    exchange:
                        name: user_events_exchange
                        type: direct
                        default_publish_routing_key: user_logged_in
                    queues:
                        user_login_queue:
                            binding_keys: [ user_logged_in ]

        routing:
            # Route your messages to the transports
            # 'App\Message\YourMessage': async
            'App\Message\UserLoggedInEvent': external_events
  • Первым делом мы создаем подписчика в блоке transports - external_events, dsn указываем переменную окружения которую создали ранее для подключения к RabbitMQ,
  • Задаем название обмена - user_events_exchange,
  • Указываем тип - direct, это тип точного совпадения с ключом сообщения, есть еще типы, но для текущего примера нам этого достаточно,
  • Задаем название очереди (user_login_queue) и привязанные к нему ключи (user_logged_in), в моем случае пока одно сообщения и соотвественно привязано только одно событие user_logged_in,
  • В блоке routing привязываем конкретный класс события к подписчику.

Важный момент, при первом обращении Symfony автоматически проверяет наличие обмена и очереди, если их нет, то они создаются, но на проде как правило пользователь настроен так, что у него нет прав на создание обменов и очередей, для этого нужно запускать команду:

php bin/console messenger:setup-transports

Теперь что бы, отправить сообщение, нам необходимо внедрить зависимость с шиной команд в том месте где мы это ходим сделать, это может быть к примеру контроллер:

private MessageBusInterface $bus,

далее, просто делаем диспатч сообщения:

$this->bus->dispatch(new UserLoggedInEvent(
    userId: $userId,
    ipAddress: $request->getClientIp(),
    createdAt: new \DateTimeImmutable(),
));

Этого достаточно для отправки сообщения, но если мы не запустим консьюмер то сообщение так и будет висеть в Rabbit не прочитанным, для того что бы прочитать сообщение, на нужно воспользоваться командой:

docker compose run --rm php-cli php bin/console messenger:consume external_events -vv