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

Builder::buildDocument()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
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) {
0 ignored issues
show
Bug introduced by
The type Dallgoot\Yaml\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
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
     * Builds a document. Basically a NodeList of children
70
     *
71
     * @param      NodeList     $list   The list
72
     * @param      integer      $key    The key
73
     *
74
     * @throws     \ParseError  (description)
75
     *
76
     * @return     YamlObject   The document as the separated part (by YAML::DOC_START) inside a whole YAML content
77
     */
78
    private static function buildDocument(NodeList $list, int $key):YamlObject
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildDocument" must be prefixed with an underscore
Loading history...
79
    {
80
        // $childTypes  = $list->getTypes();
81
        // $isaMapping  = (bool) (Y::KEY|Y::MAPPING) & $childTypes;
82
        // $isaSequence = (bool) Y::ITEM & $childTypes;
83
        // $isaSet      = (bool) Y::SET_VALUE & $childTypes;
84
        // if ($isaMapping && $isaSequence) {
85
        //     throw new \ParseError(sprintf(self::INVALID_DOCUMENT, $key));
86
        // } else {
87
        //     switch (true) {
88
        //         case $isaSequence: $list->type = Y::SEQUENCE;break;
89
        //         case $isaSet:      $list->type = Y::SET;break;
90
        //         default:           $list->type = Y::MAPPING;
91
        //     }
92
        // }
93
        // self::$_root = new YamlObject();
94
        // $string = '';
95
        // foreach ($list as $child) {
96
        //     $result = self::build($child, self::$_root);
97
        //     if (is_string($result)) {
98
        //         $string .= $result.' ';
99
        //     }
100
        // }
101
        // if (!empty($string)) {
102
        //     self::$_root->setText(rtrim($string));
103
        // }
104
        // if () {
105
        //     # code...
106
        // }
107
        // return self::$_root;
108
    }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Dallgoot\Yaml\YamlObject. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
109
110
    /**
111
     * Generic function to distinguish between Node and NodeList
112
     *
113
     * @param Node|NodeList $node   The node.
114
     * @param mixed         $parent The parent
115
     *
116
     * @return mixed  ( description_of_the_return_value )
117
     */
118
    private static function build(object $node, &$parent = null)
119
    {
120
        if ($node instanceof NodeList) return self::buildNodeList($node, $parent);
121
        return self::buildNode($node, $parent);
122
    }
123
124
    /**
125
     * Builds a node list.
126
     *
127
     * @param NodeList $node   The node
128
     * @param mixed    $parent The parent
129
     *
130
     * @return mixed    The parent (object|array) or a string representing the NodeList.
131
     */
132
    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...
133
    {
134
        if (is_null($node->type)) {
135
            $childTypes  = $node->getTypes();
136
            if ($childTypes & (Y::KEY|Y::SET_KEY)) {
137
                if ($childTypes & Y::ITEM) {
138
                    // TODO: replace the document index in HERE ----------v
139
                    throw new \ParseError(sprintf(self::INVALID_DOCUMENT, 0));
140
                } else {
141
                    $node->type = Y::MAPPING;
142
                }
143
            } else {
144
                if ($childTypes & Y::ITEM) {
145
                    $node->type = Y::SEQUENCE;
146
                } elseif (!($childTypes & Y::COMMENT))
0 ignored issues
show
Coding Style introduced by
Expected "} elseif (...) \n"; found " elseif (...)\n {\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...
147
                {
148
                    $node->type = Y::LITT_FOLDED;
149
                }
150
            }
151
        }
152
        // var_dump('nodetype:'.Y::getName($node->type) );
153
        if ($node->type & (Y::RAW | Y::LITTERALS)) {
154
            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

154
            return self::buildLitteral($node, /** @scrutinizer ignore-type */ $node->type);
Loading history...
155
        }
156
        if ($node->type & (Y::COMPACT_MAPPING|Y::MAPPING|Y::SET)) {
157
            $out = $parent ?? new \StdClass;//var_dump("PAS LA");
158
            foreach ($node as $key => $child) {
159
                if ($child->type & (Y::KEY)) {
160
                    self::buildKey($child, $out);
161
                } else {
162
                    self::build($child, $out);
163
                }
164
            }
165
        } elseif ($node->type & (Y::COMPACT_SEQUENCE|Y::SEQUENCE)) {
166
            $out = $parent ?? [];//var_dump("HERE");
167
            foreach ($node as $key => $child) {
168
                if ($child->type & Y::ITEM) {
169
                    self::buildItem($child, $out);
170
                } else {
171
                    self::build($child);
172
                }
173
            }
174
        } else {
175
            $tmpString = null;//var_dump("PAS ICI");
176
            foreach ($node as $key => $child) {
177
                 if ($child->type & (Y::SCALAR|Y::QUOTED)) {
178
                    if ($parent) {
179
                        $parent->setText(self::build($child, $parent));
180
                    } else {
181
                        $tmpString .= self::build($child, $parent);
182
                    }
183
                } else {
0 ignored issues
show
Coding Style introduced by
Closing brace indented incorrectly; expected 17 spaces, found 16
Loading history...
184
                    self::build($child, $parent);
185
                }
186
            }
187
            $out = is_null($tmpString) ? $parent : $tmpString;
188
        }
189
        return $out;
190
    }
191
192
    /**
193
     * Builds a node.
194
     *
195
     * @param Node    $node    The node of any Node->type
196
     * @param mixed  $parent  The parent
197
     *
198
     * @return mixed  The node value as scalar, array or object or null to otherwise.
199
     */
200
    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...
201
    {
202
        extract((array) $node, EXTR_REFS);
203
        if ($type & (Y::REF_DEF | Y::REF_CALL)) {
204
            if (is_object($value)) {
205
                $tmp = self::build($value, $parent) ?? $parent;
206
            } else {
207
                $tmp = Node2PHP::get($node);
208
            }
209
            if ($type === Y::REF_DEF) self::$_root->addReference($identifier, $tmp);
210
            return self::$_root->getReference($identifier);
211
        }
212
        if ($type & (Y::COMPACT_MAPPING|Y::COMPACT_SEQUENCE)) {
213
            return self::buildNodeList($node->value, $parent);
214
        }
215
        if ($type & Y::COMMENT) self::$_root->addComment($node->line, $node->value);
216
        $typesActions = [Y::DIRECTIVE => 'buildDirective',
217
                         Y::ITEM      => 'buildItem',
218
                         Y::KEY       => 'buildKey',
219
                         Y::SET_KEY   => 'buildSetKey',
220
                         Y::SET_VALUE => 'buildSetValue',
221
                         Y::TAG       => 'buildTag',
222
        ];
223
        if (isset($typesActions[$type])) {
224
            return self::{$typesActions[$type]}($node, $parent);
225
        }
226
        return is_object($value) ? self::build($value, $parent) : Node2PHP::get($node);
227
    }
228
229
    /**
230
     * Builds a key and set the property + value to the given parent
231
     *
232
     * @param Node $node       The node with type YAML::KEY
233
     * @param object|array $parent       The parent
234
     *
235
     * @throws \ParseError if Key has no name(identifier) Note: empty string is allowed
236
     * @return null
237
     */
238
    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...
239
    {
240
        extract((array) $node, EXTR_REFS);
241
        if (is_null($identifier)) {
242
            throw new \ParseError(sprintf(self::ERROR_NO_KEYNAME, $line));
243
        } else {
244
            if ($value instanceof Node) {
245
                if ($value->type & (Y::ITEM|Y::KEY)) {
246
                    $list = new NodeList();
247
                    $list->push($value);
248
                    $list->type = $value->type & Y::ITEM ? Y::SEQUENCE : Y::MAPPING;
249
                    $value = $list;
250
                } else {
251
                    $result = self::build($value);
252
                }
253
            }
254
            if ($value instanceof NodeList) {
255
                $childTypes = $value->getTypes();
256
                if (is_null($value->type) && $childTypes & Y::SCALAR && !($childTypes & Y::COMMENT)) {
257
                    $result = self::buildLitteral($value, Y::LITT_FOLDED);
258
                } else {
259
                    $result = self::buildNodeList($value);
260
                }
261
            }
262
            if (is_null($parent)) {
263
                return $result;
264
            } else {
265
                if (is_array($parent)) {
266
                    $parent[$identifier] = $result;
267
                } else {
268
                    $parent->{$identifier} = $result;
269
                }
270
            }
271
        }
272
    }
273
274
    /**
275
     * Builds an item. Adds the item value to the parent array|Iterator
276
     *
277
     * @param      Node        $node    The node with type YAML::ITEM
278
     * @param      array|\Iterator      $parent  The parent
279
     *
280
     * @throws     \Exception  if parent is another type than array or object Iterator
281
     * @return null
282
     */
283
    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...
284
    {
285
        extract((array) $node, EXTR_REFS);//var_dump(__METHOD__);
286
        if (!is_array($parent) && !($parent instanceof \ArrayIterator)) {
287
            throw new \Exception("parent must be an Iterable not ".(is_object($parent) ? get_class($parent) : gettype($parent)), 1);
288
        }
289
        $ref = $parent instanceof \ArrayIterator ? $parent->getArrayCopy() : $parent;
0 ignored issues
show
introduced by
$parent is never a sub-type of ArrayIterator.
Loading history...
290
        $numKeys = array_filter(array_keys($ref), 'is_int');
291
        $key = count($numKeys) > 0 ? max($numKeys) + 1 : 0;
292
        if ($value instanceof Node) {
293
            if($value->type & Y::KEY) {
0 ignored issues
show
Coding Style introduced by
Expected "if (...) {\n"; found "if(...) {\n"
Loading history...
294
                self::buildKey($node->value, $parent);
295
                return;
296
            } else if ($value->type & Y::ITEM) {
297
                $list = new NodeList();
298
                $list->push($value);
299
                $list->type = Y::SEQUENCE;
300
                $result = self::buildNodeList($list);
301
            } else {
302
                $result = self::build($value);
303
            }
304
        } elseif ($value instanceof NodeList) {
305
            $result = self::buildNodeList($value);
306
        }
307
        $parent[$key] = $result;
308
    }
309
310
311
    /**
312
     * Builds a litteral (folded or not) or any NodeList that has YAML::RAW type (like a multiline value)
313
     *
314
     * @param      NodeList  $children  The children
315
     * @param      integer   $type      The type
316
     *
317
     * @return     string    The litteral.
318
     */
319
    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...
320
    {
321
        $lines = [];
322
        $children->rewind();
323
        $refIndent = $children->current()->indent;
324
        //remove trailing blank nodes
325
        $max = $children->count() - 1;
326
        while ($children->offsetGet($max)->type & Y::BLANK) {
327
            $children->offsetUnset($max);
328
            $max = $children->count() - 1;
329
        }
330
        $children->rewind();
331
        // TODO : Example 6.1. Indentation Spaces  spaces must be considered as content
332
        foreach ($children as $child) {
333
            if ($child->value instanceof NodeList) {
334
                $lines[] = self::buildLitteral($child->value, $type);
335
            } else {
336
                $prefix = '';
337
                if ($type & Y::LITT_FOLDED && ($child->indent > $refIndent || ($child->type & Y::BLANK))) {
338
                    $prefix = "\n";
339
                }
340
                if (!($child->type & (Y::SCALAR|Y::BLANK))) {
341
                    switch ($child->type) {
342
                        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...
343
                        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...
344
                        default: //die(__METHOD__.Y::getName($child->type));
345
                    }
346
                }
347
                $val = $child->value;
348
                while (is_object($val)) {
349
                    $val = $val->value;
350
                }
351
                $lines[] = $prefix.$val;
352
            }
353
        }
354
        if ($type & Y::RAW)         return implode('',   $lines);
355
        if ($type & Y::LITT)        return implode("\n", $lines);
356
        if ($type & Y::LITT_FOLDED) return preg_replace(['/ +(\n)/','/(\n+) +/'], "$1", implode(' ',  $lines));
357
        return '';
358
    }
359
360
    /**
361
     * Builds a set key.
362
     *
363
     * @param      Node        $node    The node of type YAML::SET_KEY.
364
     * @param      object      $parent  The parent
365
     *
366
     * @throws     \Exception  if a problem occurs during serialisation (json format) of the key
367
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
368
    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...
369
    {
370
        $built = self::build($node->value);
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

370
        $built = self::build(/** @scrutinizer ignore-type */ $node->value);
