1<?php 2 3namespace dokuwiki\Remote\OpenApiDoc; 4 5class ClassResolver 6{ 7 /** @var ClassResolver */ 8 private static $instance; 9 10 protected $classUses = []; 11 protected $classDocs = []; 12 13 /** 14 * Get a singleton instance 15 * 16 * Constructor is public for testing purposes 17 * @return ClassResolver 18 */ 19 public static function getInstance() 20 { 21 if (self::$instance === null) { 22 self::$instance = new self(); 23 } 24 return self::$instance; 25 } 26 27 /** 28 * Resolve a class name to a fully qualified class name 29 * 30 * Results are cached in the instance for reuse 31 * 32 * @param string $classalias The class name to resolve 33 * @param string $context The classname in which context in which the class is used 34 * @return string No guarantee that the class exists! No leading backslash! 35 */ 36 public function resolve($classalias, $context) 37 { 38 if ($classalias[0] === '\\') { 39 // Fully qualified class name given 40 return ltrim($classalias, '\\'); 41 } 42 $classinfo = $this->getClassUses($context); 43 44 return $classinfo['uses'][$classalias] ?? $classinfo['ownNS'] . '\\' . $classalias; 45 } 46 47 /** 48 * Resolve a class name to a fully qualified class name and return a DocBlockClass for it 49 * 50 * Results are cached in the instance for reuse 51 * 52 * @param string $classalias The class name to resolve 53 * @param string $context The classname in which context in which the class is used 54 * @return DocBlockClass|null 55 */ 56 public function document($classalias, $context) 57 { 58 $class = $this->resolve($classalias, $context); 59 if (!class_exists($class)) return null; 60 61 if (isset($this->classDocs[$class])) { 62 $reflector = new \ReflectionClass($class); 63 $this->classDocs[$class] = new DocBlockClass($reflector); 64 } 65 66 return $this->classDocs[$class]; 67 } 68 69 /** 70 * Cached fetching of all defined class aliases 71 * 72 * @param string $class The class to parse 73 * @return array 74 */ 75 public function getClassUses($class) 76 { 77 if (!isset($this->classUses[$class])) { 78 $reflector = new \ReflectionClass($class); 79 $source = $this->readSource($reflector->getFileName(), $reflector->getStartLine()); 80 $this->classUses[$class] = [ 81 'ownNS' => $reflector->getNamespaceName(), 82 'uses' => $this->tokenizeSource($source) 83 ]; 84 } 85 return $this->classUses[$class]; 86 } 87 88 /** 89 * Parse the use statements from the given source code 90 * 91 * This is a simplified version of the code by @jasondmoss - we do not support multiple 92 * classed within one file 93 * 94 * @link https://gist.github.com/jasondmoss/6200807 95 * @param string $source 96 * @return array 97 */ 98 private function tokenizeSource($source) 99 { 100 101 $tokens = token_get_all($source); 102 103 $useStatements = []; 104 $record = false; 105 $currentUse = [ 106 'class' => '', 107 'as' => '' 108 ]; 109 110 foreach ($tokens as $token) { 111 if (!is_array($token)) { 112 // statement ended 113 if ($record) { 114 $useStatements[] = $currentUse; 115 $record = false; 116 $currentUse = [ 117 'class' => '', 118 'as' => '' 119 ]; 120 } 121 continue; 122 } 123 $tokenname = token_name($token[0]); 124 125 if ($token[0] === T_CLASS) { 126 break; // we reached the class itself, no need to parse further 127 } 128 129 if ($token[0] === T_USE) { 130 $record = 'class'; 131 continue; 132 } 133 134 if ($token[0] === T_AS) { 135 $record = 'as'; 136 continue; 137 } 138 139 if ($record) { 140 switch ($token[0]) { 141 case T_STRING: 142 case T_NS_SEPARATOR: 143 case defined('T_NAME_QUALIFIED') ? T_NAME_QUALIFIED : -1: // PHP 7.4 compatibility 144 $currentUse[$record] .= $token[1]; 145 break; 146 } 147 } 148 } 149 150 // Return a lookup table alias to FQCN 151 $table = []; 152 foreach ($useStatements as $useStatement) { 153 $class = $useStatement['class']; 154 $alias = $useStatement['as'] ?: substr($class, strrpos($class, '\\') + 1); 155 $table[$alias] = $class; 156 } 157 158 return $table; 159 } 160 161 162 /** 163 * Read file source up to the line where our class is defined. 164 * 165 * @return string 166 */ 167 protected function readSource($file, $startline) 168 { 169 $file = fopen($file, 'r'); 170 $line = 0; 171 $source = ''; 172 173 while (!feof($file)) { 174 ++$line; 175 176 if ($line >= $startline) { 177 break; 178 } 179 180 $source .= fgets($file); 181 } 182 fclose($file); 183 184 return $source; 185 } 186} 187