Completed
Pull Request — master (#578)
by Richard
07:01
created

Email   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 483
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 483
ccs 126
cts 126
cp 1
rs 8.3157
c 0
b 0
f 0
wmc 43

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getCcAddresses() 0 3 1
A withHtmlBody() 0 7 1
A withToAddresses() 0 3 1
A withAddresses() 0 12 2
A getAttachments() 0 10 3
A getHtmlBody() 0 8 2
A getFromAddress() 0 10 2
B __construct() 0 27 6
A getBccAddresses() 0 3 1
A withBody() 0 7 1
A getSubject() 0 9 2
A getReadReceiptAddress() 0 11 3
A withCcAddresses() 0 3 1
A getReplyToAddresses() 0 3 1
A withBccAddresses() 0 3 1
A withAttachments() 0 11 2
A getAddresses() 0 11 3
A getBody() 0 8 2
A getToAddresses() 0 8 2
A withReplyToAddresses() 0 3 1
A withReadReceiptAddress() 0 11 2
A withFromAddress() 0 11 2
A withSubject() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like Email often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Email, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * You may not change or alter any portion of this comment or credits of supporting
4
 * developers from this source code or any supporting source code which is considered
5
 * copyrighted (c) material of the original  comment or credit authors.
6
 *
7
 * This program is distributed in the hope that it will be useful,
8
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
 *
11
 * @copyright 2018 XOOPS Project (https://xoops.org)
12
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
13
 * @link      https://xoops.org
14
 */
15
16
namespace Xoops\Core\Service\Data;
17
18
use Xmf\Assert;
19
use Xoops\Core\Service\Data\EmailAddress;
20
use Xoops\Core\Service\Data\EmailAddressList;
21
use Xoops\Core\Service\Data\EmailAttachment;
22
use Xoops\Core\Service\Data\EmailAttachmentSet;
23
24
/**
25
 * The Email data object is a full email message using EmailAddress addresses.
26
 * This differs from the Message object by allowing communication to occur with
27
 * non-users and/or with full email capabilities such as multiple recipients,
28
 * CC, BCC, Reply To and attachments.
29
 *
30
 * This is an Immutable data object. That means any changes to the data (state)
31
 * return a new object, while the internal state of the original object is preserved.
32
 *
33
 * All data is validated for type and value, and an exception is generated when
34
 * data on any operation for a property when it is not valid.
35
 *
36
 * The Email data object is used for message and mailer services
37
 *
38
 * @category  Xoops\Core\Service\Data
39
 * @package   Xoops\Core
40
 * @author    Richard Griffith <[email protected]>
41
 * @copyright 2018 XOOPS Project (https://xoops.org)
42
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
43
 * @link      https://xoops.org
44
 */
45
class Email
46
{
47
    /** @var string $subject the subject of the message, a required non-empty string */
48
    protected $subject;
49
50
    /** @var string $body the body of the message, a required non-empty string */
51
    protected $body;
52
53
    /** @var string $htmlBody an alternate representation of the message body, a non-empty string */
54
    protected $htmlBody;
55
56
    /** @var EmailAddress $fromAddress the email address that message is from */
57
    protected $fromAddress;
58
59
    /** @var EmailAddressList $toAddresses addresses that the message is to */
60
    protected $toAddresses;
61
62
    /** @var EmailAddressList $ccAddresses addresses the message should be CC'ed to */
63
    protected $ccAddresses;
64
65
    /** @var EmailAddressList $bccAddresses addresses the message should be BCC'ed to */
66
    protected $bccAddresses;
67
68
    /** @var EmailAddressList $replyToAddresses addresses that should receive replies to this message */
69
    protected $replyToAddresses;
70
71
    /** @var EmailAddress $readReceiptAddress the email address requesting a read receipt */
72
    protected $readReceiptAddress;
73
74
    /** @var EmailAttachmentSet attachments to be part of the email */
75
    protected $attachmentSet;
76
77
    /* assert messages */
78
    protected const MESSAGE_BODY    = 'Body must be specified';
79
    protected const MESSAGE_FROM    = 'From address must be specified';
80
    protected const MESSAGE_SUBJECT = 'Subject must be specified';
81
    protected const MESSAGE_BCC     = 'Invalid BCC address specified';
82
    protected const MESSAGE_CC      = 'Invalid CC address specified';
83
    protected const MESSAGE_REPLY   = 'Invalid Reply To address specified';
84
    protected const MESSAGE_RR      = 'Invalid Read Receipt address specified';
85
    protected const MESSAGE_TO      = 'A valid To address must be specified';
86
87
    protected const PROPERTY_ADDRESS_BCC   = 'bccAddresses';
88
    protected const PROPERTY_ADDRESS_CC    = 'ccAddresses';
89
    protected const PROPERTY_ADDRESS_REPLY = 'replyToAddresses';
90
    protected const PROPERTY_ADDRESS_TO    = 'toAddresses';
91
92
    protected const VALID_ADDRESS_PROPERTIES = [
93
        self::PROPERTY_ADDRESS_BCC,
94
        self::PROPERTY_ADDRESS_CC,
95
        self::PROPERTY_ADDRESS_REPLY,
96
        self::PROPERTY_ADDRESS_TO,
97
    ];
98
99
    /**
100
     * Email constructor.
101
     *
102
     * If an argument is null, the corresponding value will not be set. Values can be set
103
     * later with the with*() methods, but each will result in a new object.
104
     *
105
     * @param null|string       $subject     the subject of the message, a non-empty string
106
     * @param null|string       $body        the body of the message, a non-empty string
107
     * @param null|EmailAddress $fromAddress the user id sending the message, a positive integer
108
     * @param null|EmailAddress $toAddress   the user id to receive the message, a positive integer
109
     *
110
     * @throws \InvalidArgumentException
111
     */
112 27
    public function __construct(
113
        ?string $subject = null,
114
        ?string $body = null,
115
        ?EmailAddress $fromAddress = null,
116
        ?EmailAddress $toAddress = null
117
    ) {
118 27
        if (null!==$subject) {
119 3
            $subject = trim($subject);
120 3
            Assert::stringNotEmpty($subject, static::MESSAGE_SUBJECT);
121 2
            $this->subject = $subject;
122
        }
123 27
        if (null!==$body) {
124 3
            $body = trim($body);
125 3
            Assert::stringNotEmpty($body, static::MESSAGE_BODY);
126 2
            $this->body = $body;
127
        }
128
        try {
129 27
            if (null!==$fromAddress) {
130 2
                $fromAddress->getEmail();
131 1
                $this->fromAddress = $fromAddress;
132
            }
133 27
            if (null!==$toAddress) {
134 2
                $toAddress->getEmail();
135 27
                $this->toAddresses = new EmailAddressList([$toAddress]);
136
            }
137 2
        } catch (\LogicException $e) {
138 2
            throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
139
        }
140 27
    }
141
142
    /**
143
     * Return a new object with a the specified body
144
     *
145
     * @param string $body message body
146
     *
147
     * @return Email
148
     *
149
     * @throws \InvalidArgumentException
150
     */
151 2
    public function withBody(string $body) : Email
152
    {
153 2
        $body = trim($body);
154 2
        Assert::stringNotEmpty($body, static::MESSAGE_BODY);
155 1
        $new = clone $this;
156 1
        $new->body = $body;
157 1
        return $new;
158
    }
159
160
    /**
161
     * Return a new object with a the specified HTML body
162
     *
163
     * The htmlBody is optional, while a body (plain text) is required, and must always be specified.
164
     *
165
     * @param string $body HTML message body
166
     *
167
     * @return Email
168
     *
169
     * @throws \InvalidArgumentException
170
     */
171 1
    public function withHtmlBody(string $body) : Email
172
    {
173 1
        $body = trim($body);
174 1
        Assert::stringNotEmpty($body, static::MESSAGE_BODY);
175 1
        $new = clone $this;
176 1
        $new->htmlBody = $body;
177 1
        return $new;
178
    }
179
180
    /**
181
     * Return a new object with a the specified fromAddress
182
     *
183
     * @param EmailAddress $fromAddress the sending/from email address
184
     *
185
     * @return Email a new object with specified change
186
     *
187
     * @throws \InvalidArgumentException (property was not properly set before used)
188
     */
189 1
    public function withFromAddress(EmailAddress $fromAddress) : Email
190
    {
191
        try {
192 1
            $fromAddress->getEmail();
193 1
        } catch (\LogicException $e) {
194 1
            throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
195
        }
196
197 1
        $new = clone $this;
198 1
        $new->fromAddress = $fromAddress;
199 1
        return $new;
200
    }
201
202
    /**
203
     * Return a new object with a the specified subject
204
     *
205
     * @param string $subject message subject
206
     *
207
     * @return Email
208
     *
209
     * @throws \InvalidArgumentException
210
     */
211 1
    public function withSubject(string $subject) : Email
212
    {
213 1
        $subject = trim($subject);
214 1
        Assert::stringNotEmpty($subject, static::MESSAGE_SUBJECT);
215 1
        $new = clone $this;
216 1
        $new->subject = $subject;
217 1
        return $new;
218
    }
219
220
    /**
221
     * withAddresses - utility method to validate and assign a set of addresses
222
     *
223
     * @param string           $property  property to set, one of VALID_ADDRESS_PROPERTIES
224
     * @param EmailAddressList $addresses addresses to be assigned to property
225
     *
226
     * @return Email a new object with a the specified property set to the specified addresses
227
     *
228
     * @throws \InvalidArgumentException (a property was not set before used)
229
     */
230 4
    protected function withAddresses(string $property, EmailAddressList $addresses) : Email
231
    {
232 4
        Assert::oneOf($property, static::VALID_ADDRESS_PROPERTIES);
233
        try {
234 4
            $addresses->getAddresses();
235 4
        } catch (\LogicException $e) {
236 4
            throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
237
        }
238
239 4
        $new = clone $this;
240 4
        $new->{$property} = $addresses;
241 4
        return $new;
242
    }
243
244
    /**
245
     * Return a new object with a the specified bccAddresses
246
     *
247
     * @param EmailAddressList $bccAddresses the addresses to be BCC'ed
248
     *
249
     * @return Email
250
     *
251
     * @throws \InvalidArgumentException (a property was not properly set before used)
252
     */
253 1
    public function withBccAddresses(EmailAddressList $bccAddresses) : Email
254
    {
255 1
        return $this->withAddresses(static::PROPERTY_ADDRESS_BCC, $bccAddresses);
256
    }
257
258
    /**
259
     * Return a new object with a the specified ccAddresses
260
     *
261
     * @param EmailAddressList $ccAddresses the addresses to be CC'ed
262
     *
263
     * @return Email
264
     *
265
     * @throws \InvalidArgumentException (a property was not properly set before used)
266
     */
267 1
    public function withCcAddresses(EmailAddressList $ccAddresses) : Email
268
    {
269 1
        return $this->withAddresses(static::PROPERTY_ADDRESS_CC, $ccAddresses);
270
    }
271
272
    /**
273
     * Return a new object with a the specified replyToAddresses
274
     *
275
     * @param EmailAddressList $replyToAddresses the addresses to receive replies
276
     *
277
     * @return Email
278
     *
279
     * @throws \InvalidArgumentException (a property was not properly set before used)
280
     */
281 1
    public function withReplyToAddresses(EmailAddressList $replyToAddresses) : Email
282
    {
283 1
        return $this->withAddresses(static::PROPERTY_ADDRESS_REPLY, $replyToAddresses);
284
    }
285
286
    /**
287
     * Return a new object with a the specified toAddresses
288
     *
289
     * @param EmailAddressList $toAddresses the addresses to receive the message
290
     *
291
     * @return Email
292
     *
293
     * @throws \InvalidArgumentException (a property was not properly set before used)
294
     */
295 1
    public function withToAddresses(EmailAddressList $toAddresses) : Email
296
    {
297 1
        return $this->withAddresses(static::PROPERTY_ADDRESS_TO, $toAddresses);
298
    }
299
300
    /**
301
     * Return a new object with a the specified fromAddress
302
     *
303
     * @param EmailAddress $readReceiptAddress requests a read receipt to this address
304
     *
305
     * @return Email a new object with specified change
306
     *
307
     * @throws \InvalidArgumentException (property was not properly set before used)
308
     */
309 1
    public function withReadReceiptAddress(EmailAddress $readReceiptAddress) : Email
310
    {
311
        try {
312 1
            $readReceiptAddress->getEmail();
313 1
        } catch (\LogicException $e) {
314 1
            throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
315
        }
316
317 1
        $new = clone $this;
318 1
        $new->readReceiptAddress = $readReceiptAddress;
319 1
        return $new;
320
    }
321
322
    /**
323
     * withAttachments - return a new object with a the specified set of attachments
324
     *
325
     * @param EmailAttachmentSet $attachmentSet
326
     *
327
     * @return Email
328
     *
329
     * @throws \InvalidArgumentException
330
     */
331 1
    public function withAttachments(EmailAttachmentSet $attachmentSet) : Email
332
    {
333
        try {
334 1
            $attachmentSet->getAttachments();
335 1
        } catch (\LogicException $e) {
336 1
            throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
337
        }
338
339 1
        $new = clone $this;
340 1
        $new->attachmentSet = $attachmentSet;
341 1
        return $new;
342
    }
343
344
    /**
345
     * getBody
346
     *
347
     * @return string the message body
348
     *
349
     * @throws \LogicException (property was not properly set before used)
350
     */
351 4
    public function getBody() : string
352
    {
353
        try {
354 4
            Assert::stringNotEmpty($this->body, static::MESSAGE_BODY);
355 1
        } catch (\InvalidArgumentException $e) {
356 1
            throw new \LogicException($e->getMessage(), $e->getCode(), $e);
357
        }
358 3
        return $this->body;
359
    }
360
361
    /**
362
     * getHtmlBody
363
     *
364
     * @return string the message body
365
     *
366
     * @throws \LogicException (property was not properly set before used)
367
     */
368 2
    public function getHtmlBody() : ?string
369
    {
370
        try {
371 2
            Assert::nullOrStringNotEmpty($this->htmlBody, static::MESSAGE_BODY);
372 1
        } catch (\InvalidArgumentException $e) {
373 1
            throw new \LogicException($e->getMessage(), $e->getCode(), $e);
374
        }
375 1
        return $this->htmlBody;
376
    }
377
378
    /**
379
     * getFromAddress
380
     *
381
     * @return EmailAddress the fromAddress
382
     *
383
     * @throws \LogicException (property was not properly set before used)
384
     */
385 4
    public function getFromAddress() : EmailAddress
386
    {
387
        try {
388 4
            Assert::notNull($this->fromAddress, static::MESSAGE_FROM);
389 3
            Assert::isInstanceOf($this->fromAddress, EmailAddress::class, static::MESSAGE_FROM);
390 3
            $this->fromAddress->getEmail();
391 2
        } catch (\InvalidArgumentException | \LogicException $e) {
392 2
            throw new \LogicException($e->getMessage(), $e->getCode(), $e);
393
        }
394 2
        return $this->fromAddress;
395
    }
396
397
    /**
398
     * getSubject
399
     *
400
     * @return string the message subject
401
     *
402
     * @throws \LogicException (property was not properly set before used)
403
     */
404 4
    public function getSubject() : string
405
    {
406
        try {
407 4
            Assert::stringNotEmpty($this->subject, static::MESSAGE_SUBJECT);
408 1
        } catch (\InvalidArgumentException $e) {
409 1
            throw new \LogicException($e->getMessage(), $e->getCode(), $e);
410
        }
411
412 3
        return $this->subject;
413
    }
414
415
    /**
416
     * getAddresses
417
     *
418
     * @param string $property addresses property to get, one of VALID_ADDRESS_PROPERTIES
419
     * @param string $message  message for any Assert exception
420
     *
421
     * @return EmailAddressList|null the specified addresses property or null if not set
422
     *
423
     * @throws \LogicException (property was not properly set before used)
424
     */
425 6
    protected function getAddresses(string $property, string $message) : ?EmailAddressList
426
    {
427
        try {
428 6
            Assert::oneOf($property, static::VALID_ADDRESS_PROPERTIES);
429 6
            if (null !== $this->{$property}) {
430 6
                Assert::allIsInstanceOf($this->{$property}->getAddresses(), EmailAddress::class, $message);
431
            }
432 1
        } catch (\InvalidArgumentException | \LogicException $e) {
433 1
            throw new \LogicException($e->getMessage(), $e->getCode(), $e);
434
        }
435 5
        return $this->{$property};
436
    }
437
438
    /**
439
     * getBccAddresses
440
     *
441
     * @return EmailAddressList|null the BCC address list or null if not set
442
     *
443
     * @throws \LogicException (property was not properly set before used)
444
     */
445 1
    public function getBccAddresses() : ?EmailAddressList
446
    {
447 1
        return $this->getAddresses(static::PROPERTY_ADDRESS_BCC, static::MESSAGE_BCC);
448
    }
449
450
    /**
451
     * getCcAddresses
452
     *
453
     * @return EmailAddressList|null the CC address list or null if not set
454
     *
455
     * @throws \LogicException (property was not properly set before used)
456
     */
457 1
    public function getCcAddresses() : ?EmailAddressList
458
    {
459 1
        return $this->getAddresses(static::PROPERTY_ADDRESS_CC, static::MESSAGE_CC);
460
    }
461
462
    /**
463
     * getReplyToAddresses
464
     *
465
     * @return EmailAddressList|null the ReplyTo address list or null if not set
466
     *
467
     * @throws \LogicException (property was not properly set before used)
468
     */
469 1
    public function getReplyToAddresses() : ?EmailAddressList
470
    {
471 1
        return $this->getAddresses(static::PROPERTY_ADDRESS_REPLY, static::MESSAGE_REPLY);
472
    }
473
474
    /**
475
     * getToAddresses
476
     *
477
     * @return EmailAddressList the To addresses
478
     *
479
     * @throws \LogicException (property was not properly set before used)
480
     */
481 5
    public function getToAddresses() : EmailAddressList
482
    {
483
        try {
484 5
            Assert::notEmpty($this->toAddresses, static::MESSAGE_TO);
485 2
        } catch (\InvalidArgumentException $e) {
486 2
            throw new \LogicException($e->getMessage(), $e->getCode(), $e);
487
        }
488 3
        return $this->getAddresses(static::PROPERTY_ADDRESS_TO, static::MESSAGE_TO);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAddress...TO, static::MESSAGE_TO) could return the type null which is incompatible with the type-hinted return Xoops\Core\Service\Data\EmailAddressList. Consider adding an additional type-check to rule them out.
Loading history...
489
    }
490
491
    /**
492
     * getReadReceiptAddress
493
     *
494
     * @return EmailAddress|null the readReceiptAddress or null if not set
495
     *
496
     * @throws \LogicException (property was not properly set before used)
497
     */
498 2
    public function getReadReceiptAddress() : ?EmailAddress
499
    {
500 2
        if (null !== $this->readReceiptAddress) {
501
            try {
502 2
                Assert::isInstanceOf($this->readReceiptAddress, EmailAddress::class, static::MESSAGE_RR);
503 2
                $this->readReceiptAddress->getEmail();
504 1
            } catch (\InvalidArgumentException | \LogicException $e) {
505 1
                throw new \LogicException($e->getMessage(), $e->getCode(), $e);
506
            }
507
        }
508 1
        return $this->readReceiptAddress;
509
    }
510
511
    /**
512
     * getAttachments
513
     *
514
     * @return EmailAttachmentSet|null the set of attachments or null if not set
515
     *
516
     * @throws \LogicException (property was not properly set before used)
517
     */
518 2
    public function getAttachments() : ?EmailAttachmentSet
519
    {
520 2
        if (null !== $this->attachmentSet) {
521
            try {
522 2
                $this->attachmentSet->getAttachments();
523 1
            } catch (\LogicException $e) {
524 1
                throw new \LogicException($e->getMessage(), $e->getCode(), $e);
525
            }
526
        }
527 1
        return $this->attachmentSet;
528
    }
529
}
530