| 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: | |