Test Failed
Push — master ( 73c405...d57cd2 )
by Konstantins
03:14
created

Mailer   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 388
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 14
dl 0
loc 388
ccs 0
cts 219
cp 0
rs 7.9487
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getMessageBuilder() 0 8 1
A getSpoolTransport() 0 4 1
A isDisabled() 0 4 1
A isSpoolEnabled() 0 4 1
A spoolWithTransport() 0 18 2
A withTransport() 0 6 1
A configureTransport() 0 17 4
A getMailerFromGlobalConfig() 0 13 4
A getTransport() 0 14 4
C prepareTransportFactory() 0 58 9
D registerTransports() 0 31 9
C setUpSpoolTransport() 0 33 7
C validateTransportSettings() 0 24 7

How to fix   Complexity   

Complex Class

Complex classes like Mailer 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Mailer, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types = 1);
2
3
namespace Venta\Mail;
4
5
use Exception;
6
use RuntimeException;
7
use Swift_DependencyContainer;
8
use Swift_Events_SimpleEventDispatcher;
9
use Swift_FileSpool;
10
use Swift_Mailer;
11
use Swift_MemorySpool;
12
use Swift_Message;
13
use Swift_SmtpTransport;
14
use Swift_Transport_MailTransport;
15
use Swift_Transport_NullTransport;
16
use Swift_Transport_SimpleMailInvoker;
17
use Swift_Transport_SpoolTransport;
18
use Venta\Contracts\Config\Config;
19
use Venta\Contracts\Event\EventDispatcher;
20
use Venta\Contracts\Mail\Mailer as MailerContract;
21
use Venta\Mail\Exception\TransportException;
22
use Venta\Mail\Exception\UnknownTransportException;
23
24
/**
25
 * Class Mailer
26
 *
27
 * @package Venta\Mail
28
 */
