Passed
Push — master ( 2f38a4...cfa3d4 )
by Eduardo Gulias
02:09
created

Parser   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 228
Duplicated Lines 5.7 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 16
dl 13
loc 228
rs 8.48
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getWarnings() 0 4 1
parse() 0 1 ?
A getOpenedParenthesis() 0 4 1
A validateQuotedPair() 4 10 3
A parseComments() 0 22 5
A isUnclosedComment() 0 9 2
B parseFWS() 3 24 8
A checkConsecutiveDots() 3 6 3
B isFWS() 0 17 7
A escaped() 0 13 4
A warnEscaping() 0 19 4
B checkDQUOTE() 3 23 6
A checkCRLFInFWS() 0 14 4

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 Parser 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 Parser, 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\AtextAfterCFWS;
7
use Egulias\EmailValidator\Exception\ConsecutiveDot;
8
use Egulias\EmailValidator\Exception\CRLFAtTheEnd;
9
use Egulias\EmailValidator\Exception\CRLFX2;
10
use Egulias\EmailValidator\Exception\CRNoLF;
11
use Egulias\EmailValidator\Exception\ExpectingQPair;
12
use Egulias\EmailValidator\Exception\ExpectingATEXT;
13
use Egulias\EmailValidator\Exception\ExpectingCTEXT;
14
use Egulias\EmailValidator\Exception\UnclosedComment;
15
use Egulias\EmailValidator\Exception\UnclosedQuotedString;
16
use Egulias\EmailValidator\Warning\CFWSNearAt;
17
use Egulias\EmailValidator\Warning\CFWSWithFWS;
18
use Egulias\EmailValidator\Warning\Comment;
19
use Egulias\EmailValidator\Warning\QuotedPart;
20
use Egulias\EmailValidator\Warning\QuotedString;
21
22
abstract class Parser
23
{
24
    /**
25
     * @var array
26
     */
27
    protected $warnings = [];
28
29
    /**
30
     * @var EmailLexer
31
     */
32
    protected $lexer;
33
34
    /**
35
     * @var int
36
     */
37
    protected $openedParenthesis = 0;
38
39
    public function __construct(EmailLexer $lexer)
40
    {
41
        $this->lexer = $lexer;
42
    }
43
44
    /**
45
     * @return \Egulias\EmailValidator\Warning\Warning[]
46
     */
47
    public function getWarnings()
48
    {
49
        return $this->warnings;
50
    }
51
52
    /**
53
     * @param string $str
54
     */
55
    abstract public function parse($str);
56
57
    /** @return int */
58
    public function getOpenedParenthesis()
59
    {
60
        return $this->openedParenthesis;
61
    }
62
63
    /**
64
     * validateQuotedPair
65
     */
66
    protected function validateQuotedPair()
67
    {
68 View Code Duplication
        if (!($this->lexer->token['type'] === EmailLexer::INVALID
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...
69
            || $this->lexer->token['type'] === EmailLexer::C_DEL)) {
70
            throw new ExpectingQPair();
71
        }
72
73
        $this->warnings[QuotedPart::CODE] =
74
            new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']);
75
    }
76
77
    protected function parseComments()
78
    {
79
        $this->openedParenthesis = 1;
80
        $this->isUnclosedComment();
81
        $this->warnings[Comment::CODE] = new Comment();
82
        while (!$this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
83
            if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) {
84
                $this->openedParenthesis++;
85
            }
86
            $this->warnEscaping();
87
            $this->lexer->moveNext();
88
        }
89
90
        $this->lexer->moveNext();
91
        if ($this->lexer->isNextTokenAny(array(EmailLexer::GENERIC, EmailLexer::S_EMPTY))) {
92
            throw new ExpectingATEXT();
93
        }
94
95
        if ($this->lexer->isNextToken(EmailLexer::S_AT)) {
96
            $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
97
        }
98
    }
99
100
    /**
101
     * @return bool
102
     */
103
    protected function isUnclosedComment()
104
    {
105
        try {
106
            $this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
107
            return true;
108
        } catch (\RuntimeException $e) {
109
            throw new UnclosedComment();
110
        }
111
    }
112
113
    protected function parseFWS()
114
    {
115
        $previous = $this->lexer->getPrevious();
116
117
        $this->checkCRLFInFWS();
118
119
        if ($this->lexer->token['type'] === EmailLexer::S_CR) {
120
            throw new CRNoLF();
121
        }
122
123
        if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type']  !== EmailLexer::S_AT) {
124
            throw new AtextAfterCFWS();
125
        }
126
127 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
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...
128
            throw new ExpectingCTEXT();
129
        }
130
131
        if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type']  === EmailLexer::S_AT) {
132
            $this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
133
        } else {
134
            $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
135
        }
136
    }
137
138
    protected function checkConsecutiveDots()
139
    {
140 View Code Duplication
        if ($this->lexer->token['type'] === EmailLexer::S_DOT && $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...
141
            throw new ConsecutiveDot();
142
        }
143
    }
144
145
    /**
146
     * @return bool
147
     */
148
    protected function isFWS()
149
    {
150
        if ($this->escaped()) {
151
            return false;
152
        }
153
154
        if ($this->lexer->token['type'] === EmailLexer::S_SP ||
155
            $this->lexer->token['type'] === EmailLexer::S_HTAB ||
156
            $this->lexer->token['type'] === EmailLexer::S_CR ||
157
            $this->lexer->token['type'] === EmailLexer::S_LF ||
158
            $this->lexer->token['type'] === EmailLexer::CRLF
159
        ) {
160
            return true;
161
        }
162
163
        return false;
164
    }
165
166
    /**
167
     * @return bool
168
     */
169
    protected function escaped()
170
    {
171
        $previous = $this->lexer->getPrevious();
172
173
        if ($previous && $previous['type'] === EmailLexer::S_BACKSLASH
0 ignored issues
show
Bug Best Practice introduced by
The expression $previous of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
174
            &&
175
            $this->lexer->token['type'] !== EmailLexer::GENERIC
176
        ) {
177
            return true;
178
        }
179
180
        return false;
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    protected function warnEscaping()
187
    {
188
        if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
189
            return false;
190
        }
191
192
        if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
193
            throw new ExpectingATEXT();
194
        }
195
196
        if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
197
            return false;
198
        }
199
200
        $this->warnings[QuotedPart::CODE] =
201
            new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']);
202
        return true;
203
204
    }
205
206
    /**
207
     * @param bool $hasClosingQuote
208
     *
209
     * @return bool
210
     */
211
    protected function checkDQUOTE($hasClosingQuote)
212
    {
213
        if ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE) {
214
            return $hasClosingQuote;
215
        }
216
        if ($hasClosingQuote) {
217
            return $hasClosingQuote;
218
        }
219
        $previous = $this->lexer->getPrevious();
220 View Code Duplication
        if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) {
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...
221
            throw new ExpectingATEXT();
222
        }
223
224
        try {
225
            $this->lexer->find(EmailLexer::S_DQUOTE);
226
            $hasClosingQuote = true;
227
        } catch (\Exception $e) {
228
            throw new UnclosedQuotedString();
229
        }
230
        $this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']);
231
232
        return $hasClosingQuote;
233
    }
234
235
    protected function checkCRLFInFWS()
236
    {
237
        if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
238
            return;
239
        }
240
241
        if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
242
            throw new CRLFX2();
243
        }
244
245
        if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
246
            throw new CRLFAtTheEnd();
247
        }
248
    }
249
}
250