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