Test Failed
Push — master ( f07b2d...569899 )
by stéphane
03:01
created

Builder::buildLitteral()   B

Complexity

Conditions 10
Paths 24

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 25
rs 7.6666
c 0
b 0
f 0
cc 10
nc 24
nop 2

How to fix   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
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
 */
14
final class Builder
15
{
16
    private static $_root;
0 ignored issues
show
Coding Style introduced by
$_root does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
17
    private static $_debug;
0 ignored issues
show
Coding Style introduced by
$_debug does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
$_root does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
32
    {
33
        self::$_debug = $_debug;
0 ignored issues
show
Coding Style introduced by
$_debug does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
34
        $totalDocStart = 0;
35
        $documents = [];
36
        $_root->value instanceof NodeList && $_root->value->setIteratorMode(NodeList::IT_MODE_DELETE);
0 ignored issues
show
Coding Style introduced by
$_root does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
37
        foreach ($_root->value as $child) {
0 ignored issues
show
Coding Style introduced by
$_root does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Bug introduced by
The expression $_root->value of type object<Dallgoot\Yaml\Nod...l\NodeList>|null|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
38
            if ($child->type & Y::DOC_START) $totalDocStart++;
39
            //if 0 or 1 DOC_START = we are still in first document
40
            $currentDoc = $totalDocStart > 1 ? $totalDocStart - 1 : 0;
41
            if (!isset($documents[$currentDoc])) $documents[$currentDoc] = new NodeList();
42
            $documents[$currentDoc]->push($child);
43
        }
44
        $content = [];
45
        foreach ($documents as $docNum => $list) {
46
            self::$_root = new YamlObject;
47
            try {
48
                $content[] = self::buildNodeList($list, self::$_root);
49
            } catch (\Exception $e) {
50
                throw new \ParseError(sprintf(self::INVALID_DOCUMENT, $docNum));
0 ignored issues
show
Unused Code introduced by
The call to ParseError::__construct() has too many arguments starting with sprintf(self::INVALID_DOCUMENT, $docNum).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
51
            }
52
        }
53
        return count($content) === 1 ? $content[0] : $content;
54
    }
55
56
    /**
57
     * Generic function to distinguish between Node and NodeList
58
     *
59
     * @param Node|NodeList $node   The node.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $node not be object?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
60
     * @param mixed         $parent The parent
61
     *
62
     * @return mixed  ( description_of_the_return_value )
63
     */
64
    private static function build(object $node, &$parent = null)
65
    {
66
        if ($node instanceof NodeList) return self::buildNodeList($node, $parent);
67
        return self::buildNode($node, $parent);
68
    }
69
70
    /**
71
     * Builds a node list.
72
     *
73
     * @param NodeList $node   The node
74
     * @param mixed    $parent The parent
75
     *
76
     * @return mixed    The parent (object|array) or a string representing the NodeList.
77
     */
78
    private static function buildNodeList(NodeList $node, &$parent=null)
79
    {
80
        $node->forceType();
81
        if ($node->type & (Y::RAW | Y::LITTERALS)) {
82
            return self::buildLitteral($node, (int) $node->type);
83
        }
84
        $action = function ($child, &$parent, &$out) {
85
            self::build($child, $out);
86
        };
87
        if ($node->type & (Y::COMPACT_MAPPING|Y::MAPPING|Y::SET)) {
88
            $out = $parent ?? new \StdClass;
89
        } elseif ($node->type & (Y::COMPACT_SEQUENCE|Y::SEQUENCE)) {
90
            $out = $parent ?? [];
91
        } else {
92
            $out = '';
93
            $action = function ($child, &$parent, &$out) {
94
                if ($child->type & (Y::SCALAR|Y::QUOTED)) {
95
                    if ($parent) {
96
                        $parent->setText(self::build($child));
97
                    } else {
98
                        $out .= self::build($child);
99
                    }
100
                }
101
            };
102
        }
103
        foreach ($node as $child) {
104
            $action($child, $parent, $out);
105
        }
106
        if ($node->type & (Y::COMPACT_SEQUENCE|Y::COMPACT_MAPPING)) {
107
            $out = new Compact($out);
108
        }
109
        return is_null($out) ? $parent : $out;
110
    }
111
112
    /**
113
     * Builds a node.
114
     *
115
     * @param Node    $node    The node of any Node->type
116
     * @param mixed  $parent  The parent
117
     *
118
     * @return mixed  The node value as scalar, array or object or null to otherwise.
119
     */
120
    private static function buildNode(Node $node, &$parent)
121
    {
122
        extract((array) $node, EXTR_REFS);
0 ignored issues
show
Bug introduced by
(array) $node cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
123
        if ($type & (Y::REF_DEF | Y::REF_CALL)) {
124
            if (is_object($value)) {
125
                $tmp = self::build($value, $parent) ?? $parent;
126
            } else {
127
                $tmp = Node2PHP::get($node);
128
            }
129
            if ($type === Y::REF_DEF) self::$_root->addReference($identifier, $tmp);
130
            return self::$_root->getReference($identifier);
131
        }
132
        if ($type & (Y::COMPACT_MAPPING|Y::COMPACT_SEQUENCE)) {
133
            return self::buildNodeList($node->value, $parent);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type null or object<Dallgoot\Yaml\Node> or string; however, Dallgoot\Yaml\Builder::buildNodeList() does only seem to accept object<Dallgoot\Yaml\NodeList>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
134
        }
135
        if ($type & Y::COMMENT) self::$_root->addComment($node->line, $node->value);
136
        $typesActions = [Y::DIRECTIVE => 'buildDirective',
137
                         Y::ITEM      => 'buildItem',
138
                         Y::KEY       => 'buildKey',
139
                         Y::SET_KEY   => 'buildSetKey',
140
                         Y::SET_VALUE => 'buildSetValue',
141
                         Y::TAG       => 'buildTag',
142
        ];
143
        if (isset($typesActions[$type])) {
144
            return self::{$typesActions[$type]}($node, $parent);
145
        }
146
        return is_object($value) ? self::build($value, $parent) : Node2PHP::get($node);
147
    }
148
149
    /**
150
     * Builds a key and set the property + value to the given parent
151
     *
152
     * @param Node $node       The node with type YAML::KEY
153
     * @param object|array $parent       The parent
0 ignored issues
show
Documentation introduced by
Should the type for parameter $parent not be object|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
154
     *
155
     * @throws \ParseError if Key has no name(identifier) Note: empty string is allowed
156
     * @return null
157
     */
158
    private static function buildKey(Node $node, &$parent=null)
159
    {
160
        extract((array) $node, EXTR_REFS);
0 ignored issues
show
Bug introduced by
(array) $node cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
161
        if (is_null($identifier)) {
162
            throw new \ParseError(sprintf(self::ERROR_NO_KEYNAME, $line));
0 ignored issues
show
Unused Code introduced by
The call to ParseError::__construct() has too many arguments starting with sprintf(self::ERROR_NO_KEYNAME, $line).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
163
        } else {
164
            if ($value instanceof Node) {
165
                if ($value->type & (Y::ITEM|Y::KEY)) {
166
                    $value = new NodeList($value);
167
                } else {
168
                    $result = self::build($value);
169
                }
170
            }
171
            if ($value instanceof NodeList) {
172
                $result = self::buildNodeList($value);
173
            }
174
            if (is_null($parent)) {
175
                return $result;
176
            } else {
177
                if (is_array($parent)) {
178
                    $parent[$identifier] = $result;
179
                } else {
180
                    $parent->{$identifier} = $result;
181
                }
182
            }
183
        }
184
    }
185
186
    /**
187
     * Builds an item. Adds the item value to the parent array|Iterator
188
     *
189
     * @param      Node        $node    The node with type YAML::ITEM
190
     * @param      array|\Iterator      $parent  The parent
191
     *
192
     * @throws     \Exception  if parent is another type than array or object Iterator
193
     * @return null
194
     */
195
    private static function buildItem(Node $node, &$parent)
196
    {
197
        extract((array) $node, EXTR_REFS);
0 ignored issues
show
Bug introduced by
(array) $node cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
198
        if (!is_array($parent) && !($parent instanceof \ArrayIterator)) {
199
            throw new \Exception("parent must be an Iterable not ".(is_object($parent) ? get_class($parent) : gettype($parent)), 1);
200
        }
201
        $ref = $parent instanceof \ArrayIterator ? $parent->getArrayCopy() : $parent;
202
        $numKeys = array_filter(array_keys($ref), 'is_int');
203
        $key = count($numKeys) > 0 ? max($numKeys) + 1 : 0;
204
        if ($value instanceof Node) {
205
            if($value->type & Y::KEY) {
206
                self::buildKey($node->value, $parent);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type null or object<Dallgoot\Yaml\NodeList> or string; however, Dallgoot\Yaml\Builder::buildKey() does only seem to accept object<Dallgoot\Yaml\Node>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
207
                return;
208
            } elseif ($value->type & Y::ITEM) {
209
                $a = [];
210
                $result = self::buildItem($value, $a);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
211
            }
212
        }
213
        $result = self::build($value);
214
        $parent[$key] = $result;
215
    }
216
217
218
    /**
219
     * Builds a litteral (folded or not) or any NodeList that has YAML::RAW type (like a multiline value)
220
     *
221
     * @param      NodeList  $children  The children
0 ignored issues
show
Bug introduced by
There is no parameter named $children. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
222
     * @param      integer   $type      The type
223
     *
224
     * @return     string    The litteral.
225
     * @todo : Example 6.1. Indentation Spaces  spaces must be considered as content
226
     */
227
    private static function buildLitteral(NodeList $list, int $type):string
228
    {
229
        $list->rewind();
230
        $refIndent = $list->current()->indent;
231
        //remove trailing blank
232
        while ($list->top()->type & Y::BLANK) $list->pop();
233
        $result = '';
234
        $separator = [ 0 => '', Y::LITT => "\n", Y::LITT_FOLDED => ' '][(int) $type];
235
        foreach ($list as $child) {
236
            if ($child->value instanceof NodeList) {
237
                $result .= self::buildLitteral($child->value, $type).$separator;
238
            } else {
239
                $val = $child->type & (Y::SCALAR|Y::BLANK) ? $child->value : substr($child->raw, $refIndent);
240
                if ($type & Y::LITT_FOLDED && ($child->indent > $refIndent || ($child->type & Y::BLANK))) {
241
                    if ($result[-1] === $separator)
242
                        $result[-1] = "\n";
243
                    if ($result[-1] === "\n")
244
                        $result .= $val;
245
                    continue;
246
                }
247
                $result .= $val.$separator;
248
            }
249
        }
250
        return rtrim($result);
251
    }
252
253
    /**
254
     * Builds a set key.
255
     *
256
     * @param      Node        $node    The node of type YAML::SET_KEY.
257
     * @param      object      $parent  The parent
258
     *
259
     * @throws     \Exception  if a problem occurs during serialisation (json format) of the key
260
     */
261
    private function buildSetKey(Node $node, &$parent)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
262
    {
263
        $built = is_object($node->value) ? self::build($node->value) : null;
0 ignored issues
show
Documentation introduced by
$node->value is of type object<Dallgoot\Yaml\Nod...Dallgoot\Yaml\NodeList>, but the function expects a object<Dallgoot\Yaml\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
264
        $stringKey = is_string($built) && Regex::isProperlyQuoted($built) ? trim($built, '\'" '): $built;
265
        $key = json_encode($stringKey, JSON_PARTIAL_OUTPUT_ON_ERROR|JSON_UNESCAPED_SLASHES);
266
        if (empty($key)) throw new \Exception("Cant serialize complex key: ".var_export($node->value, true), 1);
267
        $parent->{trim($key, '\'" ')} = null;
268
    }
269
270
    /**
271
     * Builds a set value.
272
     *
273
     * @param      Node    $node    The node of type YAML::SET_VALUE
274
     * @param      object  $parent  The parent (the document object or any previous object created through a mapping key)
275
     */
276
    private function buildSetValue(Node $node, &$parent)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
277
    {
278
        $prop = array_keys(get_object_vars($parent));
279
        $key = end($prop);
280
        if ($node->value->type & (Y::ITEM|Y::KEY )) {
281
            $node->value = new NodeList($node->value);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type object<Dallgoot\Yaml\NodeList> or string; however, Dallgoot\Yaml\NodeList::__construct() does only seem to accept null|object<Dallgoot\Yaml\Node>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
282
        }
283
        $parent->{$key} = self::build($node->value);
0 ignored issues
show
Documentation introduced by
$node->value is of type object<Dallgoot\Yaml\Nod...t\Yaml\NodeList>|string, but the function expects a object<Dallgoot\Yaml\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
284
    }
285
286
    /**
287
     * Builds a tag and its value (also built) and encapsulates them in a Tag object.
288
     *
289
     * @param      Node    $node    The node of type YAML::TAG
290
     * @param      mixed  $parent  The parent
291
     *
292
     * @return     Tag|null     The tag object of class Dallgoot\Yaml\Tag.
293
     */
294
    private static function buildTag(Node $node, &$parent)
295
    {
296
        $name = (string) $node->identifier;
297
        if ($parent === self::$_root && empty($node->value)) {
298
            $parent->addTag($name);
299
        } else {
300
            $target = $node->value;
301
            if ($node->value instanceof Node) {
302
                if ($node->value->type & (Y::KEY|Y::ITEM)) {
303
                    if (is_null($parent)) {
304
                        $target = new NodeList($node->value);
305
                    } else {
306
                        self::build($node->value, $parent);
0 ignored issues
show
Documentation introduced by
$node->value is of type object<Dallgoot\Yaml\Node>, but the function expects a object<Dallgoot\Yaml\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
307
                    }
308
                }
309
            }
310
            //TODO: have somewhere a list of common tags and their treatment
311
            // if (in_array($node->identifier, ['!binary', '!str'])) {
312
            //     $target->type = Y::RAW;
313
            // }
314
            return new Tag($name, is_object($target) ? self::build($target) : null);
0 ignored issues
show
Documentation introduced by
$target is of type object<Dallgoot\Yaml\Nod...Dallgoot\Yaml\NodeList>, but the function expects a object<Dallgoot\Yaml\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
315
        }
316
    }
317
318
    /**
319
     * Builds a directive. NOT IMPLEMENTED YET
320
     *
321
     * @param      Node  $node    The node
322
     * @param      mixed  $parent  The parent
323
     * @todo implement if requested
324
     */
325
    private function buildDirective(Node $node, $parent)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
Unused Code introduced by
The parameter $node is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $parent is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
326
    {
327
        // TODO : implement
328
    }
329
}
330