Passed
Pull Request — master (#108)
by Eduardo Gulias
03:06 queued 42s
created

DomainPart   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 334
Duplicated Lines 4.19 %

Coupling/Cohesion

Components 1
Dependencies 30

Test Coverage

Coverage 88.79%

Importance

Changes 19
Bugs 1 Features 1
Metric Value
wmc 73
c 19
b 1
f 1
lcom 1
cbo 30
dl 14
loc 334
ccs 190
cts 214
cp 0.8879
rs 1.3043

13 Methods

Rating   Name   Duplication   Size   Complexity  
D parse() 0 39 9
A getDomainPart() 0 4 1
D checkIPV6Tag() 0 42 10
C doParseDomainPart() 7 45 8
A checkNotAllowedChars() 0 7 2
A parseDomainLiteral() 0 15 4
C doParseDomainLiteral() 0 66 14
A checkIPV4Tag() 0 22 3
D checkDomainPartExceptions() 7 34 10
A hasBrackets() 0 14 3
A checkLabelLength() 0 9 4
A parseDomainComments() 0 13 3
A addTLDWarnings() 0 6 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DomainPart 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 DomainPart, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Egulias\EmailValidator\Parser;
4
5
use Egulias\EmailValidator\EmailLexer;
6
use Egulias\EmailValidator\Exception\CharNotAllowed;
7
use Egulias\EmailValidator\Exception\CommaInDomain;
8
use Egulias\EmailValidator\Exception\ConsecutiveAt;
9
use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
10
use Egulias\EmailValidator\Exception\CRNoLF;
11
use Egulias\EmailValidator\Exception\DomainHyphened;
12
use Egulias\EmailValidator\Exception\DotAtEnd;
13
use Egulias\EmailValidator\Exception\DotAtStart;
14
use Egulias\EmailValidator\Exception\ExpectingATEXT;
15
use Egulias\EmailValidator\Exception\ExpectingDomainLiteralClose;
16
use Egulias\EmailValidator\Exception\ExpectingDTEXT;
17
use Egulias\EmailValidator\Exception\NoDomainPart;
18
use Egulias\EmailValidator\Exception\UnopenedComment;
19
use Egulias\EmailValidator\Warning\AddressLiteral;
20
use Egulias\EmailValidator\Warning\CFWSWithFWS;
21
use Egulias\EmailValidator\Warning\DeprecatedComment;
22
use Egulias\EmailValidator\Warning\DomainLiteral;
23
use Egulias\EmailValidator\Warning\DomainTooLong;
24
use Egulias\EmailValidator\Warning\IPV6BadChar;
25
use Egulias\EmailValidator\Warning\IPV6ColonEnd;
26
use Egulias\EmailValidator\Warning\IPV6ColonStart;
27
use Egulias\EmailValidator\Warning\IPV6Deprecated;
28
use Egulias\EmailValidator\Warning\IPV6DoubleColon;
29
use Egulias\EmailValidator\Warning\IPV6GroupCount;
30
use Egulias\EmailValidator\Warning\IPV6MaxGroups;
31
use Egulias\EmailValidator\Warning\LabelTooLong;
32
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
33
use Egulias\EmailValidator\Warning\TLD;
34
35
class DomainPart extends Parser
36
{
37
    const DOMAIN_MAX_LENGTH = 254;
38
    protected $domainPart = '';
39
40 87
    public function parse($domainPart)
41
    {
42 87
        $this->lexer->moveNext();
43
44 87
        if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
45 1
            throw new DotAtStart();
46
        }
47
48 86
        if ($this->lexer->token['type'] === EmailLexer::S_EMPTY) {
49 4
            throw new NoDomainPart();
50
        }
51 82
        if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
52
            throw new DomainHyphened();
53
        }
54
55 82
        if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
56 3
            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
57 3
            $this->parseDomainComments();
58 1
        }
59
60 80
        $domain = $this->doParseDomainPart();
61
62 53
        $prev = $this->lexer->getPrevious();
63 53
        $length = strlen($domain);
64
65 53
        if ($prev['type'] === EmailLexer::S_DOT) {
66 2
            throw new DotAtEnd();
67
        }
68 51
        if ($prev['type'] === EmailLexer::S_HYPHEN) {
69 1
            throw new DomainHyphened();
70
        }
71 50
        if ($length > self::DOMAIN_MAX_LENGTH) {
72 2
            $this->warnings[DomainTooLong::CODE] = new DomainTooLong();
73 2
        }
74 50
        if ($prev['type'] === EmailLexer::S_CR) {
75
            throw new CRLFAtTheEnd();
76
        }
