Completed
Push — master ( c7418d...8b4244 )
by stéphane
04:44
created

Node   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 344
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 171
dl 0
loc 344
rs 3.44
c 0
b 0
f 0
wmc 62

12 Methods

Rating   Name   Duplication   Size   Complexity  
A setParent() 0 4 1
A __construct() 0 7 2
A onHyphen() 0 23 6
B getParent() 0 15 7
A getDeepestNode() 0 16 5
B identify() 0 41 10
A parse() 0 15 4
A onNodeAction() 0 12 4
B add() 0 29 10
A onKey() 0 20 5
B onCompact() 0 35 6
A __debugInfo() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like Node 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.

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

1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
namespace Dallgoot\Yaml;
4
5
use Dallgoot\Yaml\{Yaml as Y, Regex as R};
6
7
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
8
 *
9
 * @author  Stéphane Rebai <[email protected]>
10
 * @license Apache 2.0
11
 * @link    TODO : url to specific online doc
12
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
13
final class Node
14
{
15
    /** @var int */
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
16
    public $indent = -1;
17
    /** @var int */
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
18
    public $line;
19
    /** @var int */
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
20
    public $type;
21
    /** @var null|string|boolean */
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
22
    public $identifier;
23
    /** @var Node|NodeList|null|string */
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
24
    public $value = null;
25
26
    /** @var null|Node */
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
27
    private $parent;
0 ignored issues
show
Coding Style introduced by
Private member variable "parent" must be prefixed with an underscore
Loading history...
28
29
    /**
30
     * Create the Node object and parses $nodeString IF not null (else assume a root type Node)
31
     *
32
     * @param string|null $nodeString The node string
33
     * @param int|null    $line       The line
34
     */
35
    public function __construct($nodeString = null, $line = 0)
36
    {
37
        $this->line = (int) $line;
38
        if (is_null($nodeString)) {
39
            $this->type = Y::ROOT;
40
        } else {
41
            $this->parse($nodeString);
42
        }
43
    }
44
45
    /**
46
     * Sets the parent of the current Node
47
     *
48
     * @param Node $node The node
49
     *
50
     * @return Node|self The currentNode
51
     */
52
    public function setParent(Node $node):Node
53
    {
54
        $this->parent = $node;
55
        return $this;
56
    }
57
58
    /**
59
     * Gets the ancestor with specified $indent or the direct $parent OR the current Node itself
60
     *
61
     * @param int|null $indent The indent
62
     * @param int $type  first ancestor of this type is returned
63
     *
64
     * @return Node|self   The parent.
65
     */
66
    public function getParent(int $indent = null, $type = 0):Node
67
    {
68
        if ($this->type === Y::ROOT) {
69
            return $this;
70
        }
71
        if (!is_int($indent)) return $this->parent ?? $this;
72
        $cursor = $this;
73
        while ($cursor instanceof Node && $cursor->indent >= $indent) {
74
            if ($cursor->indent === $indent && $cursor->type !== $type) {
75
                $cursor = $cursor->parent;
76
                break;
77
            }
78
            $cursor = $cursor->parent;
79
        }
80
        return $cursor;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $cursor could return the type null which is incompatible with the type-hinted return Dallgoot\Yaml\Node. Consider adding an additional type-check to rule them out.
Loading history...
81
    }
82
83
    /**
84
     * Set the value for the current Node :
85
     * - if value is null , then value = $child (Node)
86
     * - if value is Node, then value is a NodeList with (previous value AND $child)
87
     * - if value is a NodeList, push $child into and set NodeList type accordingly
88
     *
89
     * @param Node $child The child
90
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
91
    public function add(Node $child)
92
    {
93
        if ($this->type & (Y::SCALAR|Y::QUOTED)) {
94
            $this->getParent()->add($child);
95
            return;
96
        }
97
        $child->setParent($this);
98
        $current = $this->value;
99
        if (is_null($current))
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if (...)\n{ "
Loading history...
Coding Style introduced by
There must be a single space between the closing parenthesis and the opening brace of a multi-line IF statement; found newline
Loading history...
100
{            $this->value = $child;
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 0
Loading history...
101
            return;
102
        }
103
        if ($current instanceof Node) {
104
            $this->value = new NodeList();
105
            if ($current->type & Y::LITTERALS) {
106
                $this->value->type = $current->type;
107
            } else {
108
                $this->value->push($current);
109
            }
110
        }
111
        //modify type according to child
112
        if (is_null($this->value->type)) {
113
            if ($child->type & Y::SET_KEY)   $this->value->type = Y::SET;
114
            if ($child->type & Y::KEY)       $this->value->type = Y::MAPPING;
115
            if ($child->type & Y::ITEM)      $this->value->type = Y::SEQUENCE;
116
        }
117
        $this->value->push($child);
0 ignored issues
show
Bug introduced by
The method push() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

117
        $this->value->/** @scrutinizer ignore-call */ 
118
                      push($child);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method push() does not exist on Dallgoot\Yaml\Node. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

117
        $this->value->/** @scrutinizer ignore-call */ 
118
                      push($child);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
118
119
        if ($this->type & Y::LITTERALS)  $this->value->type = $this->type;
120
    }
