Failed Conditions
Push — master ( 7aeedc...f7e5fc )
by Adrien
05:38 queued 02:24
created

Mailer   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 125
Duplicated Lines 0 %

Test Coverage

Coverage 50.84%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 12
eloc 52
c 1
b 0
f 0
dl 0
loc 125
ccs 30
cts 59
cp 0.5084
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A sendAllMessages() 0 7 2
A __construct() 0 9 1
A sendMessage() 0 24 3
A acquireLock() 0 18 3
A modelMessageToMailMessage() 0 19 1
A sendMessageAsync() 0 16 2
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 Laminas\Mail;
14
use Laminas\Mail\Transport\TransportInterface;
15
use Laminas\Mime\Message as MimeMessage;
16
use Laminas\Mime\Mime;
17
use Laminas\Mime\Part as MimePart;
18
19
/**
20
 * Service to send a message as an email.
21
 */
22
class Mailer
23
{
24 1
    public function __construct(
25
        private readonly EntityManager $entityManager,
26
        private readonly MessageRepository $messageRepository,
27
        private readonly TransportInterface $transport,
28
        private readonly string $phpPath,
29
        private readonly ?string $toEmailOverride,
30
        private readonly string $fromEmail,
31
        protected string $fromName
32
    ) {
33 1
    }
34
35
    /**
36
     * Send a message asynchronously in a separate process.
37
     *
38
     * This should be the preferred way to send a message, unless if we are the cron.
39
     */
40
    public function sendMessageAsync(Message $message): void
41
    {
42
        // Be sure we have an ID before "forking" process
43
        if ($message->getId() === null) {
44
            $this->entityManager->flush();
45
        }
46
47
        $args = [
48
            (string) realpath('bin/send-message.php'),
49
            (string) $message->getId(),
50
        ];
51
52
        $escapedArgs = array_map('escapeshellarg', $args);
53
54
        $cmd = escapeshellcmd($this->phpPath) . ' ' . implode(' ', $escapedArgs) . ' > /dev/null 2>&1 &';
55
        exec($cmd);
56
    }
57
58
    /**
59
     * Send a message.
60
     */
61 1
    public function sendMessage(Message $message): void
62
    {
63 1
        $mailMessage = $this->modelMessageToMailMessage($message);
64
65 1
        $email = $message->getEmail();
66 1
        $overriddenBy = '';
67 1
        if ($this->toEmailOverride) {
68
            $email = $this->toEmailOverride;
69
            $overriddenBy = ' overridden by ' . $email;
70
        }
71
72 1
        $recipient = $message->getRecipient();
73 1
        $recipientName = $recipient?->getName();
74 1
        if ($email) {
75 1
            $mailMessage->addTo($email, $recipientName);
76 1
            $this->transport->send($mailMessage);
77
        }
78
79 1
        $message->setDateSent(new Chronos());
80 1
        $this->entityManager->flush();
81
82 1
        $addressList = $mailMessage->getFrom();
83 1
        $addressList->rewind();
84 1
        echo 'email from ' . $addressList->current()->getEmail() . ' sent to: ' . $message->getEmail() . "\t" . $overriddenBy . "\t" . $message->getSubject() . PHP_EOL;
85
    }
86
87
    /**
88
     * Convert our model message to a mail message.
89
     */
90 1
    protected function modelMessageToMailMessage(Message $modelMessage): Mail\Message
91
    {
92
        // set Mime type html
93 1
        $htmlPart = new MimePart($modelMessage->getBody());
94 1
        $htmlPart->type = Mime::TYPE_HTML;
95 1
        $htmlPart->charset = 'UTF-8';
96 1
        $htmlPart->encoding = Mime::ENCODING_BASE64;
97
98 1
        $body = new MimeMessage();
99 1
        $body->setParts([$htmlPart]);
100
101 1
        $mailMessage = new Mail\Message();
102 1
        $mailMessage->setEncoding('UTF-8');
103 1
        $mailMessage->setSubject($modelMessage->getSubject());
104 1
        $mailMessage->setBody($body);
105
106 1
        $mailMessage->setFrom($this->fromEmail, $this->fromName);
107
108 1
        return $mailMessage;
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
        $lock = fopen($lockFile, 'r+b');
134
        if ($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($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