Parser::internalParse()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 5
cts 5
cp 1
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php declare(strict_types=1);
2
3
/**
4
 * @copyright   (c) 2019-present brian ridley
5
 * @author      brian ridley <[email protected]>
6
 * @license     http://opensource.org/licenses/MIT MIT
7
 */
8
9
namespace ptlis\SerializedDataEditor\Parser;
10
11
use ptlis\SerializedDataEditor\Type\ArrayType;
12
use ptlis\SerializedDataEditor\Type\BoolType;
13
use ptlis\SerializedDataEditor\Type\FloatType;
14
use ptlis\SerializedDataEditor\Type\IntegerType;
15
use ptlis\SerializedDataEditor\Type\NullType;
16
use ptlis\SerializedDataEditor\Type\ObjectCustomSerializedType;
17
use ptlis\SerializedDataEditor\Type\ObjectDefaultSerializedType;
18
use ptlis\SerializedDataEditor\Type\ReferenceType;
19
use ptlis\SerializedDataEditor\Type\StringType;
20
use ptlis\SerializedDataEditor\Type\Type;
21
use ptlis\SerializedDataEditor\TypeFragment\ArrayElementIntegerIndex;
22
use ptlis\SerializedDataEditor\TypeFragment\ArrayElementStringIndex;
23
use ptlis\SerializedDataEditor\TypeFragment\ObjectProperty;
24
25
/**
26
 * Parser that parses out a token list into an object graph representing the serialized types and data.
27
 */