121
122
    /**
123
     * Gets the deepest node.
124
     *
125
     * @return Node|self  The deepest node.
126
     */
127
    public function getDeepestNode():Node
128
    {
129
        $cursor = $this;
130
        while ($cursor->value instanceof Node || $cursor->value instanceof NodeList) {
131
            if ($cursor->value instanceof NodeList) {
132
                if ($cursor->value->count() === 1) {
133
                    $cursor = $cursor->value->OffsetGet(0);
134
                } else {
135
                    $cursor = $cursor;
136
                    break;
137
                }
138
            } else {
139
                $cursor = $cursor->value;
140
            }
141
        }
142
        return $cursor;
143
    }
144
145
    /**
146
     * Parses the string (assumed to be a line from a valid YAML)
147
     *
148
     * @param string $nodeString The node string
149
     *
150
     * @return Node|self
151
     */
152
    public function parse(string $nodeString):Node
153
    {
154
        $nodeValue = preg_replace("/^\t+/m", " ", $nodeString); //permissive to tabs but replacement
155
        $this->indent = strspn($nodeValue, ' ');
156
        $nodeValue = ltrim($nodeValue);
157
        if ($nodeValue === '') {
158
            $this->type = Y::BLANK;
159
        } elseif (substr($nodeValue, 0, 3) === '...') {//TODO: can have something on same line ?
160
            $this->type = Y::DOC_END;
161
        } elseif (preg_match(R::KEY, $nodeValue, $matches)) {
162
            $this->onKey($matches);
163
        } else {
164
            $this->identify($nodeValue);
165
        }
166
        return $this;
167
    }
