Don’t waste your time waiting - let Symfony Messenger do the job in the background in your Pimcore application.
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.
Let's say that there is a competition form on your website, even very simple one with three fields:
The controller handles the contact form, saves the object in the database, and sends an e-mail with accurate data.
<?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?
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.
This is precisely why Symfony Messenger was created, and Pimcore replaced its existing asynchronous queues. Two classes have to be created:
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.
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.
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.