Completed
Pull Request — master (#344)
by Soner
02:33
created

TokenParser::parseUseStatement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 2
eloc 1
c 3
b 1
f 0
nc 2
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Doctrine\Common\Annotations;
4
5
/**
6
 * Parses a file for namespaces/use/class declarations.
7
 *
8
 * @author Fabien Potencier <[email protected]>
9
 * @author Christian Kaps <[email protected]>
10
 */
11
class TokenParser
12
{
13
    /**
14
     * The token list.
15
     *
16
     * @var array
17
     */
18
    private $tokens;
19
20
    /**
21
     * The number of tokens.
22
     *
23
     * @var int
24
     */
25
    private $numTokens;
26
27
    /**
28
     * The current array pointer.
29
     *
30
     * @var int
31
     */
32
    private $pointer = 0;
33
34
    /**
35
     * @param string $contents
36
     */
37
    public function __construct($contents)
38
    {
39
        $this->tokens = token_get_all($contents);
40
41
        // The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
42
        // saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
43
        // doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
44
        // docblock. If the first thing in the file is a class without a doc block this would cause calls to
45
        // getDocBlock() on said class to return our long lost doc_comment. Argh.
46
        // To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
47
        // it's harmless to us.
48
        token_get_all("<?php\n/**\n *\n */");
49
50
        $this->numTokens = count($this->tokens);
51
    }
52
53
    /**
54
     * Gets the next non whitespace and non comment token.
55
     *
56
     * @param boolean $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
57
     *                                     If FALSE then only whitespace and normal comments are skipped.
58
     *
59
     * @return array|null The token if exists, null otherwise.
60
     */
61
    public function next($docCommentIsComment = TRUE)
62
    {
63
        for ($i = $this->pointer; $i < $this->numTokens; $i++) {
64
            $this->pointer++;
65
            if ($this->tokens[$i][0] === T_WHITESPACE ||
66
                $this->tokens[$i][0] === T_COMMENT ||
67
                ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) {
68
69
                continue;
70
            }
71
72
            return $this->tokens[$i];
73
        }
74
75
        return null;
76
    }
77
78
    /**
79
     * Parses a single use statement.
80
     *
81
     * @return array A list with all found class names for a use statement.
82
     */
83
    public function parseUseStatement()
84
    {
85
        return \PHP_VERSION_ID >=80000 ? $this->parseUseStatementV8() : $this->parseUseStatementV7();
86
    }
87
88
    private function parseUseStatementV7(): array
89
    {
90
        $groupRoot = '';
91
        $class = '';
92
        $alias = '';
93
        $statements = [];
94
        $explicitAlias = false;
95
        while (($token = $this->next())) {
96
            $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
97
            if (!$explicitAlias && $isNameToken) {
98
                $class .= $token[1];
99
                $alias = $token[1];
100
            } else if ($explicitAlias && $isNameToken) {
101
                $alias .= $token[1];
102
            } else if ($token[0] === T_AS) {
103
                $explicitAlias = true;
104
                $alias = '';
105
            } else if ($token === ',') {
106
                $statements[strtolower($alias)] = $groupRoot . $class;
107
                $class = '';
108
                $alias = '';
109
                $explicitAlias = false;
110
            } else if ($token === ';') {
111
                $statements[strtolower($alias)] = $groupRoot . $class;
112
                break;
113
            } else if ($token === '{' ) {
114
                $groupRoot = $class;
115
                $class = '';
116
            } else if ($token === '}' ) {
117
                continue;
118
            } else {
119
                break;
120
            }
121
        }
122
123
        return $statements;
124
    }
125
126
    private function parseUseStatementV8(): array
127
    {
128
        $groupRoot = '';
129
        $class = '';
130
        $alias = '';
131
        $statements = [];
132
        $explicitAlias = false;
133
        while (($token = $this->next())) {
134
            $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR;
135
136
            if ($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED) {
0 ignored issues
show
Bug introduced by
The constant Doctrine\Common\Annotations\T_NAME_QUALIFIED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant Doctrine\Common\Annotations\T_NAME_FULLY_QUALIFIED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
137
                $class .= $token[1];
138
139
                $classSplit = explode('\\', $token[1]);
140
                $alias = $classSplit[count($classSplit) - 1];
141
            } else if($token[0] === T_NS_SEPARATOR) {
142
                $class .= '\\';
143
            } else if ($token[0] === T_AS) {
144
                $explicitAlias = true;
145
                $alias = '';
146
            } else if ($isNameToken && !$explicitAlias) {
147
                $class = $token[1];
148
                $alias = $token[1];
149
            } else if ($isNameToken && $explicitAlias) {
150
                $alias = $token[1];
151
            } else if ($token === ',') {
152
                $statements[strtolower($alias)] = $groupRoot . $class;
153
                $class = '';
154
                $alias = '';
155
                $explicitAlias = false;
156
            } else if ($token === ';') {
157
                $statements[strtolower($alias)] = $groupRoot . $class;
158
                break;
159
            } else if ($token === '{' ) {
160
                $groupRoot = $class;
161
                $class = '';
162
            } else if ($token === '}' ) {
163
                continue;
164
            } else {
165
                break;
166
            }
167
        }
168
169
        return $statements;
170
    }
171
172
    /**
173
     * Gets all use statements.
174
     *
175
     * @param string $namespaceName The namespace name of the reflected class.
176
     *
177
     * @return array A list with all found use statements.
178
     */
179
    public function parseUseStatements($namespaceName)
180
    {
181
        $statements = [];
182
        while (($token = $this->next())) {
183
            if ($token[0] === T_USE) {
184
                $statements = array_merge($statements, $this->parseUseStatement());
185
                continue;
186
            }
187
            if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) {
188
                continue;
189
            }
190
191
            // Get fresh array for new namespace. This is to prevent the parser to collect the use statements
192
            // for a previous namespace with the same name. This is the case if a namespace is defined twice
193
            // or if a namespace with the same name is commented out.
194
            $statements = [];
195
        }
196
197
        return $statements;
198
    }
199
200
    /**
201
     * Gets the namespace.
202
     *
203
     * @return string The found namespace.
204
     */
205
    public function parseNamespace()
206
    {
207
        $name = '';
208
        while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || $token[0] === T_NAME_QUALIFIED)) {
0 ignored issues
show
Bug introduced by
The constant Doctrine\Common\Annotations\T_NAME_QUALIFIED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
209
            $name .= $token[1];
210
        }
211
212
        return $name;
213
    }
214
215
    /**
216
     * Gets the class name.
217
     *
218
     * @return string The found class name.
219
     */
220
    public function parseClass()
221
    {
222
        // Namespaces and class names are tokenized the same: T_STRINGs
223
        // separated by T_NS_SEPARATOR so we can use one function to provide
224
        // both.
225
        return $this->parseNamespace();
226
    }
227
}
228