| 1 |
<?php declare(strict_types=1); |
| 2 |
|
| 3 |
namespace PhpParser; |
| 4 |
|
| 5 |
class JsonDecoder |
| 6 |
{ |
| 7 |
/** @var \ReflectionClass[] Node type to reflection class map */ |
| 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 |
/** @var Node $node */ |
| 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 |
|