168
169
    /**
170
     *  Set the type and value according to first character
171
     *
172
     * @param string $nodeValue The node value
173
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
174
    private function identify($nodeValue)
0 ignored issues
show
Coding Style introduced by
Private method name "Node::identify" must be prefixed with an underscore
Loading history...
175
    {
176
        $v = substr($nodeValue, 1);
177
        $first = $nodeValue[0];
178
        if (in_array($first, ['"', "'"])) {
179
            $this->type = R::isProperlyQuoted($nodeValue) ? Y::QUOTED : Y::PARTIAL;
180
            $this->value = $nodeValue;
181
            return;
182
        }
183
        if (in_array($first, ['{', '['])) {
184
             $this->onCompact(trim($nodeValue));
185
             return;
186
         }
0 ignored issues
show
Coding Style introduced by
Closing brace indented incorrectly; expected 8 spaces, found 9
Loading history...
187
        if (in_array($first, ['!', '&', '*'])) {
188
            $this->onNodeAction($nodeValue);
189
            return;
190
        }
191
        // Note : php don't like '?' as an array key -_-'
192
        if(in_array($first, ['?', ':'])) {
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
193
            $this->type = $first === '?' ? Y::SET_KEY : Y::SET_VALUE;
194
            if (!empty(trim($v))) {
195
                $this->value = new NodeList;
196
                $this->add((new Node(ltrim($v), $this->line))->setParent($this));
197
            }
198
            return;
199
        }
200
        if ($first === "-") {
201
            $this->onHyphen($nodeValue);
202
            return;
203
        }
204
        $characters = [ '#' =>  [Y::COMMENT, ltrim($v)],
205
                        '%' =>  [Y::DIRECTIVE, ltrim($v)],
206
                        '>' =>  [Y::LITT_FOLDED, null],
207
                        '|' =>  [Y::LITT, null]
208
                        ];
209
        if (isset($characters[$first])) {
210
            $this->type  = $characters[$first][0];
211
            $this->value = $characters[$first][1];
212
        } else {
213
            $this->type  = Y::SCALAR;
214
            $this->value = $nodeValue;
215
        }
216
    }
217
218
    /**
219
     * Process when a "key: value" syntax is found in the parsed string
220
     * Note : key is match 1, value is match 2 as per regex from R::KEY
221
     *
222
     * @param array $matches The matches provided by 'preg_match' function in Node::parse
223
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
224
    private function onKey(array $matches)
0 ignored issues
show
Coding Style introduced by
Private method name "Node::onKey" must be prefixed with an underscore
Loading history...
225
    {
226
        $this->type = Y::KEY;
227
        $this->identifier = trim($matches[1], '"\' ');
228
        $value = $matches[2] ? trim($matches[2]) : null;
229
        if (!empty($value)) {
230
            $hasComment = strpos($value, ' #');
231
            if (is_bool($hasComment)) {
1 ignored issue
show
introduced by
The condition is_bool($hasComment) is always false.
Loading history...
232
                $n = new Node($value, $this->line);
233
            } else {
234
                $n = new Node(trim(substr($value, 0, $hasComment)), $this->line);
235
                if ($n->type !== Y::PARTIAL) {
236
                    $comment = new Node(trim(substr($value, $hasComment + 1)), $this->line);
237
                    $comment->identifier = true; //to specify it is NOT a fullline comment
238
                    $this->add($comment);
239
                    // var_dump($n, $comment);
240
                }
241
            }
242
            $n->indent = $this->indent + strlen($this->identifier);
243
            $this->add($n);
244
        }
245
    }
246
247
    /**
248
     * Determines the correct type and value when a compact object/array syntax is found
249
     *
250
     * @param string $value The value assumed to start with { or [ or characters
251
     *
252
     * @see Node::identify
253
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
254
    private function onCompact($value)
0 ignored issues
show
Coding Style introduced by
Private method name "Node::onCompact" must be prefixed with an underscore
Loading history...
255
    {
256
        $this->value = json_decode($value, false, 512, JSON_PARTIAL_OUTPUT_ON_ERROR|JSON_UNESCAPED_SLASHES);
257
        if (json_last_error() === JSON_ERROR_NONE){
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if (...){\n"
Loading history...
Coding Style introduced by
There must be a single space between the closing parenthesis and the opening brace of a multi-line IF statement; found 0 spaces
Loading history...
258
            $this->type = Y::JSON;
259
            return;
260
        }
261
        $this->value = new NodeList();
262
        if (preg_match(R::MAPPING, $value)){
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if (...){\n"
Loading history...
Coding Style introduced by
There must be a single space between the closing parenthesis and the opening brace of a multi-line IF statement; found 0 spaces
Loading history...
263
            $this->type = Y::COMPACT_MAPPING;
264
            $this->value->type = Y::COMPACT_MAPPING;
265
            $count = preg_match_all(R::MAPPING_VALUES, trim(substr($value, 1,-1)), $matches);
0 ignored issues
show
Unused Code introduced by
The assignment to $count is dead and can be removed.
Loading history...
266
            foreach ($matches['k'] as $index => $property) {
267
                $n = new Node('', $this->line);
268
                $n->type = Y::KEY;
269
                $n->identifier = trim($property, '"\' ');//TODO : maybe check for proper quoting first ?
270
                $n->value = new Node($matches['v'][$index], $this->line);
271
                $this->value->push($n);
272
            }
273
            return;
274
        }
275
        if (preg_match(R::SEQUENCE, $value)){
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if (...){\n"
Loading history...
Coding Style introduced by
There must be a single space between the closing parenthesis and the opening brace of a multi-line IF statement; found 0 spaces
Loading history...
276
            $this->type = Y::COMPACT_SEQUENCE;
277
            $this->value->type = Y::COMPACT_SEQUENCE;
278
            $count = preg_match_all(R::SEQUENCE_VALUES, trim(substr($value, 1,-1)), $matches);
279
            foreach ($matches['item'] as $key => $item) {
280
                $i = new Node('', $this->line);
281
                $i->type = Y::ITEM;
282
                $i->add(new Node($item, $this->line));
283
                $this->value->push($i);
284
            }
285
            return;
286
        }
287
        $this->value = $value;
288
        $this->type  = Y::PARTIAL;
289
    }
290
291
    /**
292
     * Determines type and value when an hyphen "-" is found
293
     *
294
     * @param string $nodeValue The node value
295
     *
296
     * @see Node::identify
297
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
298
    private function onHyphen($nodeValue)
0 ignored issues
show
Coding Style introduced by
Private method name "Node::onHyphen" must be prefixed with an underscore
Loading history...
299
    {
300
        if (substr($nodeValue, 0, 3) === '---') {
301
            $this->type = Y::DOC_START;
302
            $rest = trim(substr($nodeValue, 3));
303
            if (empty($rest)) {
304
                return;
305
            }
306
            $n = new Node($rest, $this->line);
307
            $n->indent = $this->indent + 4;
308
            $this->value = $n->setParent($this);
309
            return;
310
        }
311
        if (preg_match(R::ITEM, $nodeValue, $matches)) {
312
            $this->type = Y::ITEM;
313
            if (isset($matches[1]) && !empty(trim($matches[1]))) {
314
                $n = new Node(trim($matches[1]), $this->line);
315
                $n->indent = $this->indent + 2;
316
                $this->value = $n->setParent($this);
317
            }
318
            return;
319
        }
320
        list($this->type, $this->value) = [Y::SCALAR, $nodeValue];
321
    }
322
323
    /**
324
     * Determines the type and value according to $nodeValue when one of these characters is found : !,&,*
325
     *
326
     * @param string $nodeValue The node value
327
     *
328
     * @see  Node::identify
329
     * @todo handle tags like  <tag:clarkevans.com,2002:invoice>
330
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
331
    private function onNodeAction($nodeValue)
0 ignored issues
show
Coding Style introduced by
Private method name "Node::onNodeAction" must be prefixed with an underscore
Loading history...
332
    {
333
        $v = substr($nodeValue, 1);
334
        $this->type = ['!' => Y::TAG, '&' => Y::REF_DEF, '*' => Y::REF_CALL][$nodeValue[0]];
335
        $this->identifier = $v;
336
        $pos = strpos($v, ' ');
337
        // $this->value = new NodeList;
338
        if ($this->type & (Y::TAG|Y::REF_DEF) && is_int($pos)) {
339
            $this->identifier = strstr($v, ' ', true);
340
            $value = trim(substr($nodeValue, $pos + 1));
341
            $value = R::isProperlyQuoted($value) ? trim($value, "\"'") : $value;
342
            $this->add((new Node($value, $this->line))->setParent($this));
343
        }
344
    }
345
346
    /**
347
     * PHP internal function for debugging purpose : simplify output provided by 'var_dump'
348
     *
349
     * @return array  the Node properties and respective values displayed by 'var_dump'
350
     */
351
    public function __debugInfo():array
352
    {
353
        return ['line'  => $this->line,
354
                'indent'=> $this->indent,
355
                'type'  => Y::getName($this->type).($this->identifier ? "($this->identifier)" : ''),
356
                'value' => $this->value];
357
    }
358
}
359