ReflectionClosure.php 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  1. <?php
  2. /* ===========================================================================
  3. * Copyright (c) 2018-2021 Zindex Software
  4. *
  5. * Licensed under the MIT License
  6. * =========================================================================== */
  7. namespace Opis\Closure;
  8. defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', -4);
  9. defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', -5);
  10. defined('T_FN') || define('T_FN', -6);
  11. use Closure;
  12. use ReflectionFunction;
  13. class ReflectionClosure extends ReflectionFunction
  14. {
  15. protected $code;
  16. protected $tokens;
  17. protected $hashedName;
  18. protected $useVariables;
  19. protected $isStaticClosure;
  20. protected $isScopeRequired;
  21. protected $isBindingRequired;
  22. protected $isShortClosure;
  23. protected static $files = array();
  24. protected static $classes = array();
  25. protected static $functions = array();
  26. protected static $constants = array();
  27. protected static $structures = array();
  28. /**
  29. * ReflectionClosure constructor.
  30. * @param Closure $closure
  31. * @param string|null $code This is ignored. Do not use it
  32. * @throws \ReflectionException
  33. */
  34. public function __construct(Closure $closure, $code = null)
  35. {
  36. parent::__construct($closure);
  37. }
  38. /**
  39. * @return bool
  40. */
  41. public function isStatic()
  42. {
  43. if ($this->isStaticClosure === null) {
  44. $this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static';
  45. }
  46. return $this->isStaticClosure;
  47. }
  48. public function isShortClosure()
  49. {
  50. if ($this->isShortClosure === null) {
  51. $code = $this->getCode();
  52. if ($this->isStatic()) {
  53. $code = substr($code, 6);
  54. }
  55. $this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn';
  56. }
  57. return $this->isShortClosure;
  58. }
  59. /**
  60. * @return string
  61. */
  62. public function getCode()
  63. {
  64. if($this->code !== null){
  65. return $this->code;
  66. }
  67. $fileName = $this->getFileName();
  68. $line = $this->getStartLine() - 1;
  69. $className = null;
  70. if (null !== $className = $this->getClosureScopeClass()) {
  71. $className = '\\' . trim($className->getName(), '\\');
  72. }
  73. $builtin_types = self::getBuiltinTypes();
  74. $class_keywords = ['self', 'static', 'parent'];
  75. $ns = $this->getNamespaceName();
  76. $nsf = $ns == '' ? '' : ($ns[0] == '\\' ? $ns : '\\' . $ns);
  77. $_file = var_export($fileName, true);
  78. $_dir = var_export(dirname($fileName), true);
  79. $_namespace = var_export($ns, true);
  80. $_class = var_export(trim($className, '\\'), true);
  81. $_function = $ns . ($ns == '' ? '' : '\\') . '{closure}';
  82. $_method = ($className == '' ? '' : trim($className, '\\') . '::') . $_function;
  83. $_function = var_export($_function, true);
  84. $_method = var_export($_method, true);
  85. $_trait = null;
  86. $tokens = $this->getTokens();
  87. $state = $lastState = 'start';
  88. $inside_structure = false;
  89. $isShortClosure = false;
  90. $inside_structure_mark = 0;
  91. $open = 0;
  92. $code = '';
  93. $id_start = $id_start_ci = $id_name = $context = '';
  94. $classes = $functions = $constants = null;
  95. $use = array();
  96. $lineAdd = 0;
  97. $isUsingScope = false;
  98. $isUsingThisObject = false;
  99. for($i = 0, $l = count($tokens); $i < $l; $i++) {
  100. $token = $tokens[$i];
  101. switch ($state) {
  102. case 'start':
  103. if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
  104. $code .= $token[1];
  105. $state = $token[0] === T_FUNCTION ? 'function' : 'static';
  106. } elseif ($token[0] === T_FN) {
  107. $isShortClosure = true;
  108. $code .= $token[1];
  109. $state = 'closure_args';
  110. }
  111. break;
  112. case 'static':
  113. if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_FUNCTION) {
  114. $code .= $token[1];
  115. if ($token[0] === T_FUNCTION) {
  116. $state = 'function';
  117. }
  118. } elseif ($token[0] === T_FN) {
  119. $isShortClosure = true;
  120. $code .= $token[1];
  121. $state = 'closure_args';
  122. } else {
  123. $code = '';
  124. $state = 'start';
  125. }
  126. break;
  127. case 'function':
  128. switch ($token[0]){
  129. case T_STRING:
  130. $code = '';
  131. $state = 'named_function';
  132. break;
  133. case '(':
  134. $code .= '(';
  135. $state = 'closure_args';
  136. break;
  137. default:
  138. $code .= is_array($token) ? $token[1] : $token;
  139. }
  140. break;
  141. case 'named_function':
  142. if($token[0] === T_FUNCTION || $token[0] === T_STATIC){
  143. $code = $token[1];
  144. $state = $token[0] === T_FUNCTION ? 'function' : 'static';
  145. } elseif ($token[0] === T_FN) {
  146. $isShortClosure = true;
  147. $code .= $token[1];
  148. $state = 'closure_args';
  149. }
  150. break;
  151. case 'closure_args':
  152. switch ($token[0]){
  153. case T_NAME_QUALIFIED:
  154. list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
  155. $context = 'args';
  156. $state = 'id_name';
  157. $lastState = 'closure_args';
  158. break;
  159. case T_NS_SEPARATOR:
  160. case T_STRING:
  161. $id_start = $token[1];
  162. $id_start_ci = strtolower($id_start);
  163. $id_name = '';
  164. $context = 'args';
  165. $state = 'id_name';
  166. $lastState = 'closure_args';
  167. break;
  168. case T_USE:
  169. $code .= $token[1];
  170. $state = 'use';
  171. break;
  172. case T_DOUBLE_ARROW:
  173. $code .= $token[1];
  174. if ($isShortClosure) {
  175. $state = 'closure';
  176. }
  177. break;
  178. case ':':
  179. $code .= ':';
  180. $state = 'return';
  181. break;
  182. case '{':
  183. $code .= '{';
  184. $state = 'closure';
  185. $open++;
  186. break;
  187. default:
  188. $code .= is_array($token) ? $token[1] : $token;
  189. }
  190. break;
  191. case 'use':
  192. switch ($token[0]){
  193. case T_VARIABLE:
  194. $use[] = substr($token[1], 1);
  195. $code .= $token[1];
  196. break;
  197. case '{':
  198. $code .= '{';
  199. $state = 'closure';
  200. $open++;
  201. break;
  202. case ':':
  203. $code .= ':';
  204. $state = 'return';
  205. break;
  206. default:
  207. $code .= is_array($token) ? $token[1] : $token;
  208. break;
  209. }
  210. break;
  211. case 'return':
  212. switch ($token[0]){
  213. case T_WHITESPACE:
  214. case T_COMMENT:
  215. case T_DOC_COMMENT:
  216. $code .= $token[1];
  217. break;
  218. case T_NS_SEPARATOR:
  219. case T_STRING:
  220. $id_start = $token[1];
  221. $id_start_ci = strtolower($id_start);
  222. $id_name = '';
  223. $context = 'return_type';
  224. $state = 'id_name';
  225. $lastState = 'return';
  226. break 2;
  227. case T_NAME_QUALIFIED:
  228. list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
  229. $context = 'return_type';
  230. $state = 'id_name';
  231. $lastState = 'return';
  232. break 2;
  233. case T_DOUBLE_ARROW:
  234. $code .= $token[1];
  235. if ($isShortClosure) {
  236. $state = 'closure';
  237. }
  238. break;
  239. case '{':
  240. $code .= '{';
  241. $state = 'closure';
  242. $open++;
  243. break;
  244. default:
  245. $code .= is_array($token) ? $token[1] : $token;
  246. break;
  247. }
  248. break;
  249. case 'closure':
  250. switch ($token[0]){
  251. case T_CURLY_OPEN:
  252. case T_DOLLAR_OPEN_CURLY_BRACES:
  253. case '{':
  254. $code .= is_array($token) ? $token[1] : $token;
  255. $open++;
  256. break;
  257. case '}':
  258. $code .= '}';
  259. if(--$open === 0 && !$isShortClosure){
  260. break 3;
  261. } elseif ($inside_structure) {
  262. $inside_structure = !($open === $inside_structure_mark);
  263. }
  264. break;
  265. case '(':
  266. case '[':
  267. $code .= $token[0];
  268. if ($isShortClosure) {
  269. $open++;
  270. }
  271. break;
  272. case ')':
  273. case ']':
  274. if ($isShortClosure) {
  275. if ($open === 0) {
  276. break 3;
  277. }
  278. --$open;
  279. }
  280. $code .= $token[0];
  281. break;
  282. case ',':
  283. case ';':
  284. if ($isShortClosure && $open === 0) {
  285. break 3;
  286. }
  287. $code .= $token[0];
  288. break;
  289. case T_LINE:
  290. $code .= $token[2] - $line + $lineAdd;
  291. break;
  292. case T_FILE:
  293. $code .= $_file;
  294. break;
  295. case T_DIR:
  296. $code .= $_dir;
  297. break;
  298. case T_NS_C:
  299. $code .= $_namespace;
  300. break;
  301. case T_CLASS_C:
  302. $code .= $inside_structure ? $token[1] : $_class;
  303. break;
  304. case T_FUNC_C:
  305. $code .= $inside_structure ? $token[1] : $_function;
  306. break;
  307. case T_METHOD_C:
  308. $code .= $inside_structure ? $token[1] : $_method;
  309. break;
  310. case T_COMMENT:
  311. if (substr($token[1], 0, 8) === '#trackme') {
  312. $timestamp = time();
  313. $code .= '/**' . PHP_EOL;
  314. $code .= '* Date : ' . date(DATE_W3C, $timestamp) . PHP_EOL;
  315. $code .= '* Timestamp : ' . $timestamp . PHP_EOL;
  316. $code .= '* Line : ' . ($line + 1) . PHP_EOL;
  317. $code .= '* File : ' . $_file . PHP_EOL . '*/' . PHP_EOL;
  318. $lineAdd += 5;
  319. } else {
  320. $code .= $token[1];
  321. }
  322. break;
  323. case T_VARIABLE:
  324. if($token[1] == '$this' && !$inside_structure){
  325. $isUsingThisObject = true;
  326. }
  327. $code .= $token[1];
  328. break;
  329. case T_STATIC:
  330. case T_NS_SEPARATOR:
  331. case T_STRING:
  332. $id_start = $token[1];
  333. $id_start_ci = strtolower($id_start);
  334. $id_name = '';
  335. $context = 'root';
  336. $state = 'id_name';
  337. $lastState = 'closure';
  338. break 2;
  339. case T_NAME_QUALIFIED:
  340. list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
  341. $context = 'root';
  342. $state = 'id_name';
  343. $lastState = 'closure';
  344. break 2;
  345. case T_NEW:
  346. $code .= $token[1];
  347. $context = 'new';
  348. $state = 'id_start';
  349. $lastState = 'closure';
  350. break 2;
  351. case T_USE:
  352. $code .= $token[1];
  353. $context = 'use';
  354. $state = 'id_start';
  355. $lastState = 'closure';
  356. break;
  357. case T_INSTANCEOF:
  358. case T_INSTEADOF:
  359. $code .= $token[1];
  360. $context = 'instanceof';
  361. $state = 'id_start';
  362. $lastState = 'closure';
  363. break;
  364. case T_OBJECT_OPERATOR:
  365. case T_DOUBLE_COLON:
  366. $code .= $token[1];
  367. $lastState = 'closure';
  368. $state = 'ignore_next';
  369. break;
  370. case T_FUNCTION:
  371. $code .= $token[1];
  372. $state = 'closure_args';
  373. if (!$inside_structure) {
  374. $inside_structure = true;
  375. $inside_structure_mark = $open;
  376. }
  377. break;
  378. case T_TRAIT_C:
  379. if ($_trait === null) {
  380. $startLine = $this->getStartLine();
  381. $endLine = $this->getEndLine();
  382. $structures = $this->getStructures();
  383. $_trait = '';
  384. foreach ($structures as &$struct) {
  385. if ($struct['type'] === 'trait' &&
  386. $struct['start'] <= $startLine &&
  387. $struct['end'] >= $endLine
  388. ) {
  389. $_trait = ($ns == '' ? '' : $ns . '\\') . $struct['name'];
  390. break;
  391. }
  392. }
  393. $_trait = var_export($_trait, true);
  394. }
  395. $code .= $_trait;
  396. break;
  397. default:
  398. $code .= is_array($token) ? $token[1] : $token;
  399. }
  400. break;
  401. case 'ignore_next':
  402. switch ($token[0]){
  403. case T_WHITESPACE:
  404. case T_COMMENT:
  405. case T_DOC_COMMENT:
  406. $code .= $token[1];
  407. break;
  408. case T_CLASS:
  409. case T_NEW:
  410. case T_STATIC:
  411. case T_VARIABLE:
  412. case T_STRING:
  413. case T_CLASS_C:
  414. case T_FILE:
  415. case T_DIR:
  416. case T_METHOD_C:
  417. case T_FUNC_C:
  418. case T_FUNCTION:
  419. case T_INSTANCEOF:
  420. case T_LINE:
  421. case T_NS_C:
  422. case T_TRAIT_C:
  423. case T_USE:
  424. $code .= $token[1];
  425. $state = $lastState;
  426. break;
  427. default:
  428. $state = $lastState;
  429. $i--;
  430. }
  431. break;
  432. case 'id_start':
  433. switch ($token[0]){
  434. case T_WHITESPACE:
  435. case T_COMMENT:
  436. case T_DOC_COMMENT:
  437. $code .= $token[1];
  438. break;
  439. case T_NS_SEPARATOR:
  440. case T_NAME_FULLY_QUALIFIED:
  441. case T_STRING:
  442. case T_STATIC:
  443. $id_start = $token[1];
  444. $id_start_ci = strtolower($id_start);
  445. $id_name = '';
  446. $state = 'id_name';
  447. break 2;
  448. case T_NAME_QUALIFIED:
  449. list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
  450. $state = 'id_name';
  451. break 2;
  452. case T_VARIABLE:
  453. $code .= $token[1];
  454. $state = $lastState;
  455. break;
  456. case T_CLASS:
  457. $code .= $token[1];
  458. $state = 'anonymous';
  459. break;
  460. default:
  461. $i--;//reprocess last
  462. $state = 'id_name';
  463. }
  464. break;
  465. case 'id_name':
  466. switch ($token[0]){
  467. case T_NAME_QUALIFIED:
  468. case T_NS_SEPARATOR:
  469. case T_STRING:
  470. case T_WHITESPACE:
  471. case T_COMMENT:
  472. case T_DOC_COMMENT:
  473. $id_name .= $token[1];
  474. break;
  475. case '(':
  476. if ($isShortClosure) {
  477. $open++;
  478. }
  479. if($context === 'new' || false !== strpos($id_name, '\\')){
  480. if($id_start_ci === 'self' || $id_start_ci === 'static') {
  481. if (!$inside_structure) {
  482. $isUsingScope = true;
  483. }
  484. } elseif ($id_start !== '\\' && !in_array($id_start_ci, $class_keywords)) {
  485. if ($classes === null) {
  486. $classes = $this->getClasses();
  487. }
  488. if (isset($classes[$id_start_ci])) {
  489. $id_start = $classes[$id_start_ci];
  490. }
  491. if($id_start[0] !== '\\'){
  492. $id_start = $nsf . '\\' . $id_start;
  493. }
  494. }
  495. } else {
  496. if($id_start !== '\\'){
  497. if($functions === null){
  498. $functions = $this->getFunctions();
  499. }
  500. if(isset($functions[$id_start_ci])){
  501. $id_start = $functions[$id_start_ci];
  502. } elseif ($nsf !== '\\' && function_exists($nsf . '\\' . $id_start)) {
  503. $id_start = $nsf . '\\' . $id_start;
  504. // Cache it to functions array
  505. $functions[$id_start_ci] = $id_start;
  506. }
  507. }
  508. }
  509. $code .= $id_start . $id_name . '(';
  510. $state = $lastState;
  511. break;
  512. case T_VARIABLE:
  513. case T_DOUBLE_COLON:
  514. if($id_start !== '\\') {
  515. if($id_start_ci === 'self' || $id_start_ci === 'parent'){
  516. if (!$inside_structure) {
  517. $isUsingScope = true;
  518. }
  519. } elseif ($id_start_ci === 'static') {
  520. if (!$inside_structure) {
  521. $isUsingScope = $token[0] === T_DOUBLE_COLON;
  522. }
  523. } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){
  524. if ($classes === null) {
  525. $classes = $this->getClasses();
  526. }
  527. if (isset($classes[$id_start_ci])) {
  528. $id_start = $classes[$id_start_ci];
  529. }
  530. if($id_start[0] !== '\\'){
  531. $id_start = $nsf . '\\' . $id_start;
  532. }
  533. }
  534. }
  535. $code .= $id_start . $id_name . $token[1];
  536. $state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState;
  537. break;
  538. default:
  539. if($id_start !== '\\' && !defined($id_start)){
  540. if($constants === null){
  541. $constants = $this->getConstants();
  542. }
  543. if(isset($constants[$id_start])){
  544. $id_start = $constants[$id_start];
  545. } elseif($context === 'new'){
  546. if(in_array($id_start_ci, $class_keywords)) {
  547. if (!$inside_structure) {
  548. $isUsingScope = true;
  549. }
  550. } else {
  551. if ($classes === null) {
  552. $classes = $this->getClasses();
  553. }
  554. if (isset($classes[$id_start_ci])) {
  555. $id_start = $classes[$id_start_ci];
  556. }
  557. if ($id_start[0] !== '\\') {
  558. $id_start = $nsf . '\\' . $id_start;
  559. }
  560. }
  561. } elseif($context === 'use' ||
  562. $context === 'instanceof' ||
  563. $context === 'args' ||
  564. $context === 'return_type' ||
  565. $context === 'extends' ||
  566. $context === 'root'
  567. ){
  568. if(in_array($id_start_ci, $class_keywords)){
  569. if (!$inside_structure && !$id_start_ci === 'static') {
  570. $isUsingScope = true;
  571. }
  572. } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){
  573. if($classes === null){
  574. $classes = $this->getClasses();
  575. }
  576. if(isset($classes[$id_start_ci])){
  577. $id_start = $classes[$id_start_ci];
  578. }
  579. if($id_start[0] !== '\\'){
  580. $id_start = $nsf . '\\' . $id_start;
  581. }
  582. }
  583. }
  584. }
  585. $code .= $id_start . $id_name;
  586. $state = $lastState;
  587. $i--;//reprocess last token
  588. }
  589. break;
  590. case 'anonymous':
  591. switch ($token[0]) {
  592. case T_NS_SEPARATOR:
  593. case T_STRING:
  594. $id_start = $token[1];
  595. $id_start_ci = strtolower($id_start);
  596. $id_name = '';
  597. $state = 'id_name';
  598. $context = 'extends';
  599. $lastState = 'anonymous';
  600. break;
  601. case '{':
  602. $state = 'closure';
  603. if (!$inside_structure) {
  604. $inside_structure = true;
  605. $inside_structure_mark = $open;
  606. }
  607. $i--;
  608. break;
  609. default:
  610. $code .= is_array($token) ? $token[1] : $token;
  611. }
  612. break;
  613. }
  614. }
  615. if ($isShortClosure) {
  616. $this->useVariables = $this->getStaticVariables();
  617. } else {
  618. $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
  619. }
  620. $this->isShortClosure = $isShortClosure;
  621. $this->isBindingRequired = $isUsingThisObject;
  622. $this->isScopeRequired = $isUsingScope;
  623. $this->code = $code;
  624. return $this->code;
  625. }
  626. /**
  627. * @return array
  628. */
  629. private static function getBuiltinTypes()
  630. {
  631. // PHP 5
  632. if (\PHP_MAJOR_VERSION === 5) {
  633. return ['array', 'callable'];
  634. }
  635. // PHP 8
  636. if (\PHP_MAJOR_VERSION === 8) {
  637. return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null'];
  638. }
  639. // PHP 7
  640. switch (\PHP_MINOR_VERSION) {
  641. case 0:
  642. return ['array', 'callable', 'string', 'int', 'bool', 'float'];
  643. case 1:
  644. return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void'];
  645. default:
  646. return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object'];
  647. }
  648. }
  649. /**
  650. * @return array
  651. */
  652. public function getUseVariables()
  653. {
  654. if($this->useVariables !== null){
  655. return $this->useVariables;
  656. }
  657. $tokens = $this->getTokens();
  658. $use = array();
  659. $state = 'start';
  660. foreach ($tokens as &$token) {
  661. $is_array = is_array($token);
  662. switch ($state) {
  663. case 'start':
  664. if ($is_array && $token[0] === T_USE) {
  665. $state = 'use';
  666. }
  667. break;
  668. case 'use':
  669. if ($is_array) {
  670. if ($token[0] === T_VARIABLE) {
  671. $use[] = substr($token[1], 1);
  672. }
  673. } elseif ($token == ')') {
  674. break 2;
  675. }
  676. break;
  677. }
  678. }
  679. $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
  680. return $this->useVariables;
  681. }
  682. /**
  683. * return bool
  684. */
  685. public function isBindingRequired()
  686. {
  687. if($this->isBindingRequired === null){
  688. $this->getCode();
  689. }
  690. return $this->isBindingRequired;
  691. }
  692. /**
  693. * return bool
  694. */
  695. public function isScopeRequired()
  696. {
  697. if($this->isScopeRequired === null){
  698. $this->getCode();
  699. }
  700. return $this->isScopeRequired;
  701. }
  702. /**
  703. * @return string
  704. */
  705. protected function getHashedFileName()
  706. {
  707. if ($this->hashedName === null) {
  708. $this->hashedName = sha1($this->getFileName());
  709. }
  710. return $this->hashedName;
  711. }
  712. /**
  713. * @return array
  714. */
  715. protected function getFileTokens()
  716. {
  717. $key = $this->getHashedFileName();
  718. if (!isset(static::$files[$key])) {
  719. static::$files[$key] = token_get_all(file_get_contents($this->getFileName()));
  720. }
  721. return static::$files[$key];
  722. }
  723. /**
  724. * @return array
  725. */
  726. protected function getTokens()
  727. {
  728. if ($this->tokens === null) {
  729. $tokens = $this->getFileTokens();
  730. $startLine = $this->getStartLine();
  731. $endLine = $this->getEndLine();
  732. $results = array();
  733. $start = false;
  734. foreach ($tokens as &$token) {
  735. if (!is_array($token)) {
  736. if ($start) {
  737. $results[] = $token;
  738. }
  739. continue;
  740. }
  741. $line = $token[2];
  742. if ($line <= $endLine) {
  743. if ($line >= $startLine) {
  744. $start = true;
  745. $results[] = $token;
  746. }
  747. continue;
  748. }
  749. break;
  750. }
  751. $this->tokens = $results;
  752. }
  753. return $this->tokens;
  754. }
  755. /**
  756. * @return array
  757. */
  758. protected function getClasses()
  759. {
  760. $key = $this->getHashedFileName();
  761. if (!isset(static::$classes[$key])) {
  762. $this->fetchItems();
  763. }
  764. return static::$classes[$key];
  765. }
  766. /**
  767. * @return array
  768. */
  769. protected function getFunctions()
  770. {
  771. $key = $this->getHashedFileName();
  772. if (!isset(static::$functions[$key])) {
  773. $this->fetchItems();
  774. }
  775. return static::$functions[$key];
  776. }
  777. /**
  778. * @return array
  779. */
  780. protected function getConstants()
  781. {
  782. $key = $this->getHashedFileName();
  783. if (!isset(static::$constants[$key])) {
  784. $this->fetchItems();
  785. }
  786. return static::$constants[$key];
  787. }
  788. /**
  789. * @return array
  790. */
  791. protected function getStructures()
  792. {
  793. $key = $this->getHashedFileName();
  794. if (!isset(static::$structures[$key])) {
  795. $this->fetchItems();
  796. }
  797. return static::$structures[$key];
  798. }
  799. protected function fetchItems()
  800. {
  801. $key = $this->getHashedFileName();
  802. $classes = array();
  803. $functions = array();
  804. $constants = array();
  805. $structures = array();
  806. $tokens = $this->getFileTokens();
  807. $open = 0;
  808. $state = 'start';
  809. $lastState = '';
  810. $prefix = '';
  811. $name = '';
  812. $alias = '';
  813. $isFunc = $isConst = false;
  814. $startLine = $endLine = 0;
  815. $structType = $structName = '';
  816. $structIgnore = false;
  817. foreach ($tokens as $token) {
  818. switch ($state) {
  819. case 'start':
  820. switch ($token[0]) {
  821. case T_CLASS:
  822. case T_INTERFACE:
  823. case T_TRAIT:
  824. $state = 'before_structure';
  825. $startLine = $token[2];
  826. $structType = $token[0] == T_CLASS
  827. ? 'class'
  828. : ($token[0] == T_INTERFACE ? 'interface' : 'trait');
  829. break;
  830. case T_USE:
  831. $state = 'use';
  832. $prefix = $name = $alias = '';
  833. $isFunc = $isConst = false;
  834. break;
  835. case T_FUNCTION:
  836. $state = 'structure';
  837. $structIgnore = true;
  838. break;
  839. case T_NEW:
  840. $state = 'new';
  841. break;
  842. case T_OBJECT_OPERATOR:
  843. case T_DOUBLE_COLON:
  844. $state = 'invoke';
  845. break;
  846. }
  847. break;
  848. case 'use':
  849. switch ($token[0]) {
  850. case T_FUNCTION:
  851. $isFunc = true;
  852. break;
  853. case T_CONST:
  854. $isConst = true;
  855. break;
  856. case T_NS_SEPARATOR:
  857. $name .= $token[1];
  858. break;
  859. case T_STRING:
  860. $name .= $token[1];
  861. $alias = $token[1];
  862. break;
  863. case T_NAME_QUALIFIED:
  864. $name .= $token[1];
  865. $pieces = explode('\\', $token[1]);
  866. $alias = end($pieces);
  867. break;
  868. case T_AS:
  869. $lastState = 'use';
  870. $state = 'alias';
  871. break;
  872. case '{':
  873. $prefix = $name;
  874. $name = $alias = '';
  875. $state = 'use-group';
  876. break;
  877. case ',':
  878. case ';':
  879. if ($name === '' || $name[0] !== '\\') {
  880. $name = '\\' . $name;
  881. }
  882. if ($alias !== '') {
  883. if ($isFunc) {
  884. $functions[strtolower($alias)] = $name;
  885. } elseif ($isConst) {
  886. $constants[$alias] = $name;
  887. } else {
  888. $classes[strtolower($alias)] = $name;
  889. }
  890. }
  891. $name = $alias = '';
  892. $state = $token === ';' ? 'start' : 'use';
  893. break;
  894. }
  895. break;
  896. case 'use-group':
  897. switch ($token[0]) {
  898. case T_NS_SEPARATOR:
  899. $name .= $token[1];
  900. break;
  901. case T_NAME_QUALIFIED:
  902. $name .= $token[1];
  903. $pieces = explode('\\', $token[1]);
  904. $alias = end($pieces);
  905. break;
  906. case T_STRING:
  907. $name .= $token[1];
  908. $alias = $token[1];
  909. break;
  910. case T_AS:
  911. $lastState = 'use-group';
  912. $state = 'alias';
  913. break;
  914. case ',':
  915. case '}':
  916. if ($prefix === '' || $prefix[0] !== '\\') {
  917. $prefix = '\\' . $prefix;
  918. }
  919. if ($alias !== '') {
  920. if ($isFunc) {
  921. $functions[strtolower($alias)] = $prefix . $name;
  922. } elseif ($isConst) {
  923. $constants[$alias] = $prefix . $name;
  924. } else {
  925. $classes[strtolower($alias)] = $prefix . $name;
  926. }
  927. }
  928. $name = $alias = '';
  929. $state = $token === '}' ? 'use' : 'use-group';
  930. break;
  931. }
  932. break;
  933. case 'alias':
  934. if ($token[0] === T_STRING) {
  935. $alias = $token[1];
  936. $state = $lastState;
  937. }
  938. break;
  939. case 'new':
  940. switch ($token[0]) {
  941. case T_WHITESPACE:
  942. case T_COMMENT:
  943. case T_DOC_COMMENT:
  944. break 2;
  945. case T_CLASS:
  946. $state = 'structure';
  947. $structIgnore = true;
  948. break;
  949. default:
  950. $state = 'start';
  951. }
  952. break;
  953. case 'invoke':
  954. switch ($token[0]) {
  955. case T_WHITESPACE:
  956. case T_COMMENT:
  957. case T_DOC_COMMENT:
  958. break 2;
  959. default:
  960. $state = 'start';
  961. }
  962. break;
  963. case 'before_structure':
  964. if ($token[0] == T_STRING) {
  965. $structName = $token[1];
  966. $state = 'structure';
  967. }
  968. break;
  969. case 'structure':
  970. switch ($token[0]) {
  971. case '{':
  972. case T_CURLY_OPEN:
  973. case T_DOLLAR_OPEN_CURLY_BRACES:
  974. $open++;
  975. break;
  976. case '}':
  977. if (--$open == 0) {
  978. if(!$structIgnore){
  979. $structures[] = array(
  980. 'type' => $structType,
  981. 'name' => $structName,
  982. 'start' => $startLine,
  983. 'end' => $endLine,
  984. );
  985. }
  986. $structIgnore = false;
  987. $state = 'start';
  988. }
  989. break;
  990. default:
  991. if (is_array($token)) {
  992. $endLine = $token[2];
  993. }
  994. }
  995. break;
  996. }
  997. }
  998. static::$classes[$key] = $classes;
  999. static::$functions[$key] = $functions;
  1000. static::$constants[$key] = $constants;
  1001. static::$structures[$key] = $structures;
  1002. }
  1003. private function parseNameQualified($token)
  1004. {
  1005. $pieces = explode('\\', $token);
  1006. $id_start = array_shift($pieces);
  1007. $id_start_ci = strtolower($id_start);
  1008. $id_name = '\\' . implode('\\', $pieces);
  1009. return [$id_start, $id_start_ci, $id_name];
  1010. }
  1011. }