Passed
Push — master ( 8b4244...ea75f3 )
by stéphane
03:50
created

Builder::buildNodeList()   D

Complexity

Conditions 19
Paths 76

Size

Total Lines 63
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 44
nc 76
nop 2
dl 0
loc 63
rs 4.5166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
/**
8
 * Constructs the result (YamlObject or array) according to every Node and respecting value
9
 *
10
 * @author  Stéphane Rebai <[email protected]>
11
 * @license Apache 2.0
12
 * @link    TODO : url to specific online doc
13
 */
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...
14
final class Builder
15
{
16
    private static $_root;
17
    private static $_debug;
18
19
    const ERROR_NO_KEYNAME = self::class.": key has NO IDENTIFIER on line %d";
20
    const INVALID_DOCUMENT = self::class.": DOCUMENT %d can NOT be a mapping AND a sequence";
21
22
    /**
23
     * Builds a file.  check multiple documents & split if more than one documents
24
     *
25
     * @param   Node   $_root      The root node : Node with Node->type === YAML::ROOT
26
     * @param   int   $_debug      the level of debugging requested
27
     *
28
     * @return array|YamlObject      list of documents or just one.
29
     * @todo  implement splitting on YAML::DOC_END also
30
     */
31
    public static function buildContent(Node $_root, int $_debug)
32
    {
33
        self::$_debug = $_debug;
34
        $totalDocStart = 0;
35
        $documents = [];
36
        if ($_root->value instanceof Node) {
37
            $q = new NodeList;
38
            $q->push($_root->value);
39
            // return self::buildNodeList($q, new YamlObject);
40
            self::$_root = new YamlObject;
41
            $tmp =  self::buildNodeList($q, self::$_root);
42
            // var_dump('alone', $tmp);
43
            return $tmp;
44
        }
45
        $_root->value->setIteratorMode(NodeList::IT_MODE_DELETE);
0 ignored issues
show
Bug introduced by
The method setIteratorMode() 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

45
        $_root->value->/** @scrutinizer ignore-call */ 
46
                       setIteratorMode(NodeList::IT_MODE_DELETE);

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...
46
        foreach ($_root->value as $child) {
47
            if ($child->type & Y::DOC_START) $totalDocStart++;
48
            //if 0 or 1 DOC_START = we are still in first document
49
            $currentDoc = $totalDocStart > 1 ? $totalDocStart - 1 : 0;
50
            if (!isset($documents[$currentDoc])) $documents[$currentDoc] = new NodeList();
51
            $documents[$currentDoc]->push($child);
52
        }
53
        // $content = array_map([self::class, 'buildDocument'], $documents, array_keys($documents));
54
        $content = [];
55
        foreach ($documents as $num => $list) {
56
            try {
57
                self::$_root = new YamlObject;
58
                // $tmp = var_dump('insideforeach'.$tmp);
59
                $content[] = self::buildNodeList($list, self::$_root);
60
            } catch (\Exception $e) {
61
                throw new \ParseError(sprintf(self::INVALID_DOCUMENT, $num));
62
            }
63
        }
64
        // $content = array_map([self::class, 'buildNodeList'], $documents, array_keys($documents));
65
        return count($content) === 1 ? $content[0] : $content;
66
    }
67
68
    /**
69
     * Generic function to distinguish between Node and NodeList
70
     *
71
     * @param Node|NodeList $node   The node.
72
     * @param mixed         $parent The parent
73
     *
74
     * @return mixed  ( description_of_the_return_value )
75
     */
76
    private static function build(object $node, &$parent = null)
77
    {
78
        if ($node instanceof NodeList) return self::buildNodeList($node, $parent);
79
        return self::buildNode($node, $parent);
80
    }
81
82
    /**
83
     * Builds a node list.
84
     *
85
     * @param NodeList $node   The node
86
     * @param mixed    $parent The parent
87
     *
88
     * @return mixed    The parent (object|array) or a string representing the NodeList.
89
     */
90
    private static function buildNodeList(NodeList $node, &$parent=null)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildNodeList" must be prefixed with an underscore
Loading history...
91
    {
92
        if (is_null($node->type)) {
93
            $childTypes  = $node->getTypes();
94
            if ($childTypes & (Y::KEY|Y::SET_KEY)) {
95
                if ($childTypes & Y::ITEM) {
96
                    // TODO: replace the document index in HERE ----------v
97
                    throw new \ParseError(sprintf(self::INVALID_DOCUMENT, 0));
98
                } else {
99
                    $node->type = Y::MAPPING;
100
                }
101
            } else {
102
                if ($childTypes & Y::ITEM) {
103
                    $node->type = Y::SEQUENCE;
104
                } elseif (!($childTypes & Y::COMMENT)) {
105
                    $node->type = Y::LITT_FOLDED;
106
                }
107
            }
108
        }
109
        // var_dump('nodetype:'.Y::getName($node->type) );
110
        if ($node->type & (Y::RAW | Y::LITTERALS)) {
111
            return self::buildLitteral($node, $node->type);
0 ignored issues
show
Bug introduced by
It seems like $node->type can also be of type null; however, parameter $type of Dallgoot\Yaml\Builder::buildLitteral() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

111
            return self::buildLitteral($node, /** @scrutinizer ignore-type */ $node->type);
Loading history...
112
        }
113
        if ($node->type & (Y::COMPACT_MAPPING|Y::MAPPING|Y::SET)) {
114
            $out = $parent ?? new \StdClass;//var_dump("PAS LA");
115
            foreach ($node as $key => $child) {
116
                if ($child->type & (Y::KEY)) {
117
                    self::buildKey($child, $out);
118
                } else {
119
                    self::build($child, $out);
120
                }
121
            }
122
            if ($node->type & Y::COMPACT_MAPPING) {
123
                $out = new Compact($out);
124
            }
125
        } elseif ($node->type & (Y::COMPACT_SEQUENCE|Y::SEQUENCE)) {
126
            $out = $parent ?? [];//var_dump("HERE");
127
            foreach ($node as $key => $child) {
128
                if ($child->type & Y::ITEM) {
129
                    self::buildItem($child, $out);
130
                } else {
131
                    self::build($child);
132
                }
133
            }
134
            if ($node->type & Y::COMPACT_SEQUENCE) {
135
                $out = new Compact($out);
136
            }
137
        } else {
138
            $tmpString = null;//var_dump("PAS ICI");
139
            foreach ($node as $key => $child) {
140
                 if ($child->type & (Y::SCALAR|Y::QUOTED)) {
141
                    if ($parent) {
142
                        $parent->setText(self::build($child, $parent));
143
                    } else {
144
                        $tmpString .= self::build($child, $parent);
145
                    }
146
                } else {
0 ignored issues
show
Coding Style introduced by
Closing brace indented incorrectly; expected 17 spaces, found 16
Loading history...
147
                    self::build($child, $parent);
148
                }
149
            }
150
            $out = is_null($tmpString) ? $parent : $tmpString;
151
        }
152
        return $out;
153
    }
154
155
    /**
156
     * Builds a node.
157
     *
158
     * @param Node    $node    The node of any Node->type
159
     * @param mixed  $parent  The parent
160
     *
161
     * @return mixed  The node value as scalar, array or object or null to otherwise.
162
     */
163
    private static function buildNode(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildNode" must be prefixed with an underscore
Loading history...
164
    {
165
        extract((array) $node, EXTR_REFS);
166
        if ($type & (Y::REF_DEF | Y::REF_CALL)) {
167
            if (is_object($value)) {
168
                $tmp = self::build($value, $parent) ?? $parent;
169
            } else {
170
                $tmp = Node2PHP::get($node);
171
            }
172
            if ($type === Y::REF_DEF) self::$_root->addReference($identifier, $tmp);
173
            return self::$_root->getReference($identifier);
174
        }
175
        if ($type & (Y::COMPACT_MAPPING|Y::COMPACT_SEQUENCE)) {
176
            return self::buildNodeList($node->value, $parent);
177
        }
178
        if ($type & Y::COMMENT) self::$_root->addComment($node->line, $node->value);
179
        $typesActions = [Y::DIRECTIVE => 'buildDirective',
180
                         Y::ITEM      => 'buildItem',
181
                         Y::KEY       => 'buildKey',
182
                         Y::SET_KEY   => 'buildSetKey',
183
                         Y::SET_VALUE => 'buildSetValue',
184
                         Y::TAG       => 'buildTag',
185
        ];
186
        if (isset($typesActions[$type])) {
187
            return self::{$typesActions[$type]}($node, $parent);
188
        }
189
        return is_object($value) ? self::build($value, $parent) : Node2PHP::get($node);
190
    }
191
192
    /**
193
     * Builds a key and set the property + value to the given parent
194
     *
195
     * @param Node $node       The node with type YAML::KEY
196
     * @param object|array $parent       The parent
197
     *
198
     * @throws \ParseError if Key has no name(identifier) Note: empty string is allowed
199
     * @return null
200
     */
201
    private static function buildKey(Node $node, &$parent=null)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildKey" must be prefixed with an underscore
Loading history...
202
    {
203
        extract((array) $node, EXTR_REFS);
204
        if (is_null($identifier)) {
205
            throw new \ParseError(sprintf(self::ERROR_NO_KEYNAME, $line));
206
        } else {
207
            if ($value instanceof Node) {
208
                if ($value->type & (Y::ITEM|Y::KEY)) {
209
                    $list = new NodeList();
210
                    $list->push($value);
211
                    $list->type = $value->type & Y::ITEM ? Y::SEQUENCE : Y::MAPPING;
212
                    $value = $list;
213
                } else {
214
                    $result = self::build($value);
215
                }
216
            }
217
            if ($value instanceof NodeList) {
218
                $childTypes = $value->getTypes();
219
                if (is_null($value->type) && $childTypes & Y::SCALAR && !($childTypes & Y::COMMENT)) {
220
                    $result = self::buildLitteral($value, Y::LITT_FOLDED);
221
                } else {
222
                    $result = self::buildNodeList($value);
223
                }
224
            }
225
            if (is_null($parent)) {
226
                return $result;
227
            } else {
228
                if (is_array($parent)) {
229
                    $parent[$identifier] = $result;
230
                } else {
231
                    $parent->{$identifier} = $result;
232
                }
233
            }
234
        }
235
    }
236
237
    /**
238
     * Builds an item. Adds the item value to the parent array|Iterator
239
     *
240
     * @param      Node        $node    The node with type YAML::ITEM
241
     * @param      array|\Iterator      $parent  The parent
242
     *
243
     * @throws     \Exception  if parent is another type than array or object Iterator
244
     * @return null
245
     */
246
    private static function buildItem(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildItem" must be prefixed with an underscore
Loading history...
247
    {
248
        extract((array) $node, EXTR_REFS);//var_dump(__METHOD__);
249
        if (!is_array($parent) && !($parent instanceof \ArrayIterator)) {
250
            throw new \Exception("parent must be an Iterable not ".(is_object($parent) ? get_class($parent) : gettype($parent)), 1);
251
        }
252
        $ref = $parent instanceof \ArrayIterator ? $parent->getArrayCopy() : $parent;
0 ignored issues
show
introduced by
$parent is never a sub-type of ArrayIterator.
Loading history...
253
        $numKeys = array_filter(array_keys($ref), 'is_int');
254
        $key = count($numKeys) > 0 ? max($numKeys) + 1 : 0;
255
        if ($value instanceof Node) {
256
            if($value->type & Y::KEY) {
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
257
                self::buildKey($node->value, $parent);
258
                return;
259
            } elseif ($value->type & Y::ITEM) {
260
                $list = new NodeList();
261
                $list->push($value);
262
                $list->type = Y::SEQUENCE;
263
                $result = self::buildNodeList($list);
264
            } else {
265
                $result = self::build($value);
266
            }
267
        } elseif ($value instanceof NodeList) {
268
            $result = self::buildNodeList($value);
269
        }
270
        $parent[$key] = $result;
271
    }
272
273
274
    /**
275
     * Builds a litteral (folded or not) or any NodeList that has YAML::RAW type (like a multiline value)
276
     *
277
     * @param      NodeList  $children  The children
278
     * @param      integer   $type      The type
279
     *
280
     * @return     string    The litteral.
281
     */
282
    private static function buildLitteral(NodeList $children, int $type):string
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildLitteral" must be prefixed with an underscore
Loading history...
283
    {
284
        $lines = [];
285
        $children->rewind();
286
        $refIndent = $children->current()->indent;
287
        //remove trailing blank nodes
288
        $max = $children->count() - 1;
289
        while ($children->offsetGet($max)->type & Y::BLANK) {
290
            $children->offsetUnset($max);
291
            $max = $children->count() - 1;
292
        }
293
        $children->rewind();
294
        // TODO : Example 6.1. Indentation Spaces  spaces must be considered as content
295
        foreach ($children as $child) {
296
            if ($child->value instanceof NodeList) {
297
                $lines[] = self::buildLitteral($child->value, $type);
298
            } else {
299
                $prefix = '';
300
                if ($type & Y::LITT_FOLDED && ($child->indent > $refIndent || ($child->type & Y::BLANK))) {
301
                    $prefix = "\n";
302
                }
303
                if (!($child->type & (Y::SCALAR|Y::BLANK))) {
304
                    switch ($child->type) {
305
                        case Y::ITEM:    $child->value = '- '.$child->value;break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
306
                        case Y::COMMENT: $child->value = '# '.$child->value;break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
307
                        default: //die(__METHOD__.Y::getName($child->type));
308
                    }
309
                }
310
                $val = $child->value;
311
                while (is_object($val)) {
312
                    $val = $val->value;
313
                }
314
                $lines[] = $prefix.$val;
315
            }
316
        }
317
        if ($type & Y::RAW)         return implode('',   $lines);
318
        if ($type & Y::LITT)        return implode("\n", $lines);
319
        if ($type & Y::LITT_FOLDED) return preg_replace(['/ +(\n)/','/(\n+) +/'], "$1", implode(' ',  $lines));
320
        return '';
321
    }
322
323
    /**
324
     * Builds a set key.
325
     *
326
     * @param      Node        $node    The node of type YAML::SET_KEY.
327
     * @param      object      $parent  The parent
328
     *
329
     * @throws     \Exception  if a problem occurs during serialisation (json format) of the key
330
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
331
    private function buildSetKey(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildSetKey" must be prefixed with an underscore
Loading history...
332
    {
333
        $built = is_object($node->value) ? self::build($node->value) : null;
334
        $stringKey = is_string($built) && Regex::isProperlyQuoted($built) ? trim($built, '\'" '): $built;
335
        $key = json_encode($stringKey, JSON_PARTIAL_OUTPUT_ON_ERROR|JSON_UNESCAPED_SLASHES);
336
        // if (empty($key)) throw new \Exception("Cant serialize complex key: ".var_export($node->value, true), 1);
337
        $parent->{trim($key, '\'" ')} = null;
338
    }
339
340
    /**
341
     * Builds a set value.
342
     *
343
     * @param      Node    $node    The node of type YAML::SET_VALUE
344
     * @param      object  $parent  The parent (the document object or any previous object created through a mapping key)
345
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
346
    private function buildSetValue(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildSetValue" must be prefixed with an underscore
Loading history...
347
    {
348
        $prop = array_keys(get_object_vars($parent));
349
        $key = end($prop);
350
        if ($node->value->type & (Y::ITEM|Y::KEY|Y::SEQUENCE|Y::MAPPING)) {
351
            $p = $node->value->type === Y::ITEM ? [] : new \StdClass;
352
            self::build($node->value, $p);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type null and string; however, parameter $node of Dallgoot\Yaml\Builder::build() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

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

352
            self::build(/** @scrutinizer ignore-type */ $node->value, $p);
Loading history...
353
        } else {
354
            $p = self::build($node->value, $parent->{$key});
355
        }
356
        $parent->{$key} = $p;
357
    }
