/library/Zend/Code/Scanner/ClassScanner.php

https://github.com/jtai/zf2 · PHP · 670 lines · 465 code · 132 blank · 73 comment · 78 complexity · 4c0fa79d814e6d2e32d2350f6fd0795f MD5 · raw file

  1. <?php
  2. namespace Zend\Code\Scanner;
  3. use Zend\Code\Scanner,
  4. Zend\Code\NameInformation,
  5. Zend\Code\Annotation\AnnotationManager,
  6. Zend\Code\Exception;
  7. class ClassScanner implements Scanner
  8. {
  9. /**
  10. * @var bool
  11. */
  12. protected $isScanned = false;
  13. /**
  14. * @var string
  15. */
  16. protected $docComment = null;
  17. /**
  18. * @var string
  19. */
  20. protected $name = null;
  21. /**
  22. * @var string
  23. */
  24. protected $shortName = null;
  25. /**
  26. * @var int
  27. */
  28. protected $lineStart = null;
  29. /**
  30. * @var int
  31. */
  32. protected $lineEnd = null;
  33. /**
  34. * @var bool
  35. */
  36. protected $isFinal = false;
  37. /**
  38. * @var bool
  39. */
  40. protected $isAbstract = false;
  41. /**
  42. * @var bool
  43. */
  44. protected $isInterface = false;
  45. /**
  46. * @var string
  47. */
  48. protected $parentClass = null;
  49. /**
  50. * @var string
  51. */
  52. protected $shortParentClass = null;
  53. /**
  54. * @var string[]
  55. */
  56. protected $interfaces = array();
  57. /**
  58. * @var string[]
  59. */
  60. protected $shortInterfaces = array();
  61. /**
  62. * @var array
  63. */
  64. protected $tokens = array();
  65. /**
  66. * @var NameInformation
  67. */
  68. protected $nameInformation = null;
  69. /**
  70. * @var array[]
  71. */
  72. protected $infos = array();
  73. /**
  74. * @param array $classTokens
  75. * @param NameInformation|null $nameInformation
  76. * @param AnnotationManager|null $annotationManager
  77. * @return ClassScanner
  78. */
  79. public function __construct(array $classTokens, NameInformation $nameInformation = null)
  80. {
  81. $this->tokens = $classTokens;
  82. $this->nameInformation = $nameInformation;
  83. }
  84. public function getAnnotations()
  85. {
  86. return array();
  87. }
  88. public function getDocComment()
  89. {
  90. $this->scan();
  91. return $this->docComment;
  92. }
  93. public function getDocBlock()
  94. {
  95. if (!$docComment = $this->getDocComment()) {
  96. return false;
  97. }
  98. return new DocBlockScanner($docComment);
  99. }
  100. public function getName()
  101. {
  102. $this->scan();
  103. return $this->name;
  104. }
  105. public function getShortName()
  106. {
  107. $this->scan();
  108. return $this->shortName;
  109. }
  110. public function getLineStart()
  111. {
  112. $this->scan();
  113. return $this->lineStart;
  114. }
  115. public function getLineEnd()
  116. {
  117. $this->scan();
  118. return $this->lineEnd;
  119. }
  120. public function isFinal()
  121. {
  122. $this->scan();
  123. return $this->isFinal;
  124. }
  125. public function isInstantiable()
  126. {
  127. $this->scan();
  128. return (!$this->isAbstract && !$this->isInterface);
  129. }
  130. public function isAbstract()
  131. {
  132. $this->scan();
  133. return $this->isAbstract;
  134. }
  135. public function isInterface()
  136. {
  137. $this->scan();
  138. return $this->isInterface;
  139. }
  140. public function hasParentClass()
  141. {
  142. $this->scan();
  143. return ($this->parentClass != null);
  144. }
  145. public function getParentClass()
  146. {
  147. $this->scan();
  148. return $this->parentClass;
  149. }
  150. public function getInterfaces()
  151. {
  152. $this->scan();
  153. return $this->interfaces;
  154. }
  155. public function getConstants()
  156. {
  157. $this->scan();
  158. $return = array();
  159. foreach ($this->infos as $info) {
  160. if ($info['type'] != 'constant') {
  161. continue;
  162. }
  163. $return[] = $info['name'];
  164. }
  165. return $return;
  166. }
  167. public function getPropertyNames()
  168. {
  169. $this->scan();
  170. $return = array();
  171. foreach ($this->infos as $info) {
  172. if ($info['type'] != 'property') {
  173. continue;
  174. }
  175. $return[] = $info['name'];
  176. }
  177. return $return;
  178. }
  179. public function getProperties()
  180. {
  181. $this->scan();
  182. $return = array();
  183. foreach ($this->infos as $info) {
  184. if ($info['type'] != 'property') {
  185. continue;
  186. }
  187. $return[] = $this->getProperty($info['name']);
  188. }
  189. return $return;
  190. }
  191. public function getMethodNames()
  192. {
  193. $this->scan();
  194. $return = array();
  195. foreach ($this->infos as $info) {
  196. if ($info['type'] != 'method') {
  197. continue;
  198. }
  199. $return[] = $info['name'];
  200. }
  201. return $return;
  202. }
  203. public function getMethods()
  204. {
  205. $this->scan();
  206. $return = array();
  207. foreach ($this->infos as $info) {
  208. if ($info['type'] != 'method') {
  209. continue;
  210. }
  211. $return[] = $this->getMethod($info['name']);
  212. }
  213. return $return;
  214. }
  215. /**
  216. * @param string|int $methodNameOrInfoIndex
  217. * @return MethodScanner
  218. */
  219. public function getMethod($methodNameOrInfoIndex)
  220. {
  221. $this->scan();
  222. if (is_int($methodNameOrInfoIndex)) {
  223. $info = $this->infos[$methodNameOrInfoIndex];
  224. if ($info['type'] != 'method') {
  225. throw new Exception\InvalidArgumentException('Index of info offset is not about a method');
  226. }
  227. } elseif (is_string($methodNameOrInfoIndex)) {
  228. $methodFound = false;
  229. foreach ($this->infos as $info) {
  230. if ($info['type'] === 'method' && $info['name'] === $methodNameOrInfoIndex) {
  231. $methodFound = true;
  232. break;
  233. }
  234. }
  235. if (!$methodFound) {
  236. return false;
  237. }
  238. }
  239. if (!isset($info)) {
  240. // @todo find a way to test this
  241. die('Massive Failure, test this');
  242. }
  243. $m = new MethodScanner(
  244. array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1),
  245. $this->nameInformation
  246. );
  247. $m->setClass($this->name);
  248. $m->setScannerClass($this);
  249. return $m;
  250. }
  251. public function hasMethod($name)
  252. {
  253. $this->scan();
  254. foreach ($this->infos as $infoIndex => $info) {
  255. if ($info['type'] === 'method' && $info['name'] === $name) {
  256. return true;
  257. }
  258. }
  259. return false;
  260. }
  261. public static function export()
  262. {
  263. // @todo
  264. }
  265. public function __toString()
  266. {
  267. // @todo
  268. }
  269. protected function scan()
  270. {
  271. if ($this->isScanned) {
  272. return;
  273. }
  274. if (!$this->tokens) {
  275. throw new Exception\RuntimeException('No tokens were provided');
  276. }
  277. /**
  278. * Variables & Setup
  279. */
  280. $tokens = &$this->tokens; // localize
  281. $infos = &$this->infos; // localize
  282. $tokenIndex = null;
  283. $token = null;
  284. $tokenType = null;
  285. $tokenContent = null;
  286. $tokenLine = null;
  287. $namespace = null;
  288. $infoIndex = 0;
  289. $braceCount = 0;
  290. /**
  291. * MACRO creation
  292. */
  293. $MACRO_TOKEN_ADVANCE = function() use (&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) {
  294. static $lastTokenArray = null;
  295. $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex+1;
  296. if (!isset($tokens[$tokenIndex])) {
  297. $token = false;
  298. $tokenContent = false;
  299. $tokenType = false;
  300. $tokenLine = false;
  301. return false;
  302. }
  303. $token = $tokens[$tokenIndex];
  304. if (is_string($token)) {
  305. $tokenType = null;
  306. $tokenContent = $token;
  307. $tokenLine = $tokenLine + substr_count($lastTokenArray[1], "\n"); // adjust token line by last known newline count
  308. } else {
  309. $lastTokenArray = $token;
  310. list($tokenType, $tokenContent, $tokenLine) = $token;
  311. }
  312. return $tokenIndex;
  313. };
  314. $MACRO_INFO_ADVANCE = function() use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) {
  315. $infos[$infoIndex]['tokenEnd'] = $tokenIndex;
  316. $infos[$infoIndex]['lineEnd'] = $tokenLine;
  317. $infoIndex++;
  318. return $infoIndex;
  319. };
  320. /**
  321. * START FINITE STATE MACHINE FOR SCANNING TOKENS
  322. */
  323. // Initialize token
  324. $MACRO_TOKEN_ADVANCE();
  325. SCANNER_TOP:
  326. switch ($tokenType) {
  327. case T_DOC_COMMENT:
  328. $this->docComment = $tokenContent;
  329. goto SCANNER_CONTINUE;
  330. case T_FINAL:
  331. case T_ABSTRACT:
  332. case T_CLASS:
  333. case T_INTERFACE:
  334. // CLASS INFORMATION
  335. $classContext = null;
  336. $classInterfaceIndex = 0;
  337. SCANNER_CLASS_INFO_TOP:
  338. if (is_string($tokens[$tokenIndex+1]) && $tokens[$tokenIndex+1] === '{') {
  339. goto SCANNER_CLASS_INFO_END;
  340. }
  341. $this->lineStart = $tokenLine;
  342. switch ($tokenType) {
  343. case T_FINAL:
  344. $this->isFinal = true;
  345. goto SCANNER_CLASS_INFO_CONTINUE;
  346. case T_ABSTRACT:
  347. $this->isAbstract = true;
  348. goto SCANNER_CLASS_INFO_CONTINUE;
  349. case T_INTERFACE:
  350. $this->isInterface = true;
  351. case T_CLASS:
  352. $this->shortName = $tokens[$tokenIndex+2][1];
  353. if ($this->nameInformation && $this->nameInformation->hasNamespace()) {
  354. $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName;
  355. } else {
  356. $this->name = $this->shortName;
  357. }
  358. goto SCANNER_CLASS_INFO_CONTINUE;
  359. case T_NS_SEPARATOR:
  360. case T_STRING:
  361. switch ($classContext) {
  362. case T_EXTENDS:
  363. $this->shortParentClass .= $tokenContent;
  364. break;
  365. case T_IMPLEMENTS:
  366. $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent;
  367. break;
  368. }
  369. goto SCANNER_CLASS_INFO_CONTINUE;
  370. case T_EXTENDS:
  371. case T_IMPLEMENTS:
  372. $classContext = $tokenType;
  373. if (($this->isInterface && $classContext === T_EXTENDS) || $classContext === T_IMPLEMENTS) {
  374. $this->shortInterfaces[$classInterfaceIndex] = '';
  375. } elseif (!$this->isInterface && $classContext === T_EXTENDS) {
  376. $this->shortParentClass = '';
  377. }
  378. goto SCANNER_CLASS_INFO_CONTINUE;
  379. case null:
  380. if ($classContext == T_IMPLEMENTS && $tokenContent == ',') {
  381. $classInterfaceIndex++;
  382. $this->shortInterfaces[$classInterfaceIndex] = '';
  383. }
  384. }
  385. SCANNER_CLASS_INFO_CONTINUE:
  386. if ($MACRO_TOKEN_ADVANCE() === false) {
  387. goto SCANNER_END;
  388. }
  389. goto SCANNER_CLASS_INFO_TOP;
  390. SCANNER_CLASS_INFO_END:
  391. goto SCANNER_CONTINUE;
  392. }
  393. if ($tokenType === null && $tokenContent === '{' && $braceCount === 0) {
  394. $braceCount++;
  395. if ($MACRO_TOKEN_ADVANCE() === false) {
  396. goto SCANNER_END;
  397. }
  398. SCANNER_CLASS_BODY_TOP:
  399. if ($braceCount === 0) {
  400. goto SCANNER_CLASS_BODY_END;
  401. }
  402. switch ($tokenType) {
  403. case T_CONST:
  404. $infos[$infoIndex] = array(
  405. 'type' => 'constant',
  406. 'tokenStart' => $tokenIndex,
  407. 'tokenEnd' => null,
  408. 'lineStart' => $tokenLine,
  409. 'lineEnd' => null,
  410. 'name' => null,
  411. 'value' => null,
  412. );
  413. SCANNER_CLASS_BODY_CONST_TOP:
  414. if ($tokenContent === ';') {
  415. goto SCANNER_CLASS_BODY_CONST_END;
  416. }
  417. if ($tokenType === T_STRING) {
  418. $infos[$infoIndex]['name'] = $tokenContent;
  419. }
  420. SCANNER_CLASS_BODY_CONST_CONTINUE:
  421. if ($MACRO_TOKEN_ADVANCE() === false) {
  422. goto SCANNER_END;
  423. }
  424. goto SCANNER_CLASS_BODY_CONST_TOP;
  425. SCANNER_CLASS_BODY_CONST_END:
  426. $MACRO_INFO_ADVANCE();
  427. goto SCANNER_CLASS_BODY_CONTINUE;
  428. case T_DOC_COMMENT:
  429. case T_PUBLIC:
  430. case T_PROTECTED:
  431. case T_PRIVATE:
  432. case T_ABSTRACT:
  433. case T_FINAL:
  434. case T_VAR:
  435. case T_FUNCTION:
  436. $infos[$infoIndex] = array(
  437. 'type' => null,
  438. 'tokenStart' => $tokenIndex,
  439. 'tokenEnd' => null,
  440. 'lineStart' => $tokenLine,
  441. 'lineEnd' => null,
  442. 'name' => null,
  443. );
  444. $memberContext = null;
  445. $methodBodyStarted = false;
  446. SCANNER_CLASS_BODY_MEMBER_TOP:
  447. if ($memberContext === 'method') {
  448. switch ($tokenContent) {
  449. case '{':
  450. $methodBodyStarted = true;
  451. $braceCount++;
  452. goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
  453. case '}':
  454. $braceCount--;
  455. goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
  456. }
  457. }
  458. if ($memberContext !== null) {
  459. if (
  460. ($memberContext === 'property' && $tokenContent === ';')
  461. || ($memberContext === 'method' && $methodBodyStarted && $braceCount === 1)
  462. || ($memberContext === 'method' && $this->isInterface && $tokenContent === ';')
  463. ) {
  464. goto SCANNER_CLASS_BODY_MEMBER_END;
  465. }
  466. }
  467. switch ($tokenType) {
  468. case T_VARIABLE:
  469. if ($memberContext === null) {
  470. $memberContext = 'property';
  471. $infos[$infoIndex]['type'] = 'property';
  472. $infos[$infoIndex]['name'] = ltrim($tokenContent, '$');
  473. }
  474. goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
  475. case T_FUNCTION:
  476. $memberContext = 'method';
  477. $infos[$infoIndex]['type'] = 'method';
  478. goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
  479. case T_STRING:
  480. if ($memberContext === 'method' && $infos[$infoIndex]['name'] === null) {
  481. $infos[$infoIndex]['name'] = $tokenContent;
  482. }
  483. goto SCANNER_CLASS_BODY_MEMBER_CONTINUE;
  484. }
  485. SCANNER_CLASS_BODY_MEMBER_CONTINUE:
  486. if ($MACRO_TOKEN_ADVANCE() === false) {
  487. goto SCANNER_END;
  488. }
  489. goto SCANNER_CLASS_BODY_MEMBER_TOP;
  490. SCANNER_CLASS_BODY_MEMBER_END:
  491. $memberContext = null;
  492. $MACRO_INFO_ADVANCE();
  493. goto SCANNER_CLASS_BODY_CONTINUE;
  494. case null: // no type, is a string
  495. switch ($tokenContent) {
  496. case '{':
  497. $braceCount++;
  498. goto SCANNER_CLASS_BODY_CONTINUE;
  499. case '}':
  500. $braceCount--;
  501. goto SCANNER_CLASS_BODY_CONTINUE;
  502. }
  503. }
  504. SCANNER_CLASS_BODY_CONTINUE:
  505. if ($braceCount === 0 || $MACRO_TOKEN_ADVANCE() === false) {
  506. goto SCANNER_CONTINUE;
  507. }
  508. goto SCANNER_CLASS_BODY_TOP;
  509. SCANNER_CLASS_BODY_END:
  510. goto SCANNER_CONTINUE;
  511. }
  512. SCANNER_CONTINUE:
  513. if ($tokenContent === '}') {
  514. $this->lineEnd = $tokenLine;
  515. }
  516. if ($MACRO_TOKEN_ADVANCE() === false) {
  517. goto SCANNER_END;
  518. }
  519. goto SCANNER_TOP;
  520. SCANNER_END:
  521. // process short names
  522. if ($this->nameInformation) {
  523. if ($this->shortParentClass) {
  524. $this->parentClass = $this->nameInformation->resolveName($this->shortParentClass);
  525. }
  526. if ($this->shortInterfaces) {
  527. foreach ($this->shortInterfaces as $siIndex => $si) {
  528. $this->interfaces[$siIndex] = $this->nameInformation->resolveName($si);
  529. }
  530. }
  531. } else {
  532. $this->parentClass = $this->shortParentClass;
  533. $this->interfaces = $this->shortInterfaces;
  534. }
  535. $this->isScanned = true;
  536. return;
  537. }
  538. }