PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/active_support/reflection/base.php

https://github.com/bermi/akelos
PHP | 374 lines | 316 code | 42 blank | 16 comment | 110 complexity | fa4baf0d2b71aef61cdc57ddc73de6c0 MD5 | raw file
  1. <?php
  2. # This file is part of the Akelos Framework
  3. # (Copyright) 2004-2010 Bermi Ferrer bermi a t bermilabs com
  4. # See LICENSE and CREDITS for details
  5. class AkReflection
  6. {
  7. public
  8. $definitions = array(),
  9. $requires = array(),
  10. $tokens,
  11. $symbols;
  12. public function parse($source) {
  13. if (!function_exists('token_get_all')) {
  14. trigger_error('Function "token_get_all" is not defined');
  15. return false;
  16. }
  17. $source = preg_match('/^<\?php.*/', $source)?$source:"<?php ".$source;
  18. $this->tokens = token_get_all($source);
  19. $this->definitions = array();
  20. reset($this->tokens);
  21. $previous = array();
  22. $visibility = false;
  23. $static = false;
  24. $byReference = false;
  25. $functionIndent = '';
  26. $docBlock = '';
  27. while ($t = current($this->tokens)) {
  28. if (is_array($t)) {
  29. if ($t[0] == T_CLASS || (defined('T_INTERFACE')? $t[0] == T_INTERFACE:false) || $t[0] == T_FUNCTION) {
  30. $previous = array_reverse($previous);
  31. foreach($previous as $prev) {
  32. if ($prev[0] == T_STATIC) {
  33. $static = true;
  34. } else if ($prev[0] == T_STRING && in_array($prev[1],array('private','public','protected'))) {
  35. $visibility = $prev[1];
  36. } else if ($prev[0] == T_PAAMAYIM_NEKUDOTAYIM) {
  37. $byReference = true;
  38. } else if (((defined('T_DOC_COMMENT')?$prev[0] == T_DOC_COMMENT:false) || T_COMMENT) && !@preg_match('/<\?php.*/',$prev[1]) && @preg_match('/\/\*/',$prev[1])) {
  39. $docBlock = isset($prev[1])?$prev[1]:null;
  40. break;
  41. } else if (isset($prev[1]) && in_array($prev[1],array('private','public','protected'))){
  42. $visibility = $prev[1];
  43. }
  44. }
  45. $indent='';
  46. if(!empty($docBlock)) {
  47. $doclines = explode("\n", $docBlock);
  48. $lastLine = $doclines[count($doclines)-1];
  49. if (preg_match('/(\s*)?\*/',$lastLine,$matches)) {
  50. $indent = substr($matches[1],0,strlen($matches[1])-1);
  51. $doclines[0]=$indent.$doclines[0];
  52. foreach($doclines as $idx=>$line) {
  53. $pre = '';
  54. if ($idx>0) {
  55. $pre = ' ';
  56. }
  57. $doclines[$idx] = $pre.trim($line);
  58. }
  59. $docBlock=implode("\n",$doclines);
  60. }
  61. } else {
  62. $indent = $functionIndent;
  63. }
  64. $docBlock = str_replace('<?php','',$docBlock);
  65. $string = (!empty($docBlock)?$docBlock."\n":'').($visibility?$visibility.' ':'').($static?' static ':'');
  66. $this->readDefinition($static, $visibility, $byReference, $docBlock,$string, $indent);
  67. $previous = array();
  68. $docBlock = '';
  69. $static = false;
  70. $visibility = false;
  71. $byReference = false;
  72. $functionIndent = '';
  73. $indent = '';
  74. continue;
  75. } else if ($t[0] == T_REQUIRE || $t[0] == T_REQUIRE_ONCE || $t[0] == T_INCLUDE || $t[0] == T_INCLUDE_ONCE) {
  76. if (!isset($this->requires[$t[1]])) {
  77. $this->requires[$t[1]] = array();
  78. }
  79. $org = $t;
  80. $type= $t[1];
  81. $val='';
  82. next($this->tokens);
  83. $t = current($this->tokens);
  84. while ($t != '(') {
  85. next($this->tokens);
  86. $t = current($this->tokens);
  87. }
  88. next($this->tokens);
  89. $t = current($this->tokens);
  90. while ($t != ')') {
  91. next($this->tokens);
  92. if (is_array($t)) {
  93. $val.=$t[1];
  94. } else {
  95. $val.=$t;
  96. }
  97. $t = current($this->tokens);
  98. }
  99. $this->requires[$type][]=$val;
  100. $t = $org;
  101. }
  102. }
  103. if ($t[0] != T_WHITESPACE) {
  104. $previous[] = $t;
  105. } else if ($t[0] == T_WHITESPACE){
  106. $functionIndent.=$t[1];
  107. }
  108. next($this->tokens);
  109. }
  110. $this->definitions = array_merge($this->definitions,$this->requires);
  111. }
  112. protected function _parseTag(&$tags, $tempTag) {
  113. switch($tempTag[0]) {
  114. case 'param':
  115. if (preg_match('/\$([a-zA-Z0-9_]+)\s+(.*)/s',$tempTag[1],$pmatches)) {
  116. if (!isset($tags['params'])) {
  117. $tags['params'] = array();
  118. } else if (!is_array($tags['params'])) {
  119. $currentValue = $tags['params'];
  120. $tags['params'] = array($currentValue);
  121. }
  122. $tags['params'][$pmatches[1]] = trim($pmatches[2]);
  123. } else {
  124. $tags['_unmatched_'][] = array($tempTag[0],$tempTag[1]);
  125. }
  126. break;
  127. default:
  128. if(!empty($tags[$tempTag[0]])) {
  129. if(!is_array($tags[$tempTag[0]])) {
  130. $currentValue = $tags[$tempTag[0]];
  131. $tags[$tempTag[0]] = array($currentValue);
  132. }
  133. $tags[$tempTag[0]][]=trim($tempTag[1]);
  134. } else {
  135. $tags[$tempTag[0]]=trim($tempTag[1]);
  136. }
  137. }
  138. }
  139. protected function _parseDocBlock($string) {
  140. preg_match_all('/\/\*\*\n(\s*\*([^\n]+?\n)+)+.*?\*\//',$string,$matches);
  141. $docBlockStructure = array('comment'=>null);
  142. if (isset($matches[1][0])) {
  143. $docPart = $matches[1][0];
  144. $docPart = preg_replace('/\s*\*\s*/',"\n",$docPart);
  145. $docPart = trim($docPart);
  146. $commentLines = array();
  147. $tags = array('_unmatched_'=>array());
  148. $docLines = explode("\n",$docPart);
  149. $inComment = true;
  150. $tempTag=array();
  151. foreach ($docLines as $line) {
  152. if (preg_match('/^@([a-zA-Z0-9_]+)\s+(.+)$/',$line, $matches)) {
  153. if (!empty($tempTag)) {
  154. $this->_parseTag($tags, $tempTag);
  155. }
  156. $inComment = false;
  157. $tempTag = array($matches[1],$matches[2]);
  158. } else if ($inComment) {
  159. $commentLines[] = $line;
  160. } else {
  161. $tempTag[1].="\n".$line;
  162. }
  163. }
  164. if (!empty($tempTag)) {
  165. $this->_parseTag($tags, $tempTag);
  166. }
  167. $docBlockStructure['comment'] = trim(implode("\n",$commentLines));
  168. $docBlockStructure['tags'] = $tags;
  169. }
  170. return $docBlockStructure;
  171. }
  172. public function readDefinition($static = false, $visibility = 'public', $byReference = false, $docBlock = '', $string = '', $indent) {
  173. $t = current($this->tokens);
  174. $definitionType = $t[1];
  175. // move past the class/interface/function token
  176. next($this->tokens);
  177. $string.=$definitionType;
  178. $string.=$this->skipWhiteAndComments();
  179. $t = current($this->tokens);
  180. if (!isset($t[1])) {
  181. while(!isset($t[1])) {
  182. if ($t=='&') {
  183. $string.=$t;
  184. $byReference = true;
  185. next($this->tokens);
  186. $t = current($this->tokens);
  187. }
  188. }
  189. //$definitionType = $t[1];
  190. $string.=$t[1];
  191. } else {
  192. $string.=$t[1];
  193. }
  194. $definitionName = $t[1];
  195. $this->definitions[] = array(
  196. 'type' => $definitionType,
  197. 'name' => $definitionName,
  198. 'visibility'=>$visibility==false?(substr($definitionName,0,2)=='__')?'private':(substr($definitionName,0,1)=='_'?'protected':false):$visibility,
  199. 'static'=>$static,
  200. 'returnByReference'=>$byReference,
  201. 'docBlock' => $docBlock,
  202. 'toString' => $string
  203. );
  204. // move past the name identifier
  205. next($this->tokens);
  206. list($params,$block,$pre,$post) = $this->getCodeBlock();
  207. $default_options = false;
  208. $available_options = false;
  209. if (preg_match('/\$default_options.*?=.*?(array\(.*?\)).*?;/s',$block,$default_option_matches)) {
  210. $default_options_string=$default_option_matches[1];
  211. $default_options_string = preg_replace_callback('/\$([A-Za-z0-9_\->])+/',array($this,'_replaceVariablesInsideOptions'),$default_options_string);
  212. @eval('$default_options = '.$default_options_string.';');
  213. }
  214. if (preg_match('/\$available_options.*?=.*?(array\(.*?\)).*?;/s',$block,$available_option_matches)) {
  215. $available_options_string=$available_option_matches[1];
  216. $available_options_string = preg_replace_callback('/\$([A-Za-z0-9_\->])+/',array($this,'_replaceVariablesInsideOptions'),$available_options_string);
  217. @eval('$available_options = '.$available_options_string.';');
  218. }
  219. $string.=$pre.$block.$post;
  220. $this->definitions[count($this->definitions)-1]['code'] = $block;
  221. $this->definitions[count($this->definitions)-1]['params'] = $params;
  222. $this->definitions[count($this->definitions)-1]['toString'] = $string;
  223. $this->definitions[count($this->definitions)-1]['default_options'] = $default_options;
  224. $this->definitions[count($this->definitions)-1]['available_options'] = $available_options;
  225. $strlines = explode("\n",$string);
  226. foreach ($strlines as $idx=>$line) {
  227. $first = substr($line,0,strlen($indent));
  228. if ($first == $indent) {
  229. $line = substr($line,strlen($first));
  230. $strlines[$idx] = $line;
  231. }
  232. }
  233. $doclines = explode("\n", $docBlock);
  234. foreach ($doclines as $idx=>$line) {
  235. $first = substr($line,0,strlen($indent));
  236. if ($first == $indent) {
  237. $line = substr($line,strlen($first));
  238. $doclines[$idx] = $line;
  239. }
  240. }
  241. $this->definitions[count($this->definitions)-1]['toString'] = implode("\n",$strlines);
  242. $this->definitions[count($this->definitions)-1]['docBlock'] = implode("\n",$doclines);
  243. }
  244. protected function _replaceVariablesInsideOptions($matches) {
  245. $name = $matches[0];
  246. return '"'.str_replace('$','\$',$name).'"';
  247. }
  248. public function skipWhiteAndComments() {
  249. $string = '';
  250. while ($t = current($this->tokens)) {
  251. if (is_array($t) && ($t[0] == T_WHITESPACE || (defined('T_DOC_COMMENT')?$t[0] == T_DOC_COMMENT:false) || $t[0] == T_COMMENT)) {
  252. next($this->tokens);
  253. $string.=$t[1];
  254. } else {
  255. return $string;
  256. }
  257. }
  258. }
  259. public function skipCodeBlock() {
  260. // we go forward until we find the first "{" token
  261. while(($t = current($this->tokens)) && $t != '{') {
  262. next($this->tokens);
  263. }
  264. // we're about to enter the top level block
  265. // which is our class/interface/function definition body
  266. $nestingLevel = 0;
  267. // we go forward keeping the $nestingLevel up-to-date
  268. // until we get out of the definition body block
  269. while($t = current($this->tokens)) {
  270. if ($t == '{') {
  271. $nestingLevel++;
  272. }
  273. if ($t == '}') {
  274. $nestingLevel--;
  275. }
  276. next($this->tokens);
  277. if ($nestingLevel == 0) return;
  278. }
  279. }
  280. public function getCodeBlock() {
  281. $prestring = '';
  282. $poststring = '';
  283. $codeblock = '';
  284. // we go forward until we find the first "{" token
  285. $params = array();
  286. $preParam = '';
  287. while(($t = current($this->tokens)) && $t != '{') {
  288. if (is_array($t)) {
  289. switch ($t[0]) {
  290. case T_VARIABLE:
  291. $params[]=$preParam.$t[1];
  292. $preParam = '';
  293. break;
  294. }
  295. $prestring.=$t[1];
  296. } else if (!in_array($t,array(',','(',')'))) {
  297. $preParam.=$t;
  298. $prestring.=$t;
  299. } else {
  300. $prestring.=$t;
  301. }
  302. next($this->tokens);
  303. }
  304. // we're about to enter the top level block
  305. // which is our class/interface/function definition body
  306. $nestingLevel = 0;
  307. // we go forward keeping the $nestingLevel up-to-date
  308. // until we get out of the definition body block
  309. while($t = current($this->tokens)) {
  310. if ($t == '{') {
  311. $nestingLevel++;
  312. }
  313. if ($t == '}') {
  314. $nestingLevel--;
  315. }
  316. next($this->tokens);
  317. if ($nestingLevel == 0) {
  318. $poststring.=$t;
  319. return array($params,$codeblock,$prestring,$poststring);
  320. } else {
  321. if ($t == '{' && $nestingLevel==1) {
  322. $prestring.=$t;
  323. continue;
  324. }
  325. $codeblock.=is_array($t)?$t[1]:$t;
  326. }
  327. }
  328. }
  329. public function getDefinitions() {
  330. return $this->definitions;
  331. }
  332. }