29
class Mailer implements MailerContract
30
{
31
32
    const SPOOL_SEND_EVENT = 'swiftmailer.spool.send';
33
34
    /**
35
     * @var $configs Config
36
     */
37
    public $configs;
38
39
    /**
40
     * Stores default transport name
41
     *
42
     * @var $defaultTransport string
43
     */
44
    protected $defaultTransport;
45
46
    /**
47
     * Disabled flag
48
     *
49
     * @var bool
50
     */
51
    protected $disabled = false;
52
53
    /**
54
     * @var EventDispatcherAdapter
55
     */
56
    protected $eventDispatcherAdapter;
57
58
    /**
59
     * @var EventDispatcher
60
     */
61
    protected $eventDispatcher;
62
63
    /**
64
     * Default From:
65
     *
66
     * @var $from string
67
     */
68
    protected $from;
69
70
    /**
71
     * @var Swift_Transport_SpoolTransport
72
     */
73
    protected $spoolTransport;
74
75
    /**
76
     * Default To:
77
     *
78
     * @var $to string
79
     */
80
    protected $to;
81
82
    /**
83
     * Registered transport storage
84
     *
85
     * @var array
86
     */
87
    protected $transports = [];
88
89
    /**
90
     * Mailer constructor.
91
     *
92
     * @param \Venta\Contracts\Config\Config $config
93
     * @param EventDispatcher $eventDispatcher
94
     * @throws Exception
95
     */
96
    public function __construct(Config $config, EventDispatcher $eventDispatcher)
97
    {
98
        $this->getMailerFromGlobalConfig($config);
99
        $this->eventDispatcher = $eventDispatcher;
100
        $this->eventDispatcherAdapter = new EventDispatcherAdapter($eventDispatcher);
101
        $this->registerTransports();
102
    }
103
104
    /**
105
     * Get Swift_Message instance, setting default from if defined/not overwritten
106
     *
107
     * @return Swift_Message
108
     */
109
    public function getMessageBuilder(): Swift_Message
110
    {
111
        $messageInstance = Swift_Message::newInstance();
112
        $messageInstance->setFrom($this->from)
113
                        ->setTo($this->to);
114
115
        return $messageInstance;
116
    }
117
118
    /**
119
     * @return Swift_Transport_SpoolTransport
120
     */
121
    public function getSpoolTransport()
122
    {
123
        return $this->spoolTransport;
124
    }
125
126
    /**
127
     * @return bool
128
     */
129
    public function isDisabled() : bool
130
    {
131
        return $this->disabled;
132
    }
133
134
    /**
135
     * @return mixed
136
     */
137
    public function isSpoolEnabled()
138
    {
139
        return $this->configs->has('spool');
140
    }
141
142
    /**
143
     * @param string $transport
144
     * @return Swift_Mailer
145
     * @throws TransportException
146
     */
147
    public function spoolWithTransport($transport = '')
148
    {
149
        $spool = $this->spoolTransport;
150
        if ($spool === null) {
151
            throw new TransportException('Mailer spool is not configured.');
152
        }
153
        $spoolRealTransport = $this->getTransport($transport);
154
155
        $this->eventDispatcher->attach(self::SPOOL_SEND_EVENT, 'send.swiftmailer.spooled',
0 ignored issues
show
Bug introduced by
The method attach() does not seem to exist on object<Venta\Contracts\Event\EventDispatcher>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
156
            function () use ($spoolRealTransport, $spool) {
157
                $failedRecipients = [];
158
                $spool->getSpool()->flushQueue($spoolRealTransport(), $failedRecipients);
159
160
                return $failedRecipients;
161
            });
162
163
        return new Swift_Mailer($this->spoolTransport);
164
    }
165
166
    /**
167
     * Get Swift_Mailer with Swift_Transport
168
     *
169
     * @param string $transportName
170
     * @return Swift_Mailer
171
     */
172
    public function withTransport(string $transportName = ''): Swift_Mailer
173
    {
174
        $transport = $this->getTransport($transportName);
175
176
        return new Swift_Mailer($transport());
177
    }
178
179
    /**
180
     * Parse config file interpreting settings
181
     *
182
     * @param \Venta\Contracts\Config\Config $config
183
     * @throws RuntimeException|Exception
184
     * @return \Closure
185
     */
186
    protected function configureTransport(Config $config)
187
    {
188
        $this->validateTransportSettings($config);
189
        $transport = $config->get('transport');
190
        if ($transport === 'gmail') {
191
            $config->set('encryption', 'ssl');
192
            $config->set('auth_mode', 'login');
193
            $config->set('host', 'smtp.gmail.com');
194
            $transport = 'smtp';
195
        }
196
197
        if (!$config->get('port')) {
198
            $config->set('port', $config->get('encryption', false) ? 465 : 25);
199
        }
200
201
        return $this->prepareTransportFactory($transport, $config);
202
    }
203
204
    /**
205
     * Get mailer configs
206
     *
207
     * @param \Venta\Contracts\Config\Config $config
208
     */
209
    protected function getMailerFromGlobalConfig(Config $config)
210
    {
211
        if (!$config->has('mailer')) {
212
            throw new Exception('Mailer config was not found.');
213
        }
214
        $this->configs = clone $config->get('mailer');
215
        $this->to = ($this->configs->get('to') instanceof Config)
216
            ? $this->configs->get('to')->toArray()
217
            : $this->configs->get('to');
218
        $this->from = ($this->configs->get('from') instanceof Config)
219
            ? $this->configs->get('from')->toArray()
220
            : $this->configs->get('from');
221
    }
222
223
    /**
224
     * Returns proper transport closure factory
225
     *
226
     * @param $transportName
227
     * @return \Closure
228
     */
229
    protected function getTransport($transportName)
230
    {
231
        if ($transportName === '' || $transportName === 'default') {
232
            return $this->transports[$this->defaultTransport];
233
        }
234
235
        if (!array_key_exists($transportName, $this->transports)) {
236
            throw new UnknownTransportException(
237
                sprintf('Transport "%s" was not configured.', $transportName)
238
            );
239
        }
240
241
        return $this->transports[$transportName];
242
    }
243
244
    /**
245
     * Wrap transport instantiation into closure passing necessary config params
246
     *
247
     * @param $transport
248
     * @param $config Config
249
     * @return \Closure
250
     * @throws Exception
251
     */
252
    protected function prepareTransportFactory($transport, Config $config)
253
    {
254
        $eventDispatcherAdapter = $this->eventDispatcherAdapter;
255
        switch ($transport) {
256
            case('smtp'):
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
257
                $closure = function () use ($config) {
258
                    $transportInstance = new Swift_SmtpTransport(
259
                        $config->get('host'),
260
                        $config->get('port'),
261
                        $config->get('encryption')
262
                    );
263
                    $dependencies = Swift_DependencyContainer::getInstance()->createDependenciesFor('transport.smtp');
264
                    foreach ($dependencies as &$dependency) {
265
                        if ($dependency instanceof Swift_Events_SimpleEventDispatcher) {
266
                            $dependency = $this->eventDispatcherAdapter;
267
                            unset($dependency);
268
                        }
269
                    }
270
                    call_user_func_array([$transportInstance, 'Swift_Transport_EsmtpTransport::__construct'],
271
                        $dependencies);
272
273
                    if ($config->has('auth_mode')) {
274
                        $transportInstance->setAuthMode($config->get('auth_mode'));
275
                    }
276
                    if ($config->has('username')) {
277
                        $transportInstance->setUsername($config->get('username'));
278
                    }
279
                    if ($config->has('password')) {
280
                        $transportInstance->setPassword($config->get('password'));
281
                    }
282
283
                    return $transportInstance;
284
                };
285
                break;
286
            case('mail'):
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
287
                $closure = function () use ($eventDispatcherAdapter) {
288
                    return new Swift_Transport_MailTransport(
289
                        new Swift_Transport_SimpleMailInvoker(),
290
                        $eventDispatcherAdapter
291
                    );
292
                };
293
                break;
294
            case('null'):
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
295
                $closure = function () use ($eventDispatcherAdapter) {
296
                    return new Swift_Transport_NullTransport($eventDispatcherAdapter);
297
                };
298
                break;
299
            default:
300
                throw new UnknownTransportException(
301
                    sprintf('Unknown transport: "%s" defined in "%s" section.',
302
                        $transport,
303
                        $config->getName())
304
                );
305
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
306
        }
307
308
        return $closure;
309
    }
310
311
    /**
312
     * Register transport factories
313
     *
314
     * @throws Exception
315
     * @return array
316
     */
317
    protected function registerTransports()
318
    {
319
        $deliveryIsDisabled = $this->configs->get('disable_delivery', false);
320
        if ($deliveryIsDisabled === true || $deliveryIsDisabled === 'true') {
321
            $this->defaultTransport = 'null';
322
            $this->transports['null'] = $this->prepareTransportFactory('null', new \Venta\Config\Config());
323
            $this->disabled = true;
324
325
            return $this->transports;
326
        }
327
328
        if ($this->configs->get('transport')) {
329
            $this->transports['default'] = $this->configureTransport($this->configs);
330
        } else {
331
            foreach ($this->configs as $name => $config) {
332
                if ($config instanceof Config && !in_array($name, ['from', 'to', 'spool'], true)) {
333
                    $this->transports[$name] = $this->configureTransport($config);
334
                }
335
            }
336
        }
337
338
        if (count($this->transports) === 0) {
339
            throw new TransportException('At least one Mailer transport must be defined.');
340
        }
341
        $this->defaultTransport = $this->configs->get('default', key($this->transports));
342
        if ($this->isSpoolEnabled()) {
343
            $this->spoolTransport = $this->setUpSpoolTransport();
344
        }
345
346
        return $this->transports;
347
    }
348
349
    /**
350
     * @return Swift_Transport_SpoolTransport|null
351
     * @throws UnknownTransportException|TransportException|\Swift_IoException
352
     */
353
    protected function setUpSpoolTransport()
354
    {
355
        $spool = $this->configs->get('spool', false);
356
        if ($spool && $spool instanceof Config) {
357
            $spoolInstance = null;
0 ignored issues
show
Unused Code introduced by
$spoolInstance is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
358
            switch ($spool->get('type')) {
359
                case 'memory':
360
                    $spoolInstance = new Swift_MemorySpool();
361
                    break;
362
                case 'file':
363
                    if (!$spool->get('path', false)) {
364
                        throw new TransportException('Path must be provided to use File type Spool.');
365
                    }
366
                    $spoolInstance = new Swift_FileSpool($spool->get('path'));
367
                    break;
368
                default:
369
                    throw new UnknownTransportException(
370
                        sprintf('Unknown spool type "%s" defined in "%s" section.',
371
                            $spool->get('type'),
372
                            $this->configs->getName()
373
                        )
374
                    );
375
            }
376
377
            if ($spoolInstance !== null) {
378
                $eventDispatcherAdapter = $this->eventDispatcherAdapter;
379
380
                return new Swift_Transport_SpoolTransport($eventDispatcherAdapter, $spoolInstance);
381
            }
382
        }
383
384
        return null;
385
    }
386
387
    /**
388
     * @param \Venta\Contracts\Config\Config $config
389
     * @throws TransportException
390
     * @throws UnknownTransportException
391
     */
392
    protected function validateTransportSettings(Config $config)
393
    {
394
        if (!$config->has('transport')) {
395
            throw new TransportException(sprintf('Transport was not defined for "%s" section.', $config->getName()));
396
        }
397
        $transport = $config->get('transport');
398
        switch ($transport) {
399
            case('smtp'):
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
400
                if (!$config->has('host')) {
401
                    throw new TransportException(
402
                        sprintf('Host must be provided to use SMTP protocol in "%s" section.', $config->getName())
403
                    );
404
                }
405
                break;
406
            case('gmail'):
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
407
                if (!$config->has('username') || !$config->has('password')) {
408
                    throw new TransportException(
409
                        sprintf('Username and password must be provided to use gmail SMTP defined in "%s" section.',
410
                            $config->getName())
411
                    );
412
                }
413
                break;
414
        }
415
    }
416
}