sfWebDebug.class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * sfWebDebug creates debug information for easy debugging in the browser.
  11. *
  12. * @package symfony
  13. * @subpackage debug
  14. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  15. * @version SVN: $Id: sfWebDebug.class.php 13184 2008-11-20 11:27:28Z nicolas $
  16. */
  17. class sfWebDebug
  18. {
  19. protected
  20. $dispatcher = null,
  21. $log = array(),
  22. $maxPriority = 1000,
  23. $types = array(),
  24. $lastTimeLog = -1;
  25. public function __construct(sfEventDispatcher $dispatcher)
  26. {
  27. $this->dispatcher = $dispatcher;
  28. $this->dispatcher->connect('view.cache.filter_content', array($this, 'decorateContentWithDebug'));
  29. }
  30. /**
  31. * Logs a message to the web debug toolbar.
  32. *
  33. * @param array $logEntry An array of parameter
  34. *
  35. * @see sfWebDebugLogger
  36. */
  37. public function log($logEntry)
  38. {
  39. // elapsed time
  40. if ($this->lastTimeLog == -1)
  41. {
  42. $this->lastTimeLog = sfConfig::get('sf_timer_start');
  43. }
  44. $this->lastTimeLog = microtime(true);
  45. // update max priority
  46. if ($logEntry['priority'] < $this->maxPriority)
  47. {
  48. $this->maxPriority = $logEntry['priority'];
  49. }
  50. // update types
  51. if (!isset($this->types[$logEntry['type']]))
  52. {
  53. $this->types[$logEntry['type']] = 1;
  54. }
  55. else
  56. {
  57. ++$this->types[$logEntry['type']];
  58. }
  59. $this->log[] = $logEntry;
  60. }
  61. /**
  62. * Loads helpers needed for the web debug toolbar.
  63. */
  64. protected function loadHelpers()
  65. {
  66. sfLoader::loadHelpers(array('Helper', 'Url', 'Asset', 'Tag'));
  67. }
  68. /**
  69. * Formats a log line.
  70. *
  71. * @param string $logLine The log line to format
  72. *
  73. * @return string The formatted log lin
  74. */
  75. protected function formatLogLine($logLine)
  76. {
  77. static $constants;
  78. if (!$constants)
  79. {
  80. foreach (array('sf_app_dir', 'sf_root_dir', 'sf_symfony_lib_dir') as $constant)
  81. {
  82. $constants[realpath(sfConfig::get($constant)).DIRECTORY_SEPARATOR] = $constant.DIRECTORY_SEPARATOR;
  83. }
  84. }
  85. // escape HTML
  86. $logLine = htmlspecialchars($logLine, ENT_QUOTES, sfConfig::get('sf_charset'));
  87. // replace constants value with constant name
  88. $logLine = str_replace(array_keys($constants), array_values($constants), $logLine);
  89. $logLine = sfToolkit::pregtr($logLine, array('/&quot;(.+?)&quot;/s' => '"<span class="sfWebDebugLogInfo">\\1</span>"',
  90. '/^(.+?)\(\)\:/S' => '<span class="sfWebDebugLogInfo">\\1()</span>:',
  91. '/line (\d+)$/' => 'line <span class="sfWebDebugLogInfo">\\1</span>'));
  92. // special formatting for SQL lines
  93. $logLine = preg_replace('/\b(SELECT|FROM|AS|LIMIT|ASC|COUNT|DESC|WHERE|LEFT JOIN|INNER JOIN|RIGHT JOIN|ORDER BY|GROUP BY|IN|LIKE|DISTINCT|DELETE|INSERT|INTO|VALUES)\b/', '<span class="sfWebDebugLogInfo">\\1</span>', $logLine);
  94. // remove username/password from DSN
  95. if (strpos($logLine, 'DSN') !== false)
  96. {
  97. $logLine = preg_replace("/=&gt;\s+'?[^'\s,]+'?/", "=&gt; '****'", $logLine);
  98. }
  99. return $logLine;
  100. }
  101. /**
  102. * Returns the web debug toolbar as HTML.
  103. *
  104. * @return string The web debug toolbar HTML
  105. */
  106. public function getResults()
  107. {
  108. if (!sfConfig::get('sf_web_debug'))
  109. {
  110. return '';
  111. }
  112. $this->loadHelpers();
  113. $result = '';
  114. // max priority
  115. $maxPriority = '';
  116. if (sfConfig::get('sf_logging_enabled'))
  117. {
  118. $maxPriority = $this->getPriority($this->maxPriority);
  119. }
  120. $logs = '';
  121. $sqlLogs = array();
  122. if (sfConfig::get('sf_logging_enabled'))
  123. {
  124. $logs = '<table class="sfWebDebugLogs">
  125. <tr>
  126. <th>#</th>
  127. <th>type</th>
  128. <th>message</th>
  129. </tr>'."\n";
  130. $line_nb = 0;
  131. foreach ($this->log as $logEntry)
  132. {
  133. $log = $logEntry['message'];
  134. $priority = $this->getPriority($logEntry['priority']);
  135. if (strpos($type = $logEntry['type'], 'sf') === 0)
  136. {
  137. $type = substr($type, 2);
  138. }
  139. // xdebug information
  140. $debug_info = '';
  141. if ($logEntry['debugStack'])
  142. {
  143. $debug_info .= '&nbsp;<a href="#" onclick="sfWebDebugToggle(\'debug_'.$line_nb.'\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/toggle.gif').'</a><div class="sfWebDebugDebugInfo" id="debug_'.$line_nb.'" style="display:none">';
  144. foreach ($logEntry['debugStack'] as $i => $logLine)
  145. {
  146. $debug_info .= '#'.$i.' &raquo; '.$this->formatLogLine($logLine).'<br/>';
  147. }
  148. $debug_info .= "</div>\n";
  149. }
  150. // format log
  151. $log = $this->formatLogLine($log);
  152. // sql queries log
  153. if (preg_match('/execute(?:Query|Update).+?\:\s+(.+)$/s', $log, $match))
  154. {
  155. $sqlLogs[] = $match[1];
  156. }
  157. ++$line_nb;
  158. $logs .= sprintf("<tr class='sfWebDebugLogLine sfWebDebug%s %s'><td class=\"sfWebDebugLogNumber\">%s</td><td class=\"sfWebDebugLogType\">%s&nbsp;%s</td><td>%s%s</td></tr>\n",
  159. ucfirst($priority),
  160. $logEntry['type'],
  161. $line_nb,
  162. image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/'.$priority.'.png'),
  163. $type,
  164. $log,
  165. $debug_info
  166. );
  167. }
  168. $logs .= '</table>';
  169. ksort($this->types);
  170. $types = array();
  171. foreach ($this->types as $type => $nb)
  172. {
  173. $types[] = '<a href="#" onclick="sfWebDebugToggleMessages(\''.$type.'\'); return false;">'.$type.'</a>';
  174. }
  175. }
  176. // ignore cache link
  177. $cacheLink = '';
  178. if (sfConfig::get('sf_debug') && sfConfig::get('sf_cache'))
  179. {
  180. $selfUrl = $_SERVER['PHP_SELF'].((strpos($_SERVER['PHP_SELF'], '_sf_ignore_cache') === false) ? '?_sf_ignore_cache=1' : '');
  181. $cacheLink = '<li><a href="'.$selfUrl.'" title="reload and ignore cache">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/reload.png').'</a></li>';
  182. }
  183. // logging information
  184. $logLink = '';
  185. if (sfConfig::get('sf_logging_enabled'))
  186. {
  187. $logLink = '<li><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugLog\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/comment.png').' logs &amp; msgs</a></li>';
  188. }
  189. // database information
  190. $dbInfo = '';
  191. $dbInfoDetails = '';
  192. if ($sqlLogs)
  193. {
  194. $dbInfo = '<li><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugDatabaseDetails\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/database.png').' '.count($sqlLogs).'</a></li>';
  195. $dbInfoDetails = '
  196. <div id="sfWebDebugDatabaseLogs">
  197. <ol><li>'.implode("</li>\n<li>", $sqlLogs).'</li></ol>
  198. </div>
  199. ';
  200. }
  201. // memory used
  202. $memoryInfo = '';
  203. if (sfConfig::get('sf_debug') && function_exists('memory_get_usage'))
  204. {
  205. $totalMemory = sprintf('%.1f', (memory_get_usage() / 1024));
  206. $memoryInfo = '<li>'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/memory.png').' '.$totalMemory.' KB</li>';
  207. }
  208. // total time elapsed
  209. $timeInfo = '';
  210. if (sfConfig::get('sf_debug'))
  211. {
  212. $totalTime = (microtime(true) - sfConfig::get('sf_timer_start')) * 1000;
  213. $totalTime = sprintf(($totalTime <= 1) ? '%.2f' : '%.0f', $totalTime);
  214. $timeInfo = '<li class="last"><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugTimeDetails\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/time.png').' '.$totalTime.' ms</a></li>';
  215. }
  216. // timers
  217. $timeInfoDetails = '<table class="sfWebDebugLogs" style="width: 300px"><tr><th>type</th><th>calls</th><th>time (ms)</th><th>time (%)</th></tr>';
  218. foreach (sfTimerManager::getTimers() as $name => $timer)
  219. {
  220. $timeInfoDetails .= sprintf('<tr><td class="sfWebDebugLogType">%s</td><td class="sfWebDebugLogNumber" style="text-align: right">%d</td><td style="text-align: right">%.2f</td><td style="text-align: right">%d</td></tr>', $name, $timer->getCalls(), $timer->getElapsedTime() * 1000, $timer->getElapsedTime() * 1000 * 100 / $totalTime );
  221. }
  222. $timeInfoDetails .= '</table>';
  223. // logs
  224. $logInfo = '';
  225. if (sfConfig::get('sf_logging_enabled'))
  226. {
  227. $logInfo .= '
  228. <ul id="sfWebDebugLogMenu">
  229. <li><a href="#" onclick="sfWebDebugToggleAllLogLines(true, \'sfWebDebugLogLine\'); return false;">[all]</a></li>
  230. <li><a href="#" onclick="sfWebDebugToggleAllLogLines(false, \'sfWebDebugLogLine\'); return false;">[none]</a></li>
  231. <li><a href="#" onclick="sfWebDebugShowOnlyLogLines(\'info\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/info.png').'</a></li>
  232. <li><a href="#" onclick="sfWebDebugShowOnlyLogLines(\'warning\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/warning.png').'</a></li>
  233. <li><a href="#" onclick="sfWebDebugShowOnlyLogLines(\'error\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/error.png').'</a></li>
  234. <li>'.implode("</li>\n<li>", $types).'</li>
  235. </ul>
  236. <div id="sfWebDebugLogLines">'.$logs.'</div>
  237. ';
  238. }
  239. $result .= '
  240. <div id="sfWebDebug">
  241. <div id="sfWebDebugBar" class="sfWebDebug'.ucfirst($maxPriority).'">
  242. <a href="#" onclick="sfWebDebugToggleMenu(); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/sf.png').'</a>
  243. <ul id="sfWebDebugDetails" class="menu">
  244. <li>'.SYMFONY_VERSION.'</li>
  245. <li><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugConfig\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/config.png').' vars &amp; config</a></li>
  246. '.$cacheLink.'
  247. '.$logLink.'
  248. '.$dbInfo.'
  249. '.$memoryInfo.'
  250. '.$timeInfo.'
  251. </ul>
  252. <a href="#" onclick="document.getElementById(\'sfWebDebug\').style.display=\'none\'; return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/close.png').'</a>
  253. </div>
  254. <div id="sfWebDebugLog" class="sfWebDebugTop" style="display: none"><h1>Log and debug messages</h1>'.$logInfo.'</div>
  255. <div id="sfWebDebugConfig" class="sfWebDebugTop" style="display: none"><h1>Configuration and request variables</h1>'.$this->getCurrentConfigAsHtml().'</div>
  256. <div id="sfWebDebugDatabaseDetails" class="sfWebDebugTop" style="display: none"><h1>SQL queries</h1>'.$dbInfoDetails.'</div>
  257. <div id="sfWebDebugTimeDetails" class="sfWebDebugTop" style="display: none"><h1>Timers</h1>'.$timeInfoDetails.'</div>
  258. </div>
  259. ';
  260. return $result;
  261. }
  262. /**
  263. * Returns the current configuration as HTML.
  264. *
  265. * @return string The current configuration as HTML
  266. */
  267. protected function getCurrentConfigAsHtml()
  268. {
  269. $config = array(
  270. 'debug' => sfConfig::get('sf_debug') ? 'on' : 'off',
  271. 'xdebug' => extension_loaded('xdebug') ? 'on' : 'off',
  272. 'logging' => sfConfig::get('sf_logging_enabled') ? 'on' : 'off',
  273. 'cache' => sfConfig::get('sf_cache') ? 'on' : 'off',
  274. 'compression' => sfConfig::get('sf_compressed') ? 'on' : 'off',
  275. 'tokenizer' => function_exists('token_get_all') ? 'on' : 'off',
  276. 'eaccelerator' => extension_loaded('eaccelerator') && ini_get('eaccelerator.enable') ? 'on' : 'off',
  277. 'apc' => extension_loaded('apc') && ini_get('apc.enabled') ? 'on' : 'off',
  278. 'xcache' => extension_loaded('xcache') && ini_get('xcache.cacher') ? 'on' : 'off',
  279. );
  280. $result = '<ul id="sfWebDebugConfigSummary">';
  281. foreach ($config as $key => $value)
  282. {
  283. $result .= '<li class="is'.$value.($key == 'xcache' ? ' last' : '').'">'.$key.'</li>';
  284. }
  285. $result .= '</ul>';
  286. $context = sfContext::getInstance();
  287. $result .= $this->formatArrayAsHtml('request', sfDebug::requestAsArray($context->getRequest()));
  288. $result .= $this->formatArrayAsHtml('response', sfDebug::responseAsArray($context->getResponse()));
  289. $result .= $this->formatArrayAsHtml('user', sfDebug::userAsArray($context->getUser()));
  290. $result .= $this->formatArrayAsHtml('settings', sfDebug::settingsAsArray());
  291. $result .= $this->formatArrayAsHtml('globals', sfDebug::globalsAsArray());
  292. $result .= $this->formatArrayAsHtml('php', sfDebug::phpInfoAsArray());
  293. $result .= $this->formatArrayAsHtml('symfony', sfDebug::symfonyInfoAsArray());
  294. return $result;
  295. }
  296. /**
  297. * Converts an array to HTML.
  298. *
  299. * @param string $id The identifier to use
  300. * @param array $values The array of values
  301. *
  302. * @return string An HTML string
  303. */
  304. protected function formatArrayAsHtml($id, $values)
  305. {
  306. $id = ucfirst(strtolower($id));
  307. $content = '
  308. <h2>'.$id.' <a href="#" onclick="sfWebDebugToggle(\'sfWebDebug'.$id.'\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/toggle.gif').'</a></h2>
  309. <div id="sfWebDebug'.$id.'" style="display: none"><pre>'.htmlspecialchars(sfYaml::dump(self::removeObjects($values)), ENT_QUOTES, sfConfig::get('sf_charset')).'</pre></div>
  310. ';
  311. return $content;
  312. }
  313. /**
  314. * Listens to the 'view.cache.filter_content' event to decorate a chunk of HTML with cache information.
  315. *
  316. * @param sfEvent $event A sfEvent instance
  317. * @param string $content The HTML content
  318. *
  319. * @return string The decorated HTML string
  320. */
  321. public function decorateContentWithDebug(sfEvent $event, $content)
  322. {
  323. // don't decorate if not html or if content is null
  324. if (!sfConfig::get('sf_web_debug') || !$content || false === strpos($event['response']->getContentType(), 'html'))
  325. {
  326. return $content;
  327. }
  328. $viewCacheManager = $event->getSubject();
  329. sfLoader::loadHelpers(array('Helper', 'Url', 'Asset', 'Tag'));
  330. $bgColor = $event['new'] ? '#9ff' : '#ff9';
  331. $lastModified = $viewCacheManager->getLastModified($event['uri']);
  332. $id = md5($event['uri']);
  333. return '
  334. <div id="main_'.$id.'" class="sfWebDebugActionCache" style="border: 1px solid #f00">
  335. <div id="sub_main_'.$id.'" class="sfWebDebugCache" style="background-color: '.$bgColor.'; border-right: 1px solid #f00; border-bottom: 1px solid #f00;">
  336. <div style="height: 16px; padding: 2px"><a href="#" onclick="sfWebDebugToggle(\'sub_main_info_'.$id.'\'); return false;"><strong>cache information</strong></a>&nbsp;<a href="#" onclick="sfWebDebugToggle(\'sub_main_'.$id.'\'); document.getElementById(\'main_'.$id.'\').style.border = \'none\'; return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/close.png').'</a>&nbsp;</div>
  337. <div style="padding: 2px; display: none" id="sub_main_info_'.$id.'">
  338. [uri]&nbsp;'.htmlspecialchars($event['uri'], ENT_QUOTES, sfConfig::get('sf_charset')).'<br />
  339. [life&nbsp;time]&nbsp;'.$viewCacheManager->getLifeTime($event['uri']).'&nbsp;seconds<br />
  340. [last&nbsp;modified]&nbsp;'.(time() - $lastModified).'&nbsp;seconds<br />
  341. &nbsp;<br />&nbsp;
  342. </div>
  343. </div><div>
  344. '.$content.'
  345. </div></div>
  346. ';
  347. }
  348. /**
  349. * Converts a priority value to a string.
  350. *
  351. * @param integer $value The priority value
  352. *
  353. * @return string The priority as a string
  354. */
  355. protected function getPriority($value)
  356. {
  357. if ($value >= sfLogger::INFO)
  358. {
  359. return 'info';
  360. }
  361. else if ($value >= sfLogger::WARNING)
  362. {
  363. return 'warning';
  364. }
  365. else
  366. {
  367. return 'error';
  368. }
  369. }
  370. static protected function removeObjects($values)
  371. {
  372. $nvalues = array();
  373. foreach ($values as $key => $value)
  374. {
  375. if (is_array($value))
  376. {
  377. $nvalues[$key] = self::removeObjects($value);
  378. }
  379. else if (is_object($value))
  380. {
  381. $nvalues[$key] = sprintf('%s Object()', get_class($value));
  382. }
  383. else
  384. {
  385. $nvalues[$key] = $value;
  386. }
  387. }
  388. return $nvalues;
  389. }
  390. }