Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
15 / 15
CRAP
100.00% covered (success)
100.00%
1 / 1
MessageBuilder
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
15 / 15
22
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 from
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 to
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 cc
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 bcc
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 replyTo
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 subject
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 textBody
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 htmlBody
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 attach
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 attachContent
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 header
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 build
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
6
 resolveEmailAddress
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10namespace Phexium\Plugin\Mailer;
11
12final class MessageBuilder
13{
14    private ?EmailAddress $from = null;
15
16    /** @var EmailAddress[] */
17    private array $to = [];
18
19    /** @var EmailAddress[] */
20    private array $cc = [];
21
22    /** @var EmailAddress[] */
23    private array $bcc = [];
24
25    private ?EmailAddress $replyTo = null;
26
27    private ?string $subject = null;
28
29    private ?string $textBody = null;
30
31    private ?string $htmlBody = null;
32
33    /** @var Attachment[] */
34    private array $attachments = [];
35
36    /** @var Header[] */
37    private array $headers = [];
38
39    private function __construct()
40    {
41        // Private constructor: use static create() factory method
42    }
43
44    public static function create(): self
45    {
46        return new self();
47    }
48
49    public function from(EmailAddress|string $email, ?string $name = null): self
50    {
51        $this->from = $this->resolveEmailAddress($email, $name);
52
53        return $this;
54    }
55
56    public function to(EmailAddress|string $email, ?string $name = null): self
57    {
58        $this->to[] = $this->resolveEmailAddress($email, $name);
59
60        return $this;
61    }
62
63    public function cc(EmailAddress|string $email, ?string $name = null): self
64    {
65        $this->cc[] = $this->resolveEmailAddress($email, $name);
66
67        return $this;
68    }
69
70    public function bcc(EmailAddress|string $email): self
71    {
72        $this->bcc[] = $this->resolveEmailAddress($email, null);
73
74        return $this;
75    }
76
77    public function replyTo(EmailAddress|string $email, ?string $name = null): self
78    {
79        $this->replyTo = $this->resolveEmailAddress($email, $name);
80
81        return $this;
82    }
83
84    public function subject(string $subject): self
85    {
86        $this->subject = $subject;
87
88        return $this;
89    }
90
91    public function textBody(string $body): self
92    {
93        $this->textBody = $body;
94
95        return $this;
96    }
97
98    public function htmlBody(string $body): self
99    {
100        $this->htmlBody = $body;
101
102        return $this;
103    }
104
105    public function attach(string $path, ?string $filename = null, ?string $mimeType = null): self
106    {
107        $this->attachments[] = Attachment::fromPath($path, $filename, $mimeType);
108
109        return $this;
110    }
111
112    public function attachContent(string $content, string $filename, ?string $mimeType = null): self
113    {
114        $this->attachments[] = Attachment::fromContent($content, $filename, $mimeType);
115
116        return $this;
117    }
118
119    public function header(string $name, string $value): self
120    {
121        $this->headers[] = new Header($name, $value);
122
123        return $this;
124    }
125
126    public function build(): Message
127    {
128        if (!$this->from instanceof EmailAddress) {
129            throw new InvalidMessageException('Sender (from) is required');
130        }
131
132        if ($this->to === []) {
133            throw InvalidMessageException::missingRecipient();
134        }
135
136        if ($this->subject === null) {
137            throw new InvalidMessageException('Subject is required');
138        }
139
140        if ($this->textBody === null && $this->htmlBody === null) {
141            throw InvalidMessageException::missingBody();
142        }
143
144        return new Message(
145            from: $this->from,
146            to: $this->to,
147            subject: $this->subject,
148            textBody: $this->textBody,
149            htmlBody: $this->htmlBody,
150            cc: $this->cc,
151            bcc: $this->bcc,
152            replyTo: $this->replyTo,
153            attachments: $this->attachments,
154            headers: $this->headers,
155        );
156    }
157
158    private function resolveEmailAddress(EmailAddress|string $email, ?string $name): EmailAddress
159    {
160        if (is_string($email)) {
161            $email = EmailAddress::withEmail($email);
162        }
163
164        if ($name !== null) {
165            return $email->withName($name);
166        }
167
168        return $email;
169    }
170}