Completed
Push — master ( ecebf9...83c0fc )
by Alexander
03:38
created

ReflectionEngine::locateClassFile()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.1158
Metric Value
dl 0
loc 18
ccs 10
cts 12
cp 0.8333
rs 8.8571
cc 5
eloc 11
nc 4
nop 1
crap 5.1158
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 PhpParser\Lexer;
14
use PhpParser\Node;
15
use PhpParser\Node\Stmt\ClassLike;
16
use PhpParser\Node\Stmt\ClassMethod;
17
use PhpParser\Node\Stmt\Namespace_;
18
use PhpParser\Node\Stmt\Property;
19
use PhpParser\NodeTraverser;
20
use PhpParser\NodeVisitor\NameResolver;
21
use PhpParser\Parser;
22
23
/**
24
 * AST-based reflection engine, powered by PHP-Parser
25
 */
26
class ReflectionEngine
27
{
28
    /**
29
     * @var null|LocatorInterface
30
     */
31
    protected static $locator = null;
32
33
    /**
34
     * @var array|Node[]
35
     */
36
    protected static $parsedFiles = array();
37
38
    /**
39
     * @var null|Parser
40
     */
41
    protected static $parser = null;
42
43
    /**
44
     * @var null|NodeTraverser
45
     */
46
    protected static $traverser = null;
47
48
    private function __construct() {}
49
50 40
    public static function init(LocatorInterface $locator)
51
    {
52 40
        self::$parser = new Parser(new Lexer(['usedAttributes' => [
53 40
            'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos'
54 40
        ]]));
55
56 40
        self::$traverser = $traverser = new NodeTraverser();
57 40
        $traverser->addVisitor(new NameResolver());
58
59 40
        self::$locator = $locator;
60 40
    }
61
62
    /**
63
     * Locates a file name for class
64
     *
65
     * @param string $fullClassName Full name of the class
66
     *
67
     * @return string
68
     */
69 7
    public static function locateClassFile($fullClassName)
70
    {
71 7
        if (class_exists($fullClassName, false)
72 1
            || interface_exists($fullClassName, false)
73 1
            || trait_exists($fullClassName, false)
74 7
        ) {
75 7
            $refClass      = new \ReflectionClass($fullClassName);
76 7
            $classFileName = $refClass->getFileName();
77 7
        } else {
78
            $classFileName = self::$locator->locateClass($fullClassName);
79
        }
80
81 7
        if (!$classFileName) {
82
            throw new \InvalidArgumentException("Class $fullClassName was not found by locator");
83
        }
84
85 7
        return $classFileName;
86
    }
87
88
    /**
89
     * Tries to parse a class by name using LocatorInterface
90
     *
91
     * @param string $fullClassName Class name to load
92
     *
93
     * @return ClassLike
94
     */
95 7
    public static function parseClass($fullClassName)
96
    {
97 7
        $classFileName  = self::locateClassFile($fullClassName);
98 7
        $namespaceParts = explode('\\', $fullClassName);
99 7
        $className      = array_pop($namespaceParts);
100 7
        $namespaceName  = join('\\', $namespaceParts);
101
102 7
        if ($namespaceName) {
103
            // we have a namespace nodes somewhere
104 7
            $namespace      = self::parseFileNamespace($classFileName, $namespaceName);
105 7
            $namespaceNodes = $namespace->stmts;
106 7
        } else {
107
            // global namespace
108
            $namespaceNodes = self::parseFile($classFileName);
109
        }
110
111 7
        foreach ($namespaceNodes as $namespaceLevelNode) {
112 7
            if ($namespaceLevelNode instanceof ClassLike && $namespaceLevelNode->name == $className) {
113 7
                return $namespaceLevelNode;
114
            }
115 2
        }
116
117
        throw new \InvalidArgumentException("Class $fullClassName was not found in the $classFileName");
118
    }
119
120
    /**
121
     * Parses class method
122
     *
123
     * @param string $fullClassName Name of the class
124
     * @param string $methodName Name of the method
125
     *
126
     * @return ClassMethod
127
     */
128
    public static function parseClassMethod($fullClassName, $methodName)
129
    {
130
        $class      = self::parseClass($fullClassName);
131
        $classNodes = $class->stmts;
132
133
        foreach ($classNodes as $classLevelNode) {
134
            if ($classLevelNode instanceof ClassMethod && $classLevelNode->name == $methodName) {
135
                return $classLevelNode;
136
            }
137
        }
138
139
        throw new \InvalidArgumentException("Method $methodName was not found in the $fullClassName");
140
    }
141
142
    /**
143
     * Parses class property
144
     *
145
     * @param string $fullClassName Name of the class
146
     * @param string $propertyName Name of the property
147
     *
148
     * @return array Pair of [Property and PropertyProperty] nodes
149
     */
150 2
    public static function parseClassProperty($fullClassName, $propertyName)
151
    {
152 2
        $class      = self::parseClass($fullClassName);
153 2
        $classNodes = $class->stmts;
154
155 2
        foreach ($classNodes as $classLevelNode) {
156 2
            if ($classLevelNode instanceof Property) {
157 2
                foreach ($classLevelNode->props as $classProperty) {
158 2
                    if ($classProperty->name == $propertyName) {
159 2
                        return [$classLevelNode, $classProperty];
160
                    }
161 2
                }
162 2
            }
163 2
        }
164
165
        throw new \InvalidArgumentException("Property $propertyName was not found in the $fullClassName");
166
    }
167
168
    /**
169
     * Parses a file and returns an AST for it
170
     *
171
     * @param string $fileName Name of the file
172
     *
173
     * @return Node[]
174
     */
175 40
    public static function parseFile($fileName)
176
    {
177 40
        if (isset(self::$parsedFiles[$fileName])) {
178 37
            return self::$parsedFiles[$fileName];
179
        }
180
181 7
        $fileContent = file_get_contents($fileName);
182 7
        $treeNode    = self::$parser->parse($fileContent);
183 7
        $treeNode    = self::$traverser->traverse($treeNode);
184
185 7
        self::$parsedFiles[$fileName] = $treeNode;
186
187 7
        return $treeNode;
188
    }
189
190
    /**
191
     * Parses a file namespace and returns an AST for it
192
     *
193
     * @param string $fileName Name of the file
194
     * @param string $namespaceName Namespace name
195
     *
196
     * @return Namespace_
197
     */
198 13
    public static function parseFileNamespace($fileName, $namespaceName)
199
    {
200 13
        $topLevelNodes = self::parseFile($fileName);
201
        // namespaces can be only top-level nodes, so we can scan them directly
202 13
        foreach ($topLevelNodes as $topLevelNode) {
203 13
            if ($topLevelNode instanceof Namespace_ && ($topLevelNode->name->toString() == $namespaceName)) {
204 13
                return $topLevelNode;
205
            }
206
        }
207
208
        throw new ReflectionException("Namespace $namespaceName was not found in the file $fileName");
209
    }
210
211
}
212