phpsymfonysymfony4api-platform.comsymfony-messenger

Executing mail sending via Symfony Messenger


I am in search for a big help with my code. I tried everything in my power to connect some Symfony features, and I think I am going somewhere..

I am using Symfony 4.2 and API Platform and trying to add process that sending of the email message to be consumed asynchronously.

I have my Comment entity which is triggering it on persisting entity. It's triggering my __invoke function but there is a problem. I don't quite understand what should I do next.

As it says in documentation here, first I need to configure Data Persister:

<?php

namespace App\DataPersister;

use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Entity\Comment;

final class EmailNotificationDataPersister implements ContextAwareDataPersisterInterface
{
private $decorated;
private $mailer;

public function __construct(ContextAwareDataPersisterInterface $decorated, \Swift_Mailer $mailer)
{
    $this->decorated = $decorated;
    $this->mailer = $mailer;
}

public function supports($data, array $context = []): bool
{
    return $this->decorated->supports($data, $context);
}

public function persist($data, array $context = [])
{
    $result = $this->decorated->persist($data, $context);

    if (
        $data instanceof Comment && (
            ($context['collection_operation_name'] ?? null) === 'post')
    ) {
        $this->sendWelcomeEmail($data);
    }

    return $result;
}

public function remove($data, array $context = [])
{
    return $this->decorated->remove($data, $context);
}

private function sendWelcomeEmail(Comment $comment)
{
    // Your welcome email logic...
    // $this->mailer->send(...);
}
}

I am using Symfony 4.2 so I have installed swift mailer email client.

Also I have defined EmailSubscriber:

<?php

namespace App\EventSubscriber;

use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\Comment;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\Messenger\MessageBusInterface;

final class EmailNotificationSubscriber implements EventSubscriberInterface

{

private $messageBus;

public function __construct(MessageBusInterface $messageBus)
{
    $this->messageBus = $messageBus;
}

public static function getSubscribedEvents()
{
    return [
        KernelEvents::VIEW => [ 'sendMail', EventPriorities::POST_WRITE],
    ];
}

public function sendMail(GetResponseForControllerResultEvent $event)
{
    $comment = $event->getControllerResult();
    $method = $event->getRequest()->getMethod();


    if (!$comment instanceof Comment || Request::METHOD_POST !== $method) {
        return;
    }

    $this->messageBus->dispatch(new Comment());
}
}

And finally I have implemented handler:

<?php

namespace App\Handler;

use App\Entity\Comment;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;

final class EmailNotificationHandler implements MessageHandlerInterface
{
    public function __invoke(Comment $comment)
    {
        // do something with the resource
    }
}

When I trigger persist entity in my api platform the var_dump() from invoke function is caught.

I have no idea if something is wrong and what can I do next. I need for email sending to be executed asynchronously by using the Symfony Messenger.

In my .env file:

MESSENGER_TRANSPORT_DSN=amqp://127.0.0.1:8000/api/messages

And framework .yaml

messenger:
    transports:
        amqp: "%env(MESSENGER_TRANSPORT_DSN)%"

    routing:
        'App\Entity\Comment': amqp

And also is there any way to send an email to any random mail that is posted in user table? I want to set mailer config for that.


Solution

  • For run action asynchronously your need to transport. For local testing symfony server and docker works good.

    #config/packages/messenger.yaml
    transports:
        async: '%env(RABBITMQ_DSN)%'
    

    Place docker-compose.yaml at project root.

    rabbitmq:
        image: rabbitmq:3-management
        ports: [5672, 15672]
    

    Run in console

    docker-compose up -d
    symfony server:start -d
    

    For consuming message run in console

    symfony console messenger:consume async -vv
    

    At production your have to add RABBITMQ_DSN (or MESSENGER_TRANSPORT_DSN) with real credentials for RabbitMQ, Amazon SQS etc and probably use Supervisor for consuming messages.