77 50
        $this->domainPart = $domain;
78 50
    }
79
80 50
    public function getDomainPart()
81
    {
82 50
        return $this->domainPart;
83
    }
84
85 7
    public function checkIPV6Tag($addressLiteral, $maxGroups = 8)
86
    {
87 7
        $prev = $this->lexer->getPrevious();
88 7
        if ($prev['type'] === EmailLexer::S_COLON) {
89 1
            $this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
90 1
        }
91
92 7
        $IPv6       = substr($addressLiteral, 5);
93
        //Daniel Marschall's new IPv6 testing strategy
94 7
        $matchesIP  = explode(':', $IPv6);
95 7
        $groupCount = count($matchesIP);
96 7
        $colons     = strpos($IPv6, '::');
97
98 7
        if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
99 1
            $this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
100 1
        }
101
102 7
        if ($colons === false) {
103
            // We need exactly the right number of groups
104 4
            if ($groupCount !== $maxGroups) {
105 1
                $this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
106 1
            }
107 4
            return;
108
        }
109
110 3
        if ($colons !== strrpos($IPv6, '::')) {
111 1
            $this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
112 1
            return;
113
        }
114
115 2
        if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
116
            // RFC 4291 allows :: at the start or end of an address
117
            //with 7 other groups in addition
118 2
            ++$maxGroups;
119 2
        }
120
121 2
        if ($groupCount > $maxGroups) {
122 1
            $this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
123 2
        } elseif ($groupCount === $maxGroups) {
124 1
            $this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
125 1
        }
126 2
    }
127
128 80
    protected function doParseDomainPart()
129
    {
130 80
        $domain = '';
131 80
        $openedParenthesis = 0;
132
        do {
133 80
            $prev = $this->lexer->getPrevious();
134
135 80
            $this->checkNotAllowedChars($this->lexer->token);
136
137 80
            if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
138 3
                $this->parseComments();
139 3
                $openedParenthesis += $this->getOpenedParenthesis();
140 3
                $this->lexer->moveNext();
141 3
                $tmpPrev = $this->lexer->getPrevious();
142 3
                if ($tmpPrev['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
143 3
                    $openedParenthesis--;
144 3
                }
145 3
            }
146 80 View Code Duplication
            if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147 3
                if ($openedParenthesis === 0) {
148 3
                    throw new UnopenedComment();
149
                } else {
150
                    $openedParenthesis--;
151
                }
152
            }
153
154 79
            $this->checkConsecutiveDots();
155 79
            $this->checkDomainPartExceptions($prev);
156
157 78
            if ($this->hasBrackets()) {
158 13
                $this->parseDomainLiteral();
159 11
            }
160
161 76
            $this->checkLabelLength($prev);
162
163 76
            if ($this->isFWS()) {
164 9
                $this->parseFWS();
165 7
            }
166
167 76
            $domain .= $this->lexer->token['value'];
168 76
            $this->lexer->moveNext();
169 76
        } while ($this->lexer->token);
170
171 53
        return $domain;
172
    }
173
    
174 80
    private function checkNotAllowedChars($token)
175
    {
176 80
        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
177 80
        if (isset($notAllowed[$token['type']])) {
178 8
            throw new CharNotAllowed();
179
        }
180 80
    }
181
182 13
    protected function parseDomainLiteral()
183
    {
184 13
        if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
185
            $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
186
        }
187 13
        if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
188 7
            $lexer = clone $this->lexer;
189 7
            $lexer->moveNext();
190 7
            if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
191 1
                $this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
192 1
            }
193 7
        }
194
195 13
        return $this->doParseDomainLiteral();
196
    }
197
198 13
    protected function doParseDomainLiteral()
199
    {
200 13
        $IPv6TAG = false;
201 13
        $addressLiteral = '';
202
        do {
203 13
            if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
204
                throw new ExpectingDTEXT();
205
            }
206
207 13
            if ($this->lexer->token['type'] === EmailLexer::INVALID ||
208 13
                $this->lexer->token['type'] === EmailLexer::C_DEL   ||
209 13
                $this->lexer->token['type'] === EmailLexer::S_LF
210 13
            ) {
211 1
                $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
212 1
            }
213
214 13
            if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENQBRACKET, EmailLexer::S_OPENBRACKET))) {
215 1
                throw new ExpectingDTEXT();
216
            }
