Failed Conditions
Push — master ( 5f5e96...daf37e )
by Adrien
03:02
created

Mailer   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 116
Duplicated Lines 0 %

Test Coverage

Coverage 44.23%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 14
eloc 45
dl 0
loc 116
rs 10
c 1
b 0
f 0
ccs 23
cts 52
cp 0.4423

6 Methods

Rating   Name   Duplication   Size   Complexity  
A sendMessageAsync() 0 16 2
A messageToEmail() 0 9 1
A sendAllMessages() 0 7 2
A __construct() 0 9 1
A sendMessage() 0 24 5
A acquireLock() 0 18 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Service;
6
7
use Cake\Chronos\Chronos;
8
use Doctrine\ORM\EntityManager;
9
use Ecodev\Felix\Model\Message;
10
use Ecodev\Felix\Repository\LogRepository;
11
use Ecodev\Felix\Repository\MessageRepository;
12
use Exception;
13
use Symfony\Component\Mailer\Transport\TransportInterface;
14
use Symfony\Component\Mime\Address;
15
use Symfony\Component\Mime\Email;
16
17
/**
18
 * Service to send a message as an email.
19
 */
20
class Mailer
21
{
22 1
    public function __construct(
23
        private readonly EntityManager $entityManager,
24
        private readonly MessageRepository $messageRepository,
25
        private readonly TransportInterface $transport,
26
        private readonly string $phpPath,
27
        private readonly ?string $toEmailOverride,
28
        private readonly string $fromEmail,
29
        protected string $fromName
30
    ) {
31 1
    }
32
33
    /**
34
     * Send a message asynchronously in a separate process.
35
     *
36
     * This should be the preferred way to send a message, unless if we are the cron.
37
     */
38
    public function sendMessageAsync(Message $message): void
39
    {
40
        // Be sure we have an ID before "forking" process
41
        if ($message->getId() === null) {
42
            $this->entityManager->flush();
43
        }
44
45
        $args = [
46
            (string) realpath('bin/send-message.php'),
47
            (string) $message->getId(),
48
        ];
49
50
        $escapedArgs = array_map('escapeshellarg', $args);
51
52
        $cmd = escapeshellcmd($this->phpPath) . ' ' . implode(' ', $escapedArgs) . ' > /dev/null 2>&1 &';
53
        exec($cmd);
54
    }
55
56
    /**
57
     * Send a message.
58
     */
59 1
    public function sendMessage(Message $message): void
60
    {
61 1
        $mailMessage = $this->messageToEmail($message);
62
63 1
        $email = $message->getEmail();
64 1
        $overriddenBy = '';
65 1
        if ($this->toEmailOverride) {
66
            $email = $this->toEmailOverride;
67
            $overriddenBy = ' overridden by ' . $email;
68
        }
69
70 1
        $recipient = $message->getRecipient();
71 1
        $recipientName = $recipient?->getName() ?: '';
72 1
        if ($email) {
73 1
            $mailMessage->addTo(new Address($email, $recipientName));
74 1
            $this->transport->send($mailMessage);
75
        }
76
77 1
        $message->setDateSent(new Chronos());
78 1
        $this->entityManager->flush();
79
80 1
        $addressList = $mailMessage->getFrom();
81 1
        if ($addressList) {
0 ignored issues
show
introduced by
$addressList is an empty array, thus is always false.
Loading history...
82 1
            echo 'email from ' . $addressList[0]->getAddress() . ' sent to: ' . $message->getEmail() . "\t" . $overriddenBy . "\t" . $message->getSubject() . PHP_EOL;
83
        }
84
    }
85
86
    /**
87
     * Convert our model message to an email.
88
     */
89 1
    protected function messageToEmail(Message $message): Email
90
    {
91 1
        $email = new Email();
92 1
        $email->subject($message->getSubject());
93 1
        $email->html($message->getBody());
94
95 1
        $email->from(new Address($this->fromEmail, $this->fromName));
96
97 1
        return $email;
98
    }
99
100
    /**
101
     * Send all messages that are not sent yet.
102
     */
103
    public function sendAllMessages(): void
104
    {
105
        $this->acquireLock();
106
107
        $messages = $this->messageRepository->getAllMessageToSend();
108
        foreach ($messages as $message) {
109
            $this->sendMessage($message);
110
        }
111
    }
112
113
    /**
114
     * Acquire an exclusive lock.
115
     *
116
     * This is to ensure only one mailer can run at any given time. This is to prevent sending the same email twice.
117
     */
118
    private function acquireLock(): void
119
    {
120
        $lockFile = 'data/tmp/mailer.lock';
121
        touch($lockFile);
122
        $lock = fopen($lockFile, 'r+b');
123
        if ($lock === false) {
124
            throw new Exception('Could not read lock file. This is not normal and might be a permission issue');
125
        }
126
127
        if (!flock($lock, LOCK_EX | LOCK_NB)) {
128
            $message = LogRepository::MAILER_LOCKED;
129
            _log()->info($message);
130
131
            echo $message . PHP_EOL;
132
            echo 'If the problem persist and another mailing is not in progress, try deleting ' . $lockFile . PHP_EOL;
133
134
            // Not getting the lock is not considered as error to avoid being spammed
135
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
136
        }
137
    }
138
}
139