1. sfCodeViewer.class.php
  2. /** * Class to reflect on your own written code * * It's a simple parser of 'token_get_all()' tokens, it prints a html representation of the code, with links * * Sjoerd de Jong, 2009 * Use for your own purposes, no guarantees */
  3. class sfCodeViewer
  4. {
  5. protected
  6. $tokens = array(),
  7. $reflector = null,
  8. $url = "";
  9. public function __construct($class)
  10. {
  11. $this->reflector = new ReflectionClass(is_array($class) ? $class[0] : $class);
  12. if (is_array($class))
  13. {
  14. $this->reflector = $this->reflector->getMethod($class[1]);
  15. }
  16. $this->eol = PHP_EOL;
  17. }
  18. public function setUrl($url)
  19. {
  20. $this->url = $url.(substr($url,-1) != '/' ? '/' : '');
  21. }
  22. public function getClassReflector()
  23. {
  24. if ($this->reflector instanceof ReflectionMethod)
  25. {
  26. return $this->reflector->getDeclaringClass();
  27. }
  28. return $this->reflector;
  29. }
  30. private function getWhiteAndComments()
  31. {
  32. $output = "";
  33. while ($t = current($this->tokens))
  34. {
  35. if (is_array($t))
  36. {
  37. list ($token, $text, $line) = $t;
  38. if ($token == T_WHITESPACE || $token == T_DOC_COMMENT || $token == T_COMMENT)
  39. {
  40. $output .= $this->renderToken();
  41. continue;
  42. }
  43. }
  44. break;
  45. }
  46. return $output;
  47. }
  48. private function renderToken($tag = 'span', $attributes = array())
  49. {
  50. $t = current($this->tokens);
  51. if (is_array($t))
  52. {
  53. list ($token, $text, $line) = $t;
  54. $name = token_name($token);
  55. }
  56. else
  57. {
  58. $token = null;
  59. $text = $t;
  60. $name = 'T_LITERAL';
  61. }
  62. $output = "";
  63. foreach (explode($this->eol, $text) as $index => $line)
  64. {
  65. $output .= $index == 0 ? "" : "</li><li>";
  66. switch ($token)
  67. {
  68. case null:
  69. case T_WHITESPACE:
  70. $output .= htmlentities($line);
  71. break;
  72. default:
  73. $output .= $this->renderContentTag($tag, htmlentities($line), array_merge(array('class'=>$name),$attributes));
  74. break;
  75. }
  76. }
  77. next($this->tokens);
  78. return $output;
  79. }
  80. private function renderDefinition()
  81. {
  82. $definition = array();
  83. $output = "";
  84. // get the definition type
  85. $tType = current($this->tokens);
  86. $output .= $this->renderToken();
  87. list ($typeToken, $text, $line) = $tType;
  88. $typeName = token_name($typeToken);
  89. // skip following non-string tokens
  90. $output .= $this->getWhiteAndComments();
  91. if ($typeToken == T_FUNCTION)
  92. {
  93. list ($token, $text, $line) = $this->curTok();
  94. $output .= "<a name='".$text."'>".$this->renderToken()."</a>";
  95. $output .= $this->getWhiteAndComments();
  96. $output .= $this->renderFormalParameter($text);
  97. $output .= $this->getWhiteAndComments();
  98. $output .= $this->renderCodeBlock($this->currentParameterScope);
  99. }
  100. else
  101. {
  102. $output .= $this->renderToken();
  103. }
  104. return $output;
  105. }
  106. private $currentParameterScope = null;
  107. function renderFormalParameter($methodName = "")
  108. {
  109. $output = "";
  110. $this->currentParameterScope = array();
  111. $t = current($this->tokens);
  112. if ($t === '(')
  113. {
  114. $output .= $this->renderToken(); //'('
  115. $depth = 1;
  116. $type = ""; //to determine scope
  117. while (true)
  118. {
  119. $t = current($this->tokens);
  120. $depth += ($t === '(' ? 1 : 0);
  121. $depth -= ($t === ')' ? 1 : 0);
  122. if ($depth == 0) break;
  123. //determine scope
  124. list ($token, $text, $line) = $this->curTok();
  125. switch ($token)
  126. {
  127. case ",":
  128. $type = "";
  129. break;
  130. case T_STRING:
  131. // a typecast
  132. $type = $text;
  133. break;
  134. case T_VARIABLE:
  135. // a variable declaration
  136. if (!empty($type) && class_exists($type))
  137. {
  138. $this->currentParameterScope[$text] = new ReflectionClass($type);
  139. }
  140. elseif ($this->reflector instanceof ReflectionClass && $this->reflector->hasMethod($methodName))
  141. {
  142. //try to figure out from docblock
  143. $comment = $this->reflector->getMethod($methodName)->getDocComment();
  144. $matches = array();
  145. if (preg_match('/\@param\s+(\w+)\s\\'.$text.'/',$comment, $matches))
  146. {
  147. if (class_exists($matches[1]))
  148. {
  149. $this->currentParameterScope[$text] = new ReflectionClass($matches[1]);
  150. }
  151. }
  152. }
  153. }
  154. $output .= $this->renderToken();
  155. }
  156. $output .= $this->renderToken(); //')'
  157. }
  158. return $output;
  159. }
  160. private static function getClassNameForMethod($scope, $method)
  161. {
  162. while ($scope instanceof ReflectionClass)
  163. {
  164. if ($scope->hasMethod($method))
  165. {
  166. return $scope->getName();
  167. }
  168. $scope = $scope->getParentClass();
  169. }
  170. return "";
  171. }
  172. public function renderStatement(array $scope = array())
  173. {
  174. list ($statementToken, $statementText, $line) = $this->curTok();
  175. $statementClass = isset($scope[$statementText]) ? $scope[$statementText] : null;
  176. $output = $this->renderToken();
  177. list ($token, $text, $line) = $this->curTok();
  178. if ($token === '(')
  179. {
  180. //method call
  181. $className = self::getClassNameForMethod($statementClass,$statementText);
  182. $statementClass = null;
  183. if (!empty($className))
  184. {
  185. // try to figure out the new scope
  186. $rm = new ReflectionMethod($className, $statementText);
  187. $comment = $rm->getDocComment();
  188. $matches = array();
  189. if (preg_match('/\@return\s+(\w+)\s/',$rm->getDocComment(), $matches))
  190. {
  191. if (class_exists($matches[1]))
  192. {
  193. $statementClass = new ReflectionClass($matches[1]);
  194. }
  195. }
  196. // wrap the statement in a link
  197. $output = $this->renderContentTag('a', $output, array('class'=>'class_link','href'=>$this->url.$className.'#'.$statementText, 'target'=>'_self'));
  198. }
  199. $output .= $this->renderFormalParameter();
  200. list ($token, $text, $line) = $this->curTok();
  201. }
  202. switch ($token)
  203. {
  204. case T_DOUBLE_COLON:
  205. if (is_null($statementClass) && $statementToken == T_STRING && class_exists($statementText))
  206. {
  207. $statementClass = new ReflectionClass($statementText);
  208. }
  209. case T_OBJECT_OPERATOR:
  210. $output .= $this->renderToken();
  211. list ($token, $text, $line) = $this->curTok();
  212. $output .= $this->renderStatement(is_null($statementClass) ? array() : array($text=>$statementClass));
  213. break;
  214. }
  215. return $output;
  216. }
  217. public function curTok()
  218. {
  219. static $line = 0;
  220. $t = current($this->tokens);
  221. if (is_array($t)){
  222. $line = $t[2];
  223. return $t;
  224. }
  225. return array($t,$t,$line);
  226. }
  227. function renderCodeBlock($scope = array())
  228. {
  229. if (is_null($scope))
  230. {
  231. $scope = array();
  232. }
  233. //determine scope
  234. $reflector = ($this->reflector instanceof ReflectionMethod ? $this->reflector->getDeclaringClass() : $this->reflector);
  235. $scope['self'] = $reflector;
  236. $scope['$this'] = $reflector;
  237. $scope['parent'] = $reflector->getParentClass();
  238. $output = "";
  239. $t = current($this->tokens);
  240. if ($t === '{')
  241. {
  242. $thisIndex = rand();
  243. $output .= "<span class='blockmarker block".$thisIndex."'>".$this->renderToken()."</span>"; //'{'
  244. while ('}' !== current($this->tokens))
  245. {
  246. list ($token, $text, $line) = $this->curTok();
  247. switch ($token)
  248. {
  249. case T_NEW:
  250. case T_INSTANCEOF:
  251. $output .= $this->renderClassName();
  252. continue 2;
  253. case T_STRING:
  254. // if (strtolower($text) !== 'parent' && strtolower($text) !== 'self') break;
  255. case T_VARIABLE:
  256. $output .= $this->renderStatement($scope);
  257. continue 2;
  258. case '{':
  259. $output .= $this->renderCodeBlock($scope);
  260. continue 2;
  261. }
  262. $output .= $this->renderToken();
  263. }
  264. $output .= "<span class='blockmarker block".$thisIndex."'>".$this->renderToken()."</span>"; //'}'
  265. }
  266. return $output;
  267. }
  268. private function renderClassName()
  269. {
  270. list ($tType, $text, $line) = current($this->tokens);
  271. $output = $this->renderToken();
  272. // skip following non-string tokens
  273. $output .= $this->getWhiteAndComments();
  274. // get the class name
  275. $tName = current($this->tokens);
  276. list ($token, $className, $line) = $tName;
  277. $output .= $this->renderContentTag('a', $this->renderToken(), array('class'=>'class_link', 'href'=>$this->url.$className.($tType == T_NEW ? '#__construct' : ''), 'target'=>'_self'));
  278. return $output;
  279. }
  280. public function render($url)
  281. {
  282. $this->setUrl($url);
  283. $output = "";
  284. $this->eol = "";
  285. $source = file_get_contents($this->reflector->getFileName());
  286. if ($i = strpos($source, chr(10)))
  287. {
  288. // unix or windows style eol
  289. $this->eol = chr(10);
  290. if (ord(substr($source,$i+1,1))==13)
  291. {
  292. // windows style eol
  293. $this->eol .= chr(13);
  294. }
  295. }
  296. else
  297. {
  298. // mac style
  299. $this->eol = chr(13);
  300. }
  301. $lines = explode($this->eol, $source);
  302. $trimmedLines = array_slice($lines, $this->reflector->getStartLine()-1,$this->reflector->getEndLine() - $this->reflector->getStartLine() + 1);
  303. $trimmedSource = implode($this->eol,$trimmedLines);
  304. $this->tokens = token_get_all("<?php\n".$trimmedSource);
  305. reset($this->tokens);
  306. next($this->tokens);
  307. while ($t = current($this->tokens))
  308. {
  309. if (is_array($t))
  310. {
  311. list ($token, $text, $line) = $t;
  312. switch ($token) {
  313. case T_CLASS:
  314. case T_INTERFACE:
  315. case T_FUNCTION:
  316. $output .= $this->renderDefinition();
  317. continue 2;
  318. case T_EXTENDS:
  319. $output .= $this->renderClassName();
  320. continue 2;
  321. }
  322. }
  323. $output .= $this->renderToken();
  324. }
  325. $lineNr = $this->reflector->getStartLine()-1;
  326. $comments = $this->reflector->getDocComment();
  327. if (!empty($comments))
  328. {
  329. $lineNr--;
  330. $comments = "<li class='doc_comment'>".$comments."</li>";
  331. }
  332. $link = $this->renderContentTag('a',basename($this->reflector->getFileName()),array('href'=>$this->url.$this->getClassReflector()->getName()));
  333. return "<ol class='code_block'><li class='filename' value='".$lineNr."'>".$link."</li>".$comments."<li>".$output."</li></ol>";
  334. }
  335. public function renderMethods($url)
  336. {
  337. $this->setUrl($url);
  338. $output = "<div class='filename'>Methods: ".basename($this->reflector->getFileName())."</div>";
  339. $methods = array();
  340. $reflector = $this->reflector instanceof ReflectionMethod ? $this->reflector->getDeclaringClass() : $this->reflector;
  341. foreach ($reflector->getMethods() as $method)
  342. {
  343. $name = $method->getName();
  344. $methods[$name] = array();
  345. $ref = $method->getDeclaringClass();
  346. while ($ref instanceof ReflectionClass && $ref->hasMethod($name))
  347. {
  348. $ref = $ref->getMethod($name)->getDeclaringClass();
  349. $methods[$name][] = $ref;
  350. $ref = $ref->getParentClass();
  351. }
  352. }
  353. // ksort($methods);
  354. foreach ($methods as $name => $classes)
  355. {
  356. $class = $classes[0];
  357. $text = "";
  358. $inherits = array();
  359. foreach ($classes as $index => $class)
  360. {
  361. $method = $class->getMethod($name);
  362. $docComment = $method->getDocComment();
  363. if ($index == 0)
  364. {
  365. $text .= ($method->isFinal() ? ' final' : '');
  366. $text .= ($method->isAbstract() ? ' abstract' : '');
  367. $text .= ($method->isPublic() ? ' public' : '');
  368. $text .= ($method->isPrivate() ? ' private' : '');
  369. $text .= ($method->isProtected() ? ' protected' : '');
  370. $text .= ($method->isStatic() ? ' static' : '');
  371. if ($class->getName() == $reflector->getName())
  372. {
  373. $text = $this->renderContentTag('a',$name,array('href'=>'#'.$name, 'target'=>'_self', 'title'=>$docComment)).$text;
  374. }
  375. else
  376. {
  377. $inherits[] = $this->renderContentTag('a',$class->getName().'::'.$name,array('href'=>$this->url.$class->getName().'#'.$name, 'target'=>'_self', 'title'=>$docComment)).$text;
  378. $text = "";
  379. }
  380. }
  381. else
  382. {
  383. $inherits[] = $this->renderContentTag('a',$class->getName().'::'.$name,array('href'=>$this->url.$class->getName().'#'.$name, 'target'=>'_self', 'title'=>$docComment));
  384. }
  385. }
  386. $t = "";
  387. while ($inherit = array_pop($inherits))
  388. {
  389. $t = $this->renderContentTag('div',$inherit.$t,array('class'=>'inherited'));
  390. }
  391. $output .= $this->renderContentTag('li',$text.$t);
  392. }
  393. return $this->renderContentTag('ul',$output,array('class'=>'methods'));
  394. }
  395. /**
  396. * Renders a HTML content tag.
  397. *
  398. * @param string $tag The tag name
  399. * @param string $content The content of the tag
  400. * @param array $attributes An array of HTML attributes to be merged with the default HTML attributes
  401. *
  402. * @param string An HTML tag string
  403. */
  404. public function renderContentTag($tag, $content = null, $attributes = array())
  405. {
  406. if (empty($tag))
  407. {
  408. return '';
  409. }
  410. return sprintf('<%s%s>%s</%s>', $tag, $this->attributesToHtml($attributes), $content, $tag);
  411. }
  412. /**
  413. * Escapes a string.
  414. *
  415. * @param string $value string to escape
  416. * @return string escaped string
  417. */
  418. static public function escapeOnce($value)
  419. {
  420. $value = is_object($value) ? $value->__toString() : (string) $value;
  421. return self::fixDoubleEscape(htmlentities($value));
  422. }
  423. /**
  424. * Fixes double escaped strings.
  425. *
  426. * @param string $escaped string to fix
  427. * @return string single escaped string
  428. */
  429. static public function fixDoubleEscape($escaped)
  430. {
  431. return preg_replace('/&amp;([a-z]+|(#\d+)|(#x[\da-f]+));/i', '&$1;', $escaped);
  432. }
  433. /**
  434. * Converts an array of attributes to its HTML representation.
  435. *
  436. * @param array $attributes An array of attributes
  437. *
  438. * @return string The HTML representation of the HTML attribute array.
  439. */
  440. public function attributesToHtml($attributes)
  441. {
  442. return implode('', array_map(array($this, 'attributesToHtmlCallback'), array_keys($attributes), array_values($attributes)));
  443. }
  444. /**
  445. * Prepares an attribute key and value for HTML representation.
  446. *
  447. * It removes empty attributes, except for the value one.
  448. *
  449. * @param string $k The attribute key
  450. * @param string $v The attribute value
  451. *
  452. * @return string The HTML representation of the HTML key attribute pair.
  453. */
  454. protected function attributesToHtmlCallback($k, $v)
  455. {
  456. return false === $v || is_null($v) || ('' === $v && 'value' != $k) ? '' : sprintf(' %s="%s"', $k, $this->escapeOnce($v));
  457. }
  458. }

Debug toolbar