PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://github.com/sidealice/zf2
PHP | 494 lines | 461 code | 28 blank | 5 comment | 32 complexity | 5fe72fe85eff470f5aa97dea93266d8d MD5 | raw file
  1. <?php
  2. namespace Zend\Code\Scanner;
  3. use Zend\Code\Scanner,
  4. Zend\Code\Exception;
  5. class ClassScanner implements Scanner
  6. {
  7. protected $isScanned = false;
  8. protected $namespace = null;
  9. protected $uses = array();
  10. protected $name = null;
  11. protected $shortName = null;
  12. protected $isFinal = false;
  13. protected $isAbstract = false;
  14. protected $isInterface = false;
  15. protected $parentClass = null;
  16. protected $shortParentClass = null;
  17. protected $interfaces = array();
  18. protected $shortInterfaces = array();
  19. protected $tokens = array();
  20. protected $infos = array();
  21. public function __construct(array $classTokens, $namespace = null, array $uses = array())
  22. {
  23. $this->tokens = $classTokens;
  24. $this->namespace = $namespace;
  25. $this->uses = $uses;
  26. }
  27. protected function scan()
  28. {
  29. if ($this->isScanned) {
  30. return;
  31. }
  32. if (!$this->tokens) {
  33. throw new Exception\RuntimeException('No tokens were provided');
  34. }
  35. for ($tokenIndex = 0; $tokenIndex < count($this->tokens); $tokenIndex++) {
  36. $token = $this->tokens[$tokenIndex];
  37. if (is_string($token)) {
  38. continue;
  39. }
  40. // tokens with some value are arrays (will have a token identifier, & line num)
  41. $fastForward = 0;
  42. switch ($token[0]) {
  43. case T_CLASS:
  44. case T_INTERFACE:
  45. $this->scanClassInfo($tokenIndex, $fastForward);
  46. break;
  47. case T_CONST:
  48. $this->scanConstant($tokenIndex, $fastForward);
  49. break;
  50. case T_FINAL:
  51. case T_ABSTRACT:
  52. if (!$this->name) {
  53. break;
  54. }
  55. case T_PUBLIC:
  56. case T_PROTECTED:
  57. case T_PRIVATE:
  58. case T_STATIC:
  59. case T_FUNCTION:
  60. case T_VAR:
  61. $subTokenIndex = $tokenIndex;
  62. do {
  63. $subToken = $this->tokens[$subTokenIndex++];
  64. } while (!(is_array($subToken) && $subToken[0] == T_FUNCTION)
  65. && !(is_string($subToken) && $subToken == ';')
  66. );
  67. if (is_array($subToken)) {
  68. $this->scanMethod($tokenIndex, $fastForward);
  69. } else {
  70. $this->scanProperty($tokenIndex, $fastForward);
  71. }
  72. break;
  73. }
  74. if ($fastForward) {
  75. $tokenIndex += $fastForward - 1;
  76. }
  77. }
  78. $this->isScanned = true;
  79. }
  80. protected function scanClassInfo($tokenIndex, &$fastForward)
  81. {
  82. if (isset($this->tokens[$tokenIndex-2]) && is_array($this->tokens[$tokenIndex-2])) {
  83. $tokenTwoBack = $this->tokens[$tokenIndex-2];
  84. }
  85. // T_ABSTRACT & T_FINAL will have been bypassed if no class name, and
  86. // will alwasy be 2 tokens behind T_CLASS
  87. $this->isAbstract = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_ABSTRACT));
  88. $this->isFinal = (isset($tokenTwoBack) && ($tokenTwoBack[0] === T_FINAL));
  89. $this->isInterface = (is_array($this->tokens[$tokenIndex]) && $this->tokens[$tokenIndex][0] == T_INTERFACE);
  90. $this->shortName = $this->tokens[$tokenIndex+2][1];
  91. $this->name = (($this->namespace) ? $this->namespace . '\\' : '') . $this->shortName;
  92. $context = null;
  93. $interfaceIndex = 0;
  94. while (true) {
  95. $fastForward++;
  96. $tokenIndex++;
  97. $token = $this->tokens[$tokenIndex];
  98. // BREAK ON
  99. if (is_string($token) && $token == '{') {
  100. break;
  101. }
  102. // ANALYZE
  103. if (is_string($token) && $context == T_IMPLEMENTS && $token == ',') {
  104. $interfaceIndex++;
  105. $this->shortInterfaces[$interfaceIndex] = '';
  106. }
  107. if (is_array($token)) {
  108. if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
  109. if ($context == T_EXTENDS) {
  110. $this->shortParentClass .= $token[1];
  111. } elseif ($context == T_IMPLEMENTS) {
  112. $this->shortInterfaces[$interfaceIndex] .= $token[1];
  113. }
  114. }
  115. if ($token[0] == T_EXTENDS && !$this->isInterface) {
  116. $context = T_EXTENDS;
  117. $this->shortParentClass = '';
  118. }
  119. if ($token[0] == T_IMPLEMENTS || ($this->isInterface && $token[0] == T_EXTENDS)) {
  120. $context = T_IMPLEMENTS;
  121. $this->shortInterfaces[$interfaceIndex] = '';
  122. }
  123. }
  124. }
  125. $data = (object) array(
  126. 'namespace' => $this->namespace,
  127. 'uses' => $this->uses,
  128. );
  129. if ($this->shortInterfaces) {
  130. $this->interfaces = $this->shortInterfaces;
  131. array_walk($this->interfaces, array('Zend\Code\Scanner\Util', 'resolveImports'), $data);
  132. }
  133. if ($this->shortParentClass) {
  134. $this->parentClass = $this->shortParentClass;
  135. Util::resolveImports($this->parentClass, null, $data);
  136. }
  137. }
  138. protected function scanConstant($tokenIndex, &$fastForward)
  139. {
  140. $info = array(
  141. 'type' => 'constant',
  142. 'tokenStart' => $tokenIndex,
  143. 'tokenEnd' => null,
  144. 'lineStart' => $this->tokens[$tokenIndex][2],
  145. 'lineEnd' => null,
  146. 'name' => null,
  147. 'value' => null,
  148. );
  149. while (true) {
  150. $fastForward++;
  151. $tokenIndex++;
  152. $token = $this->tokens[$tokenIndex];
  153. // BREAK ON
  154. if (is_string($token) && $token == ';') {
  155. break;
  156. }
  157. if ((is_array($token) && $token[0] == T_WHITESPACE)
  158. || (is_string($token) && $token == '=')
  159. ) {
  160. continue;
  161. }
  162. $info['value'] .= (is_array($token)) ? $token[1] : $token;
  163. if (is_array($token)) {
  164. $info['lineEnd'] = $token[2];
  165. }
  166. }
  167. $info['tokenEnd'] = $tokenIndex;
  168. $this->infos[] = $info;
  169. }
  170. protected function scanMethod($tokenIndex, &$fastForward)
  171. {
  172. $info = array(
  173. 'type' => 'method',
  174. 'tokenStart' => $tokenIndex,
  175. 'tokenEnd' => null,
  176. 'lineStart' => $this->tokens[$tokenIndex][2],
  177. 'lineEnd' => null,
  178. 'name' => null,
  179. );
  180. // start on first token, not second
  181. $fastForward--;
  182. $tokenIndex--;
  183. $braceCount = 0;
  184. while (true) {
  185. $fastForward++;
  186. $tokenIndex++;
  187. if (!isset($this->tokens[$tokenIndex])) {
  188. break;
  189. }
  190. $token = $this->tokens[$tokenIndex];
  191. // BREAK ON
  192. if (is_string($token) && $token == '}' && $braceCount == 1) {
  193. break;
  194. }
  195. // ANALYZE
  196. if (is_string($token)) {
  197. if ($token == '{') {
  198. $braceCount++;
  199. }
  200. if ($token == '}') {
  201. $braceCount--;
  202. }
  203. }
  204. if ($info['name'] === null && $token[0] === T_FUNCTION) {
  205. // next token after T_WHITESPACE is name
  206. $info['name'] = $this->tokens[$tokenIndex+2][1];
  207. continue;
  208. }
  209. if (is_array($token)) {
  210. $info['lineEnd'] = $token[2];
  211. }
  212. }
  213. $info['tokenEnd'] = $tokenIndex;
  214. $this->infos[] = $info;
  215. }
  216. protected function scanProperty($tokenIndex, &$fastForward)
  217. {
  218. $info = array(
  219. 'type' => 'property',
  220. 'tokenStart' => $tokenIndex,
  221. 'tokenEnd' => null,
  222. 'lineStart' => $this->tokens[$tokenIndex][2],
  223. 'lineEnd' => null,
  224. 'name' => null,
  225. );
  226. $index = $tokenIndex;
  227. while (true) {
  228. $fastForward++;
  229. $tokenIndex++;
  230. $token = $this->tokens[$tokenIndex];
  231. // BREAK ON
  232. if (is_string($token) && $token = ';') {
  233. break;
  234. }
  235. // ANALYZE
  236. if ($token[0] === T_VARIABLE) {
  237. $info['name'] = ltrim($token[1], '$');
  238. continue;
  239. }
  240. if (is_array($token)) {
  241. $info['lineEnd'] = $token[2];
  242. }
  243. }
  244. $info['tokenEnd'] = $index;
  245. $this->infos[] = $info;
  246. }
  247. public function getName()
  248. {
  249. $this->scan();
  250. return $this->name;
  251. }
  252. public function getShortName()
  253. {
  254. $this->scan();
  255. return $this->shortName;
  256. }
  257. public function isFinal()
  258. {
  259. $this->scan();
  260. return $this->isFinal;
  261. }
  262. public function isInstantiable()
  263. {
  264. $this->scan();
  265. return (!$this->isAbstract && !$this->isInterface);
  266. }
  267. public function isAbstract()
  268. {
  269. $this->scan();
  270. return $this->isAbstract;
  271. }
  272. public function isInterface()
  273. {
  274. $this->scan();
  275. return $this->isInterface;
  276. }
  277. public function hasParentClass()
  278. {
  279. $this->scan();
  280. return ($this->parentClass != null);
  281. }
  282. public function getParentClass()
  283. {
  284. $this->scan();
  285. return $this->parentClass;
  286. }
  287. public function getInterfaces()
  288. {
  289. $this->scan();
  290. return $this->interfaces;
  291. }
  292. public function getConstants()
  293. {
  294. $this->scan();
  295. $return = array();
  296. foreach ($this->infos as $info) {
  297. if ($info['type'] != 'constant') {
  298. continue;
  299. }
  300. $return[] = $info['name'];
  301. }
  302. return $return;
  303. }
  304. public function getProperties($returnScannerProperty = false)
  305. {
  306. $this->scan();
  307. $return = array();
  308. foreach ($this->infos as $info) {
  309. if ($info['type'] != 'property') {
  310. continue;
  311. }
  312. if (!$returnScannerProperty) {
  313. $return[] = $info['name'];
  314. } else {
  315. $return[] = $this->getClass($info['name'], $returnScannerProperty);
  316. }
  317. }
  318. return $return;
  319. }
  320. public function getMethods($returnScannerMethod = false)
  321. {
  322. $this->scan();
  323. $return = array();
  324. foreach ($this->infos as $info) {
  325. if ($info['type'] != 'method') {
  326. continue;
  327. }
  328. if (!$returnScannerMethod) {
  329. $return[] = $info['name'];
  330. } else {
  331. $return[] = $this->getMethod($info['name'], $returnScannerMethod);
  332. }
  333. }
  334. return $return;
  335. }
  336. /**
  337. * @param string|int $methodNameOrInfoIndex
  338. * @param string $returnScannerClass
  339. * @return Zend\Code\Scanner\MethodScanner
  340. */
  341. public function getMethod($methodNameOrInfoIndex, $returnScannerClass = 'Zend\Code\Scanner\MethodScanner')
  342. {
  343. $this->scan();
  344. // process the class requested
  345. // Static for performance reasons
  346. static $baseScannerClass = 'Zend\Code\Scanner\MethodScanner';
  347. if ($returnScannerClass !== $baseScannerClass) {
  348. if (!is_string($returnScannerClass)) {
  349. $returnScannerClass = $baseScannerClass;
  350. }
  351. $returnScannerClass = ltrim($returnScannerClass, '\\');
  352. if ($returnScannerClass !== $baseScannerClass
  353. && !is_subclass_of($returnScannerClass, $baseScannerClass)
  354. ) {
  355. throw new Exception\RuntimeException(sprintf(
  356. 'Class must be or extend "%s"', $baseScannerClass
  357. ));
  358. }
  359. }
  360. if (is_int($methodNameOrInfoIndex)) {
  361. $info = $this->infos[$methodNameOrInfoIndex];
  362. if ($info['type'] != 'method') {
  363. throw new Exception\InvalidArgumentException('Index of info offset is not about a method');
  364. }
  365. } elseif (is_string($methodNameOrInfoIndex)) {
  366. $methodFound = false;
  367. foreach ($this->infos as $infoIndex => $info) {
  368. if ($info['type'] === 'method' && $info['name'] === $methodNameOrInfoIndex) {
  369. $methodFound = true;
  370. break;
  371. }
  372. }
  373. if (!$methodFound) {
  374. return false;
  375. }
  376. }
  377. if (!isset($info)) {
  378. die();
  379. }
  380. $m = new $returnScannerClass(
  381. array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1),
  382. $this->namespace,
  383. $this->uses
  384. );
  385. $m->setClass($this->name);
  386. $m->setScannerClass($this);
  387. return $m;
  388. }
  389. public function hasMethod($name)
  390. {
  391. $this->scan();
  392. foreach ($this->infos as $infoIndex => $info) {
  393. if ($info['type'] === 'method' && $info['name'] === $name) {
  394. return true;
  395. }
  396. }
  397. return false;
  398. }
  399. public static function export()
  400. {
  401. // @todo
  402. }
  403. public function __toString()
  404. {
  405. // @todo
  406. }
  407. }