src/Service/EmailService.php line 207

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Service;
  4. use App\Exception\ApiException\ApiEmailSearchException;
  5. use App\Exception\ApiException\ApiEmailSendException;
  6. use App\Exception\ApiException\ApiValidationException;
  7. use App\Exception\ApiException\NotFoundException;
  8. use App\Exception\ApiException\SendGridApiException;
  9. use App\Model\Email;
  10. use App\Validator\TemplatePath;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  13. use Symfony\Component\HttpKernel\KernelInterface;
  14. use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
  15. use Symfony\Component\Mailer\MailerInterface;
  16. use Symfony\Component\Mime\Address;
  17. use Symfony\Component\Mime\BodyRendererInterface;
  18. use Symfony\Component\Translation\Translator;
  19. use Symfony\Component\Validator\Validator\ValidatorInterface;
  20. use Symfony\Contracts\Translation\TranslatorInterface;
  21. use Twig\Environment;
  22. class EmailService
  23. {
  24.     /** @var LoggerInterface */
  25.     protected $log;
  26.     /** @var MailerInterface */
  27.     private $mailer;
  28.     /** @var Translator */
  29.     private $translator;
  30.     /** @var BodyRendererInterface */
  31.     private $bodyRenderer;
  32.     /** @var ValidatorInterface */
  33.     private $validator;
  34.     /** @var Environment */
  35.     private $twig;
  36.     /** @var KernelInterface */
  37.     private $kernel;
  38.     /** @var SendGridApi */
  39.     private $sendGridApi;
  40.     public function __construct(
  41.         LoggerInterface $log,
  42.         MailerInterface $mailer,
  43.         TranslatorInterface $translator,
  44.         BodyRendererInterface $bodyRenderer,
  45.         ValidatorInterface $validator,
  46.         Environment $twig,
  47.         KernelInterface $kernel,
  48.         SendGridApi $sendGridApi
  49.     ) {
  50.         $this->log $log;
  51.         $this->mailer $mailer;
  52.         $this->translator $translator;
  53.         $this->bodyRenderer $bodyRenderer;
  54.         $this->validator $validator;
  55.         $this->twig $twig;
  56.         $this->kernel $kernel;
  57.         $this->sendGridApi $sendGridApi;
  58.     }
  59.     /**
  60.      * @param TemplatedEmail $templatedEmail
  61.      * @throws ApiEmailSendException
  62.      */
  63.     public function send(TemplatedEmail $templatedEmail): void
  64.     {
  65.         try {
  66.             $this->mailer->send($templatedEmail);
  67.         } catch (TransportExceptionInterface $e) {
  68.             $this->log->error(get_class($e).' - '.$e->getMessage());
  69.             throw new ApiEmailSendException($e->getMessage());
  70.         }
  71.     }
  72.     /**
  73.      * @param string $query
  74.      * @param int    $limit
  75.      *
  76.      * @return array
  77.      * @throws ApiEmailSearchException
  78.      */
  79.     public function findMessagesByQuery(string $queryint $limit 0): array
  80.     {
  81.         try {
  82.             if ($limit && $limit <= SendGridApi::FIND_MESSAGES_MAX_LIMIT) {
  83.                 return $this->sendGridApi->findMessagesByQuery($query$limit);
  84.             }
  85.             // init first batch
  86.             $messages $messagesId = [];
  87.             $batchQuery $query;
  88.             $batchCpt 0;
  89.             do {
  90.                 ++$batchCpt;
  91.                 // messages recovery for current batch
  92.                 $tmpMessages = [];
  93.                 // get messages for current batch
  94.                 $this->log->info(sprintf(
  95.                     'Batch #%d: Try to get %d messages by API (%d already recovered)...',
  96.                     $batchCpt,
  97.                     SendGridApi::FIND_MESSAGES_MAX_LIMIT,
  98.                     count($messages),
  99.                 ));
  100.                 $this->log->debug(sprintf(
  101.                     'Batch #%d: limit = %d, query = %s...',
  102.                     $batchCpt,
  103.                     $limit,
  104.                     $batchQuery,
  105.                 ));
  106.                 $batchMessages $this->sendGridApi->findMessagesByQuery($batchQuerySendGridApi::FIND_MESSAGES_MAX_LIMIT);
  107.                 $this->log->debug(sprintf(
  108.                     'Batch #%d: %d messages recovered by API.',
  109.                     $batchCpt,
  110.                     count($batchMessages),
  111.                 ));
  112.                 // filter messages already recovery by previous batch
  113.                 foreach ($batchMessages as $batchMessage) {
  114.                     if (in_array($batchMessage['msg_id'], $messagesId)) {
  115.                         continue;
  116.                     }
  117.                     $messagesId[] = $batchMessage['msg_id'];
  118.                     $tmpMessages[] = $batchMessage;
  119.                 }
  120.                 $this->log->debug(sprintf(
  121.                     'Batch #%d: %d messages after remove duplicate messages.',
  122.                     $batchCpt,
  123.                     count($tmpMessages),
  124.                 ));
  125.                 // finish here if no new messages recovered. Avoid infinite loop
  126.                 if (!$tmpMessages) {
  127.                     $this->log->debug(sprintf('Batch #%d: [STOP] No new messages recovered'$batchCpt));
  128.                     break;
  129.                 }
  130.                 // determine period for next batch, by last event time.
  131.                 // Messages return by API sorted by last event time in descending order
  132.                 $batchPeriodEnd $tmpMessages[array_key_last($tmpMessages)]['last_event_time'];
  133.                 $batchPeriodStart = (new \DateTime($batchPeriodEnd))->modify('-1 month')->format(\DateTime::ATOM);
  134.                 // build query for next batch
  135.                 $batchQuery 'last_event_time BETWEEN TIMESTAMP "'.$batchPeriodStart.'" AND TIMESTAMP "'.$batchPeriodEnd.'"';
  136.                 if ($query) {
  137.                     $batchQuery $query.' AND '.$batchQuery;
  138.                 }
  139.                 $messages array_merge($messages$tmpMessages);
  140.                 if ($limit && count($messages) >= $limit) {
  141.                     $this->log->debug(sprintf('Batch #%d: [STOP] Message limit is reached'$batchCpt));
  142.                     $messages array_slice($messages0$limit);
  143.                     break;
  144.                 }
  145.             } while (SendGridApi::FIND_MESSAGES_MAX_LIMIT === count($batchMessages));
  146.             $this->log->info(sprintf(
  147.                 '[OK] %d messages recovered by API in %d batch',
  148.                 count($messages),
  149.                 $batchCpt,
  150.             ));
  151.             return $messages;
  152.         } catch (SendGridApiException $e) {
  153.             $this->log->error(get_class($e).' - '.$e->getMessage());
  154.             throw new ApiEmailSearchException($e->getMessage());
  155.         }
  156.     }
  157.     /**
  158.      * @param string $messageId
  159.      *
  160.      * @return array
  161.      * @throws ApiEmailSearchException
  162.      * @throws NotFoundException
  163.      */
  164.     public function findMessageById(string $messageId): array
  165.     {
  166.         try {
  167.             return $this->sendGridApi->findMessageById($messageId);
  168.         } catch (SendGridApiException $e) {
  169.             $this->log->error(get_class($e).' - '.$e->getMessage());
  170.             throw new ApiEmailSearchException($e->getMessage());
  171.         }
  172.     }
  173.     /**
  174.      * @param Email $email
  175.      * @return TemplatedEmail
  176.      * @throws ApiValidationException
  177.      * @throws \Twig\Error\LoaderError
  178.      * @throws \Twig\Error\RuntimeError
  179.      * @throws \Twig\Error\SyntaxError
  180.      */
  181.     public function generateTemplatedEmail(Email $email): TemplatedEmail
  182.     {
  183.         $errors $this->validator->validate($email);
  184.         if (count($errors) > 0) {
  185.             throw new ApiValidationException($errors);
  186.         }
  187.         $templatedEmail = (new TemplatedEmail())
  188.             ->from($this->buildFromAddress($email->getFrom(), $email->getFromName()))
  189.             ->to(...Address::createArray($email->getTo()))
  190.             ->htmlTemplate(TemplatePath::getTemplateRealPath($email->getTemplatePath()))
  191.             ->context(array_merge($email->getTemplateData(), ['email_locale' => $email->getLocale()]));
  192.         if (is_array($email->getCc())) {
  193.             $templatedEmail->cc(...Address::createArray($email->getCc()));
  194.         }
  195.         if (is_array($email->getBcc())) {
  196.             $templatedEmail->bcc(...Address::createArray($email->getBcc()));
  197.         }
  198.         if (is_array($email->getReply())) {
  199.             $templatedEmail->replyTo(...Address::createArray($email->getReply()));
  200.         }
  201.         $attachments $email->getAttachments();
  202.         if (is_array($attachments)) {
  203.             foreach ($attachments as $attachment) {
  204.                 $fileString base64_decode($attachment['content']);
  205.                 $templatedEmail->attach($fileString$attachment['filename'], $attachment['contentType']);
  206.             }
  207.         }
  208.         // Set locale for email template rendering
  209.         $originalLocale $this->translator->getLocale();
  210.         if (!is_null($email->getLocale())) {
  211.             $this->translator->setLocale($email->getLocale());
  212.         }
  213.         // Set default email subject
  214.         if (is_null($email->getSubject())) {
  215.             $subject trim($this->twig->render(
  216.                 TemplatePath::getSubjectTemplateRealPath($email->getTemplatePath()),
  217.                 $email->getTemplateData()
  218.             ));
  219.             $templatedEmail->subject($subject);
  220.         } else {
  221.             $templatedEmail->subject($email->getSubject());
  222.         }
  223.         // Render email
  224.         $this->bodyRenderer->render($templatedEmail);
  225.         // Reset locale
  226.         $this->translator->setLocale($originalLocale);
  227.         return $templatedEmail;
  228.     }
  229.     /**
  230.      * @param string $from
  231.      * @param string $fromName
  232.      * @return Address
  233.      * @throws \Exception
  234.      */
  235.     private function buildFromAddress(string $fromstring $fromName): Address
  236.     {
  237.         switch ($this->kernel->getEnvironment()) {
  238.             case 'prod':
  239.                 return new Address("$from@hipay.com"$fromName);
  240.             case 'stage':
  241.                 return new Address("$from@stage.hipay.com""$fromName STAGE");
  242.             case 'qa':
  243.                 return new Address("$from@qa.hipay.com""$fromName QA");
  244.             case 'test':
  245.                 return new Address("$from@test.hipay.com""$fromName TEST");
  246.             case 'dev':
  247.             case 'devgcp':
  248.                 return new Address("$from@dev.hipay.com""$fromName DEV");
  249.             default:
  250.                 throw new \Exception('Unsuppported environment');
  251.         }
  252.     }
  253. }