Test Setup Failed
Push — parse-domain-literal ( 97f1d2 )
by Eduardo Gulias
01:49
created

DomainPart   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 233
Duplicated Lines 18.45 %

Coupling/Cohesion

Components 1
Dependencies 21

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 21
dl 43
loc 233
rs 8.4
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
B parse() 0 29 6
A performDomainStartChecks() 0 17 4
A checkEmptyDomain() 0 12 4
A checkInvalidTokensAfterAT() 6 10 3
A getDomainPart() 0 4 1
A parseComments() 11 11 2
B doParseDomainPart() 16 48 10
A checkNotAllowedChars() 0 8 2
A parseDomainLiteral() 0 16 2
B checkDomainPartExceptions() 10 36 10
A checkLabelLength() 0 9 4
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\CommaInDomain;
7
use Egulias\EmailValidator\Exception\ConsecutiveAt;
8
use Egulias\EmailValidator\Exception\ExpectingATEXT;
9
use Egulias\EmailValidator\Result\InvalidEmail;
10
use Egulias\EmailValidator\Result\Reason\CharNotAllowed as ReasonCharNotAllowed;
11
use Egulias\EmailValidator\Result\Reason\DomainHyphened as ReasonDomainHyphened;
12
use Egulias\EmailValidator\Result\Reason\DotAtEnd as ReasonDotAtEnd;
13
use Egulias\EmailValidator\Result\Reason\DotAtStart;
14
use Egulias\EmailValidator\Result\Reason\NoDomainPart as ReasonNoDomainPart;
15
use Egulias\EmailValidator\Result\Result;
16
use Egulias\EmailValidator\Result\ValidEmail;
17
use Egulias\EmailValidator\Warning\DeprecatedComment;
18
use Egulias\EmailValidator\Warning\DomainLiteral;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Egulias\EmailValidator\Parser\DomainLiteral.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
use Egulias\EmailValidator\Warning\DomainTooLong;
20
use Egulias\EmailValidator\Warning\LabelTooLong;
21
use Egulias\EmailValidator\Warning\TLD;
22
use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
23
use Egulias\EmailValidator\Result\Reason\ConsecutiveAt as ReasonConsecutiveAt;
24
use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose;
25
26
class DomainPart extends Parser
27
{
28
    const DOMAIN_MAX_LENGTH = 254;
29
30
    /**
31
     * @var string
32
     */
33
    protected $domainPart = '';
34
35
    public function parse($domainPart)
36
    {
37
        $this->lexer->moveNext();
38
39
        $domainChecks = $this->performDomainStartChecks();
40
        if ($domainChecks->isInvalid()) {
41
            return $domainChecks;
42
        }
43
44
        $domain = $this->doParseDomainPart();
45
        if ($domain->isInvalid()) {
46
            return $domain;
47
        }
48
49
        $prev = $this->lexer->getPrevious();
50
        $length = strlen($this->domainPart);
51
52
        if ($prev['type'] === EmailLexer::S_DOT) {
53
            return new InvalidEmail(new ReasonDotAtEnd(), $this->lexer->token['value']);
54
        }
55
        if ($prev['type'] === EmailLexer::S_HYPHEN) {
56
            return new InvalidEmail(new ReasonDomainHyphened('Hypen found at the end of the domain'), $prev['value']);
57
        }
58
        if ($length > self::DOMAIN_MAX_LENGTH) {
59
            $this->warnings[DomainTooLong::CODE] = new DomainTooLong();
60
        }
61
62
        return new ValidEmail();
63
    }
64
65
    private function performDomainStartChecks() : Result
66
    {
67
        $invalidTokens = $this->checkInvalidTokensAfterAT();
68
        if ($invalidTokens->isInvalid()) {
69
            return $invalidTokens;
70
        }
71
        
72
        $missingDomain = $this->checkEmptyDomain();
73
        if ($missingDomain->isInvalid()) {
74
            return $missingDomain;
75
        }
76
77
        if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
78
            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
79
        }
80
        return new ValidEmail();
81
    }
82
83
    private function checkEmptyDomain() : Result
84
    {
85
        $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
86
            ($this->lexer->token['type'] === EmailLexer::S_SP &&
87
            !$this->lexer->isNextToken(EmailLexer::GENERIC));
88
89
        if ($thereIsNoDomain) {
90
            return new InvalidEmail(new ReasonNoDomainPart(), $this->lexer->token['value']);
91
        }
92
93
        return new ValidEmail();
94
    }
95
96
    private function checkInvalidTokensAfterAT() : Result
