1: | <?php declare(strict_types=1); |
2: | |
3: | namespace PhpParser; |
4: | |
5: | class JsonDecoder |
6: | { |
7: | |
8: | private $reflectionClassCache; |
9: | |
10: | public function decode(string $json) { |
11: | $value = json_decode($json, true); |
12: | if (json_last_error()) { |
13: | throw new \RuntimeException('JSON decoding error: ' . json_last_error_msg()); |
14: | } |
15: | |
16: | return $this->decodeRecursive($value); |
17: | } |
18: | |
19: | private function decodeRecursive($value) { |
20: | if (\is_array($value)) { |
21: | if (isset($value['nodeType'])) { |
22: | if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') { |
23: | return $this->decodeComment($value); |
24: | } |
25: | return $this->decodeNode($value); |
26: | } |
27: | return $this->decodeArray($value); |
28: | } |
29: | return $value; |
30: | } |
31: | |
32: | private function decodeArray(array $array) : array { |
33: | $decodedArray = []; |
34: | foreach ($array as $key => $value) { |
35: | $decodedArray[$key] = $this->decodeRecursive($value); |
36: | } |
37: | return $decodedArray; |
38: | } |
39: | |
40: | private function decodeNode(array $value) : Node { |
41: | $nodeType = $value['nodeType']; |
42: | if (!\is_string($nodeType)) { |
43: | throw new \RuntimeException('Node type must be a string'); |
44: | } |
45: | |
46: | $reflectionClass = $this->reflectionClassFromNodeType($nodeType); |
47: | |
48: | $node = $reflectionClass->newInstanceWithoutConstructor(); |
49: | |
50: | if (isset($value['attributes'])) { |
51: | if (!\is_array($value['attributes'])) { |
52: | throw new \RuntimeException('Attributes must be an array'); |
53: | } |
54: | |
55: | $node->setAttributes($this->decodeArray($value['attributes'])); |
56: | } |
57: | |
58: | foreach ($value as $name => $subNode) { |
59: | if ($name === 'nodeType' || $name === 'attributes') { |
60: | continue; |
61: | } |
62: | |
63: | $node->$name = $this->decodeRecursive($subNode); |
64: | } |
65: | |
66: | return $node; |
67: | } |
68: | |
69: | private function decodeComment(array $value) : Comment { |
70: | $className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class; |
71: | if (!isset($value['text'])) { |
72: | throw new \RuntimeException('Comment must have text'); |
73: | } |
74: | |
75: | return new $className( |
76: | $value['text'], |
77: | $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1, |
78: | $value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1 |
79: | ); |
80: | } |
81: | |
82: | private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass { |
83: | if (!isset($this->reflectionClassCache[$nodeType])) { |
84: | $className = $this->classNameFromNodeType($nodeType); |
85: | $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className); |
86: | } |
87: | return $this->reflectionClassCache[$nodeType]; |
88: | } |
89: | |
90: | private function classNameFromNodeType(string $nodeType) : string { |
91: | $className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\'); |
92: | if (class_exists($className)) { |
93: | return $className; |
94: | } |
95: | |
96: | $className .= '_'; |
97: | if (class_exists($className)) { |
98: | return $className; |
99: | } |
100: | |
101: | throw new \RuntimeException("Unknown node type \"$nodeType\""); |
102: | } |
103: | } |
104: | |