1<?php
2/*
3 * This file is part of code-unit-reverse-lookup.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace SebastianBergmann\CodeUnitReverseLookup;
12
13/**
14 * @since Class available since Release 1.0.0
15 */
16class Wizard
17{
18    /**
19     * @var array
20     */
21    private $lookupTable = [];
22
23    /**
24     * @var array
25     */
26    private $processedClasses = [];
27
28    /**
29     * @var array
30     */
31    private $processedFunctions = [];
32
33    /**
34     * @param string $filename
35     * @param int    $lineNumber
36     *
37     * @return string
38     */
39    public function lookup($filename, $lineNumber)
40    {
41        if (!isset($this->lookupTable[$filename][$lineNumber])) {
42            $this->updateLookupTable();
43        }
44
45        if (isset($this->lookupTable[$filename][$lineNumber])) {
46            return $this->lookupTable[$filename][$lineNumber];
47        } else {
48            return $filename . ':' . $lineNumber;
49        }
50    }
51
52    private function updateLookupTable()
53    {
54        $this->processClassesAndTraits();
55        $this->processFunctions();
56    }
57
58    private function processClassesAndTraits()
59    {
60        foreach (array_merge(get_declared_classes(), get_declared_traits()) as $classOrTrait) {
61            if (isset($this->processedClasses[$classOrTrait])) {
62                continue;
63            }
64
65            $reflector = new \ReflectionClass($classOrTrait);
66
67            foreach ($reflector->getMethods() as $method) {
68                $this->processFunctionOrMethod($method);
69            }
70
71            $this->processedClasses[$classOrTrait] = true;
72        }
73    }
74
75    private function processFunctions()
76    {
77        foreach (get_defined_functions()['user'] as $function) {
78            if (isset($this->processedFunctions[$function])) {
79                continue;
80            }
81
82            $this->processFunctionOrMethod(new \ReflectionFunction($function));
83
84            $this->processedFunctions[$function] = true;
85        }
86    }
87
88    /**
89     * @param \ReflectionFunctionAbstract $functionOrMethod
90     */
91    private function processFunctionOrMethod(\ReflectionFunctionAbstract $functionOrMethod)
92    {
93        if ($functionOrMethod->isInternal()) {
94            return;
95        }
96
97        $name = $functionOrMethod->getName();
98
99        if ($functionOrMethod instanceof \ReflectionMethod) {
100            $name = $functionOrMethod->getDeclaringClass()->getName() . '::' . $name;
101        }
102
103        if (!isset($this->lookupTable[$functionOrMethod->getFileName()])) {
104            $this->lookupTable[$functionOrMethod->getFileName()] = [];
105        }
106
107        foreach (range($functionOrMethod->getStartLine(), $functionOrMethod->getEndLine()) as $line) {
108            $this->lookupTable[$functionOrMethod->getFileName()][$line] = $name;
109        }
110    }
111}
112