97
    {
98 View Code Duplication
        if ($this->lexer->token['type'] === 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...
99
            return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
100
        }
101 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
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...
102
            return new InvalidEmail(new ReasonDomainHyphened('After AT'), $this->lexer->token['value']);
103
        }
104
        return new ValidEmail();
105
    }
106
107
    /**
108
     * @return string
109
     */
110
    public function getDomainPart()
111
    {
112
        return $this->domainPart;
113
    }
114
115 View Code Duplication
    protected function parseComments()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
116
    {
117
        $commentParser = new Comment($this->lexer, new DomainComment());
118
        $result = $commentParser->parse('remove');
119
        if($result->isInvalid()) {
120
            return $result;
121
        }
122
123
        $this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
124
        return $result;
125
    }
126
127
    protected function doParseDomainPart() : Result
128
    {
129
        $domain = '';
130
        do {
131
            $prev = $this->lexer->getPrevious();
132
133
            $notAllowedChars = $this->checkNotAllowedChars($this->lexer->token);
134
            if ($notAllowedChars->isInvalid()) {
135
                return $notAllowedChars;
136
            }
137
138 View Code Duplication
            if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS || 
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...
139
                $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
140
                $commentsResult = $this->parseComments();
141
142
                //Invalid comment parsing
143
                if($commentsResult->isInvalid()) {
144
                    return $commentsResult;
145
                }
146
            }
147
148
            $this->checkConsecutiveDots();
149
            $result = $this->checkDomainPartExceptions($prev);
150
            if ($result->isInvalid()) {
151
                return $result;
152
            }
153
154 View Code Duplication
            if ($this->lexer->token['type'] !== EmailLexer::S_OPENBRACKET) {
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...
155
                $literalResult = $this->parseDomainLiteral();
156
                //Invalid literal parsing
157
                if($literalResult->isInvalid()) {
158
                    return $literalResult;
159
                }
160
            }
161
162
            $this->checkLabelLength($prev);
163
164
            if ($this->isFWS()) {
165
                $this->parseFWS();
166
            }
167
168
            $domain .= $this->lexer->token['value'];
169
            $this->lexer->moveNext();
170
        } while (null !== $this->lexer->token['type']);
171
172
        $this->domainPart = $domain;
173
        return new ValidEmail();
174
    }
175
176
    private function checkNotAllowedChars(array $token) : Result
177
    {
178
        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
179
        if (isset($notAllowed[$token['type']])) {
180
            return new InvalidEmail(new ReasonCharNotAllowed(), $token['value']);
181
        }
182
        return new ValidEmail();
183
    }
184
185
    /**
186
     * @return string|false
187
     */
188
    protected function parseDomainLiteral() : Result
189
    {
190
191
        try {
192
            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
193
        } catch (\RuntimeException $e) {
194
            return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->token['value']);
195
        }
196
197
        $domainLiteralParser = new DomainLiteralParser($this->lexer);
198
        $result = $domainLiteralParser->parse('remove');
199
        $this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings());
200
        return $result;
201
202
        //return $this->doParseDomainLiteral();
203
    }
204
205
    protected function checkDomainPartExceptions(array $prev)
206
    {
207
        $invalidDomainTokens = array(
208
            EmailLexer::S_DQUOTE => true,
209
            EmailLexer::S_SEMICOLON => true,
210
            EmailLexer::S_GREATERTHAN => true,
211
            EmailLexer::S_LOWERTHAN => true,
212
        );
213
214
        if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
215
            throw new ExpectingATEXT();
216
        }
217
218
        if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
219
            throw new CommaInDomain();
220
        }
221
222 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_AT) {
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...
223
            return new InvalidEmail(new ReasonConsecutiveAt(), $this->lexer->token['value']);
224
        }
225
226
        if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
227
            throw new ExpectingATEXT();
228
        }
229
230 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...
231
            return new InvalidEmail(new ReasonDomainHyphened('Hypen found near DOT'), $this->lexer->token['value']);
232
        }
233
234 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...
235
            && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
236
            throw new ExpectingATEXT();
237
        }
238
239
        return new ValidEmail();
240
    }
241
242
    protected function checkLabelLength(array $prev)
243
    {
244
        if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
245
            $prev['type'] === EmailLexer::GENERIC &&
246
            strlen($prev['value']) > 63
247
        ) {
248
            $this->warnings[LabelTooLong::CODE] = new LabelTooLong();
249
        }
250
    }
251
252
    protected function addTLDWarnings()
253
    {
254
        if ($this->warnings[DomainLiteral::CODE]) {
255
            $this->warnings[TLD::CODE] = new TLD();
256
        }
257
    }
258
}
259