Passed
Branch master (ae28f9)
by Alexey
03:06
created

Mailer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1.0028

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 6
cts 7
cp 0.8571
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 2
crap 1.0028
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 18
    public function __construct(Config $config, EventDispatcher $eventDispatcher)
97
    {
98 18
        $this->getMailerFromGlobalConfig($config);
99 17
        $this->eventDispatcher = $eventDispatcher;
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
100 17
        $this->eventDispatcherAdapter = new EventDispatcherAdapter($eventDispatcher);
101 17
        $this->registerTransports();
102 10
    }
103
104
    /**
105
     * Get Swift_Message instance, setting default from if defined/not overwritten
106
     *
107
     * @return Swift_Message
108
     */
109 4
    public function getMessageBuilder(): Swift_Message
110
    {
111 4
        $messageInstance = Swift_Message::newInstance();
112 4
        $messageInstance->setFrom($this->from)
113 4
                        ->setTo($this->to);
114
115 4
        return $messageInstance;
116
    }
117
118
    /**
119
     * @return Swift_Transport_SpoolTransport
120
     */
121 1
    public function getSpoolTransport()
122
    {
123 1
        return $this->spoolTransport;
124
    }
125
126
    /**
127
     * @return bool
128
     */
129 2
    public function isDisabled() : bool
130
    {
131 2
        return $this->disabled;
132
    }
133
134
    /**
135
     * @return mixed
136
     */
137 11
    public function isSpoolEnabled()
138
    {
139 11
        return $this->configs->has('spool');
140
    }
141
142
    /**
143
     * @param string $transport
144
     * @return Swift_Mailer
145
     * @throws TransportException
146
     */
147 1
    public function spoolWithTransport($transport = '')