28
final class Parser
29
{
30
    /**
31
     * @param Token[] $tokenList
32
     * @return Type
33
     */
34 21
    public function parse(array $tokenList): Type
35
    {
36 21
        return $this->internalParse($tokenList);
37
    }
38
39
    /**
40
     * Internal parse method that tracks the token offset while traversing the token list.
41
     *
42
     * @param Token[] $tokenList
43
     * @param int $tokenOffset Tracks offset when iterating through token list.
44
     * @return Type
45
     */
46 21
    private function internalParse(array $tokenList, int &$tokenOffset = 0): Type
47
    {
48
        // Handle simple (single token) types (e.g. string, integer, etc)
49 21
        if ($this->isSimpleType($tokenList[$tokenOffset])) {
50 20
            $type = $this->parseSimple($tokenList[$tokenOffset], $tokenOffset);
51
52
        // Handle complex types (arrays, objects)
53
        } else {
54 15
            $type = $this->parseComplex($tokenList, $tokenOffset);
55
        }
56
57 21
        return $type;
58
    }
59
60
    /**
61
     * Returns true if the token represents a simple type (consisting of a single token).
62
     */
63 21
    private function isSimpleType(Token $token): bool
64
    {
65 21
        return in_array(
66 21
            $token->getType(),
67
            [
68 21
                Token::NULL,
69
                Token::BOOL,
70
                Token::INTEGER,
71
                Token::FLOAT,
72
                Token::STRING,
73
                Token::REFERENCE
74
            ]
75
        );
76
    }
77
78
    /**
79
     * Parse a simple (single token) type.
80
     *
81
     * @param Token $token
82
     * @param int $tokenOffset Tracks offset when iterating through token list.
83
     * @return Type
84
     */
85 20
    public function parseSimple(Token $token, int &$tokenOffset): Type
86
    {
87 20
        switch ($token->getType()) {
88
            case Token::NULL:
89 1
                $type = new NullType();
90 1
                break;
91
92
            case Token::BOOL:
93 1
                $type = new BoolType('1' === $token->getValue() ? true : false);
94 1
                break;
95
96
            case Token::INTEGER:
97 1
                $type = new IntegerType(intval($token->getValue()));
98 1
                break;
99
100
            case Token::FLOAT:
101 1
                $type = new FloatType(floatval($token->getValue()));
102 1
                break;
103
104
            case Token::STRING:
105 15
                $type = new StringType($token->getValue());
106 15
                break;
107
108
            case Token::REFERENCE:
109 1
                $type = new ReferenceType(intval($token->getValue()));
110 1
                break;
111
        }
112
113 20
        $tokenOffset++;
114
115 20
        return $type;
116
    }
117
118
    /**
119
     * Parse a complex (multi token) type.
120
     *
121
     * @param Token[] $tokenList
122
     * @param int $tokenOffset Tracks offset when iterating through token list.
123
     * @return Type
124
     */
125 15
    private function parseComplex(array $tokenList, int &$tokenOffset): Type
126
    {
127 15
        switch ($tokenList[$tokenOffset]->getType()) {
128
            case Token::ARRAY_START:
129 12
                $type = $this->parseArray($tokenList, $tokenOffset);
130 12
                break;
131
132
            case Token::OBJECT_DEFAULT_NAME:
133 9
                $type = $this->parseObjectDefaultSerialization($tokenList, $tokenOffset);
134 9
                break;
135
136
            case Token::OBJECT_CUSTOM_NAME:
137 2
                $type = $this->parseObjectCustomSerialization($tokenList, $tokenOffset);
138 2
                break;
139
        }
140
141 15
        return $type;
142
    }
143
144
    /**
145
     * Parse an array type.
146
     *
147
     * @param Token[] $tokenList
148
     * @param int $tokenOffset Tracks offset when iterating through token list.
149
     * @return Type
150
     */
151 12
    private function parseArray(array $tokenList, int &$tokenOffset): Type
152
    {
153
        // Skip array open '{'
154 12
        $tokenOffset++;
155
156
        /** @var Token $indexToken */
157 12
        $indexToken = null;
158
159
        // Iterate through tokens array elements
160 12
        $arrayElementList = [];
161 12
        $arrayEnd = false;
162 12
        while (!$arrayEnd) {
163
            switch (true) {
164
                // Do nothing, we're done here
165 12
                case Token::COMPOUND_END === $tokenList[$tokenOffset]->getType():
166 12
                    $tokenOffset++;
167 12
                    $arrayEnd = true;
168 12
                    break;
169
170
                // Array index, store for use is next iteration
171 12
                case is_null($indexToken):
172 12
                    $indexToken = $tokenList[$tokenOffset];
173 12
                    $tokenOffset++;
174 12
                    break;
175
176
                // Element value, combine with index token to create array element
177 12
                case !is_null($indexToken):
178 12
                    $type = $this->internalParse($tokenList, $tokenOffset);
179
180 12
                    if (Token::INTEGER === $indexToken->getType()) {
181 11
                        $arrayElementList[] = new ArrayElementIntegerIndex(intval($indexToken->getValue()), $type);
182
                    } else {
183 2
                        $arrayElementList[] = new ArrayElementStringIndex($indexToken->getValue(), $type);
184
                    }
185
186 12
                    $indexToken = null;
187 12
                    break;
188
            }
189
        }
190
191 12
        return new ArrayType($arrayElementList);
192
    }
193
194
    /**
195
     * Parse a PHP-serialized object.
196
     *
197
     * @param Token[] $tokenList
198
     * @param int $tokenOffset Tracks offset when iterating through token list.
199
     * @return Type
200
     */
201 9
    public function parseObjectDefaultSerialization(array $tokenList, int &$tokenOffset): Type
202
    {
203 9
        $className = $tokenList[$tokenOffset]->getValue();
204
205
        // Skip object open and property count
206 9
        $tokenOffset += 2;
207
208
        /** @var Token $propertyNameToken */
209 9
        $propertyNameToken = null;
210
211
        // Iterate through tokens building object properties
212 9
        $propertyList = [];
213 9
        $objectEnd = false;
214 9
        while (!$objectEnd) {
215
            switch (true) {
216
                // Do nothing, we're done here
217 9
                case Token::COMPOUND_END === $tokenList[$tokenOffset]->getType():
218 9
                    $tokenOffset++;
219 9
                    $objectEnd = true;
220 9
                    break;
221
222
                // Property name
223 9
                case is_null($propertyNameToken):
224 9
                    $propertyNameToken = $tokenList[$tokenOffset];
225 9
                    $tokenOffset++;
226 9
                    break;
227
228
                // Property Value
229 9
                case !is_null($propertyNameToken):
230 9
                    $propertyList[] = $this->parseProperty(
231 9
                        $className,
232
                        $propertyNameToken,
233 9
                        $this->internalParse($tokenList, $tokenOffset)
234
                    );
235
236 9
                    $propertyNameToken = null;
237 9
                    break;
238
            }
239
        }
240
241 9
        return new ObjectDefaultSerializedType(
242 9
            $className,
243
            $propertyList
244
        );
245
    }
246
247
    /**
248
     * Parse a property of a PHP-serialized object.
249
     */
250 9
    private function parseProperty(
251
        string $className,
252
        Token $propertyNameToken,
253
        Type $type
254
    ): ObjectProperty {
255
256
        // Default to public property
257 9
        $visibility = ObjectProperty::PUBLIC;
258 9
        $propertyName = $propertyNameToken->getValue();
259
260
        // Split property name on NUL character
261 9
        $parts = array_values(array_filter(explode("\0", $propertyNameToken->getValue())));
262
263
        // Protected or private property
264 9
        if (count($parts) > 1) {
265 2
            $propertyName = $parts[1];
266 2
            $visibility = ObjectProperty::PROTECTED;
267
268
            // Private property
269 2
            if ($className === $parts[0]) {
270 2
                $visibility = ObjectProperty::PRIVATE;
271
            }
272
        }
273
274 9
        return new ObjectProperty(
275 9
            $visibility,
276
            $className,
277
            $propertyName,
278
            $type
279
        );
280
    }
281
282
    /**
283
     * Parse a custom serialized object.
284
     *
285
     * @param Token[] $tokenList
286
     * @param int $tokenOffset Tracks offset when iterating through token list.
287
     * @return Type
288
     */
289 2
    public function parseObjectCustomSerialization(array $tokenList, int &$tokenOffset): Type
290
    {
291
        // Read class name
292 2
        $className = $tokenList[$tokenOffset]->getValue();
293 2
        $tokenOffset++;
294
295
        // Read data
296 2
        $customSerializedData = $tokenList[$tokenOffset]->getValue();
297 2
        $tokenOffset++;
298
299
        // Skip terminator
300 2
        $tokenOffset++;
301
302 2
        return new ObjectCustomSerializedType($className, $customSerializedData);
303
    }
304
}