File: ClassResolver.php

package info (click to toggle)
dokuwiki 2024-02-06b%2Bdfsg-9
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 24,624 kB
  • sloc: php: 97,851; javascript: 3,724; sh: 599; makefile: 70; xml: 34
file content (186 lines) | stat: -rw-r--r-- 5,168 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<?php

namespace dokuwiki\Remote\OpenApiDoc;

class ClassResolver
{
    /** @var ClassResolver */
    private static $instance;

    protected $classUses = [];
    protected $classDocs = [];

    /**
     * Get a singleton instance
     *
     * Constructor is public for testing purposes
     * @return ClassResolver
     */
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Resolve a class name to a fully qualified class name
     *
     * Results are cached in the instance for reuse
     *
     * @param string $classalias The class name to resolve
     * @param string $context The classname in which context in which the class is used
     * @return string No guarantee that the class exists! No leading backslash!
     */
    public function resolve($classalias, $context)
    {
        if ($classalias[0] === '\\') {
            // Fully qualified class name given
            return ltrim($classalias, '\\');
        }
        $classinfo = $this->getClassUses($context);

        return $classinfo['uses'][$classalias] ?? $classinfo['ownNS'] . '\\' . $classalias;
    }

    /**
     * Resolve a class name to a fully qualified class name and return a DocBlockClass for it
     *
     * Results are cached in the instance for reuse
     *
     * @param string $classalias The class name to resolve
     * @param string $context The classname in which context in which the class is used
     * @return DocBlockClass|null
     */
    public function document($classalias, $context)
    {
        $class = $this->resolve($classalias, $context);
        if (!class_exists($class)) return null;

        if (isset($this->classDocs[$class])) {
            $reflector = new \ReflectionClass($class);
            $this->classDocs[$class] = new DocBlockClass($reflector);
        }

        return $this->classDocs[$class];
    }

    /**
     * Cached fetching of all defined class aliases
     *
     * @param string $class The class to parse
     * @return array
     */
    public function getClassUses($class)
    {
        if (!isset($this->classUses[$class])) {
            $reflector = new \ReflectionClass($class);
            $source = $this->readSource($reflector->getFileName(), $reflector->getStartLine());
            $this->classUses[$class] = [
                'ownNS' => $reflector->getNamespaceName(),
                'uses' => $this->tokenizeSource($source)
            ];
        }
        return $this->classUses[$class];
    }

    /**
     * Parse the use statements from the given source code
     *
     * This is a simplified version of the code by @jasondmoss - we do not support multiple
     * classed within one file
     *
     * @link https://gist.github.com/jasondmoss/6200807
     * @param string $source
     * @return array
     */
    private function tokenizeSource($source)
    {

        $tokens = token_get_all($source);

        $useStatements = [];
        $record = false;
        $currentUse = [
            'class' => '',
            'as' => ''
        ];

        foreach ($tokens as $token) {
            if (!is_array($token)) {
                // statement ended
                if ($record) {
                    $useStatements[] = $currentUse;
                    $record = false;
                    $currentUse = [
                        'class' => '',
                        'as' => ''
                    ];
                }
                continue;
            }
            $tokenname = token_name($token[0]);

            if ($token[0] === T_CLASS) {
                break;  // we reached the class itself, no need to parse further
            }

            if ($token[0] === T_USE) {
                $record = 'class';
                continue;
            }

            if ($token[0] === T_AS) {
                $record = 'as';
                continue;
            }

            if ($record) {
                switch ($token[0]) {
                    case T_STRING:
                    case T_NS_SEPARATOR:
                    case defined('T_NAME_QUALIFIED') ? T_NAME_QUALIFIED : -1: // PHP 7.4 compatibility
                        $currentUse[$record] .= $token[1];
                        break;
                }
            }
        }

        // Return a lookup table alias to FQCN
        $table = [];
        foreach ($useStatements as $useStatement) {
            $class = $useStatement['class'];
            $alias = $useStatement['as'] ?: substr($class, strrpos($class, '\\') + 1);
            $table[$alias] = $class;
        }

        return $table;
    }


    /**
     * Read file source up to the line where our class is defined.
     *
     * @return string
     */
    protected function readSource($file, $startline)
    {
        $file = fopen($file, 'r');
        $line = 0;
        $source = '';

        while (!feof($file)) {
            ++$line;

            if ($line >= $startline) {
                break;
            }

            $source .= fgets($file);
        }
        fclose($file);

        return $source;
    }
}