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