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