diff --git a/XWebDebugRouter.php b/XWebDebugRouter.php index e1335e4..3e60af8 100644 --- a/XWebDebugRouter.php +++ b/XWebDebugRouter.php @@ -1,5 +1,14 @@ /', '', $result, 1); + self::$_output = preg_replace('/<\\?php/', '', $result, 1); } return self::$_output; @@ -175,7 +184,42 @@ private static function dumpInternal($var, $level) { * Render debug panel to document using view and configuration parameters */ class yiiDebugPanel { - public function render($items = array(), $config = array()) { + protected static $instance = null; + protected static $output = null; + protected static $config = null; + protected static $items; + public static $isRendered = false; + + public function setConfig($config) { + self::$config['alignLeft'] = !empty($config['alignLeft']); + self::$config['opaque'] = !empty($config['opaque']); + self::$config['fixedPos'] = !empty($config['fixedPos']); + self::$config['collapsed'] = !empty($config['collapsed']); + } + + public function setItems($items) { + self::$items = $items; + } + + public function getItems() { + return self::$items; + } + + public static function getInstance() { + if( null === self::$instance ) { + self::$instance = new self; + } + return self::$instance; + } + + protected function __clone() {} + + protected function __construct() {} + + public static function render() { + $items = self::$items; + $config = self::$config; + $msg = "Run rendering...\n"; $alignLeft = !empty($config['alignLeft']); $opaque = !empty($config['opaque']); @@ -183,8 +227,9 @@ public function render($items = array(), $config = array()) { $collapsed = !empty($config['collapsed']); $viewFile = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'debugPanel.php'; - include(Yii::app()->findLocalizedFile($viewFile, 'en')); + require(Yii::app()->findLocalizedFile($viewFile, 'en')); } + } /** @@ -198,26 +243,37 @@ public static function timestampToTime($timestamp) { return date('H:i:s.', $timestamp) . (int)(($timestamp - (int)$timestamp) * 1000000); } - public static function render($items) { + public static function render($items, $config=null) { $result = ''; $odd = true; + $categories = array(); foreach ($items as $item) { list($message, $level, $category, $timestamp) = $item; + if( !in_array($category, $categories) ) + $categories[] = $category; $message = CHtml::encode($message); // put each source file on its own line $message = implode("
", explode("\n", $message)); $time = yiiDebugTrace::timestampToTime($timestamp); $odd = !$odd; - $result .= '' . $time . '' . $level . '' . $category . '' . $message . ''; + $result .= '' . $time . '' . $level . '' . $category . '' . $message . ''; } if ($result !== '') { $result = '' . $result . ''; } - $result = '' . $result . '
TimeLevelCategoryMessage
'; + $category_controls = ''; + foreach($categories as $category) { + $category_controls .= '
'.$category.'
'; + } + + $comment = ''; + if(!is_null($config) && isset($config['comment'])) + $comment = '
'.$config['comment'].'
'.PHP_EOL; + $result = $comment.$category_controls.PHP_EOL.'' . $result . '
TimeLevelCategoryMessage
'; return $result; } @@ -230,68 +286,197 @@ public static function getInfo($data, $config = null) { } class yiiDebugDB extends yiiDebugClass { + static $count = 0; + static $cached = 0; + static $items = array(); + static $minimized = false; + static $information_schema_count = 0; + static $queryCount = 0; + static $activeRecordCount = 0; + static $config = ''; + static $textHighlighter = null; + static $highlightSql = true; + public static function getInfo($data, $config = null) { parent::getInfo($data); $result = array(); $result['panelTitle'] = 'Database Queries'; - - $count = 0; - $cached = 0; - $items = array(); + if( self::$minimized ) $result['panelTitle'].=' ('.self::$information_schema_count.' information_schema Queries)'; foreach ($data as $row) { + switch($row[2]) { + case 'system.db.ar.CActiveRecord' : self::$activeRecordCount++; break; + } if (substr($row[2], 0, 9) == 'system.db') { - $items[] = $row; - - if ($row[2] == 'system.db.CDbCommand') { + if ($row[2] == 'system.db.CDbCommand') { if (strpos($row[0], 'Querying SQL') !== false) { - $count++; + $row[0] = str_replace('Querying SQL:', 'Querying SQL:'.PHP_EOL,$row[0]); + self::$count++; } if (strpos($row[0], 'Query result found in cache') !== false) { - $cached++; + self::$cached++; + } + + if( strpos($row[0], '[INFORMATION_SCHEMA]') !== false ) { + $row[2] = $row[2].'_information'; // we modify the category + self::$information_schema_count++; + // don't log information schema if minimized is enabled + if( self::$minimized ) + return $result; + } else { + // Query Count contains only Application Queries, no DBAL (information_schema) + self::$queryCount++; } } + $row = self::formatLogEntry($row); + self::$items[] = $row; } } - if (count($items) > 0) { - $result['content'] = yiiDebugTrace::render($items); + self::$config['comment'] = 'Operations: '.self::$count.' | Application Queries: '.self::$queryCount.' | DBAL Queries: '.self::$information_schema_count.' | ActiveRecords: '.self::$activeRecordCount; + + if (count(self::$items) > 0) { + $result['content'] = yiiDebugDB::render(self::$items, self::$config); } - $result['title'] = 'DB Query: ' . $count; + $result['title'] = 'DB Ops: ' . self::$count; - if ($cached > 0) { - $result['title'] .= " ($cached found in cache)"; + if (self::$cached > 0) { + $result['title'] .= " (".self::$cached." cached)"; } return $result; } + + public static function render($items, $config=null) { + $result = ''; + $odd = true; + $categories = array(); + + foreach ($items as $item) { + list($message, $level, $category, $timestamp) = $item; + if( !in_array($category, $categories) ) + $categories[] = $category; + //$message = CHtml::encode($message); + // put each source file on its own line + $message = implode("
", explode("\n", $message)); + $time = yiiDebugTrace::timestampToTime($timestamp); + $odd = !$odd; + + $result .= '' . $time . '' . $level . '' . $category . '' . $message . ''; + } + + if ($result !== '') { + $result = '' . $result . ''; + } + + $category_controls = ''; + foreach($categories as $category) { + $category_controls .= '
'.$category.'
'; + } + + $comment = ''; + if(!is_null($config) && isset($config['comment'])) + $comment = '
'.$config['comment'].'
'.PHP_EOL; + $result = $comment.$category_controls.PHP_EOL.'' . $result . '
TimeLevelCategoryMessage
'; + + return $result; + } + + /** + * Format log entry + * + * @param array $entry + * @return array + */ + public static function formatLogEntry(array $entry) + { + // extract query from the entry + $queryString = $entry[0]; + $sqlStart = strpos($queryString, "Querying SQL:"); + // if we have no query we return the whole entry as is + if($sqlStart === FALSE) return $entry; + $sqlStart += 14; + $sqlEnd = strpos($queryString , "\nin D:"); + //$sqlEnd = strlen($queryString); + $sqlLength = $sqlEnd - $sqlStart; + + $beforeQuery = substr($queryString, 0, $sqlStart); + $afterQuery = substr($queryString, $sqlEnd); + $queryString = substr($queryString, $sqlStart, $sqlLength); + + if (false !== strpos($queryString, '. Bound with ')) + { + list($query, $params) = explode('. Bound with ', $queryString); + + $binds = array(); + $matchResult = preg_match_all("/(?[a-z0-9\.\_\-\:]+)=(?[\d\.e\-\+]+|''|'.+?(?highlight($entry[0]); + $entry[0] = $entry[0].$afterQuery; + $entry[0] = $entry[0].PHP_EOL.'Origin: '.str_replace('Bound with',PHP_EOL.'Bound with',$queryString); + } + + $entry[0] = strip_tags($entry[0], '
,'); + return $entry; + } + + /** + * @return CTextHighlighter the text highlighter + */ + private static function getTextHighlighter() + { + if (null === self::$textHighlighter) + { + self::$textHighlighter = Yii::createComponent(array( + 'class' => 'CTextHighlighter', + 'language' => 'sql', + 'showLineNumbers' => false, + )); + } + return self::$textHighlighter; + } + } class yiiDebugTrace extends yiiDebugClass { + static $items = array(); + public static function getInfo($data, $config = null) { parent::getInfo($data); $result = array(); $result['title'] = 'App Log'; $result['panelTitle'] = 'Application Log'; - $items = array(); foreach ($data as $row) { if (substr($row[2], 0, 9) != 'system.db') - $items[] = $row; + self::$items[] = $row; } - if (count($items) > 0) { - $result['content'] = yiiDebugTrace::render($items); + if (count(self::$items) > 0) { + $result['content'] = yiiDebugTrace::render(self::$items); } return $result; } } - class yiiDebugTime extends yiiDebugClass { public static function getInfo($data, $config = null) { parent::getInfo($data); @@ -319,7 +504,6 @@ public static function getInfo($data, $config = null) { return $result; } } - class yiiDebugConfig extends yiiDebugClass { public static $yamlStyle = false; @@ -427,7 +611,7 @@ public static function requestAsArray() { public static function yiiAppAsArray() { $result = Yii::app(); - return Yii::app(); + return $result; } protected static function formatArrayAsHtml($id, $values, $highlight = false) { @@ -465,6 +649,7 @@ public static function getInfo($data, $config = null) { * and renders self output to the end of server output (after tag of document). */ class XWebDebugRouter extends CLogRoute { + private $isRegistered = false; public $allowedIPs = array('127.0.0.1', '::1'); // IPv4 and IPv6 localhost addresses private $_config = array( @@ -479,9 +664,13 @@ class XWebDebugRouter extends CLogRoute { public function init() { parent::init(); - if ( !empty($this->_config['dbProfiling']) && $this->_isLoggerAllowed() ) { - Yii::app()->db->enableProfiling = true; - Yii::app()->db->enableParamLogging = true; + if( $this->_isLoggerAllowed() ) { + if ( !empty($this->_config['dbProfiling']) ) { + Yii::app()->db->enableProfiling = true; + Yii::app()->db->enableParamLogging = true; + } + $panel = yiiDebugPanel::getInstance(); + $panel->setConfig($this->_config); } } @@ -507,8 +696,30 @@ public function processLogs($logs) { $items[] = yiiDebugDB::getInfo($logs); $items[] = yiiDebugTrace::getInfo($logs); - $panel = new yiiDebugPanel(); - $panel->render($items, $this->_config); + // new code + $panel = yiiDebugPanel::getInstance(); + $panel->setConfig($this->_config); + + $panel->setItems($items); + + // If any Logger sets autoFlush to 1 (like PHPConsole) we don't want to render here, instead we wait for onEndRequest + // actually we need to find out if we render more than once - onEndRequest would be great actually + // probably another way would be to see if log count is same as autoflush + $autoFlush = Yii::getLogger()->autoFlush; + if( $autoFlush == 1 ) { + // we only want to register the event if it's not done already + if(!($this->isRegistered)) { + // this event is not raised, without phpconsole (or autoflush before end) - doesn't make any sense + // even registering it in init doesn't work + Yii::app()->onEndRequest = array('YiiDebugPanel', 'render'); + $this->isRegistered = true; + } + } else { + // without php console we have to render here, as processlogs happens after onEndRequest + // problem - with php console it renders here twice (on first entry and later on endrequest event) + $panel->render($items, $this->_config); + } + // end new code } public function setConfig($config) { diff --git a/views/debugPanel.php b/views/debugPanel.php index df275e2..3f797c2 100644 --- a/views/debugPanel.php +++ b/views/debugPanel.php @@ -1,290 +1,306 @@ - - -
- - - - -
    - -
  • [  - '.$item['title'].'' : $item['title'] ?> -  ] -
  • - -
- - - - -
- -
- - - - - -
- - \ No newline at end of file