Mailer::acquireLock()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 11
c 2
b 0
f 0
dl 0
loc 18
ccs 0
cts 12
cp 0
rs 9.9
cc 3
nc 3
nop 0
crap 12
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
    /**
23
     * DO NOT REMOVE THIS PROPERTY !
24
     *
25
     * When it is garbage collected, the lock will be released.
26
     * And the lock must only be released at the end of PHP process,
27
     * never at the end of the method.
28
     *
29
     * @var false|resource
30
     */
31
    private $lock;
32
33 1
    public function __construct(
34
        private readonly EntityManager $entityManager,
35
        private readonly MessageRepository $messageRepository,
36
        private readonly TransportInterface $transport,
37
        private readonly string $phpPath,
38
        private readonly ?string $toEmailOverride,
39
        private readonly string $fromEmail,
40
        protected string $fromName
41
    ) {
42 1
    }
43
44
    /**
45
     * Send a message asynchronously in a separate process.
46
     *
47
     * This should be the preferred way to send a message, unless if we are the cron.
48
     */
49
    public function sendMessageAsync(Message $message): void
50
    {
51
        // Be sure we have an ID before "forking" process
52
        if ($message->getId() === null) {
53
            $this->entityManager->flush();
54
        }
55
56
        $args = [
57
            (string) realpath('bin/send-message.php'),
58
            (string) $message->getId(),
59
        ];
60
61
        $escapedArgs = array_map('escapeshellarg', $args);
62
63
        $cmd = escapeshellcmd($this->phpPath) . ' ' . implode(' ', $escapedArgs) . ' > /dev/null 2>&1 &';
64
        exec($cmd);
65
    }
66
67
    /**
68
     * Send a message.
69
     */
70 1
    public function sendMessage(Message $message): void
71
    {
72 1
        $mailMessage = $this->messageToEmail($message);
73
74 1
        $email = $message->getEmail();
75 1
        $overriddenBy = '';
76 1
        if ($this->toEmailOverride) {
77
            $email = $this->toEmailOverride;
78
            $overriddenBy = ' overridden by ' . $email;
79
        }
80
81 1
        $recipient = $message->getRecipient();
82 1
        $recipientName = $recipient?->getName() ?: '';
83 1
        if ($email) {
84 1
            $mailMessage->addTo(new Address($email, $recipientName));
85 1
            $this->transport->send($mailMessage);
86
        }
87
88 1
        $message->setDateSent(new Chronos());
89 1
        $this->entityManager->flush();
90
91 1
        $addressList = $mailMessage->getFrom();
92 1
        if ($addressList) {
0 ignored issues
show
introduced by
$addressList is an empty array, thus is always false.
Loading history...
93 1
            echo 'email from ' . $addressList[0]->getAddress() . ' sent to: ' . $message->getEmail() . "\t" . $overriddenBy . "\t" . $message->getSubject() . PHP_EOL;
94
        }
95
    }
96
97
    /**
98
     * Convert our model message to an email.
99
     */
100 1
    protected function messageToEmail(Message $message): Email
101
    {
102 1
        $email = new Email();
103 1
        $email->subject($message->getSubject());
104 1
        $email->html($message->getBody());
105
106 1
        $email->from(new Address($this->fromEmail, $this->fromName));
107
108 1
        return $email;
109
    }
110
111
    /**
112
     * Send all messages that are not sent yet.
113
     */
114
    public function sendAllMessages(): void
115
    {
116
        $this->acquireLock();
117
118
        $messages = $this->messageRepository->getAllMessageToSend();
119
        foreach ($messages as $message) {
120
            $this->sendMessage($message);
121
        }
122
    }
123
124
    /**
125
     * Acquire an exclusive lock.
126
     *
127
     * This is to ensure only one mailer can run at any given time. This is to prevent sending the same email twice.
128
     */
129
    private function acquireLock(): void
130
    {
131
        $lockFile = 'data/tmp/mailer.lock';
132
        touch($lockFile);
133
        $this->lock = fopen($lockFile, 'r+b');
134
        if ($this->lock === false) {
135
            throw new Exception('Could not read lock file. This is not normal and might be a permission issue');
136
        }
137
138
        if (!flock($this->lock, LOCK_EX | LOCK_NB)) {
139
            $message = LogRepository::MAILER_LOCKED;
140
            _log()->info($message);
141
142
            echo $message . PHP_EOL;
143
            echo 'If the problem persist and another mailing is not in progress, try deleting ' . $lockFile . PHP_EOL;
144
145
            // Not getting the lock is not considered as error to avoid being spammed
146
            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...
147
        }
148
    }
149
}
150