Completed
Push — master ( 5875b6...d65a71 )
by brian
01:43
created

src/Parser/Parser.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
final class Parser
26
{
27
    /**
28
     * @param Token[] $tokenList
29
     * @return Type
30
     */
31 15
    public function parse(array $tokenList): Type
32
    {
33 15
        return $this->internalParse($tokenList);
34
    }
35
36
    /**
37
     * @param Token[] $tokenList
38
     * @param int $tokenOffset Tracks offset when iterating through token list.
39
     * @return Type
40
     */
41 15
    private function internalParse(array $tokenList, int &$tokenOffset = 0): Type
42
    {
43
        // Handle simple types
44 15
        if ($this->isSimpleType($tokenList[$tokenOffset])) {
45 14
            $type = $this->parseSimple($tokenList[$tokenOffset], $tokenOffset);
46
47
        // Handle complex types (arrays, objects)
48
        } else {
49 9
            $type = $this->parseComplex($tokenList, $tokenOffset);
50
        }
51
52 15
        return $type;
53
    }
54
55 15
    private function isSimpleType(Token $token): bool
56
    {
57 15
        return in_array(
58 15
            $token->getType(),
59
            [
60 15
                Token::NULL,
61
                Token::BOOL,
62
                Token::INTEGER,
63
                Token::FLOAT,
64
                Token::STRING,
65
                Token::REFERENCE
66
            ]
67
        );
68
    }
69
70
    /**
71
     * Parse a simple (single token) type.
72
     *
73
     * @param Token $tokenList
74
     * @param int $tokenOffset Tracks offset when iterating through token list.
75
     * @return Type
76
     */
77 14
    public function parseSimple(Token $token, int &$tokenOffset): Type
78
    {
79 14
        switch ($token->getType()) {
80 14
            case Token::NULL:
81 1
                $type = new NullType();
82 1
                break;
83
84 13
            case Token::BOOL:
85 1
                $type = new BoolType('1' === $token->getValue() ? true : false);
86 1
                break;
87
88 12
            case Token::INTEGER:
89 1
                $type = new IntegerType(intval($token->getValue()));
90 1
                break;
91
92 11
            case Token::FLOAT:
93 1
                $type = new FloatType(floatval($token->getValue()));
94 1
                break;
95
96 10
            case Token::STRING:
97 9
                $type = new StringType($token->getValue());
98 9
                break;
99
100 1
            case Token::REFERENCE:
101 1
                $type = new ReferenceType(intval($token->getValue()));
102 1
                break;
103
104
            default:
105
                throw new \RuntimeException('Could not parse simple type "' . $token->getType() . '"');
106
        }
107
108 14
        $tokenOffset++;
109
110 14
        return $type;
111
    }
112
113
    /**
114
     * Parse a complex (multi token) type.
115
     *
116
     * @param Token[] $tokenList
117
     * @param int $tokenOffset Tracks offset when iterating through token list.
118
     * @return Type
119
     */
120 9
    private function parseComplex(array $tokenList, int &$tokenOffset): Type
121
    {
122 9
        switch ($tokenList[$tokenOffset]->getType()) {
123 9
            case Token::ARRAY_START:
124 6
                $type = $this->parseArray($tokenList, $tokenOffset);
125 6
                break;
126
127 6
            case Token::OBJECT_DEFAULT_NAME:
128 4
                $type = $this->parseObjectDefaultSerialization($tokenList, $tokenOffset);
129 4
                break;
130
131 2
            case Token::OBJECT_CUSTOM_NAME:
132 2
                $type = $this->parseObjectCustomSerialization($tokenList, $tokenOffset);
133 2
                break;
134
135
            default:
136
                throw new \RuntimeException('Could not parse complex type "' . $tokenList[$tokenOffset]->getType() . '"');
137
        }
138
139 9
        return $type;
140
    }
141
142
    /**
143
     * Parse an array type.
144
     *
145
     * @param Token[] $tokenList
146
     * @param int $tokenOffset Tracks offset when iterating through token list.
147
     * @return Type
148
     */
149 6
    private function parseArray(array $tokenList, int &$tokenOffset): Type
150
    {
151
        // Skip array open
152 6
        $tokenOffset++;
153
154
        /** @var Token $indexToken */
155 6
        $indexToken = null;
156
157
        // Iterate through tokens array elements
158 6
        $arrayElementList = [];
159 6
        while ($tokenOffset < count($tokenList)) {
160
            switch (true) {
161
                // Do nothing, we're done here
162 6
                case Token::COMPOUND_END === $tokenList[$tokenOffset]->getType():
163 5
                    $tokenOffset++;
164 5
                    break;
165
166
                // Array index
167 6
                case is_null($indexToken):
168 6
                    $indexToken = $tokenList[$tokenOffset];
169 6
                    $tokenOffset++;
170 6
                    break;
171
172
                // Element value
173 6
                case !is_null($indexToken):
174 6
                    $type = $this->internalParse($tokenList, $tokenOffset);
175
176 6
                    if (Token::INTEGER === $indexToken->getType()) {
177 5
                        $arrayElementList[] = new ArrayElementIntegerIndex(intval($indexToken->getValue()), $type);
178
                    } else {
179 2
                        $arrayElementList[] = new ArrayElementStringIndex($indexToken->getValue(), $type);
180
                    }
181
182 6
                    $indexToken = null;
183 6
                    break;
184
            }
185
        }
186
187 6
        return new ArrayType($arrayElementList);
188
    }
189
190
    /**
191
     * Parse a PHP-serialized object.
192
     *
193
     * @param Token[] $tokenList
194
     * @param int $tokenOffset Tracks offset when iterating through token list.
195
     * @return Type
196
     */
197 4
    public function parseObjectDefaultSerialization(array $tokenList, int &$tokenOffset): Type
198
    {
199 4
        $className = $tokenList[$tokenOffset]->getValue();
200
201
        // Skip object open and property count
202 4
        $tokenOffset += 2;
203
204
        /** @var Token $propertyNameToken */
205 4
        $propertyNameToken = null;
206
207
        // Iterate through tokens building object properties
208 4
        $propertyList = [];
209 4
        while ($tokenOffset < count($tokenList)) {
210
            switch (true) {
211
                // Do nothing, we're done here
212 4
                case Token::COMPOUND_END === $tokenList[$tokenOffset]->getType():
213 3
                    $tokenOffset++;
214 3
                    break;
215
216
                // Property name
217 4
                case is_null($propertyNameToken):
218 4
                    $propertyNameToken = $tokenList[$tokenOffset];
219 4
                    $tokenOffset++;
220 4
                    break;
221
222
                // Property Value
223 4
                case !is_null($propertyNameToken):
224 4
                    $propertyList[] = $this->parseProperty(
225 4
                        $className,
226 4
                        $propertyNameToken,
227 4
                        $this->internalParse($tokenList, $tokenOffset)
228
                    );
229
230 4
                    $propertyNameToken = null;
231 4
                    break;
232
            }
233
        }
234
235 4
        return new ObjectDefaultSerializedType(
236 4
            $className,
237 4
            $propertyList
238
        );
239
    }
240
241
    /**
242
     * Parse a property of a PHP-serialized object.
243
     *
244
     * @param Token[] $tokenList
245
     * @param int $tokenOffset Tracks offset when iterating through token list.
246
     * @return Type
0 ignored issues
show
Should the return type not be ObjectProperty?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
247
     */
248 4
    private function parseProperty(
249
        string $className,
250
        Token $propertyNameToken,
251
        Type $type
252
    ): ObjectProperty {
253
254
        // Default to public property
255 4
        $visibility = ObjectProperty::PUBLIC;
256 4
        $propertyName = $propertyNameToken->getValue();
257
258
        // Split property name on NUL character
259 4
        $parts = array_values(array_filter(explode("\0", $propertyNameToken->getValue())));
260
261
        // Protected or private property
262 4
        if (count($parts) > 1) {
263 2
            $propertyName = $parts[1];
264 2
            $visibility = ObjectProperty::PROTECTED;
265
266
            // Private property
267 2
            if ($className === $parts[0]) {
268 2
                $visibility = ObjectProperty::PRIVATE;
269
            }
270
        }
271
272 4
        return new ObjectProperty(
273 4
            $visibility,
274 4
            $className,
275 4
            $propertyName,
276 4
            $type
277
        );
278
    }
279
280
    /**
281
     * Parse a custom serialized object.
282
     *
283
     * @param Token[] $tokenList
284
     * @param int $tokenOffset Tracks offset when iterating through token list.
285
     * @return Type
286
     */
287 2
    public function parseObjectCustomSerialization(array $tokenList, int &$tokenOffset): Type
288
    {
289 2
        $className = $tokenList[$tokenOffset]->getValue();
290 2
        $tokenOffset++;
291
292 2
        $customSerializedData = $tokenList[$tokenOffset]->getValue();
293 2
        $tokenOffset++;
294
295
        // Skip end
296 2
        $tokenOffset++;
297
298 2
        return new ObjectCustomSerializedType($className, $customSerializedData);
299
    }
300
}