Test Setup Failed
Pull Request — master (#21)
by Alexandr
02:04
created

RData   F

Complexity

Total Complexity 77

Size/Duplication

Total Lines 327
Duplicated Lines 2.45 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 95.17%

Importance

Changes 0
Metric Value
wmc 77
lcom 1
cbo 2
dl 8
loc 327
ccs 138
cts 145
cp 0.9517
rs 2.24
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A isKnownType() 0 4 1
A tokenize() 0 10 2
D defaultExtractor() 0 74 25
A ignoreComment() 8 8 3
B endRecord() 0 32 11
D txtExtractor() 0 61 25
B extractCharSet() 0 23 8

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 RData 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 RData, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author: Viskov Sergey
4
 * @date  : 14.04.16
5
 * @time  : 4:50
6
 */
7
8
namespace LTDBeget\dns\record;
9
10
use LTDBeget\ascii\AsciiChar;
11
use LTDBeget\dns\SyntaxErrorException;
12
use LTDBeget\stringstream\StringStream;
13
14
/**
15
 * Class RData
16
 *
17
 * @package LTDBeget\dns\record
18
 */
19
class RData
20
{
21
    /**
22
     * @var array
23
     */
24
    private static $rdataFormats = [
25
        'SOA'   => [
26
            'MNAME'   => 'defaultExtractor',
27
            'RNAME'   => 'defaultExtractor',
28
            'SERIAL'  => 'defaultExtractor',
29
            'REFRESH' => 'defaultExtractor',
30
            'RETRY'   => 'defaultExtractor',
31
            'EXPIRE'  => 'defaultExtractor',
32
            'MINIMUM' => 'defaultExtractor',
33
        ],
34
        'A'     => [
35
            'ADDRESS' => 'defaultExtractor'
36
        ],
37
        'AAAA'  => [
38
            'ADDRESS' => 'defaultExtractor'
39
        ],
40
        'CNAME' => [
41
            'CNAME' => 'defaultExtractor'
42
        ],
43
        'MX'    => [
44
            'PREFERENCE' => 'defaultExtractor',
45
            'EXCHANGE'   => 'defaultExtractor'
46
        ],
47
        'NS'    => [
48
            'NSDNAME' => 'defaultExtractor'
49
        ],
50
        'PTR'   => [
51
            'PTRDNAME' => 'defaultExtractor'
52
        ],
53
        'SRV'   => [
54
            'PRIORITY' => 'defaultExtractor',
55
            'WEIGHT'   => 'defaultExtractor',
56
            'PORT'     => 'defaultExtractor',
57
            'TARGET'   => 'defaultExtractor'
58
        ],
59
        'TXT'   => [
60
            'TXTDATA' => 'txtExtractor'
61
        ],
62
        'CAA' => [
63
            'FLAGS' => 'defaultExtractor',
64
            'TAG'   => 'defaultExtractor',
65
            'VALUE' => 'defaultExtractor'
66
        ],
67
        'NAPTR' => [
68
            'ORDER'       => 'defaultExtractor',
69
            'PREFERENCE'  => 'defaultExtractor',
70
            'FLAGS'       => 'defaultExtractor',
71
            'SERVICES'    => 'defaultExtractor',
72
            'REGEXP'      => 'defaultExtractor',
73
            'REPLACEMENT' => 'defaultExtractor'
74
        ]
75
    ];
76
    /**
77
     * @var string
78
     */
79
    private $type;
80
    /**
81
     * @var array
82
     */
83
    private $tokens = [];
84
85
    /**
86
     * @var bool
87
     */
88 6
    private $commentOpen = false;
89
90 6
    /**
91
     * @var bool
92
     */
93
    private $multiLineOpened = false;
94 6
95 6
    /**
96 6
     * RData constructor.
97
     *
98
     * @param StringStream $stream
99
     * @param string       $type
100
     */
101
    public function __construct(StringStream $stream, string $type)
102 6
    {
103
        if (! self::isKnownType($type)) {
104 6
            throw new SyntaxErrorException($stream);
105
        }
106
107
        $this->stream = $stream;
0 ignored issues
show
Bug introduced by
The property stream does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
108
        $this->type   = $type;
109
    }
110 6
111
    /**
112 6
     * @param string $type
113 6
     * @return bool
114
     */
115
    public static function isKnownType(string $type) : bool
116 6
    {
117
        return array_key_exists($type, self::$rdataFormats);
118 6
    }
119
120
    /**
121
     * @return array
122
     */
123
    public function tokenize() : array
124 6
    {
125
        foreach (self::$rdataFormats[$this->type] as $tokenName => $extractor) {
126 6
            $this->$extractor($tokenName);
127 5
        }
128
        
129 6
        $this->endRecord();
130
131
        return $this->tokens;
132 6
    }
133
    
134 6
    /**
135 6
     * @param string $tokenName
136
     */
137
    protected function defaultExtractor(string $tokenName)
138
    {
139
        if($this->multiLineOpened) {
140 6
            $this->stream->ignoreWhitespace();
141
        } else {
142 6
            $this->stream->ignoreHorizontalSpace();
143 4
        }
144
        
145
        $this->commentOpen = false;
146 6
        
147 5
        if (!array_key_exists($tokenName, $this->tokens)) {
148 5
            $this->tokens[$tokenName] = '';
149 5
        }
150 6
        
151 1
        start:
152 1
153 1
        if ($this->stream->isEnd()){
154 6
            return;
155 4
        }
156 4
        $ord = $this->stream->ord();
157 6
158 4
        if($ord === AsciiChar::OPEN_BRACKET && !$this->commentOpen) {
159
            $this->multiLineOpened = true;
160 6
            $this->stream->next();
161 4
            goto start;
162 4
        } elseif($this->multiLineOpened && !$this->commentOpen && $ord === AsciiChar::CLOSE_BRACKET) {
163
            $this->multiLineOpened = false;
164 3
            $this->stream->next();
165 3
            goto start;
166 3
        } elseif($this->multiLineOpened && !$this->commentOpen && $ord === AsciiChar::LINE_FEED) {
167
            $this->stream->next();
168 1
            goto start;
169 1
        } elseif($ord === AsciiChar::LINE_FEED && !$this->commentOpen) {
170 1
            return;
171
        } else {
172 4
            if($ord === AsciiChar::SEMICOLON) {
173 6
                $this->stream->previous();
174 3
                if($this->stream->currentAscii()->isHorizontalSpace()) {
175 3
176 3
                    $this->commentOpen = true;
177 3
                    $this->stream->next();
178 6
                    $this->stream->next();
179 3
                } else {
180 3
                    $this->stream->next();
181 3
                    $this->tokens[$tokenName] .= $this->stream->current();
182 6
                    $this->stream->next();
183 6
                }
184 4
                goto start;
185 4
            } elseif(($this->stream->currentAscii()->isVerticalSpace() || $ord === AsciiChar::NULL) && $this->commentOpen) {
186 6
                $this->stream->next();
187 5
                $this->stream->ignoreHorizontalSpace();
188
                $this->commentOpen = false;
189 6
                goto start;
190 6
            } elseif($this->commentOpen) {
191 6
                $this->commentOpen = true;
192 4
                $this->ignoreComment();
193
                goto start;
194 6
            } elseif(!$this->commentOpen) {
195
                if($ord === AsciiChar::SPACE && $this->tokens[$tokenName] === '') {
196
                    $this->stream->next();
197
                    goto start;
198
                } elseif($this->stream->currentAscii()->isWhiteSpace()) {
199
                    return;
200 3
                } else {
201
                    $this->tokens[$tokenName] .= $this->stream->current();
202
                    $this->stream->next();
203 3
                    if($this->stream->isEnd()) {
204 3
                        $this->tokens[$tokenName] = trim($this->tokens[$tokenName]);
205 3
                    }
206
                    goto start;
207 3
                }
208
            }
209 6
        }
210
    }
211
212 6 View Code Duplication
    private function ignoreComment()
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...
213 6
    {
214 4
        start:
215
        if (!$this->stream->currentAscii()->isVerticalSpace() && !$this->stream->isEnd()) {
216 5
            $this->stream->next();
217 2
            goto start;
218 2
        }
219 2
    }
220 5
    
221 2
    protected function endRecord()
222 2
    {
223 2
        start:
224
        $ord = $this->stream->ord();
225 2
        if($ord === AsciiChar::NULL) {
226 2
            return;
227 2
        }
228
        if($ord === AsciiChar::SEMICOLON) {
229 5
            $this->stream->next();
230 5
            $this->commentOpen = true;
231 4
            goto start;
232 4
        } elseif($this->commentOpen) {
233
            if($ord === AsciiChar::NULL() || $ord === AsciiChar::LINE_FEED) {
234 4
                $this->commentOpen = false;
235 4
                goto start;
236 5
            } else {
237 5
                $this->stream->next();
238
                $this->commentOpen = true;
239
                goto start;
240 3
            }
241
        } elseif(!$this->commentOpen)  {
242
            if($this->multiLineOpened) {
243
                if($ord === AsciiChar::CLOSE_BRACKET) {
244
                    $this->multiLineOpened = false;
245 1
                }
246
                $this->stream->next();
247 1
                goto start;
248 1
            } elseif($ord === AsciiChar::NULL() || $ord === AsciiChar::LINE_FEED) {
249
                return;
250
            }
251
        }
252 1
    }
253 1
254
    /**
255 1
     * @param string $tokenName
256
     */
257
    private function txtExtractor(string $tokenName)
258
    {
259
        if (!array_key_exists($tokenName, $this->tokens)) {
260 1
            $this->tokens[$tokenName] = '';
261 1
        }
262 1
263 1
        start:
264 1
        $ord = $this->stream->ord();
265 1
        $this->stream->next();
266 1
267 1
        if($ord === 0) { // if end of record
268 1
            return;
269 1
        }
270
271
        // comment starts
272 1
        if($ord === 59) {
273 1
            $this->commentOpen = true;
274
            goto start;
275
        } elseif($this->commentOpen === true && $ord !== 10) {
276
            $this->commentOpen = true;
277 1
            goto start;
278 1
        } elseif($this->commentOpen === true && ($ord === 10 || $ord === 0)) {
279
            $this->stream->previous();
280
            $this->commentOpen = false;
281
            goto start;
282 1
        } else {
283 1
            // ignore blanck line
284 1
            if($ord === 32) {
285
                goto start;
286
            }
287
288 1
            // Find starts of char set
289 1
            if($ord === 34 && !$this->commentOpen) { // "
290 1
                $this->extractCharSet($tokenName);
291
            }
292
293
            // multi line opened
294 1
            if($ord === 40 && !$this->commentOpen) {
295
                $this->multiLineOpened = true;
296
                goto start;
297
            }
298
299 1
            // multi line closed
300 1
            if($this->multiLineOpened && !$this->commentOpen && $ord === 41) {
301 1
                $this->multiLineOpened = false;
302 1
                goto start;
303
            }
304
305 1
            // comment end in multi line TXT record
306
            if($ord === 10 && $this->commentOpen && $this->multiLineOpened) {
307
                goto start;
308
            }
309
310 1
            // is record ends?
311
            if(!$this->multiLineOpened && ($ord === 10 || $ord === 0)) {
312 1
                return;
313
            } elseif($this->multiLineOpened && $ord === 10) {
314 1
                goto start;
315 1
            }
316
        }
317 1
    }
318
319
    /**
320
     * @param string $tokenName
321 1
     */
322 1
    private function extractCharSet(string $tokenName)
323
    {
324 1
        $escaping_open = false;
325
        start:
326
        $ord = $this->stream->ord();
327
        $this->stream->next();
328 1
329 1
        if($ord === AsciiChar::NULL) { // if end of record
330 1
            throw new SyntaxErrorException($this->stream);
331
        }
332 1
333
        if(!$escaping_open && $ord === 34) {
334
            $this->txtExtractor($tokenName);
335
        } else {
336
            if($ord === AsciiChar::LINE_FEED || $ord === AsciiChar::VERTICAL_TAB || $ord === AsciiChar::NULL) {
337
                $this->stream->previous();
338
                throw new SyntaxErrorException($this->stream);
339
            }
340
            $this->tokens[$tokenName] .= chr($ord);
341
            $escaping_open = ($ord === 92 && !$escaping_open);
342
            goto start;
343
        }
344
    }
345
}