PageRenderTime 25ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php

https://gitlab.com/madwanz64/laravel
PHP | 420 lines | 334 code | 35 blank | 51 comment | 19 complexity | c1358182b3d367934cb707aceb98991d MD5 | raw file
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of phpDocumentor.
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. *
  9. * @link http://phpdoc.org
  10. */
  11. namespace phpDocumentor\Reflection\Types;
  12. use ArrayIterator;
  13. use InvalidArgumentException;
  14. use ReflectionClass;
  15. use ReflectionClassConstant;
  16. use ReflectionMethod;
  17. use ReflectionParameter;
  18. use ReflectionProperty;
  19. use Reflector;
  20. use RuntimeException;
  21. use UnexpectedValueException;
  22. use function define;
  23. use function defined;
  24. use function file_exists;
  25. use function file_get_contents;
  26. use function get_class;
  27. use function in_array;
  28. use function is_string;
  29. use function strrpos;
  30. use function substr;
  31. use function token_get_all;
  32. use function trim;
  33. use const T_AS;
  34. use const T_CLASS;
  35. use const T_CURLY_OPEN;
  36. use const T_DOLLAR_OPEN_CURLY_BRACES;
  37. use const T_NAME_FULLY_QUALIFIED;
  38. use const T_NAME_QUALIFIED;
  39. use const T_NAMESPACE;
  40. use const T_NS_SEPARATOR;
  41. use const T_STRING;
  42. use const T_USE;
  43. if (!defined('T_NAME_QUALIFIED')) {
  44. define('T_NAME_QUALIFIED', 'T_NAME_QUALIFIED');
  45. }
  46. if (!defined('T_NAME_FULLY_QUALIFIED')) {
  47. define('T_NAME_FULLY_QUALIFIED', 'T_NAME_FULLY_QUALIFIED');
  48. }
  49. /**
  50. * Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor.
  51. *
  52. * For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to
  53. * provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to
  54. * Fully Qualified names.
  55. *
  56. * @see Context for more information.
  57. */
  58. final class ContextFactory
  59. {
  60. /** The literal used at the end of a use statement. */
  61. private const T_LITERAL_END_OF_USE = ';';
  62. /** The literal used between sets of use statements */
  63. private const T_LITERAL_USE_SEPARATOR = ',';
  64. /**
  65. * Build a Context given a Class Reflection.
  66. *
  67. * @see Context for more information on Contexts.
  68. */
  69. public function createFromReflector(Reflector $reflector): Context
  70. {
  71. if ($reflector instanceof ReflectionClass) {
  72. //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
  73. /** @var ReflectionClass<object> $reflector */
  74. return $this->createFromReflectionClass($reflector);
  75. }
  76. if ($reflector instanceof ReflectionParameter) {
  77. return $this->createFromReflectionParameter($reflector);
  78. }
  79. if ($reflector instanceof ReflectionMethod) {
  80. return $this->createFromReflectionMethod($reflector);
  81. }
  82. if ($reflector instanceof ReflectionProperty) {
  83. return $this->createFromReflectionProperty($reflector);
  84. }
  85. if ($reflector instanceof ReflectionClassConstant) {
  86. return $this->createFromReflectionClassConstant($reflector);
  87. }
  88. throw new UnexpectedValueException('Unhandled \Reflector instance given: ' . get_class($reflector));
  89. }
  90. private function createFromReflectionParameter(ReflectionParameter $parameter): Context
  91. {
  92. $class = $parameter->getDeclaringClass();
  93. if (!$class) {
  94. throw new InvalidArgumentException('Unable to get class of ' . $parameter->getName());
  95. }
  96. return $this->createFromReflectionClass($class);
  97. }
  98. private function createFromReflectionMethod(ReflectionMethod $method): Context
  99. {
  100. $class = $method->getDeclaringClass();
  101. return $this->createFromReflectionClass($class);
  102. }
  103. private function createFromReflectionProperty(ReflectionProperty $property): Context
  104. {
  105. $class = $property->getDeclaringClass();
  106. return $this->createFromReflectionClass($class);
  107. }
  108. private function createFromReflectionClassConstant(ReflectionClassConstant $constant): Context
  109. {
  110. //phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
  111. /** @phpstan-var ReflectionClass<object> $class */
  112. $class = $constant->getDeclaringClass();
  113. return $this->createFromReflectionClass($class);
  114. }
  115. /**
  116. * @phpstan-param ReflectionClass<object> $class
  117. */
  118. private function createFromReflectionClass(ReflectionClass $class): Context
  119. {
  120. $fileName = $class->getFileName();
  121. $namespace = $class->getNamespaceName();
  122. if (is_string($fileName) && file_exists($fileName)) {
  123. $contents = file_get_contents($fileName);
  124. if ($contents === false) {
  125. throw new RuntimeException('Unable to read file "' . $fileName . '"');
  126. }
  127. return $this->createForNamespace($namespace, $contents);
  128. }
  129. return new Context($namespace, []);
  130. }
  131. /**
  132. * Build a Context for a namespace in the provided file contents.
  133. *
  134. * @see Context for more information on Contexts.
  135. *
  136. * @param string $namespace It does not matter if a `\` precedes the namespace name,
  137. * this method first normalizes.
  138. * @param string $fileContents The file's contents to retrieve the aliases from with the given namespace.
  139. */
  140. public function createForNamespace(string $namespace, string $fileContents): Context
  141. {
  142. $namespace = trim($namespace, '\\');
  143. $useStatements = [];
  144. $currentNamespace = '';
  145. $tokens = new ArrayIterator(token_get_all($fileContents));
  146. while ($tokens->valid()) {
  147. $currentToken = $tokens->current();
  148. switch ($currentToken[0]) {
  149. case T_NAMESPACE:
  150. $currentNamespace = $this->parseNamespace($tokens);
  151. break;
  152. case T_CLASS:
  153. // Fast-forward the iterator through the class so that any
  154. // T_USE tokens found within are skipped - these are not
  155. // valid namespace use statements so should be ignored.
  156. $braceLevel = 0;
  157. $firstBraceFound = false;
  158. while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
  159. $currentToken = $tokens->current();
  160. if (
  161. $currentToken === '{'
  162. || in_array($currentToken[0], [T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES], true)
  163. ) {
  164. if (!$firstBraceFound) {
  165. $firstBraceFound = true;
  166. }
  167. ++$braceLevel;
  168. }
  169. if ($currentToken === '}') {
  170. --$braceLevel;
  171. }
  172. $tokens->next();
  173. }
  174. break;
  175. case T_USE:
  176. if ($currentNamespace === $namespace) {
  177. $useStatements += $this->parseUseStatement($tokens);
  178. }
  179. break;
  180. }
  181. $tokens->next();
  182. }
  183. return new Context($namespace, $useStatements);
  184. }
  185. /**
  186. * Deduce the name from tokens when we are at the T_NAMESPACE token.
  187. *
  188. * @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
  189. */
  190. private function parseNamespace(ArrayIterator $tokens): string
  191. {
  192. // skip to the first string or namespace separator
  193. $this->skipToNextStringOrNamespaceSeparator($tokens);
  194. $name = '';
  195. $acceptedTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED];
  196. while ($tokens->valid() && in_array($tokens->current()[0], $acceptedTokens, true)) {
  197. $name .= $tokens->current()[1];
  198. $tokens->next();
  199. }
  200. return $name;
  201. }
  202. /**
  203. * Deduce the names of all imports when we are at the T_USE token.
  204. *
  205. * @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
  206. *
  207. * @return string[]
  208. * @psalm-return array<string, string>
  209. */
  210. private function parseUseStatement(ArrayIterator $tokens): array
  211. {
  212. $uses = [];
  213. while ($tokens->valid()) {
  214. $this->skipToNextStringOrNamespaceSeparator($tokens);
  215. $uses += $this->extractUseStatements($tokens);
  216. $currentToken = $tokens->current();
  217. if ($currentToken[0] === self::T_LITERAL_END_OF_USE) {
  218. return $uses;
  219. }
  220. }
  221. return $uses;
  222. }
  223. /**
  224. * Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token.
  225. *
  226. * @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
  227. */
  228. private function skipToNextStringOrNamespaceSeparator(ArrayIterator $tokens): void
  229. {
  230. while ($tokens->valid()) {
  231. $currentToken = $tokens->current();
  232. if (in_array($currentToken[0], [T_STRING, T_NS_SEPARATOR], true)) {
  233. break;
  234. }
  235. if ($currentToken[0] === T_NAME_QUALIFIED) {
  236. break;
  237. }
  238. if (defined('T_NAME_FULLY_QUALIFIED') && $currentToken[0] === T_NAME_FULLY_QUALIFIED) {
  239. break;
  240. }
  241. $tokens->next();
  242. }
  243. }
  244. /**
  245. * Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
  246. * a USE statement yet. This will return a key/value array of the alias => namespace.
  247. *
  248. * @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
  249. *
  250. * @return string[]
  251. * @psalm-return array<string, string>
  252. *
  253. * @psalm-suppress TypeDoesNotContainType
  254. */
  255. private function extractUseStatements(ArrayIterator $tokens): array
  256. {
  257. $extractedUseStatements = [];
  258. $groupedNs = '';
  259. $currentNs = '';
  260. $currentAlias = '';
  261. $state = 'start';
  262. while ($tokens->valid()) {
  263. $currentToken = $tokens->current();
  264. $tokenId = is_string($currentToken) ? $currentToken : $currentToken[0];
  265. $tokenValue = is_string($currentToken) ? null : $currentToken[1];
  266. switch ($state) {
  267. case 'start':
  268. switch ($tokenId) {
  269. case T_STRING:
  270. case T_NS_SEPARATOR:
  271. $currentNs .= (string) $tokenValue;
  272. $currentAlias = $tokenValue;
  273. break;
  274. case T_NAME_QUALIFIED:
  275. case T_NAME_FULLY_QUALIFIED:
  276. $currentNs .= (string) $tokenValue;
  277. $currentAlias = substr(
  278. (string) $tokenValue,
  279. (int) (strrpos((string) $tokenValue, '\\')) + 1
  280. );
  281. break;
  282. case T_CURLY_OPEN:
  283. case '{':
  284. $state = 'grouped';
  285. $groupedNs = $currentNs;
  286. break;
  287. case T_AS:
  288. $state = 'start-alias';
  289. break;
  290. case self::T_LITERAL_USE_SEPARATOR:
  291. case self::T_LITERAL_END_OF_USE:
  292. $state = 'end';
  293. break;
  294. default:
  295. break;
  296. }
  297. break;
  298. case 'start-alias':
  299. switch ($tokenId) {
  300. case T_STRING:
  301. $currentAlias = $tokenValue;
  302. break;
  303. case self::T_LITERAL_USE_SEPARATOR:
  304. case self::T_LITERAL_END_OF_USE:
  305. $state = 'end';
  306. break;
  307. default:
  308. break;
  309. }
  310. break;
  311. case 'grouped':
  312. switch ($tokenId) {
  313. case T_STRING:
  314. case T_NS_SEPARATOR:
  315. $currentNs .= (string) $tokenValue;
  316. $currentAlias = $tokenValue;
  317. break;
  318. case T_AS:
  319. $state = 'grouped-alias';
  320. break;
  321. case self::T_LITERAL_USE_SEPARATOR:
  322. $state = 'grouped';
  323. $extractedUseStatements[(string) $currentAlias] = $currentNs;
  324. $currentNs = $groupedNs;
  325. $currentAlias = '';
  326. break;
  327. case self::T_LITERAL_END_OF_USE:
  328. $state = 'end';
  329. break;
  330. default:
  331. break;
  332. }
  333. break;
  334. case 'grouped-alias':
  335. switch ($tokenId) {
  336. case T_STRING:
  337. $currentAlias = $tokenValue;
  338. break;
  339. case self::T_LITERAL_USE_SEPARATOR:
  340. $state = 'grouped';
  341. $extractedUseStatements[(string) $currentAlias] = $currentNs;
  342. $currentNs = $groupedNs;
  343. $currentAlias = '';
  344. break;
  345. case self::T_LITERAL_END_OF_USE:
  346. $state = 'end';
  347. break;
  348. default:
  349. break;
  350. }
  351. }
  352. if ($state === 'end') {
  353. break;
  354. }
  355. $tokens->next();
  356. }
  357. if ($groupedNs !== $currentNs) {
  358. $extractedUseStatements[(string) $currentAlias] = $currentNs;
  359. }
  360. return $extractedUseStatements;
  361. }
  362. }