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

Parser::parseSimple()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 8.0052

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 22
cts 23
cp 0.9565
rs 8.1155
c 0
b 0
f 0
cc 8
nc 7
nop 2
crap 8.0052
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
0 ignored issues
show
Bug introduced by
There is no parameter named $tokenList. 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...
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
0 ignored issues
show
Bug introduced by
There is no parameter named $tokenList. 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...
245
     * @param int $tokenOffset Tracks offset when iterating through token list.
0 ignored issues
show
Bug introduced by
There is no parameter named $tokenOffset. 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...
246
     * @return Type
0 ignored issues
show
Documentation introduced by
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
}