358
359
    /**
360
     * Builds a tag and its value (also built) and encapsulates them in a Tag object.
361
     *
362
     * @param      Node    $node    The node of type YAML::TAG
363
     * @param      mixed  $parent  The parent
364
     *
365
     * @return     Tag|null     The tag object of class Dallgoot\Yaml\Tag.
366
     */
367
    private static function buildTag(Node $node, &$parent)
1 ignored issue
show
Coding Style introduced by
Private method name "Builder::buildTag" must be prefixed with an underscore
Loading history...
368
    {
369
        if ($parent === self::$_root && empty($node->value)) {
370
            $parent->addTag((string) $node->identifier);
371
            return;
372
        }
373
        $target = $node->value;
374
        if ($node->value instanceof Node) {
375
            if ($node->value->type & (Y::KEY|Y::ITEM)) {
376
                if (is_null($parent)) {
377
                    $target = new NodeList;
378
                    $target->push($node->value);
379
                    $target->type = $node->value->type & Y::KEY ? Y::MAPPING : Y::SEQUENCE;
380
                } else {
381
                    $node->value->type & Y::KEY ? self::buildKey($node->value, $parent) : self::buildItem($node->value, $parent);
382
                }
383
            }
384
        }
385
        //TODO: have somewhere a list of common tags and their treatment
386
        // if (in_array($node->identifier, ['!binary', '!str'])) {
387
        //     $target->type = Y::RAW;
388
        // }
389
390
        return new Tag($node->identifier, is_object($target) ? self::build($target) : null);
0 ignored issues
show
Bug introduced by
It seems like $node->identifier can also be of type boolean and null; however, parameter $tagName of Dallgoot\Yaml\Tag::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

390
        return new Tag(/** @scrutinizer ignore-type */ $node->identifier, is_object($target) ? self::build($target) : null);
Loading history...
391
    }
392
393
    /**
394
     * Builds a directive. NOT IMPLEMENTED YET
395
     *
396
     * @param      Node  $node    The node
397
     * @param      mixed  $parent  The parent
398
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
399
    private function buildDirective(Node $node, $parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildDirective" must be prefixed with an underscore
Loading history...
400
    {
401
        // TODO : implement
402
    }
403
}
404