Normaliser   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 98.89%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 2
dl 0
loc 227
ccs 89
cts 90
cp 0.9889
rs 9.2
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A normalise() 0 4 1
A process() 0 13 2
A handleComment() 0 19 6
B handleTxt() 0 25 6
A handleMultiline() 0 29 5
A removeWhitespace() 0 15 4
A append() 0 11 6
B appendComment() 0 32 9

How to fix   Complexity   

Complex Class

Complex classes like Normaliser 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 Normaliser, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Badcow DNS Library.
7
 *
8
 * (c) Samuel Williams <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Badcow\DNS\Parser;
15
16
class Normaliser
17
{
18
    /**
19
     * @var StringIterator
20
     */
21
    private $string;
22
23
    /**
24
     * @var string
25
     */
26
    private $normalisedString = '';
27
28
    /**
29
     * @var int
30
     */
31
    private $commentOptions;
32
33
    /**
34
     * @var string
35
     */
36
    private $comment = '';
37
38
    /**
39
     * Comments that are within a multiline context, i.e. between brackets.
40
     *
41
     * @var string
42
     */
43
    private $multilineComments = '';
44
45
    /**
46
     * Normaliser constructor.
47
     */
48 40
    public function __construct(string $zone, int $commentOptions = Comments::NONE)
49
    {
50
        //Remove Windows line feeds and tabs
51 40
        $zone = str_replace([Tokens::CARRIAGE_RETURN, Tokens::TAB], ['', Tokens::SPACE], $zone);
52
53 40
        $this->string = new StringIterator($zone);
54 40
        $this->commentOptions = $commentOptions;
55 40
    }
56
57
    /**
58
     * @throws ParseException
59
     */
60 40
    public static function normalise(string $zone, int $includeComments = Comments::NONE): string
61
    {
62 40
        return (new self($zone, $includeComments))->process();
63
    }
64
65
    /**
66
     * @throws ParseException
67
     */
68 40
    public function process(): string
69
    {
70 40
        while ($this->string->valid()) {
71 40
            $this->handleTxt();
72 40
            $this->handleMultiline();
73 40
            $this->handleComment(Comments::END_OF_ENTRY | Comments::ORPHAN);
74 40
            $this->append();
75
        }
76
77 37
        $this->removeWhitespace();
78
79 37
        return $this->normalisedString;
80
    }
81
82
    /**
83
     * Parses the comments.
84
     *
85
     * @param int $condition
86
     */
87 40
    private function handleComment($condition = Comments::ALL): void
88
    {
89 40
        if ($this->string->isNot(Tokens::SEMICOLON)) {
90 40
            return;
91
        }
92
93 22
        $this->string->next();
94
95 22
        while ($this->string->isNot(Tokens::LINE_FEED) && $this->string->valid()) {
96 22
            if ($this->commentOptions & $condition) {
97 10
                if ($condition & Comments::MULTILINE) {
98 4
                    $this->multilineComments .= $this->string->current();
99
                } else {
100 10
                    $this->comment .= $this->string->current();
101
                }
102
            }
103 22
            $this->string->next();
104
        }
105 22
    }
106
107
    /**
108
     * Handle text inside of double quotations. When this function is called, the String pointer MUST be at the
109
     * double quotation mark.
110
     *
111
     * @throws ParseException
112
     */
113 40
    private function handleTxt(): void
114
    {
115 40
        if ($this->string->isNot(Tokens::DOUBLE_QUOTES)) {
116 40
            return;
117
        }
118
119 22
        $this->append();
120
121 22
        while ($this->string->isNot(Tokens::DOUBLE_QUOTES)) {
122 22
            if (!$this->string->valid()) {
123 1
                throw new ParseException('Unbalanced double quotation marks. End of file reached.');
124
            }
125
126
            //If escape character
127 22
            if ($this->string->is(Tokens::BACKSLASH)) {
128 13
                $this->append();
129
            }
130
131 22
            if ($this->string->is(Tokens::LINE_FEED)) {
132 1
                throw new ParseException('Line Feed found within double quotation marks context.', $this->string);
133
            }
134
135 22
            $this->append();
136
        }
137 20
    }
138
139
    /**
140
     * Move multi-line records onto single line.
141
     *
142
     * @throws ParseException
143
     */
144 40
    private function handleMultiline(): void
145
    {
146 40
        if ($this->string->isNot(Tokens::OPEN_BRACKET)) {
147 40
            return;
148
        }
149
150 19
        $this->string->next();
151 19
        while ($this->string->valid()) {
152 19
            $this->handleTxt();
153 19
            $this->handleComment(Comments::MULTILINE);
154
155 19
            if ($this->string->is(Tokens::LINE_FEED)) {
156 19
                $this->string->next();
157 19
                continue;
158
            }
159
160 19
            if ($this->string->is(Tokens::CLOSE_BRACKET)) {
161 18
                $this->string->next();
162
163 18
                $this->process();
164
165 18
                return;
166
            }
167
168 19
            $this->append();
169
        }
170
171 1
        throw new ParseException('End of file reached. Unclosed bracket.');
172
    }
173
174
    /**
175
     * Remove superfluous whitespace characters from string.
176
     *
177
     * @throws \UnexpectedValueException
178
     */
179 37
    private function removeWhitespace(): void
180
    {
181 37
        if (null === $string = preg_replace('/ {2,}/', Tokens::SPACE, $this->normalisedString)) {
182
            throw new \UnexpectedValueException('Unexpected value returned from \preg_replace().');
183
        }
184
185 37
        $lines = [];
186
187 37
        foreach (explode(Tokens::LINE_FEED, $string) as $line) {
188 37
            if ('' !== $line = rtrim($line)) {
189 37
                $lines[] = $line;
190
            }
191
        }
192 37
        $this->normalisedString = implode(Tokens::LINE_FEED, $lines);
193 37
    }
194
195
    /**
196
     * Add current entry to normalisedString and moves iterator to next entry.
197
     */
198 40
    private function append(): void
199
    {
200 40
        if (($this->string->is(Tokens::LINE_FEED) || !$this->string->valid()) &&
201 40
            $this->commentOptions &&
202 40
            ('' !== $this->comment || '' !== $this->multilineComments)) {
203 10
            $this->appendComment();
204
        }
205
206 40
        $this->normalisedString .= $this->string->current();
207 40
        $this->string->next();
208 40
    }
209
210 10
    private function appendComment(): void
211
    {
212 10
        $zone = rtrim($this->normalisedString, Tokens::SPACE);
213
214
        //If there is no Resource Record on the line
215 10
        if ((Tokens::LINE_FEED === substr($zone, -1, 1) || 0 === strlen($zone))) {
216 8
            if ($this->commentOptions & Comments::ORPHAN) {
217 5
                $this->normalisedString = sprintf('%s;%s', $zone, trim($this->comment));
218
            }
219 8
            $this->comment = '';
220 8
            $this->multilineComments = '';
221
222 8
            return;
223
        }
224
225 10
        $comments = '';
226
227 10
        if (($this->commentOptions & Comments::MULTILINE) && '' !== $this->multilineComments) {
228 4
            $comments .= $this->multilineComments;
229
        }
230
231 10
        if (($this->commentOptions & Comments::END_OF_ENTRY) && '' !== $this->comment) {
232 10
            $comments .= $this->comment;
233
        }
234
235 10
        if ('' !== $comments = trim($comments)) {
236 10
            $this->normalisedString = sprintf('%s;%s', $zone, $comments);
237
        }
238
239 10
        $this->comment = '';
240 10
        $this->multilineComments = '';
241 10
    }
242
}
243