Completed
Push — master ( 63afd1...deb91d )
by Alexander
02:56
created

ReflectionEngine::parseClassMethod()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0467
Metric Value
dl 0
loc 13
ccs 6
cts 7
cp 0.8571
rs 9.2
cc 4
eloc 7
nc 3
nop 2
crap 4.0467
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
use PhpParser\ParserFactory;
23
24
/**
25
 * AST-based reflection engine, powered by PHP-Parser
26
 */
27
class ReflectionEngine
28
{
29
    /**
30
     * @var null|LocatorInterface
31
     */
32
    protected static $locator = null;
33
34
    /**
35
     * @var array|Node[]
36
     */
37
    protected static $parsedFiles = array();
38
39
    /**
40
     * @var null|Parser
41
     */
42
    protected static $parser = null;
43
44
    /**
45
     * @var null|NodeTraverser
46
     */
47
    protected static $traverser = null;
48
49
    private function __construct() {}
50
51
    public static function init(LocatorInterface $locator)
52
    {
53
        $refParser   = new \ReflectionClass(Parser::class);
54
        $isNewParser = $refParser->isInterface();
55
        if (!$isNewParser) {
56
            self::$parser = new Parser(new Lexer(['usedAttributes' => [
0 ignored issues
show
Unused Code introduced by
The call to Parser::__construct() has too many arguments starting with new \PhpParser\Lexer(arr...lePos', 'endFilePos'))).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
57
                'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos', 'startFilePos', 'endFilePos'
58
            ]]));
59
        } else {
60
            self::$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
61
        }
62
63
        self::$traverser = $traverser = new NodeTraverser();
64
        $traverser->addVisitor(new NameResolver());
65
66
        self::$locator = $locator;
67
    }
68
69
    /**
70
     * Locates a file name for class
71
     *
72
     * @param string $fullClassName Full name of the class
73
     *
74
     * @return string
75
     */
76 15
    public static function locateClassFile($fullClassName)
77
    {
78 15
        if (class_exists($fullClassName, false)
79 1
            || interface_exists($fullClassName, false)
80 15
            || trait_exists($fullClassName, false)
81
        ) {
82 15
            $refClass      = new \ReflectionClass($fullClassName);
83 15
            $classFileName = $refClass->getFileName();
84
        } else {
85
            $classFileName = self::$locator->locateClass($fullClassName);
86
        }
87
88 15
        if (!$classFileName) {
89
            throw new \InvalidArgumentException("Class $fullClassName was not found by locator");
90
        }
91
92 15
        return $classFileName;
93
    }
94
95
    /**
96
     * Tries to parse a class by name using LocatorInterface
97
     *
98
     * @param string $fullClassName Class name to load
99
     *
100
     * @return ClassLike
101
     */
102 15
    public static function parseClass($fullClassName)
103
    {
104 15
        $classFileName  = self::locateClassFile($fullClassName);
105 15
        $namespaceParts = explode('\\', $fullClassName);
106 15
        $className      = array_pop($namespaceParts);
107 15
        $namespaceName  = join('\\', $namespaceParts);
108
109 15
        if ($namespaceName) {
110
            // we have a namespace nodes somewhere
111 15
            $namespace      = self::parseFileNamespace($classFileName, $namespaceName);
112 15
            $namespaceNodes = $namespace->stmts;
113
        } else {
114
            // global namespace
115
            $namespaceNodes = self::parseFile($classFileName);
116
        }
117
118 15
        foreach ($namespaceNodes as $namespaceLevelNode) {
119 15
            if ($namespaceLevelNode instanceof ClassLike && $namespaceLevelNode->name == $className) {
120 15
                return $namespaceLevelNode;
121
            }
122
        }
123
124
        throw new \InvalidArgumentException("Class $fullClassName was not found in the $classFileName");
125
    }
126
127
    /**
128
     * Parses class method
129
     *
130
     * @param string $fullClassName Name of the class
131
     * @param string $methodName Name of the method
132
     *
133
     * @return ClassMethod
134
     */
135 1
    public static function parseClassMethod($fullClassName, $methodName)
136
    {
137 1
        $class      = self::parseClass($fullClassName);
138 1
        $classNodes = $class->stmts;
139
140 1
        foreach ($classNodes as $classLevelNode) {
141 1
            if ($classLevelNode instanceof ClassMethod && $classLevelNode->name == $methodName) {
142 1
                return $classLevelNode;
143
            }
144
        }
145
146
        throw new \InvalidArgumentException("Method $methodName was not found in the $fullClassName");
147
    }
148
149
    /**
150
     * Parses class property
151
     *
152
     * @param string $fullClassName Name of the class
153
     * @param string $propertyName Name of the property
154
     *
155
     * @return array Pair of [Property and PropertyProperty] nodes
156
     */
157 2
    public static function parseClassProperty($fullClassName, $propertyName)
158
    {
159 2
        $class      = self::parseClass($fullClassName);
160 2
        $classNodes = $class->stmts;
161
162 2
        foreach ($classNodes as $classLevelNode) {
163 2
            if ($classLevelNode instanceof Property) {
164 2
                foreach ($classLevelNode->props as $classProperty) {
165 2
                    if ($classProperty->name == $propertyName) {
166 2
                        return [$classLevelNode, $classProperty];
167
                    }
168
                }
169
            }
170
        }
171
172
        throw new \InvalidArgumentException("Property $propertyName was not found in the $fullClassName");
173
    }
174
175
    /**
176
     * Parses a file and returns an AST for it
177
     *
178
     * @param string $fileName Name of the file
179
     *
180
     * @return Node[]
181
     */
182 139
    public static function parseFile($fileName)
183
    {
184 139
        if (isset(self::$parsedFiles[$fileName])) {
185 135
            return self::$parsedFiles[$fileName];
186
        }
187
188 9
        $fileContent = file_get_contents($fileName);
189 9
        $treeNode    = self::$parser->parse($fileContent);
190 9
        $treeNode    = self::$traverser->traverse($treeNode);
191
192 9
        self::$parsedFiles[$fileName] = $treeNode;
193
194 9
        return $treeNode;
195
    }
196
197
    /**
198
     * Parses a file namespace and returns an AST for it
199
     *
200
     * @param string $fileName Name of the file
201
     * @param string $namespaceName Namespace name
202
     *
203
     * @return Namespace_
204
     */
205 23
    public static function parseFileNamespace($fileName, $namespaceName)
206
    {
207 23
        $topLevelNodes = self::parseFile($fileName);
208
        // namespaces can be only top-level nodes, so we can scan them directly
209 23
        foreach ($topLevelNodes as $topLevelNode) {
210 23
            if ($topLevelNode instanceof Namespace_ && ($topLevelNode->name->toString() == $namespaceName)) {
211 23
                return $topLevelNode;
212
            }
213
        }
214
215
        throw new ReflectionException("Namespace $namespaceName was not found in the file $fileName");
216
    }
217
218
}
219