Completed
Pull Request — master (#1)
by David
04:52
created

FindReflectionOnLine::computeReflections()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 2
nop 1
1
<?php
2
3
namespace TheCodingMachine\PHPStan\BetterReflection;
4
5
use BetterReflection\Identifier\IdentifierType;
6
use BetterReflection\Reflection\Reflection;
7
use BetterReflection\Reflection\ReflectionClass;
8
use BetterReflection\Reflection\ReflectionFunction;
9
use BetterReflection\Reflection\ReflectionMethod;
10
use BetterReflection\Reflector\ClassReflector;
11
use BetterReflection\SourceLocator\Type\AggregateSourceLocator;
12
use BetterReflection\SourceLocator\Type\AutoloadSourceLocator;
13
use BetterReflection\SourceLocator\Type\EvaledCodeSourceLocator;
14
use BetterReflection\SourceLocator\Type\PhpInternalSourceLocator;
15
use BetterReflection\SourceLocator\Type\SingleFileSourceLocator;
16
use BetterReflection\SourceLocator\Type\SourceLocator;
17
18
/**
19
 * TODO: remove this when https://github.com/Roave/BetterReflection/pull/286 is merged
20
 */
21
final class FindReflectionOnLine
22
{
23
    /**
24
     * @var SourceLocator
25
     */
26
    private $sourceLocator;
27
28
    public function __construct(SourceLocator $sourceLocator = null)
29
    {
30
        $this->sourceLocator = $sourceLocator;
31
    }
32
33
    /**
34
     * @return self
35
     */
36
    public static function buildDefaultFinder() : self
37
    {
38
        return new self(new AggregateSourceLocator([
39
            new PhpInternalSourceLocator(),
40
            new EvaledCodeSourceLocator(),
41
            new AutoloadSourceLocator(),
42
        ]));
43
    }
44
45
    /**
46
     * Find a reflection on the specified line number.
47
     *
48
     * Returns null if no reflections found on the line.
49
     *
50
     * @param string $filename
51
     * @param int $lineNumber
52
     * @return ReflectionMethod|ReflectionClass|ReflectionFunction|null
53
     * @throws \InvalidArgumentException
54
     */
55
    public function __invoke($filename, $lineNumber)
56
    {
57
        $lineNumber = (int)$lineNumber;
58
        $reflections = $this->computeReflections($filename);
59
60
        foreach ($reflections as $reflection) {
61
            if ($reflection instanceof ReflectionClass && $this->containsLine($reflection, $lineNumber)) {
62
                foreach ($reflection->getMethods() as $method) {
63
                    if ($this->containsLine($method, $lineNumber)) {
64
                        return $method;
65
                    }
66
                }
67
                return $reflection;
68
            }
69
70
            if ($reflection instanceof ReflectionFunction && $this->containsLine($reflection, $lineNumber)) {
71
                return $reflection;
72
            }
73
        }
74
75
        return null;
76
    }
77
78
    /**
79
     * Find all class and function reflections in the specified file
80
     *
81
     * @param string $filename
82
     * @return Reflection[]
83
     */
84
    private function computeReflections($filename)
85
    {
86
        $sourceLocator = new SingleFileSourceLocator($filename);
87
        if ($this->sourceLocator !== null) {
88
            $reflector = new ClassReflector(new AggregateSourceLocator([$this->sourceLocator, $sourceLocator]));
89
        } else {
90
            $reflector = new ClassReflector($sourceLocator);
91
        }
92
93
        return array_merge(
94
            $sourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_CLASS)),
95
            $sourceLocator->locateIdentifiersByType($reflector, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION))
96
        );
97
    }
98
99
    /**
100
     * Check to see if the line is within the boundaries of the reflection specified.
101
     *
102
     * @param mixed $reflection
103
     * @param int $lineNumber
104
     * @return bool
105
     * @throws \InvalidArgumentException
106
     */
107
    private function containsLine($reflection, $lineNumber)
108
    {
109
        if (!method_exists($reflection, 'getStartLine')) {
110
            throw new \InvalidArgumentException('Reflection does not have getStartLine method');
111
        }
112
113
        if (!method_exists($reflection, 'getEndLine')) {
114
            throw new \InvalidArgumentException('Reflection does not have getEndLine method');
115
        }
116
117
        return $lineNumber >= $reflection->getStartLine() && $lineNumber <= $reflection->getEndLine();
118
    }
119
}
120