18ddd9b69SAndreas Gohr<?php 28ddd9b69SAndreas Gohr 38ddd9b69SAndreas Gohrnamespace dokuwiki\Remote\OpenApiDoc; 48ddd9b69SAndreas Gohr 58ddd9b69SAndreas Gohrclass ClassResolver 68ddd9b69SAndreas Gohr{ 78ddd9b69SAndreas Gohr /** @var ClassResolver */ 88ddd9b69SAndreas Gohr private static $instance; 98ddd9b69SAndreas Gohr 108ddd9b69SAndreas Gohr protected $classUses = []; 118ddd9b69SAndreas Gohr protected $classDocs = []; 128ddd9b69SAndreas Gohr 138ddd9b69SAndreas Gohr /** 148ddd9b69SAndreas Gohr * Get a singleton instance 158ddd9b69SAndreas Gohr * 168ddd9b69SAndreas Gohr * Constructor is public for testing purposes 178ddd9b69SAndreas Gohr * @return ClassResolver 188ddd9b69SAndreas Gohr */ 198ddd9b69SAndreas Gohr public static function getInstance() 208ddd9b69SAndreas Gohr { 218ddd9b69SAndreas Gohr if (self::$instance === null) { 228ddd9b69SAndreas Gohr self::$instance = new self(); 238ddd9b69SAndreas Gohr } 248ddd9b69SAndreas Gohr return self::$instance; 258ddd9b69SAndreas Gohr } 268ddd9b69SAndreas Gohr 278ddd9b69SAndreas Gohr /** 288ddd9b69SAndreas Gohr * Resolve a class name to a fully qualified class name 298ddd9b69SAndreas Gohr * 308ddd9b69SAndreas Gohr * Results are cached in the instance for reuse 318ddd9b69SAndreas Gohr * 328ddd9b69SAndreas Gohr * @param string $classalias The class name to resolve 338ddd9b69SAndreas Gohr * @param string $context The classname in which context in which the class is used 348ddd9b69SAndreas Gohr * @return string No guarantee that the class exists! No leading backslash! 358ddd9b69SAndreas Gohr */ 368ddd9b69SAndreas Gohr public function resolve($classalias, $context) 378ddd9b69SAndreas Gohr { 388ddd9b69SAndreas Gohr if ($classalias[0] === '\\') { 398ddd9b69SAndreas Gohr // Fully qualified class name given 408ddd9b69SAndreas Gohr return ltrim($classalias, '\\'); 418ddd9b69SAndreas Gohr } 428ddd9b69SAndreas Gohr $classinfo = $this->getClassUses($context); 438ddd9b69SAndreas Gohr 44*d48c2b25SAndreas Gohr return $classinfo['uses'][$classalias] ?? $classinfo['ownNS'] . '\\' . $classalias; 458ddd9b69SAndreas Gohr } 468ddd9b69SAndreas Gohr 478ddd9b69SAndreas Gohr /** 488ddd9b69SAndreas Gohr * Resolve a class name to a fully qualified class name and return a DocBlockClass for it 498ddd9b69SAndreas Gohr * 508ddd9b69SAndreas Gohr * Results are cached in the instance for reuse 518ddd9b69SAndreas Gohr * 528ddd9b69SAndreas Gohr * @param string $classalias The class name to resolve 538ddd9b69SAndreas Gohr * @param string $context The classname in which context in which the class is used 548ddd9b69SAndreas Gohr * @return DocBlockClass|null 558ddd9b69SAndreas Gohr */ 568ddd9b69SAndreas Gohr public function document($classalias, $context) 578ddd9b69SAndreas Gohr { 588ddd9b69SAndreas Gohr $class = $this->resolve($classalias, $context); 598ddd9b69SAndreas Gohr if (!class_exists($class)) return null; 608ddd9b69SAndreas Gohr 618ddd9b69SAndreas Gohr if (isset($this->classDocs[$class])) { 628ddd9b69SAndreas Gohr $reflector = new \ReflectionClass($class); 638ddd9b69SAndreas Gohr $this->classDocs[$class] = new DocBlockClass($reflector); 648ddd9b69SAndreas Gohr } 658ddd9b69SAndreas Gohr 668ddd9b69SAndreas Gohr return $this->classDocs[$class]; 678ddd9b69SAndreas Gohr } 688ddd9b69SAndreas Gohr 698ddd9b69SAndreas Gohr /** 708ddd9b69SAndreas Gohr * Cached fetching of all defined class aliases 718ddd9b69SAndreas Gohr * 728ddd9b69SAndreas Gohr * @param string $class The class to parse 738ddd9b69SAndreas Gohr * @return array 748ddd9b69SAndreas Gohr */ 758ddd9b69SAndreas Gohr public function getClassUses($class) 768ddd9b69SAndreas Gohr { 778ddd9b69SAndreas Gohr if (!isset($this->classUses[$class])) { 788ddd9b69SAndreas Gohr $reflector = new \ReflectionClass($class); 798ddd9b69SAndreas Gohr $source = $this->readSource($reflector->getFileName(), $reflector->getStartLine()); 808ddd9b69SAndreas Gohr $this->classUses[$class] = [ 818ddd9b69SAndreas Gohr 'ownNS' => $reflector->getNamespaceName(), 828ddd9b69SAndreas Gohr 'uses' => $this->tokenizeSource($source) 838ddd9b69SAndreas Gohr ]; 848ddd9b69SAndreas Gohr } 858ddd9b69SAndreas Gohr return $this->classUses[$class]; 868ddd9b69SAndreas Gohr } 878ddd9b69SAndreas Gohr 888ddd9b69SAndreas Gohr /** 898ddd9b69SAndreas Gohr * Parse the use statements from the given source code 908ddd9b69SAndreas Gohr * 918ddd9b69SAndreas Gohr * This is a simplified version of the code by @jasondmoss - we do not support multiple 928ddd9b69SAndreas Gohr * classed within one file 938ddd9b69SAndreas Gohr * 948ddd9b69SAndreas Gohr * @link https://gist.github.com/jasondmoss/6200807 958ddd9b69SAndreas Gohr * @param string $source 968ddd9b69SAndreas Gohr * @return array 978ddd9b69SAndreas Gohr */ 988ddd9b69SAndreas Gohr private function tokenizeSource($source) 998ddd9b69SAndreas Gohr { 1008ddd9b69SAndreas Gohr 1018ddd9b69SAndreas Gohr $tokens = token_get_all($source); 1028ddd9b69SAndreas Gohr 1038ddd9b69SAndreas Gohr $useStatements = []; 1048ddd9b69SAndreas Gohr $record = false; 1058ddd9b69SAndreas Gohr $currentUse = [ 1068ddd9b69SAndreas Gohr 'class' => '', 1078ddd9b69SAndreas Gohr 'as' => '' 1088ddd9b69SAndreas Gohr ]; 1098ddd9b69SAndreas Gohr 1108ddd9b69SAndreas Gohr foreach ($tokens as $token) { 1118ddd9b69SAndreas Gohr if (!is_array($token)) { 1128ddd9b69SAndreas Gohr // statement ended 1138ddd9b69SAndreas Gohr if ($record) { 1148ddd9b69SAndreas Gohr $useStatements[] = $currentUse; 1158ddd9b69SAndreas Gohr $record = false; 1168ddd9b69SAndreas Gohr $currentUse = [ 1178ddd9b69SAndreas Gohr 'class' => '', 1188ddd9b69SAndreas Gohr 'as' => '' 1198ddd9b69SAndreas Gohr ]; 1208ddd9b69SAndreas Gohr } 1218ddd9b69SAndreas Gohr continue; 1228ddd9b69SAndreas Gohr } 1238ddd9b69SAndreas Gohr $tokenname = token_name($token[0]); 1248ddd9b69SAndreas Gohr 1258ddd9b69SAndreas Gohr if ($token[0] === T_CLASS) { 1268ddd9b69SAndreas Gohr break; // we reached the class itself, no need to parse further 1278ddd9b69SAndreas Gohr } 1288ddd9b69SAndreas Gohr 1298ddd9b69SAndreas Gohr if ($token[0] === T_USE) { 1308ddd9b69SAndreas Gohr $record = 'class'; 1318ddd9b69SAndreas Gohr continue; 1328ddd9b69SAndreas Gohr } 1338ddd9b69SAndreas Gohr 1348ddd9b69SAndreas Gohr if ($token[0] === T_AS) { 1358ddd9b69SAndreas Gohr $record = 'as'; 1368ddd9b69SAndreas Gohr continue; 1378ddd9b69SAndreas Gohr } 1388ddd9b69SAndreas Gohr 1398ddd9b69SAndreas Gohr if ($record) { 1408ddd9b69SAndreas Gohr switch ($token[0]) { 1418ddd9b69SAndreas Gohr case T_STRING: 1428ddd9b69SAndreas Gohr case T_NS_SEPARATOR: 143ebae58f7SAndreas Gohr case defined('T_NAME_QUALIFIED') ? T_NAME_QUALIFIED : -1: // PHP 7.4 compatibility 1448ddd9b69SAndreas Gohr $currentUse[$record] .= $token[1]; 1458ddd9b69SAndreas Gohr break; 1468ddd9b69SAndreas Gohr } 1478ddd9b69SAndreas Gohr } 1488ddd9b69SAndreas Gohr } 1498ddd9b69SAndreas Gohr 1508ddd9b69SAndreas Gohr // Return a lookup table alias to FQCN 1518ddd9b69SAndreas Gohr $table = []; 1528ddd9b69SAndreas Gohr foreach ($useStatements as $useStatement) { 1538ddd9b69SAndreas Gohr $class = $useStatement['class']; 1548ddd9b69SAndreas Gohr $alias = $useStatement['as'] ?: substr($class, strrpos($class, '\\') + 1); 1558ddd9b69SAndreas Gohr $table[$alias] = $class; 1568ddd9b69SAndreas Gohr } 1578ddd9b69SAndreas Gohr 1588ddd9b69SAndreas Gohr return $table; 1598ddd9b69SAndreas Gohr } 1608ddd9b69SAndreas Gohr 1618ddd9b69SAndreas Gohr 1628ddd9b69SAndreas Gohr /** 1638ddd9b69SAndreas Gohr * Read file source up to the line where our class is defined. 1648ddd9b69SAndreas Gohr * 1658ddd9b69SAndreas Gohr * @return string 1668ddd9b69SAndreas Gohr */ 1678ddd9b69SAndreas Gohr protected function readSource($file, $startline) 1688ddd9b69SAndreas Gohr { 1698ddd9b69SAndreas Gohr $file = fopen($file, 'r'); 1708ddd9b69SAndreas Gohr $line = 0; 1718ddd9b69SAndreas Gohr $source = ''; 1728ddd9b69SAndreas Gohr 1738ddd9b69SAndreas Gohr while (!feof($file)) { 1748ddd9b69SAndreas Gohr ++$line; 1758ddd9b69SAndreas Gohr 1768ddd9b69SAndreas Gohr if ($line >= $startline) { 1778ddd9b69SAndreas Gohr break; 1788ddd9b69SAndreas Gohr } 1798ddd9b69SAndreas Gohr 1808ddd9b69SAndreas Gohr $source .= fgets($file); 1818ddd9b69SAndreas Gohr } 1828ddd9b69SAndreas Gohr fclose($file); 1838ddd9b69SAndreas Gohr 1848ddd9b69SAndreas Gohr return $source; 1858ddd9b69SAndreas Gohr } 1868ddd9b69SAndreas Gohr} 187