1 <?php declare(strict_types=1);
7 /** @var \ReflectionClass[] Node type to reflection class map */
8 private $reflectionClassCache;
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());
16 return $this->decodeRecursive($value);
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);
25 return $this->decodeNode($value);
27 return $this->decodeArray($value);
32 private function decodeArray(array $array) : array {
34 foreach ($array as $key => $value) {
35 $decodedArray[$key] = $this->decodeRecursive($value);
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');
46 $reflectionClass = $this->reflectionClassFromNodeType($nodeType);
47 /** @var Node $node */
48 $node = $reflectionClass->newInstanceWithoutConstructor();
50 if (isset($value['attributes'])) {
51 if (!\is_array($value['attributes'])) {
52 throw new \RuntimeException('Attributes must be an array');
55 $node->setAttributes($this->decodeArray($value['attributes']));
58 foreach ($value as $name => $subNode) {
59 if ($name === 'nodeType' || $name === 'attributes') {
63 $node->$name = $this->decodeRecursive($subNode);
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');
75 return new $className(
76 $value['text'], $value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1
80 private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass {
81 if (!isset($this->reflectionClassCache[$nodeType])) {
82 $className = $this->classNameFromNodeType($nodeType);
83 $this->reflectionClassCache[$nodeType] = new \ReflectionClass($className);
85 return $this->reflectionClassCache[$nodeType];
88 private function classNameFromNodeType(string $nodeType) : string {
89 $className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\');
90 if (class_exists($className)) {
95 if (class_exists($className)) {
99 throw new \RuntimeException("Unknown node type \"$nodeType\"");