XML::unserialize()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 10
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
3
namespace PhpParser\Unserializer;
4
5
use XMLReader;
6
use DomainException;
7
use PhpParser\Unserializer;
8
9
class XML implements Unserializer
10
{
11
    protected $reader;
12
13
    public function __construct() {
14
        $this->reader = new XMLReader;
15
    }
16
17
    public function unserialize($string) {
18
        $this->reader->XML($string);
19
20
        $this->reader->read();
21
        if ('AST' !== $this->reader->name) {
22
            throw new DomainException('AST root element not found');
23
        }
24
25
        return $this->read($this->reader->depth);
26
    }
27
28
    protected function read($depthLimit, $throw = true, &$nodeFound = null) {
29
        $nodeFound = true;
30
        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
31
            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
32
                continue;
33
            }
34
35
            if ('node' === $this->reader->prefix) {
36
                return $this->readNode();
37
            } elseif ('scalar' === $this->reader->prefix) {
38
                return $this->readScalar();
39
            } elseif ('comment' === $this->reader->name) {
40
                return $this->readComment();
41
            } else {
42
                throw new DomainException(sprintf('Unexpected node of type "%s"', $this->reader->name));
43
            }
44
        }
45
46
        $nodeFound = false;
47
        if ($throw) {
48
            throw new DomainException('Expected node or scalar');
49
        }
50
    }
51
52
    protected function readNode() {
53
        $className = $this->getClassNameFromType($this->reader->localName);
54
55
        // create the node without calling it's constructor
56
        $node = unserialize(
57
            sprintf(
58
                "O:%d:\"%s\":1:{s:13:\"\0*\0attributes\";a:0:{}}",
59
                strlen($className), $className
60
            )
61
        );
62
63
        $depthLimit = $this->reader->depth;
64
        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
65
            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
66
                continue;
67
            }
68
69
            $type = $this->reader->prefix;
70
            if ('subNode' !== $type && 'attribute' !== $type) {
71
                throw new DomainException(
72
                    sprintf('Expected sub node or attribute, got node of type "%s"', $this->reader->name)
73
                );
74
            }
75
76
            $name = $this->reader->localName;
77
            $value = $this->read($this->reader->depth);
78
79
            if ('subNode' === $type) {
80
                $node->$name = $value;
81
            } else {
82
                $node->setAttribute($name, $value);
83
            }
84
        }
85
86
        return $node;
87
    }
88
89
    protected function readScalar() {
90
        switch ($name = $this->reader->localName) {
91
            case 'array':
92
                $depth = $this->reader->depth;
93
                $array = array();
94
                while (true) {
95
                    $node = $this->read($depth, false, $nodeFound);
96
                    if (!$nodeFound) {
97
                        break;
98
                    }
99
                    $array[] = $node;
100
                }
101
                return $array;
102
            case 'string':
103
                return $this->reader->readString();
104
            case 'int':
105
                return $this->parseInt($this->reader->readString());
106
            case 'float':
107
                $text = $this->reader->readString();
108
                if (false === $float = filter_var($text, FILTER_VALIDATE_FLOAT)) {
109
                    throw new DomainException(sprintf('"%s" is not a valid float', $text));
110
                }
111
                return $float;
112
            case 'true':
113
            case 'false':
114
            case 'null':
115
                if (!$this->reader->isEmptyElement) {
116
                    throw new DomainException(sprintf('"%s" scalar must be empty', $name));
117
                }
118
                return constant($name);
119
            default:
120
                throw new DomainException(sprintf('Unknown scalar type "%s"', $name));
121
        }
122
    }
123
124
    private function parseInt($text) {
125
        if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) {
126
            throw new DomainException(sprintf('"%s" is not a valid integer', $text));
127
        }
128
        return $int;
129
    }
130
131
    protected function readComment() {
132
        $className = $this->reader->getAttribute('isDocComment') === 'true'
133
            ? 'PhpParser\Comment\Doc'
134
            : 'PhpParser\Comment'
135
        ;
136
        return new $className(
137
            $this->reader->readString(),
138
            $this->parseInt($this->reader->getAttribute('line'))
139
        );
140
    }
141
142
    protected function getClassNameFromType($type) {
143
        $className = 'PhpParser\\Node\\' . strtr($type, '_', '\\');
144
        if (!class_exists($className)) {
145
            $className .= '_';
146
        }
147
        if (!class_exists($className)) {
148
            throw new DomainException(sprintf('Unknown node type "%s"', $type));
149
        }
150
        return $className;
151
    }
152
}
153