Loading history...
371
        $stringKey = is_string($built) && Regex::isProperlyQuoted($built) ? trim($built, '\'" '): $built;
372
        $key = json_encode($stringKey, JSON_PARTIAL_OUTPUT_ON_ERROR|JSON_UNESCAPED_SLASHES);
373
        // if (empty($key)) throw new \Exception("Cant serialize complex key: ".var_export($node->value, true), 1);
374
        $parent->{trim($key, '\'" ')} = null;
375
    }
376
377
    /**
378
     * Builds a set value.
379
     *
380
     * @param      Node    $node    The node of type YAML::SET_VALUE
381
     * @param      object  $parent  The parent (the document object or any previous object created through a mapping key)
382
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
383
    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...
384
    {
385
        $prop = array_keys(get_object_vars($parent));
386
        $key = end($prop);
387
        if ($node->value->type & (Y::ITEM|Y::KEY|Y::SEQUENCE|Y::MAPPING)) {
388
            $p = $node->value->type === Y::ITEM ? [] : new \StdClass;
389
            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

389
            self::build(/** @scrutinizer ignore-type */ $node->value, $p);
Loading history...
390
        } else {
391
            $p = self::build($node->value, $parent->{$key});
392
        }
393
        $parent->{$key} = $p;
394
    }
