ReflectionEngine   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 76.39%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 11
dl 0
loc 230
ccs 55
cts 72
cp 0.7639
rs 9.76
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 1 1
A init() 0 20 1
A setMaximumCachedFiles() 0 7 2
A locateClassFile() 0 18 5
A parseClass() 0 21 4
A parseClassMethod() 0 13 4
A parseClassProperty() 0 17 5
B parseFile() 0 21 6
A parseFileNamespace() 0 16 5
1
<?php
2
/**
3
 * Parser Reflection API
4
 *
5
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace Go\ParserReflection;
12
13
use Go\ParserReflection\Instrument\PathResolver;
14
use Go\ParserReflection\NodeVisitor\RootNamespaceNormalizer;
15
use PhpParser\Lexer;
16
use PhpParser\Node;
17
use PhpParser\Node\Stmt\ClassLike;
18
use PhpParser\Node\Stmt\ClassMethod;
19
use PhpParser\Node\Stmt\Namespace_;
20
use PhpParser\Node\Stmt\Property;
21
use PhpParser\NodeTraverser;
22
use PhpParser\NodeVisitor\NameResolver;
23
use PhpParser\Parser;
24
use PhpParser\ParserFactory;
25
26
/**
27
 * AST-based reflection engine, powered by PHP-Parser
28
 */
29
class ReflectionEngine
30
{
31
    /**
32
     * @var null|LocatorInterface
33
     */
34
    protected static $locator = null;
35
36
    /**
37
     * @var array|Node[]
38
     */
39
    protected static $parsedFiles = array();
40
41
    /**
42
     * @var null|integer
43
     */
44
    protected static $maximumCachedFiles;
45
46
    /**
47
     * @var null|Parser
48
     */
49
    protected static $parser = null;
50
51
    /**
52
     * @var null|NodeTraverser
53
     */
54
    protected static $traverser = null;
55
56
    /**
57
     * @var null|Lexer
58
     */
59
    protected static $lexer = null;
60
61
    private function __construct() {}
62
63 1
    public static function init(LocatorInterface $locator)
64
    {
65 1
        self::$lexer = new Lexer(['usedAttributes' => [
66
            'comments',
67
            'startLine',
68
            'endLine',
69
            'startTokenPos',
70
            'endTokenPos',
71
            'startFilePos',
72
            'endFilePos'
73
        ]]);
74
75 1
        self::$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7, self::$lexer);
76
77 1
        self::$traverser = $traverser = new NodeTraverser();
78 1
        $traverser->addVisitor(new NameResolver());
79 1
        $traverser->addVisitor(new RootNamespaceNormalizer());
80
81 1
        self::$locator = $locator;
82 1
    }
83
84
    /**
85
     * Limits number of files, that can be cached at any given moment
86
     *
87
     * @param integer $newLimit New limit
88
     *
89
     * @return void
90
     */
91
    public static function setMaximumCachedFiles($newLimit)
92
    {
93
        self::$maximumCachedFiles = $newLimit;
94
        if (count(self::$parsedFiles) > $newLimit) {
95
            self::$parsedFiles = array_slice(self::$parsedFiles, 0, $newLimit);
96
        }
97
    }
98
99
    /**
100
     * Locates a file name for class
101
     *
102
     * @param string $fullClassName Full name of the class
103
     *
104
     * @return string
105
     */
106 20
    public static function locateClassFile($fullClassName)
107
    {
108 20
        if (class_exists($fullClassName, false)
109 3
            || interface_exists($fullClassName, false)
110 20
            || trait_exists($fullClassName, false)
111
        ) {
112 19
            $refClass      = new \ReflectionClass($fullClassName);
113 19
            $classFileName = $refClass->getFileName();
114
        } else {
115 1
            $classFileName = self::$locator->locateClass($fullClassName);
116
        }
117
118 20
        if (!$classFileName) {
119
            throw new \InvalidArgumentException("Class $fullClassName was not found by locator");
120
        }
121
122 20
        return $classFileName;
123
    }
124
125
    /**
126
     * Tries to parse a class by name using LocatorInterface
127
     *
128
     * @param string $fullClassName Class name to load
129
     *
130
     * @return ClassLike
131
     */
132 20
    public static function parseClass($fullClassName)
