Passed
Branch master (f496ba)
by stéphane
02:11
created

Builder.php (5 issues)

1
<?php
2
3
namespace Dallgoot\Yaml;
4
5
use Dallgoot\Yaml\{Node as Node, Types as T, YamlObject, Tag, Builder};
6
use \SplDoublyLinkedList as DLL;
7
8
/**
9
 *
10
 */
11
class Builder
12
{
13
    private static $root;
14
    private static $debug;
15
16
    const ERROR_NO_KEYNAME = self::class.": key has NO NAME on line %d";
17
    const INVALID_DOCUMENT = self::class.": DOCUMENT %d can NOT be a mapping AND a sequence";
18
19
20
    private static function build(object $node, &$parent = null)
21
    {
22
        if ($node instanceof DLL) return self::buildDLL($node, $parent);
23
        return self::buildNode($node, $parent);
24
    }
25
26
    private static function buildDLL(DLL $node, &$parent)
27
    {
28
        $type = property_exists($node, "type") ? $node->type : null;
29
        if (in_array($type, [T::RAW, T::LITTERAL, T::LITTERAL_FOLDED])) {
30
            return self::litteral($node, $type);
31
        }
32
        $p = $parent;
33
        switch ($type) {
34
            case T::MAPPING: //fall through
35
            case T::SET:      $p = new \StdClass;break;
36
            case T::SEQUENCE: $p = [];break;
37
            // case T::KEY: $p = $parent;break;
38
        }
39
        $out = null;
40
        foreach ($node as $child) {
41
            $result = self::build($child, $p);
42
            if (!is_null($result)) {
43
                if (is_string($result)) {
44
                    $out .= $result.' ';
45
                } else {
46
                    return $result;
47
                }
48
            }
49
        }
50
        return is_null($out) ? $p : rtrim($out);
51
    }
52
53
    private static function buildNode(Node $node, &$parent)
54
    {
55
        list($line, $type, $value) = [$node->line, $node->type, $node->value];
56
        $name  = property_exists($node, "name") ? $node->name : null;
57
        switch ($type) {
58
            case T::COMMENT: self::$root->addComment($line, $value);return;
59
            case T::DIRECTIVE: return;//TODO
60
            case T::ITEM: self::buildItem($value, $parent);return;
61
            case T::KEY:  self::buildKey($node, $parent);return;
62
            case T::REF_DEF: //fall through
63
            case T::REF_CALL:
64
                $tmp = is_object($value) ? self::build($value, $parent) : $node->getPhpValue();
65
                if ($type === T::REF_DEF) self::$root->addReference($name, $tmp);
66
                return self::$root->getReference($name);
67
            case T::SET_KEY:
68
                $key = json_encode(self::build($value, $parent), JSON_PARTIAL_OUTPUT_ON_ERROR|JSON_UNESCAPED_SLASHES);
69
                if (empty($key))
70
                    throw new \Exception("Cant serialize complex key: ".var_export($value, true), 1);
71
                $parent->{$key} = null;
72
                return;
73
            case T::SET_VALUE:
74
                $prop = array_keys(get_object_vars($parent));
75
                $key = end($prop);
76
                if (property_exists($value, "type") && in_array($value->type, [T::ITEM, T::MAPPING])) {
77
                    $p = $value->type === T::ITEM  ? [] : new \StdClass;
78
                    self::build($value, $p);
79
                } else {
80
                    $p = self::build($value, $parent->{$key});
81
                }
82
                $parent->{$key} = $p;
83
                return;
84
            case T::TAG:
85
                if ($parent === self::$root) {
86
                    $parent->addTag($name);return;
87
                } else {
88
                    if (in_array($name, ['!binary', '!str'])) {
89
                        if (is_object($value->value)) $value->value->type = T::RAW;
0 ignored issues
show
The property value does not seem to exist on SplDoublyLinkedList.
Loading history...
The property type does not seem to exist on SplDoublyLinkedList.
Loading history...
90
                        else $value->type = T::RAW;
91
                    }
92
                    $val = is_null($value) ? null : self::build($value, $node);
93
                    return new Tag($name, $val);
94
                }
95
            default:
96
                return is_object($value) ? self::build($value, $parent) : $node->getPhpValue();
97
        }
98
    }
99
100
    /**
101
     * Builds a key and set the property + value to the parent given
102
     *
103
     * @param      Node   $node    The node
104
     * @param      object|array  $parent  The parent
105
     * @throws \ParseError if Key has no name
106
     */
107
    private static function buildKey($node, &$parent):void
108
    {
109
        list($name, $value) = [$node->name, $node->value];
110
        if (is_null($name)) {
111
            throw new \ParseError(sprintf(self::ERROR_NO_KEYNAME, $node->line));
112
        } else {
113
            if ($value instanceof Node && in_array($value->type, [T::KEY, T::ITEM])) {
114
                $parent->{$name} = $value->type === T::KEY ? new \StdClass : [];
115
                self::build($value, $parent->{$name});
116
            } elseif (is_object($value)) {
117
                $parent->{$name} = self::build($value, $parent->{$name});
0 ignored issues
show
Are you sure the assignment to $parent->$name is correct as self::build($value, $parent->$name) targeting Dallgoot\Yaml\Builder::build() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
118
            } else {
119
                $parent->{$name} = $node->getPhpValue();
120
            }
121
        }
122
    }
123
124
    private static function buildItem($value, &$parent):void
125
    {
126
        if ($value instanceof Node && $value->type === T::KEY) {
127
            $parent[$value->name] = self::build($value->value, $parent[$value->name]);
0 ignored issues
show
It seems like $value->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

127
            $parent[$value->name] = self::build(/** @scrutinizer ignore-type */ $value->value, $parent[$value->name]);
Loading history...
Are you sure the assignment to $parent[$value->name] is correct as self::build($value->value, $parent[$value->name]) targeting Dallgoot\Yaml\Builder::build() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
128
        } else {
129
            $index = count($parent);
130
            $parent[$index] = self::build($value, $parent[$index]);
131
        }
132
    }
133
134
    /**
135
     * Builds a file.  check multiple documents & split if more than one documents
136
     *
137
     * @param      Node   $root   The root node
138
     * @return     array|YamlObject  list of documents or juste one.
139
     */
140
    public static function buildContent(Node $root, int $debug)
141
    {
142
        self::$debug = $debug;
143
        $totalDocStart = 0;
144
        $documents = [];
145
        if ($root->value instanceof Node) {
146
            $q = new DLL;
147
            $q->push($root->value);
148
            return [self::buildDocument($q, 0)];
149
        }
150
        $root->value->setIteratorMode(DLL::IT_MODE_DELETE);
151
        foreach ($root->value as $child) {
152
            if ($child->type === T::DOC_START) $totalDocStart++;
153
            //if 0 or 1 DOC_START = we are still in first document
154
            $currentDoc = $totalDocStart > 1 ? $totalDocStart - 1 : 0;
155
            if (!isset($documents[$currentDoc])) $documents[$currentDoc] = new DLL();
156
            $documents[$currentDoc]->push($child);
157
        }
158
        $debug >= 2 && var_dump($documents);
159
        $content = array_map([self::class, 'buildDocument'], $documents, array_keys($documents));
160
        return count($content) === 1 ? $content[0] : $content;
161
    }
162
163
    private static function buildDocument(DLL $list, int $key):YamlObject
164
    {
165
        self::$root = new YamlObject();
166
        $childTypes = self::getChildrenTypes($list);
167
        $isMapping  = count(array_intersect($childTypes, [T::KEY, T::MAPPING])) > 0;
168
        $isSequence = in_array(T::ITEM, $childTypes);
169
        $isSet      = in_array(T::SET_VALUE, $childTypes);
170
        if ($isMapping && $isSequence) {
171
            throw new \ParseError(sprintf(self::INVALID_DOCUMENT, $key));
172
        } else {
173
            switch (true) {
174
                case $isSequence: $list->type = T::SEQUENCE;break;
175
                case $isSet: $list->type = T::SET;break;
176
                case $isMapping://fall through
177
                default:$list->type = T::MAPPING;
178
            }
179
        }
180
        self::$debug >= 3 && var_dump(self::$root, $list);
181
        $string = '';
182
        foreach ($list as $child) {
183
            $result = self::build($child, self::$root);
184
            if (is_string($result)) {
185
                $string .= $result.' ';
186
            }
187
        }
188
        if (!empty($string)) {
189
            self::$root->setText(rtrim($string));
190
        }
191
        return self::$root;
192
    }
193
194
    private static function litteral(DLL $children, $type):string
195
    {
196
        $children->rewind();
197
        $refIndent = $children->current()->indent;
198
        $separator = $type === T::RAW ? '' : "\n";
199
        $action = function ($c) { return $c->value; };
200
        if ($type === T::LITTERAL_FOLDED) {
201
            $separator = ' ';
202
            $action = function ($c) use ($refIndent) {
203
                return $c->indent > $refIndent || $c->type === T::EMPTY ? "\n".$c->value : $c->value;
204
            };
205
        }
206
        $tmp = [];
207
        $children->rewind();
208
        foreach ($children as $child) {
209
            $tmp[] = $action($child);
210
        }
211
        return implode($separator, $tmp);
212
    }
213
214
    private static function getChildrenTypes(DLL $children):array
215
    {
216
        $types = [];
217
        foreach ($children as $child) {
218
            $types[] = $child->type;
219
        }
220
        return array_unique($types);
221
    }
222
}
223