395
396
    /**
397
     * Builds a tag and its value (also built) and encapsulates them in a Tag object.
398
     *
399
     * @param      Node    $node    The node of type YAML::TAG
400
     * @param      mixed  $parent  The parent
401
     *
402
     * @return     Tag|null     The tag object of class Dallgoot\Yaml\Tag.
403
     */
404
    private function buildTag(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildTag" must be prefixed with an underscore
Loading history...
405
    {
406
        if ($parent === self::$_root && empty($node->value)) {
407
            $parent->addTag($node->identifier);
408
            return;
409
        }
410
        $target = $node->value;
411
        if ($node->value instanceof Node) {
412
            if ($node->value->type & (Y::KEY|Y::ITEM)) {
413
                if (is_null($parent)) {
414
                    $target = new NodeList;
415
                    $target->push($node->value);
416
                    $target->type = $node->value->type & Y::KEY ? Y::MAPPING : Y::SEQUENCE;
417
                } else {
418
                    $node->value->type & Y::KEY ? self::buildKey($node->value, $parent) : self::buildItem($node->value, $parent);
419
                }
420
            }
421
        }
422
        //TODO: have somewhere a list of common tags and their treatment
423
        // if (in_array($node->identifier, ['!binary', '!str'])) {
424
        //     $target->type = Y::RAW;
425
        // }
426
427
        return new Tag($node->identifier, empty($target) ? null : self::build($target));
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

427
        return new Tag(/** @scrutinizer ignore-type */ $node->identifier, empty($target) ? null : self::build($target));
Loading history...
Bug introduced by
It seems like $target can also be of type 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

427
        return new Tag($node->identifier, empty($target) ? null : self::build(/** @scrutinizer ignore-type */ $target));
Loading history...
428
    }
429
430
    /**
431
     * Builds a directive. NOT IMPLEMENTED YET
432
     *
433
     * @param      Node  $node    The node
434
     * @param      mixed  $parent  The parent
435
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
436
    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...
437
    {
438
        // TODO : implement
439
    }
440
}
441