133
    {
134 20
        $classFileName  = self::locateClassFile($fullClassName);
135 20
        $namespaceParts = explode('\\', $fullClassName);
136 20
        $className      = array_pop($namespaceParts);
137 20
        $namespaceName  = join('\\', $namespaceParts);
138
139
        // we have a namespace node somewhere
140 20
        $namespace      = self::parseFileNamespace($classFileName, $namespaceName);
141 20
        $namespaceNodes = $namespace->stmts;
142
143 20
        foreach ($namespaceNodes as $namespaceLevelNode) {
144 20
            if ($namespaceLevelNode instanceof ClassLike && $namespaceLevelNode->name == $className) {
145 20
                $namespaceLevelNode->setAttribute('fileName', $classFileName);
146
147 20
                return $namespaceLevelNode;
148
            }
149
        }
150
151
        throw new \InvalidArgumentException("Class $fullClassName was not found in the $classFileName");
152
    }
153
154
    /**
155
     * Parses class method
156
     *
157
     * @param string $fullClassName Name of the class
158
     * @param string $methodName Name of the method
159
     *
160
     * @return ClassMethod
161
     */
162
    public static function parseClassMethod($fullClassName, $methodName)
163
    {
164
        $class      = self::parseClass($fullClassName);
165
        $classNodes = $class->stmts;
166
167
        foreach ($classNodes as $classLevelNode) {
168
            if ($classLevelNode instanceof ClassMethod && $classLevelNode->name->toString() == $methodName) {
169
                return $classLevelNode;
170
            }
171
        }
172
173
        throw new \InvalidArgumentException("Method $methodName was not found in the $fullClassName");
174
    }
175
176
    /**
177
     * Parses class property
178
     *
179
     * @param string $fullClassName Name of the class
180
     * @param string $propertyName Name of the property
181
     *
182
     * @return array Pair of [Property and PropertyProperty] nodes
183
     */
184 1
    public static function parseClassProperty($fullClassName, $propertyName)
185
    {
186 1
        $class      = self::parseClass($fullClassName);
187 1
        $classNodes = $class->stmts;
188
189 1
        foreach ($classNodes as $classLevelNode) {
190 1
            if ($classLevelNode instanceof Property) {
191 1
                foreach ($classLevelNode->props as $classProperty) {
192 1
                    if ($classProperty->name->toString() == $propertyName) {
193 1
                        return [$classLevelNode, $classProperty];
194
                    }
195
                }
196
            }
197
        }
198
199
        throw new \InvalidArgumentException("Property $propertyName was not found in the $fullClassName");
200
    }
201
202
    /**
203
     * Parses a file and returns an AST for it
204
     *
205
     * @param string      $fileName Name of the file
206
     * @param string|null $fileContent Optional content of the file
207
     *
208
     * @return \PhpParser\Node[]
209
     */
210 3047
    public static function parseFile($fileName, $fileContent = null)
211
    {
212 3047
        $fileName = PathResolver::realpath($fileName);
213 3047
        if (isset(self::$parsedFiles[$fileName]) && !isset($fileContent)) {
214 3045
            return self::$parsedFiles[$fileName];
215
        }
216
217 9
        if (isset(self::$maximumCachedFiles) && (count(self::$parsedFiles) === self::$maximumCachedFiles)) {
218
            array_shift(self::$parsedFiles);
219
        }
220
221 9
        if (!isset($fileContent)) {
222 9
            $fileContent = file_get_contents($fileName);
223
        }
224 9
        $treeNode = self::$parser->parse($fileContent);
225 9
        $treeNode = self::$traverser->traverse($treeNode);
226
227 9
        self::$parsedFiles[$fileName] = $treeNode;
228
229 9
        return $treeNode;
230
    }
231
232
    /**
233
     * Parses a file namespace and returns an AST for it
234
     *
235
     * @param string $fileName Name of the file
236
     * @param string $namespaceName Namespace name
237
     *
238
     * @return Namespace_
239
     * @throws ReflectionException
240
     */
241 35
    public static function parseFileNamespace($fileName, $namespaceName)
242
    {
243 35
        $topLevelNodes = self::parseFile($fileName);
244
        // namespaces can be only top-level nodes, so we can scan them directly
245 35
        foreach ($topLevelNodes as $topLevelNode) {
246 35
            if (!$topLevelNode instanceof Namespace_) {
247 1
                continue;
248
            }
249 35
            $topLevelNodeName = $topLevelNode->name ? $topLevelNode->name->toString() : '';
250 35
            if (ltrim($topLevelNodeName, '\\') === trim($namespaceName, '\\')) {
251 35
                return $topLevelNode;
252
            }
253
        }
254
255
        throw new ReflectionException("Namespace $namespaceName was not found in the file $fileName");
256
    }
257
258
}
259