Test Failed
Pull Request — 3.x (#339)
by
unknown
01:52
created

DomainPart   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Test Coverage

Coverage 96%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 143
c 3
b 0
f 0
dl 0
loc 288
ccs 144
cts 150
cp 0.96
rs 4.08
wmc 59

15 Methods

Rating   Name   Duplication   Size   Complexity  
A performDomainStartChecks() 0 16 4
A parseComments() 0 7 1
A checkInvalidTokensAfterAT() 0 9 3
A checkEmptyDomain() 0 11 4
A domainPart() 0 3 1
A isLabelTooLong() 0 7 2
B checkDomainPartExceptions() 0 18 7
B parse() 0 35 6
A checkEndOfDomain() 0 14 4
A addTLDWarnings() 0 4 2
A checkNotAllowedChars() 0 7 2
A parseDomainLiteral() 0 12 2
C doParseDomainPart() 0 69 14
A checkLabelLength() 0 10 4
A validateTokens() 0 18 3

How to fix   Complexity   

Complex Class

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.

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 Doctrine\Common\Lexer\Token;
6
use Egulias\EmailValidator\EmailLexer;
7
use Egulias\EmailValidator\Warning\TLD;
8
use Egulias\EmailValidator\Result\Result;
9
use Egulias\EmailValidator\Result\ValidEmail;
10
use Egulias\EmailValidator\Result\InvalidEmail;
11
use Egulias\EmailValidator\Result\Reason\DotAtEnd;
12
use Egulias\EmailValidator\Result\Reason\DotAtStart;
13
use Egulias\EmailValidator\Warning\DeprecatedComment;
14
use Egulias\EmailValidator\Result\Reason\CRLFAtTheEnd;
15
use Egulias\EmailValidator\Result\Reason\LabelTooLong;
16
use Egulias\EmailValidator\Result\Reason\NoDomainPart;
17
use Egulias\EmailValidator\Result\Reason\ConsecutiveAt;
18
use Egulias\EmailValidator\Result\Reason\DomainTooLong;
19
use Egulias\EmailValidator\Result\Reason\CharNotAllowed;
20
use Egulias\EmailValidator\Result\Reason\DomainHyphened;
21
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
22
use Egulias\EmailValidator\Parser\CommentStrategy\DomainComment;
23
use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose;
24
use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
25
26
class DomainPart extends PartParser
27
{
28
    public const DOMAIN_MAX_LENGTH = 253;
29
    public const LABEL_MAX_LENGTH = 63;
30
31
    /**
32
     * @var string
33
     */
34
    protected $domainPart = '';
35
36
    /**
37
     * @var string
38
     */
39
    protected $label = '';
40
41 148
    public function parse(): Result
42
    {
43 148
        $this->lexer->clearRecorded();
44 148
        $this->lexer->startRecording();
45
46 148
        $this->lexer->moveNext();
47
48 148
        $domainChecks = $this->performDomainStartChecks();
49 148
        if ($domainChecks->isInvalid()) {
50 12
            return $domainChecks;
51
        }
52
53 136
        if ($this->lexer->token->isA(EmailLexer::S_AT)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_AT of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

53
        if ($this->lexer->token->isA(/** @scrutinizer ignore-type */ EmailLexer::S_AT)) {
Loading history...
54 1
            return new InvalidEmail(new ConsecutiveAt(), $this->lexer->token->value);
55
        }
56
57 135
        $result = $this->doParseDomainPart();
58 135
        if ($result->isInvalid()) {
59 67
            return $result;
60
        }
61
62 68
        $end = $this->checkEndOfDomain();
63 68
        if ($end->isInvalid()) {
64 4
            return $end;
65
        }
66
67 64
        $this->lexer->stopRecording();
68 64
        $this->domainPart = $this->lexer->getAccumulatedValues();
69
70 64
        $length = strlen($this->domainPart);
71 64
        if ($length > self::DOMAIN_MAX_LENGTH) {
72
            return new InvalidEmail(new DomainTooLong(), $this->lexer->token->value);
73
        }
74
75 64
        return new ValidEmail();
76
    }
77
78 68
    private function checkEndOfDomain(): Result
79
    {
80 68
        $prev = $this->lexer->getPrevious();
81 68
        if ($prev->isA(EmailLexer::S_DOT)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_DOT of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

81
        if ($prev->isA(/** @scrutinizer ignore-type */ EmailLexer::S_DOT)) {
Loading history...
82 3
            return new InvalidEmail(new DotAtEnd(), $this->lexer->token->value);
83
        }
84 65
        if ($prev->isA(EmailLexer::S_HYPHEN)) {
85 1
            return new InvalidEmail(new DomainHyphened('Hypen found at the end of the domain'), $prev->value);
86
        }
87
88 64
        if ($this->lexer->token->isA(EmailLexer::S_SP)) {
89
            return new InvalidEmail(new CRLFAtTheEnd(), $prev->value);
90
        }
91 64
        return new ValidEmail();
92
    }
93
94 148
    private function performDomainStartChecks(): Result
95
    {
96 148
        $invalidTokens = $this->checkInvalidTokensAfterAT();
97 148
        if ($invalidTokens->isInvalid()) {
98 2
            return $invalidTokens;
99
        }
100
101 146
        $missingDomain = $this->checkEmptyDomain();
102 146
        if ($missingDomain->isInvalid()) {
103 10
            return $missingDomain;
104
        }
105
106 136
        if ($this->lexer->token->isA(EmailLexer::S_OPENPARENTHESIS)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\E...exer::S_OPENPARENTHESIS of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

106
        if ($this->lexer->token->isA(/** @scrutinizer ignore-type */ EmailLexer::S_OPENPARENTHESIS)) {
Loading history...
107 3
            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
108
        }
109 136
        return new ValidEmail();
110
    }
111
112 146
    private function checkEmptyDomain(): Result
113
    {
114 146
        $thereIsNoDomain = $this->lexer->token->isA(EmailLexer::S_EMPTY) ||
115 146
            ($this->lexer->token->isA(EmailLexer::S_SP) &&
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_SP of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
            ($this->lexer->token->isA(/** @scrutinizer ignore-type */ EmailLexer::S_SP) &&
Loading history...
116 146
                !$this->lexer->isNextToken(EmailLexer::GENERIC));
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::GENERIC of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $type of Doctrine\Common\Lexer\AbstractLexer::isNextToken(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

116
                !$this->lexer->isNextToken(/** @scrutinizer ignore-type */ EmailLexer::GENERIC));
Loading history...
117
118 146
        if ($thereIsNoDomain) {
119 10
            return new InvalidEmail(new NoDomainPart(), $this->lexer->token->value);
120
        }
121
122 136
        return new ValidEmail();
123
    }
124
125 148
    private function checkInvalidTokensAfterAT(): Result
126
    {
127 148
        if ($this->lexer->token->isA(EmailLexer::S_DOT)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_DOT of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

127
        if ($this->lexer->token->isA(/** @scrutinizer ignore-type */ EmailLexer::S_DOT)) {
Loading history...
128 1
            return new InvalidEmail(new DotAtStart(), $this->lexer->token->value);
129
        }
130 147
        if ($this->lexer->token->isA(EmailLexer::S_HYPHEN)) {
131 1
            return new InvalidEmail(new DomainHyphened('After AT'), $this->lexer->token->value);
132
        }
133 146
        return new ValidEmail();
134
    }
135
136 7
    protected function parseComments(): Result
137
    {
138 7
        $commentParser = new Comment($this->lexer, new DomainComment());
139 7
        $result = $commentParser->parse();
140 7
        $this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
141
142 7
        return $result;
143
    }
144
145 135
    protected function doParseDomainPart(): Result
146
    {
147 135
        $tldMissing = true;
148 135
        $hasComments = false;
149 135
        $domain = '';
150
        do {
151 135
            $prev = $this->lexer->getPrevious();
152
153 135
            $notAllowedChars = $this->checkNotAllowedChars($this->lexer->token);
154 135
            if ($notAllowedChars->isInvalid()) {
155 4
                return $notAllowedChars;
156
            }
157
158
            if (
159 135
                $this->lexer->token->isA(EmailLexer::S_OPENPARENTHESIS) ||
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\E...exer::S_OPENPARENTHESIS of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

159
                $this->lexer->token->isA(/** @scrutinizer ignore-type */ EmailLexer::S_OPENPARENTHESIS) ||
Loading history...
160 135
                $this->lexer->token->isA(EmailLexer::S_CLOSEPARENTHESIS)
161
            ) {
162 7
                $hasComments = true;
163 7
                $commentsResult = $this->parseComments();
164
165
                //Invalid comment parsing
166 7
                if ($commentsResult->isInvalid()) {
167 5
                    return $commentsResult;
168
                }
169
            }
170
171 132
            $dotsResult = $this->checkConsecutiveDots();
172 132
            if ($dotsResult->isInvalid()) {
173 2
                return $dotsResult;
174
            }
175
176 132
            if ($this->lexer->token->isA(EmailLexer::S_OPENBRACKET)) {
177 18
                $literalResult = $this->parseDomainLiteral();
178
179 18
                $this->addTLDWarnings($tldMissing);
180 18
                return $literalResult;
181
            }
182
183 114
            $labelCheck = $this->checkLabelLength();
184 114
            if ($labelCheck->isInvalid()) {
185 5
                return $labelCheck;
186
            }
187
188 114
            $FwsResult = $this->parseFWS();
189 114
            if ($FwsResult->isInvalid()) {
190 5
                return $FwsResult;
191
            }
192
193 114
            $domain .= $this->lexer->token->value;
194
195 114
            if ($this->lexer->token->isA(EmailLexer::S_DOT) && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::GENERIC of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $type of Doctrine\Common\Lexer\AbstractLexer::isNextToken(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

195
            if ($this->lexer->token->isA(EmailLexer::S_DOT) && $this->lexer->isNextToken(/** @scrutinizer ignore-type */ EmailLexer::GENERIC)) {
Loading history...
196 56
                $tldMissing = false;
197
            }
198
199 114
            $exceptionsResult = $this->checkDomainPartExceptions($prev, $hasComments);
200 114
            if ($exceptionsResult->isInvalid()) {
201 41
                return $exceptionsResult;
202
            }
203 112
            $this->lexer->moveNext();
204 112
        } while (null !== $this->lexer->token->type);
205
206 55
        $labelCheck = $this->checkLabelLength(true);
207 55
        if ($labelCheck->isInvalid()) {
208 2
            return $labelCheck;
209
        }
210 53
        $this->addTLDWarnings($tldMissing);
211
212 53
        $this->domainPart = $domain;
213 53
        return new ValidEmail();
214
    }
215
216 135
    private function checkNotAllowedChars(Token $token): Result
217
    {
218 135
        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH => true];
219 135
        if (isset($notAllowed[$token->type])) {
220 4
            return new InvalidEmail(new CharNotAllowed(), $token->value);
221
        }
222 135
        return new ValidEmail();
223
    }
224
225
    /**
226
     * @return Result
227
     */
228 18
    protected function parseDomainLiteral(): Result
229
    {
230
        try {
231 18
            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
232
        } catch (\RuntimeException $e) {
233
            return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->token->value);
234
        }
235
236 18
        $domainLiteralParser = new DomainLiteralParser($this->lexer);
237 18
        $result = $domainLiteralParser->parse();
238 18
        $this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings());
239 18
        return $result;
240
    }
241
242 114
    protected function checkDomainPartExceptions(Token $prev, bool $hasComments): Result
243
    {
244 114
        if ($this->lexer->token->isA(EmailLexer::S_OPENBRACKET) && $prev->type !== EmailLexer::S_AT) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_OPENBRACKET of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

244
        if ($this->lexer->token->isA(/** @scrutinizer ignore-type */ EmailLexer::S_OPENBRACKET) && $prev->type !== EmailLexer::S_AT) {
Loading history...
245
            return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->token->value);
246
        }
247
248 114
        if ($this->lexer->token->isA(EmailLexer::S_HYPHEN) && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_DOT of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $type of Doctrine\Common\Lexer\AbstractLexer::isNextToken(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

248
        if ($this->lexer->token->isA(EmailLexer::S_HYPHEN) && $this->lexer->isNextToken(/** @scrutinizer ignore-type */ EmailLexer::S_DOT)) {
Loading history...
249 1
            return new InvalidEmail(new DomainHyphened('Hypen found near DOT'), $this->lexer->token->value);
250
        }
251
252
        if (
253 114
            $this->lexer->token->isA(EmailLexer::S_BACKSLASH)
254 114
            && $this->lexer->isNextToken(EmailLexer::GENERIC)
255
        ) {
256
            return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->token->value);
257
        }
258
259 114
        return $this->validateTokens($hasComments);
260
    }
261
262 108
    protected function validateTokens(bool $hasComments): Result
263
    {
264 108
        $validDomainTokens = array(
265 108
            EmailLexer::GENERIC => true,
266 108
            EmailLexer::S_HYPHEN => true,
267 108
            EmailLexer::S_DOT => true,
268 108
        );
269
270 108
        if ($hasComments) {
271 2
            $validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true;
272 2
            $validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true;
273
        }
274
275 108
        if (!isset($validDomainTokens[$this->lexer->token->type])) {
276 40
            return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token->value), $this->lexer->token->value);
277
        }
278
279 106
        return new ValidEmail();
280
    }
281
282 114
    private function checkLabelLength(bool $isEndOfDomain = false): Result
283
    {
284 114
        if ($this->lexer->token->isA(EmailLexer::S_DOT) || $isEndOfDomain) {
0 ignored issues
show
Bug introduced by
Egulias\EmailValidator\EmailLexer::S_DOT of type integer is incompatible with the type Doctrine\Common\Lexer\T expected by parameter $types of Doctrine\Common\Lexer\Token::isA(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

284
        if ($this->lexer->token->isA(/** @scrutinizer ignore-type */ EmailLexer::S_DOT) || $isEndOfDomain) {
Loading history...
285 72
            if ($this->isLabelTooLong($this->label)) {
286 7
                return new InvalidEmail(new LabelTooLong(), $this->lexer->token->value);
287
            }
288 66
            $this->label = '';
289
        }
290 114
        $this->label .= $this->lexer->token->value;
291 114
        return new ValidEmail();
292
    }
293
294
295 72
    private function isLabelTooLong(string $label): bool
296
    {
297 72
        if (preg_match('/[^\x00-\x7F]/', $label)) {
298 10
            idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
299 10
            return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
300
        }
301 66
        return strlen($label) > self::LABEL_MAX_LENGTH;
302
    }
303
304 71
    private function addTLDWarnings(bool $isTLDMissing): void
305
    {
306 71
        if ($isTLDMissing) {
307 26
            $this->warnings[TLD::CODE] = new TLD();
308
        }
309
    }
310
311 148
    public function domainPart(): string
312
    {
313 148
        return $this->domainPart;
314
    }
315
}
316