Completed
Push — master ( 6d3e15...682097 )
by Frederik
02:26
created

ClientFactory   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 90.91%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 9
dl 0
loc 261
ccs 100
cts 110
cp 0.9091
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A withTimeout() 0 6 1
A withAuthentication() 0 12 2
A withEhlo() 0 6 1
A withInsecureConnectionAllowed() 0 6 1
A withStartTls() 0 6 1
A withoutStartTls() 0 6 1
A newClient() 0 37 4
F fromString() 0 85 15
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail\Protocol\Smtp;
5
6
use Genkgo\Mail\Protocol\AutomaticConnection;
7
use Genkgo\Mail\Protocol\ConnectionInterface;
8
use Genkgo\Mail\Protocol\CryptoConstant;
9
use Genkgo\Mail\Protocol\PlainTcpConnection;
10
use Genkgo\Mail\Protocol\SecureConnection;
11
use Genkgo\Mail\Protocol\SecureConnectionOptions;
12
use Genkgo\Mail\Protocol\Smtp\Negotiation\AuthNegotiation;
13
use Genkgo\Mail\Protocol\Smtp\Negotiation\ForceTlsUpgradeNegotiation;
14
use Genkgo\Mail\Protocol\Smtp\Negotiation\TryTlsUpgradeNegotiation;
15
16
final class ClientFactory
17
{
18
    private const AUTH_ENUM = [Client::AUTH_NONE, Client::AUTH_PLAIN, Client::AUTH_LOGIN, Client::AUTH_AUTO];
19
20
    /**
21
     * @var ConnectionInterface
22
     */
23
    private $connection;
24
25
    /**
26
     * @var string
27
     */
28
    private $password = '';
29
30
    /**
31
     * @var float
32
     */
33
    private $timeout = 1;
34
35
    /**
36
     * @var string
37
     */
38
    private $username = '';
39
40
    /**
41
     * @var string
42
     */
43
    private $ehlo = '127.0.0.1';
44
45
    /**
46
     * @var int
47
     */
48
    private $authMethod = Client::AUTH_NONE;
49
50
    /**
51
     * @var bool
52
     */
53
    private $insecureConnectionAllowed = false;
54
55
    /**
56
     * @var string
57
     */
58
    private $reconnectAfter = 'PT300S';
59
60
    /**
61
     * @var int
62
     */
63
    private $startTls;
64
65
    /**
66
     * @param ConnectionInterface $connection
67
     */
68 9
    public function __construct(ConnectionInterface $connection)
69
    {
70 9
        $this->connection = $connection;
71 9
        $this->startTls = CryptoConstant::getDefaultMethod(PHP_VERSION);
72 9
    }
73
74
    /**
75
     * @param float $connectionTimeout
76
     * @return ClientFactory
77
     */
78 1
    public function withTimeout(float $connectionTimeout): ClientFactory
79
    {
80 1
        $clone = clone $this;
81 1
        $clone->timeout = $connectionTimeout;
82 1
        return $clone;
83
    }
84
85
    /**
86
     * @param int $method
87
     * @param string $password
88
     * @param string $username
89
     * @return ClientFactory
90
     */
91 3
    public function withAuthentication(int $method, string $username, string $password): ClientFactory
92
    {
93 3
        if (!\in_array($method, self::AUTH_ENUM)) {
94 1
            throw new \InvalidArgumentException('Invalid authentication method');
95
        }
96
97 2
        $clone = clone $this;
98 2
        $clone->authMethod = $method;
99 2
        $clone->username = $username;
100 2
        $clone->password = $password;
101 2
        return $clone;
102
    }
103
104
    /**
105
     * @param string $ehlo
106
     * @return ClientFactory
107
     */
108 4
    public function withEhlo(string $ehlo): ClientFactory
109
    {
110 4
        $clone = clone $this;
111 4
        $clone->ehlo = $ehlo;
112 4
        return $clone;
113
    }
114
115
    /**
116
     * @return ClientFactory
117
     */
118 2
    public function withInsecureConnectionAllowed(): ClientFactory
119
    {
120 2
        $clone = clone $this;
121 2
        $clone->insecureConnectionAllowed = true;
122 2
        return $clone;
123
    }
124
125
    /**
126
     * @param int $crypto
127
     * @return ClientFactory
128
     */
129 1
    public function withStartTls(int $crypto): ClientFactory
130
    {
131 1
        $clone = clone $this;
132 1
        $clone->startTls = $crypto;
133 1
        return $clone;
134
    }
135
136
    /**
137
     * @return ClientFactory
138
     */
139 1
    public function withoutStartTls(): ClientFactory
140
    {
141 1
        $clone = clone $this;
142 1
        $clone->startTls = 0;
143 1
        return $clone;
144
    }
145
146
    /**
147
     * @return Client
148
     */
149 7
    public function newClient(): Client
150
    {
151 7
        $negotiators = [];
152
153 7
        if ($this->startTls !== 0) {
154 5
            if ($this->insecureConnectionAllowed) {
155
                $negotiators[] = new TryTlsUpgradeNegotiation(
156
                    $this->connection,
157
                    $this->ehlo,
158
                    $this->startTls
159
                );
160
            } else {
161 5
                $negotiators[] = new ForceTlsUpgradeNegotiation(
162 5
                    $this->connection,
163 5
                    $this->ehlo,
164 5
                    $this->startTls
165
                );
166
            }
167
        }
168
169 7
        if ($this->authMethod !== Client::AUTH_NONE) {
170 2
            $negotiators[] = new AuthNegotiation(
171 2
                $this->ehlo,
172 2
                $this->authMethod,
173 2
                $this->username,
174 2
                $this->password
175
            );
176
        }
177
178 7
        return new Client(
179 7
            new AutomaticConnection(
180 7
                $this->connection,
181 7
                new \DateInterval($this->reconnectAfter)
182
            ),
183
            $negotiators
184
        );
185
    }
186
187
    /**
188
     * @param string $dataSourceName
189
     * @return ClientFactory
190
     */
191 6
    public static function fromString(string $dataSourceName):ClientFactory
192
    {
193 6
        $components = \parse_url($dataSourceName);
194 6
        if ($components === false || !isset($components['scheme']) || !isset($components['host'])) {
195 1
            throw new \InvalidArgumentException('Scheme and host are required');
196
        }
197
198 5
        if (isset($components['query'])) {
199 2
            \parse_str($components['query'], $query);
200
        } else {
201 3
            $query = [];
202
        }
203
204 5
        $insecureConnectionAllowed = false;
205 5
        $startTls = true;
206 5
        switch ($components['scheme']) {
207 5
            case 'smtp':
208 1
                $connection = new PlainTcpConnection(
209 1
                    $components['host'],
210 1
                    $components['port'] ?? 587
211
                );
212 1
                break;
213 4
            case 'smtps':
214 2
                $connection = new SecureConnection(
215 2
                    $components['host'],
216 2
                    $components['port'] ?? 465,
217 2
                    new SecureConnectionOptions(
218 2
                        (int)($query['crypto'] ?? CryptoConstant::getDefaultMethod(PHP_VERSION))
219
                    )
220
                );
221 2
                break;
222 2
            case 'smtp-starttls':
223 1
                $insecureConnectionAllowed = true;
224 1
                $startTls = false;
225
226 1
                $connection = new PlainTcpConnection(
227 1
                    $components['host'],
228 1
                    $components['port'] ?? 25
229
                );
230 1
                break;
231 1
            case 'smtp+starttls':
232
                $insecureConnectionAllowed = true;
233
                $startTls = true;
234
235
                $connection = new PlainTcpConnection(
236
                    $components['host'],
237
                    $components['port'] ?? 25
238
                );
239
                break;
240
            default:
241 1
                throw new \InvalidArgumentException(\sprintf(
242 1
                    'Provided scheme "%s://" is invalid. Only smtp:// smtps:// and smtp-starttls:// are supported',
243 1
                    $components['scheme']
244
                ));
245
        }
246
247 4
        $factory = new self($connection);
248 4
        $factory->insecureConnectionAllowed = $insecureConnectionAllowed;
249
250 4
        if (isset($components['user'], $components['pass'])) {
251 1
            $factory->authMethod = Client::AUTH_AUTO;
252 1
            $factory->username = \urldecode($components['user']);
253 1
            $factory->password = \urldecode($components['pass']);
254
        }
255
256 4
        if (isset($query['ehlo'])) {
257 1
            $factory->ehlo = $query['ehlo'];
258
        }
259
260 4
        if (isset($query['timeout'])) {
261 1
            $factory->timeout = (float)$query['timeout'];
262
        }
263
264 4
        if (isset($query['reconnectAfter'])) {
265 1
            $factory->reconnectAfter = $query['reconnectAfter'];
266
        }
267
268 4
        if (!$startTls) {
269 1
            $factory->startTls = 0;
270 3
        } elseif (isset($query['crypto'])) {
271 1
            $factory->startTls = (int)$query['crypto'];
272
        }
273
274 4
        return $factory;
275
    }
276
}
277