Completed
Push — 3.0.0-dev ( 34d49c...6abede )
by Eduardo Gulias
02:52 queued 01:22
created

DomainPart   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 239
Duplicated Lines 16.32 %

Coupling/Cohesion

Components 1
Dependencies 21

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 21
dl 39
loc 239
rs 7.44
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
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
C doParseDomainPart() 9 54 12
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
B parse() 3 29 6

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\ExpectingATEXT;
8
use Egulias\EmailValidator\Result\InvalidEmail;
9
use Egulias\EmailValidator\Result\Reason\CharNotAllowed as ReasonCharNotAllowed;
10
use Egulias\EmailValidator\Result\Reason\DomainHyphened as ReasonDomainHyphened;
11
use Egulias\EmailValidator\Result\Reason\DotAtEnd as ReasonDotAtEnd;
12
use Egulias\EmailValidator\Result\Reason\DotAtStart;
13
use Egulias\EmailValidator\Result\Reason\NoDomainPart as ReasonNoDomainPart;
14
use Egulias\EmailValidator\Result\Result;
15
use Egulias\EmailValidator\Result\ValidEmail;
16
use Egulias\EmailValidator\Warning\DeprecatedComment;
17
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...
18
use Egulias\EmailValidator\Warning\DomainTooLong;
19
use Egulias\EmailValidator\Warning\LabelTooLong;
20
use Egulias\EmailValidator\Warning\TLD;
21
use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
22
use Egulias\EmailValidator\Result\Reason\ConsecutiveAt as ReasonConsecutiveAt;
23
use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose;
24
25
class DomainPart extends Parser
26
{
27
    const DOMAIN_MAX_LENGTH = 254;
28
29
    /**
30
     * @var string
31
     */
32
    protected $domainPart = '';
33
34
    public function parse($domainPart)
35
    {
36
        $this->lexer->moveNext();
37
38
        $domainChecks = $this->performDomainStartChecks();
39
        if ($domainChecks->isInvalid()) {
40
            return $domainChecks;
41
        }
42
43
        $domain = $this->doParseDomainPart();
44
        if ($domain->isInvalid()) {
45
            return $domain;
46
        }
47
48
        $prev = $this->lexer->getPrevious();
49
        $length = strlen($this->domainPart);
50
51 View Code Duplication
        if ($prev['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...
52
            return new InvalidEmail(new ReasonDotAtEnd(), $this->lexer->token['value']);
53
        }
54
        if ($prev['type'] === EmailLexer::S_HYPHEN) {
55
            return new InvalidEmail(new ReasonDomainHyphened('Hypen found at the end of the domain'), $prev['value']);
56
        }
57
        if ($length > self::DOMAIN_MAX_LENGTH) {
58
            $this->warnings[DomainTooLong::CODE] = new DomainTooLong();
59
        }
60
61
        return new ValidEmail();
62
    }
63
64
    private function performDomainStartChecks() : Result
65
    {
66
        $invalidTokens = $this->checkInvalidTokensAfterAT();
67
        if ($invalidTokens->isInvalid()) {
68
            return $invalidTokens;
69
        }
70
        
71
        $missingDomain = $this->checkEmptyDomain();
72
        if ($missingDomain->isInvalid()) {
73
            return $missingDomain;
74
        }
75
76
        if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
77
            $this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
78
        }
79
        return new ValidEmail();
80
    }
81
82
    private function checkEmptyDomain() : Result
83
    {
84
        $thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
85
            ($this->lexer->token['type'] === EmailLexer::S_SP &&
86
            !$this->lexer->isNextToken(EmailLexer::GENERIC));
87
88
        if ($thereIsNoDomain) {
89
            return new InvalidEmail(new ReasonNoDomainPart(), $this->lexer->token['value']);
90
        }
91
92
        return new ValidEmail();
93
    }
94
95
    private function checkInvalidTokensAfterAT() : Result
96
    {
97 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...
98
            return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
99
        }
100 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...
101
            return new InvalidEmail(new ReasonDomainHyphened('After AT'), $this->lexer->token['value']);
102
        }
103
        return new ValidEmail();
104
    }
105
106
    /**
107
     * @return string
108
     */
109
    public function getDomainPart()
110
    {
111
        return $this->domainPart;
112
    }
113
114 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...
115
    {
116
        $commentParser = new Comment($this->lexer, new DomainComment());
117
        $result = $commentParser->parse('remove');
118
        if($result->isInvalid()) {
119
            return $result;
120
        }
121
122
        $this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
123
        return $result;
124
    }