217
218 12
            if ($this->lexer->isNextTokenAny(
219 12
                array(EmailLexer::S_HTAB, EmailLexer::S_SP, $this->lexer->token['type'] === EmailLexer::CRLF)
220 12
            )) {
221
                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
222
                $this->parseFWS();
223
            }
224
225 12
            if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
226 1
                throw new CRNoLF();
227
            }
228
229 11
            if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
230
                $this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
231
                $addressLiteral .= $this->lexer->token['value'];
232
                $this->lexer->moveNext();
233
                $this->validateQuotedPair();
234
            }
235 11
            if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
236 7
                $IPv6TAG = true;
237 7
            }
238 11
            if ($this->lexer->token['type'] === EmailLexer::S_CLOSEQBRACKET) {
239
                break;
240
            }
241
242 11
            $addressLiteral .= $this->lexer->token['value'];
243
244 11
        } while ($this->lexer->moveNext());
245
246 11
        $addressLiteral = str_replace('[', '', $addressLiteral);
247 11
        $addressLiteral = $this->checkIPV4Tag($addressLiteral);
248
249 11
        if (false === $addressLiteral) {
250 1
            return $addressLiteral;
251
        }
252
253 10
        if (!$IPv6TAG) {
254 3
            $this->warnings[DomainLiteral::CODE] = new DomainLiteral();
255 3
            return $addressLiteral;
256
        }
257
258 7
        $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
259
260 7
        $this->checkIPV6Tag($addressLiteral);
261
262 7
        return $addressLiteral;
263
    }
264
265 11
    protected function checkIPV4Tag($addressLiteral)
266
    {
267 11
        $matchesIP  = array();
268
269
        // Extract IPv4 part from the end of the address-literal (if there is one)
270 11
        if (preg_match(
271 11
            '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/',
272 11
            $addressLiteral,
273
            $matchesIP
274 11
        ) > 0
275 11
        ) {
276 2
            $index = strrpos($addressLiteral, $matchesIP[0]);
277 2
            if ($index === 0) {
278 1
                $this->warnings[AddressLiteral::CODE] = new AddressLiteral();
279 1
                return false;
280
            }
281
            // Convert IPv4 part to IPv6 format for further testing
282 1
            $addressLiteral = substr($addressLiteral, 0, $index) . '0:0';
283 1
        }
284
285 10
        return $addressLiteral;
286
    }
287
288 79
    protected function checkDomainPartExceptions($prev)
289
    {
290
        $invalidDomainTokens = array(
291 79
            EmailLexer::S_DQUOTE => true,
292 79
            EmailLexer::S_SEMICOLON => true,
293 79
            EmailLexer::S_GREATERTHAN => true,
294 79
            EmailLexer::S_LOWERTHAN => true,
295 79
        );
296
297 79
        if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
298 4
            throw new ExpectingATEXT();
299
        }
300
301 79
        if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
302 1
            throw new CommaInDomain();
303
        }
304
305 79
        if ($this->lexer->token['type'] === EmailLexer::S_AT) {
306 2
            throw new ConsecutiveAt();
307
        }
308
309 78
        if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
310 1
            throw new ExpectingATEXT();
311
        }
312
313 78 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
314 1
            throw new DomainHyphened();
315
        }
316
317 78 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318 78
            && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
319
            throw new ExpectingATEXT();
320
        }
321 78
    }
322
323 78
    protected function hasBrackets()
324
    {
325 78
        if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
326 65
            return false;
327
        }
328
329
        try {
330 13
            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
331 13
        } catch (\RuntimeException $e) {
332
            throw new ExpectingDomainLiteralClose();
333
        }
334
335 13
        return true;
336
    }
337
338 76
    protected function checkLabelLength($prev)
339
    {
340 76
        if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
341 76
            $prev['type'] === EmailLexer::GENERIC &&
342 39
            strlen($prev['value']) > 63
343 76
        ) {
344 1
            $this->warnings[LabelTooLong::CODE] = new LabelTooLong();
345 1
        }
346 76
    }
347
348 3
    protected function parseDomainComments()
349
    {
350 3
        $this->isUnclosedComment();
351 1
        while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
352 1
            $this->warnEscaping();
353 1
            $this->lexer->moveNext();
354 1
        }
355
356 1
        $this->lexer->moveNext();
357 1
        if ($this->lexer->isNextToken(EmailLexer::S_DOT)) {
358
            throw new ExpectingATEXT();
359
        }
360 1
    }
361
362
    protected function addTLDWarnings()
363
    {
364
        if ($this->warnings[DomainLiteral::CODE]) {
365
            $this->warnings[TLD::CODE] = new TLD();
366
        }
367
    }
368
}
369