Blog | Pimcore

How to use Symfony Messenger with Pimcore? | pimcore.com

Written by Mateusz Soroka | Nov 22, 2022 11:00:00 PM

Don’t waste your time waiting - let Symfony Messenger do the job in the background in your Pimcore application.

What is Symfony Messenger?

Symfony Messenger is a component that helps applications manage queues, especially when communicating with other systems or delegating work to different services. Since Pimcore replaced all existing asynchronous queues with Symfony Messenger, it's even easier to implement it in your project.


Use case in real life

Let's say that there is a competition form on your website, even very simple one with three fields:

  • firstname
  • e-mail
  • message

And it's very similar to the class definition which objects will be stored in Pimcore.


The controller handles the contact form, saves the object in the database, and sends an e-mail with accurate data.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
<?php
namespace App\Controller;
use App\Form\Type\CompetitionApplicationType;
use Carbon\Carbon;
use Exception;
use Pimcore\Controller\FrontendController;
use Pimcore\Mail;
use Pimcore\Model\DataObject\CompetitionApplication;
use Pimcore\Model\DataObject\Service;
use Pimcore\Translation\Translator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class DefaultController extends FrontendController
{
public function defaultAction(Request $request, Translator $translator): Response
{
$competitionApplication = new CompetitionApplication();
$form = $this->createForm(CompetitionApplicationType::class, $competitionApplication);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$competitionApplication = $form->getData();
try {
$this->processCompetitionApplication($competitionApplication);
$this->addFlash('success', $translator->trans('form.competition-application.success'));
} catch (Exception $e) {
$this->addFlash('error', $translator->trans('form.competition-application.error'));
}
}
return $this->render('default/default.html.twig', [
'form' => $form->createView()
]);
}
/**
* @throws Exception
*/
private function processCompetitionApplication(CompetitionApplication $competitionApplication): void
{
$now = Carbon::now();
$competitionApplication->setDate($now);
$competitionApplication->setKey($now->timestamp);
$competitionApplication->setPublished(true);
$competitionApplication->setParent(Service::createFolderByPath($now->format('/Y/m/d')));
$competitionApplication->save();
$mail = new Mail();
$mail->to('competition@example.com');
$mail->subject('New competition application');
$mail->html("<p>Firstname: </p><p>Email: </p><p>Message: </p>");
$mail->setParams([
'firstname' => $competitionApplication->getFirstname(),
'email' => $competitionApplication->getEmail(),
'message' => $competitionApplication->getMessage(),
]);
$mail->send();
}
}

Well, this already works fine, but is there an even better way? 

Where is the problem?

Before the implementation of Symfony Messenger, processing the contact form, saving the object, and sending an e-mail took about 4 seconds.


Why? The main reason is communication with the SMTP server responsible for sending e-mails. There is a huge difference in processing time when skipping this part and just saving the object. The difference is noticeable and can affect how users experience the website's performance and usability.



Magic of queues

This is precisely why Symfony Messenger was created, and Pimcore replaced its existing asynchronous queues. Two classes have to be created:

  • CompetitionApplicationMessage, which stores data, CompetitionApplication in this case
  • CompetitionApplicationMessageHandler, which in this case is responsible for saving the object and sending the e-mail.

Now, in the controller, instead of executing the saving object and sending an e-mail, call CompetitionApplicationMessage by injecting the MessageBusInterface into the method responsible for handling requests. Again, remember to change your services.yaml configuration and tell your handler that it is a message handler now.

It's very exciting. But there is a big disappointment because the form is still processing for about 4 seconds.

Asynchronicity is the key

By default, Symfony Messenger executes tasks immediately. To make it asynchronous, you have to create config/packages/messenger.yaml configuration file with a definition of how the Message should be executed and handled. MESSENGER_TRANSPORT_DSN environmental variable indicates which transporter will be responsible for queues. It can be redis, RabbitMQ, or even a database. For queue tasks to execute correctly using cron or supervisord cyclically, the command php bin/console messenger:consume async must be run.


Results

By processing our form asynchronously with Symfony Messenger, we can see a significant increase in performance.


Also, using a system to control processes such as supervisord, the logic executed within our task is charged to the service responsible for handling the processes, rather than the application, which with high traffic and a large number of processes can give a gigantic increase in perceived performance and stability.

Another advantage is that in a situation where the SMTP service is unavailable, the task will not be marked as completed, and there will be three attempts to restart it. If the logic remained in the controller and the email was not sent correctly, there would never be a retry. According to the Messenger configuration, the number of retry attempts and the intervals between them can be managed.

Resources