125
126
    protected function doParseDomainPart() : Result
127
    {
128
        $domain = '';
129
        do {
130
            $prev = $this->lexer->getPrevious();
131
132
            $notAllowedChars = $this->checkNotAllowedChars($this->lexer->token);
133
            if ($notAllowedChars->isInvalid()) {
134
                return $notAllowedChars;
135
            }
136
137 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...
138
                $this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
139
                $commentsResult = $this->parseComments();
140
141
                //Invalid comment parsing
142
                if($commentsResult->isInvalid()) {
143
                    return $commentsResult;
144
                }
145
            }
146
147
            $dotsResult = $this->checkConsecutiveDots();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $dotsResult is correct as $this->checkConsecutiveDots() (which targets Egulias\EmailValidator\P...:checkConsecutiveDots()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
148
            if ($dotsResult) {
149
                return $dotsResult;
150
            }
151
            $exceptionsResult = $this->checkDomainPartExceptions($prev);
152
            if ($exceptionsResult->isInvalid()) {
153
                return $exceptionsResult;
154
            }
155
156
            if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET) {
157
                $literalResult = $this->parseDomainLiteral();
158
                //Invalid literal parsing
159
                if($literalResult->isInvalid()) {
160
                    return $literalResult;
161
                }
162
            }
163
164
            $this->checkLabelLength($prev);
165
166
            if ($this->isFWS()) {
167
                $FwsResult = $this->parseFWS();
168
                if($FwsResult->isInvalid()) {
169
                    return $FwsResult;
170
                }
171
            }
172
173
            $domain .= $this->lexer->token['value'];
174
            $this->lexer->moveNext();
175
        } while (null !== $this->lexer->token['type']);
176
177
        $this->domainPart = $domain;
178
        return new ValidEmail();
179
    }
180
181
    private function checkNotAllowedChars(array $token) : Result
182
    {
183
        $notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
184
        if (isset($notAllowed[$token['type']])) {
185
            return new InvalidEmail(new ReasonCharNotAllowed(), $token['value']);
186
        }
187
        return new ValidEmail();
188
    }
189
190
    /**
191
     * @return string|false
192
     */
193
    protected function parseDomainLiteral() : Result
194
    {
195
196
        try {
197
            $this->lexer->find(EmailLexer::S_CLOSEBRACKET);
198
        } catch (\RuntimeException $e) {
199
            return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->token['value']);
200
        }
201
202
        $domainLiteralParser = new DomainLiteralParser($this->lexer);
203
        $result = $domainLiteralParser->parse('remove');
204
        $this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings());
205
        return $result;
206
207
        //return $this->doParseDomainLiteral();
208
    }
209
210
    protected function checkDomainPartExceptions(array $prev)
211
    {
212
        $invalidDomainTokens = array(
213
            EmailLexer::S_DQUOTE => true,
214
            EmailLexer::S_SEMICOLON => true,
215
            EmailLexer::S_GREATERTHAN => true,
216
            EmailLexer::S_LOWERTHAN => true,
217
        );
218
219
        if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
220
            throw new ExpectingATEXT();
221
        }
222
223
        if ($this->lexer->token['type'] === EmailLexer::S_COMMA) {
224
            throw new CommaInDomain();
225
        }
226
227 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...
228
            return new InvalidEmail(new ReasonConsecutiveAt(), $this->lexer->token['value']);
229
        }
230
231
        if ($this->lexer->token['type'] === EmailLexer::S_OPENQBRACKET && $prev['type'] !== EmailLexer::S_AT) {
232
            throw new ExpectingATEXT();
233
        }
234
235 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...
236
            return new InvalidEmail(new ReasonDomainHyphened('Hypen found near DOT'), $this->lexer->token['value']);
237
        }
238
239 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...
240
            && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
241
            throw new ExpectingATEXT();
242
        }
243
244
        return new ValidEmail();
245
    }
246
247
    protected function checkLabelLength(array $prev)
248
    {
249
        if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
250
            $prev['type'] === EmailLexer::GENERIC &&
251
            strlen($prev['value']) > 63
252
        ) {
253
            $this->warnings[LabelTooLong::CODE] = new LabelTooLong();
254
        }
255
    }
256
257
    protected function addTLDWarnings()
258
    {
259
        if ($this->warnings[DomainLiteral::CODE]) {
260
            $this->warnings[TLD::CODE] = new TLD();
261
        }
262
    }
263
}
264