StaticReflectionParser::getReflectionClass()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 0
cts 2
cp 0
crap 2
1
<?php
2
3
namespace Doctrine\Common\Reflection;
4
5
use Doctrine\Common\Annotations\TokenParser;
6
use ReflectionException;
7
use const T_CLASS;
8
use const T_DOC_COMMENT;
9
use const T_EXTENDS;
10
use const T_FUNCTION;
11
use const T_NEW;
12
use const T_PAAMAYIM_NEKUDOTAYIM;
13
use const T_PRIVATE;
14
use const T_PROTECTED;
15
use const T_PUBLIC;
16
use const T_STRING;
17
use const T_USE;
18
use const T_VAR;
19
use const T_VARIABLE;
20
use function array_merge;
21
use function file_get_contents;
22
use function ltrim;
23
use function preg_match;
24
use function sprintf;
25
use function strpos;
26
use function strrpos;
27
use function strtolower;
28
use function substr;
29
30
/**
31
 * Parses a file for namespaces/use/class declarations.
32
 */
33
class StaticReflectionParser implements ReflectionProviderInterface
34
{
35
    /**
36
     * The fully qualified class name.
37
     *
38
     * @var string
39
     */
40
    protected $className;
41
42
    /**
43
     * The short class name.
44
     *
45
     * @var string
46
     */
47
    protected $shortClassName;
48
49
    /**
50
     * Whether the caller only wants class annotations.
51
     *
52
     * @var bool
53
     */
54
    protected $classAnnotationOptimize;
55
56
    /**
57
     * A ClassFinder object which finds the class.
58
     *
59
     * @var ClassFinderInterface
60
     */
61
    protected $finder;
62
63
    /**
64
     * Whether the parser has run.
65
     *
66
     * @var bool
67
     */
68
    protected $parsed = false;
69
70
    /**
71
     * The namespace of the class.
72
     *
73
     * @var string
74
     */
75
    protected $namespace = '';
76
77
    /**
78
     * The use statements of the class.
79
     *
80
     * @var string[]
81
     */
82
    protected $useStatements = [];
83
84
    /**
85
     * The docComment of the class.
86
     *
87
     * @var mixed[]
88
     */
89
    protected $docComment = [
90
        'class' => '',
91
        'property' => [],
92
        'method' => [],
93
    ];
94
95
    /**
96
     * The name of the class this class extends, if any.
97
     *
98
     * @var string
99
     */
100
    protected $parentClassName = '';
101
102
    /**
103
     * The parent PSR-0 Parser.
104
     *
105
     * @var \Doctrine\Common\Reflection\StaticReflectionParser
106
     */
107
    protected $parentStaticReflectionParser;
108
109
    /**
110
     * Parses a class residing in a PSR-0 hierarchy.
111
     *
112
     * @param string               $className               The full, namespaced class name.
113
     * @param ClassFinderInterface $finder                  A ClassFinder object which finds the class.
114
     * @param bool                 $classAnnotationOptimize Only retrieve the class docComment.
115
     *                                                         Presumes there is only one statement per line.
116
     */
117 16
    public function __construct($className, $finder, $classAnnotationOptimize = false)
118
    {
119 16
        $this->className = ltrim($className, '\\');
120 16
        $lastNsPos       = strrpos($this->className, '\\');
121
122 16
        if ($lastNsPos !== false) {
123 16
            $this->namespace      = substr($this->className, 0, $lastNsPos);
124 16
            $this->shortClassName = substr($this->className, $lastNsPos + 1);
125
        } else {
126
            $this->shortClassName = $this->className;
127
        }
128
129 16
        $this->finder                  = $finder;
130 16
        $this->classAnnotationOptimize = $classAnnotationOptimize;
131 16
    }
132
133
    /**
134
     * @return void
135
     */
136 16
    protected function parse()
137
    {
138 16
        $fileName = $this->finder->findFile($this->className);
139
140 16
        if ($this->parsed || ! $fileName) {
141
            return;
142
        }
143 16
        $this->parsed = true;
144 16
        $contents     = file_get_contents($fileName);
145 16
        if ($this->classAnnotationOptimize) {
146 8
            $regex = sprintf('/\A.*^\s*((abstract|final)\s+)?class\s+%s\s+/sm', $this->shortClassName);
147
148 8
            if (preg_match($regex, $contents, $matches)) {
149 8
                $contents = $matches[0];
150
            }
151
        }
152 16
        $tokenParser = new TokenParser($contents);
153 16
        $docComment  = '';
154 16
        $last_token  = false;
155
156 16
        while ($token = $tokenParser->next(false)) {
157 16
            switch ($token[0]) {
158 16
                case T_USE:
159 8
                    $this->useStatements = array_merge($this->useStatements, $tokenParser->parseUseStatement());
160 8
                    break;
161 16
                case T_DOC_COMMENT:
162 11
                    $docComment = $token[1];
163 11
                    break;
164 16
                case T_CLASS:
165 16
                    if ($last_token !== T_PAAMAYIM_NEKUDOTAYIM && $last_token !== T_NEW) {
166 16
                        $this->docComment['class'] = $docComment;
167 16
                        $docComment                = '';
168
                    }
169 16
                    break;
170 16
                case T_VAR:
171 16
                case T_PRIVATE:
172 16
                case T_PROTECTED:
173 16
                case T_PUBLIC:
174 7
                    $token = $tokenParser->next();
175 7
                    if ($token[0] === T_VARIABLE) {
176 5
                        $propertyName                                = substr($token[1], 1);
177 5
                        $this->docComment['property'][$propertyName] = $docComment;
178 5
                        continue 2;
179
                    }
180 2
                    if ($token[0] !== T_FUNCTION) {
181
                        // For example, it can be T_FINAL.
182 1
                        continue 2;
183
                    }
184
                    // No break.
185 16
                case T_FUNCTION:
186
                    // The next string after function is the name, but
187
                    // there can be & before the function name so find the
188
                    // string.
189 1
                    while (($token = $tokenParser->next()) && $token[0] !== T_STRING) {
190
                        continue;
191
                    }
192 1
                    $methodName                              = $token[1];
193 1
                    $this->docComment['method'][$methodName] = $docComment;
194 1
                    $docComment                              = '';
195 1
                    break;
196 16
                case T_EXTENDS:
197 4
                    $this->parentClassName = $tokenParser->parseClass();
198 4
                    $nsPos                 = strpos($this->parentClassName, '\\');
199 4
                    $fullySpecified        = false;
200 4
                    if ($nsPos === 0) {
201
                        $fullySpecified = true;
202
                    } else {
203 4
                        if ($nsPos) {
204 1
                            $prefix  = strtolower(substr($this->parentClassName, 0, $nsPos));
205 1
                            $postfix = substr($this->parentClassName, $nsPos);
206
                        } else {
207 3
                            $prefix  = strtolower($this->parentClassName);
208 3
                            $postfix = '';
209
                        }
210 4
                        foreach ($this->useStatements as $alias => $use) {
211 1
                            if ($alias !== $prefix) {
212
                                continue;
213
                            }
214
215 1
                            $this->parentClassName = '\\' . $use . $postfix;
216 1
                            $fullySpecified        = true;
217
                        }
218
                    }
219 4
                    if (! $fullySpecified) {
220 3
                        $this->parentClassName = '\\' . $this->namespace . '\\' . $this->parentClassName;
221
                    }
222 4
                    break;
223
            }
224
225 16
            $last_token = $token[0];
226
        }
227 16
    }
228
229
    /**
230
     * @return StaticReflectionParser
231
     */
232 4
    protected function getParentStaticReflectionParser()
233
    {
234 4
        if (empty($this->parentStaticReflectionParser)) {
235 4
            $this->parentStaticReflectionParser = new static($this->parentClassName, $this->finder);
236
        }
237
238 4
        return $this->parentStaticReflectionParser;
239
    }
240
241
    /**
242
     * @return string
243
     */
244 5
    public function getClassName()
245
    {
246 5
        return $this->className;
247
    }
248
249
    /**
250
     * @return string
251
     */
252
    public function getNamespaceName()
253
    {
254
        return $this->namespace;
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260
    public function getReflectionClass()
261
    {
262
        return new StaticReflectionClass($this);
263
    }
264
265
    /**
266
     * {@inheritDoc}
267
     */
268
    public function getReflectionMethod($methodName)
269
    {
270
        return new StaticReflectionMethod($this, $methodName);
271
    }
272
273
    /**
274
     * {@inheritDoc}
275
     */
276
    public function getReflectionProperty($propertyName)
277
    {
278
        return new StaticReflectionProperty($this, $propertyName);
279
    }
280
281
    /**
282
     * Gets the use statements from this file.
283
     *
284
     * @return string[]
285
     */
286
    public function getUseStatements()
287
    {
288
        $this->parse();
289
290
        return $this->useStatements;
291
    }
292
293
    /**
294
     * Gets the doc comment.
295
     *
296
     * @param string $type The type: 'class', 'property' or 'method'.
297
     * @param string $name The name of the property or method, not needed for 'class'.
298
     *
299
     * @return string The doc comment, empty string if none.
300
     */
301 6
    public function getDocComment($type = 'class', $name = '')
302
    {
303 6
        $this->parse();
304
305 6
        return $name ? $this->docComment[$type][$name] : $this->docComment[$type];
306
    }
307
308
    /**
309
     * Gets the PSR-0 parser for the declaring class.
310
     *
311
     * @param string $type The type: 'property' or 'method'.
312
     * @param string $name The name of the property or method.
313
     *
314
     * @return StaticReflectionParser A static reflection parser for the declaring class.
315
     *
316
     * @throws ReflectionException
317
     */
318 10
    public function getStaticReflectionParserForDeclaringClass($type, $name)
319
    {
320 10
        $this->parse();
321 10
        if (isset($this->docComment[$type][$name])) {
322 5
            return $this;
323
        }
324 9
        if (! empty($this->parentClassName)) {
325 4
            return $this->getParentStaticReflectionParser()->getStaticReflectionParserForDeclaringClass($type, $name);
326
        }
327 5
        throw new ReflectionException('Invalid ' . $type . ' "' . $name . '"');
328
    }
329
}
330