148
    {
149 1
        $spool = $this->spoolTransport;
150 1
        if ($spool === null) {
151 1
            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 4
    public function withTransport(string $transportName = ''): Swift_Mailer
173
    {
174 4
        $transport = $this->getTransport($transportName);
175
176 3
        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 15
    protected function configureTransport(Config $config)
187
    {
188 15
        $this->validateTransportSettings($config);
189 12
        $transport = $config->get('transport');
190 12
        if ($transport === 'gmail') {
191 1
            $config->set('encryption', 'ssl');
192 1
            $config->set('auth_mode', 'login');
193 1
            $config->set('host', 'smtp.gmail.com');
194 1
            $transport = 'smtp';
195
        }
196
197 12
        if (!$config->get('port')) {
198 12
            $config->set('port', $config->get('encryption', false) ? 465 : 25);
199
        }
200
201 12
        return $this->prepareTransportFactory($transport, $config);
202
    }
203
204
    /**
205
     * Get mailer configs
206
     *
207
     * @param \Venta\Contracts\Config\Config $config
208
     */
209 18
    protected function getMailerFromGlobalConfig(Config $config)
210
    {
211 18
        if (!$config->has('mailer')) {
212 1
            throw new Exception('Mailer config was not found.');
213
        }
214 17
        $this->configs = clone $config->get('mailer');
215 17
        $this->to = ($this->configs->get('to') instanceof Config)
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
216 2
            ? $this->configs->get('to')->toArray()
217 15
            : $this->configs->get('to');
218 17
        $this->from = ($this->configs->get('from') instanceof Config)
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
219 2
            ? $this->configs->get('from')->toArray()
220 15
            : $this->configs->get('from');
221 17
    }
222
223
    /**
224
     * Returns proper transport closure factory
225
     *
226
     * @param $transportName
227
     * @return \Closure
228
     */
229 4
    protected function getTransport($transportName)
230
    {
231 4
        if ($transportName === '' || $transportName === 'default') {
232 2
            return $this->transports[$this->defaultTransport];
233
        }
234
235 2
        if (!array_key_exists($transportName, $this->transports)) {
236 1
            throw new UnknownTransportException(
237 1
                sprintf('Transport "%s" was not configured.', $transportName)
238
            );
239
        }
240
241 1
        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 13
    protected function prepareTransportFactory($transport, Config $config)
253
    {
254 13
        $eventDispatcherAdapter = $this->eventDispatcherAdapter;
255
        switch ($transport) {
256 13
            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 2
                    $transportInstance = new Swift_SmtpTransport(
259 2
                        $config->get('host'),
260 2
                        $config->get('port'),
261 2
                        $config->get('encryption')
262
                    );
263 2
                    $dependencies = Swift_DependencyContainer::getInstance()->createDependenciesFor('transport.smtp');
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
264 2
                    foreach ($dependencies as &$dependency) {
265 2
                        if ($dependency instanceof Swift_Events_SimpleEventDispatcher) {
266 2
                            $dependency = $this->eventDispatcherAdapter;
267 2
                            unset($dependency);
268
                        }
269
                    }
270 2
                    call_user_func_array([$transportInstance, 'Swift_Transport_EsmtpTransport::__construct'],
271
                        $dependencies);
272
273 2
                    if ($config->has('auth_mode')) {
274 1
                        $transportInstance->setAuthMode($config->get('auth_mode'));
275
                    }
276 2
                    if ($config->has('username')) {
277 1
                        $transportInstance->setUsername($config->get('username'));
278
                    }
279 2
                    if ($config->has('password')) {
280 1
                        $transportInstance->setPassword($config->get('password'));
281
                    }
282
283 2
                    return $transportInstance;
284 2
                };
285 2
                break;
286 12
            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 1
                    return new Swift_Transport_MailTransport(
289 1
                        new Swift_Transport_SimpleMailInvoker(),
290
                        $eventDispatcherAdapter
291
                    );
292 10
                };
293 10
                break;
294 2
            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 1
                $closure = function () use ($eventDispatcherAdapter) {
296 1
                    return new Swift_Transport_NullTransport($eventDispatcherAdapter);
297 1
                };
298 1
                break;
299
            default:
300 1
                throw new UnknownTransportException(
301 1
                    sprintf('Unknown transport: "%s" defined in "%s" section.',
302
                        $transport,
303 1
                        $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 12
        return $closure;
309
    }
310
311
    /**
312
     * Register transport factories
313
     *
314
     * @throws Exception
315
     * @return array
316
     */
317 17
    protected function registerTransports()
318
    {
319 17
        $deliveryIsDisabled = $this->configs->get('disable_delivery', false);
320 17
        if ($deliveryIsDisabled === true || $deliveryIsDisabled === 'true') {
321 1
            $this->defaultTransport = 'null';
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
322 1
            $this->transports['null'] = $this->prepareTransportFactory('null', new \Venta\Config\Config());
323 1
            $this->disabled = true;
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 11 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
324
325 1
            return $this->transports;
326
        }
327
328 16
        if ($this->configs->get('transport')) {
329 7
            $this->transports['default'] = $this->configureTransport($this->configs);
330
        } else {
331 9
            foreach ($this->configs as $name => $config) {
332 9
                if ($config instanceof Config && !in_array($name, ['from', 'to', 'spool'], true)) {
333 9
                    $this->transports[$name] = $this->configureTransport($config);
334
                }
335
            }
336
        }
337
338 12
        if (count($this->transports) === 0) {
339 1
            throw new TransportException('At least one Mailer transport must be defined.');
340
        }
341 11
        $this->defaultTransport = $this->configs->get('default', key($this->transports));
342 11
        if ($this->isSpoolEnabled()) {
343 4
            $this->spoolTransport = $this->setUpSpoolTransport();
344
        }
345
346 9
        return $this->transports;
347
    }
348
349
    /**
350
     * @return Swift_Transport_SpoolTransport|null
351
     * @throws UnknownTransportException|TransportException|\Swift_IoException
352
     */
353 4
    protected function setUpSpoolTransport()
354
    {
355 4
        $spool = $this->configs->get('spool', false);
356 4
        if ($spool && $spool instanceof Config) {
357 3
            $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 3
            switch ($spool->get('type')) {
359 3
                case 'memory':
360
                    $spoolInstance = new Swift_MemorySpool();
361
                    break;
362 3
                case 'file':
363 2
                    if (!$spool->get('path', false)) {
364 1
                        throw new TransportException('Path must be provided to use File type Spool.');
365
                    }
366 1
                    $spoolInstance = new Swift_FileSpool($spool->get('path'));
367 1
                    break;
368
                default:
369 1
                    throw new UnknownTransportException(
370 1
                        sprintf('Unknown spool type "%s" defined in "%s" section.',
371 1
                            $spool->get('type'),
372 1
                            $this->configs->getName()
373
                        )
374
                    );
375
            }
376
377 1
            if ($spoolInstance !== null) {
378 1
                $eventDispatcherAdapter = $this->eventDispatcherAdapter;
379
380 1
                return new Swift_Transport_SpoolTransport($eventDispatcherAdapter, $spoolInstance);
381
            }
382
        }
383
384 1
        return null;
385
    }
386
387
    /**
388
     * @param \Venta\Contracts\Config\Config $config
389
     * @throws TransportException
390
     * @throws UnknownTransportException
391
     */
392 15
    protected function validateTransportSettings(Config $config)
393
    {
394 15
        if (!$config->has('transport')) {
395 1
            throw new TransportException(sprintf('Transport was not defined for "%s" section.', $config->getName()));
396
        }
397 14
        $transport = $config->get('transport');
398
        switch ($transport) {
399 14
            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 2
                if (!$config->has('host')) {
401 1
                    throw new TransportException(
402 1
                        sprintf('Host must be provided to use SMTP protocol in "%s" section.', $config->getName())
403
                    );
404
                }
405 1
                break;
406 13
            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 2
                if (!$config->has('username') || !$config->has('password')) {
408 1
                    throw new TransportException(
409 1
                        sprintf('Username and password must be provided to use gmail SMTP defined in "%s" section.',
410 1
                            $config->getName())
411
                    );
412
                }
413 1
                break;
414
        }
415
    }
416
}