From 4a29e38cea171f1d062a488d32878d70c5ab4141 Mon Sep 17 00:00:00 2001 From: maximebf Date: Tue, 18 Jun 2013 12:47:22 +0900 Subject: [PATCH] added Exceptions and PDO collectors --- demo/demo_ajax_exception.php | 2 +- demo/demo_pdo.php | 34 +++ src/DebugBar/Bridge/MonologCollector.php | 11 +- src/DebugBar/DataCollector/DataCollector.php | 30 ++- .../DataCollector/DependencyCollector.php | 30 --- .../DataCollector/ExceptionsCollector.php | 100 +++++++++ .../DataCollector/MemoryCollector.php | 16 +- .../DataCollector/MessagesCollector.php | 13 +- .../DataCollector/PDO/PDOCollector.php | 65 ++++++ .../DataCollector/PDO/TraceablePDO.php | 211 ++++++++++++++++++ .../PDO/TraceablePDOStatement.php | 81 +++++++ .../DataCollector/PDO/TracedStatement.php | 189 ++++++++++++++++ .../DataCollector/RequestDataCollector.php | 2 +- .../DataCollector/TimeDataCollector.php | 15 +- src/DebugBar/JavascriptRenderer.php | 8 +- src/DebugBar/StandardDebugBar.php | 2 + web/debugbar.css | 101 +++++++++ web/debugbar.js | 23 +- web/widgets.js | 86 +++++++ 19 files changed, 948 insertions(+), 71 deletions(-) create mode 100644 demo/demo_pdo.php delete mode 100644 src/DebugBar/DataCollector/DependencyCollector.php create mode 100644 src/DebugBar/DataCollector/ExceptionsCollector.php create mode 100644 src/DebugBar/DataCollector/PDO/PDOCollector.php create mode 100644 src/DebugBar/DataCollector/PDO/TraceablePDO.php create mode 100644 src/DebugBar/DataCollector/PDO/TraceablePDOStatement.php create mode 100644 src/DebugBar/DataCollector/PDO/TracedStatement.php diff --git a/demo/demo_ajax_exception.php b/demo/demo_ajax_exception.php index bf7884e..31b623a 100644 --- a/demo/demo_ajax_exception.php +++ b/demo/demo_ajax_exception.php @@ -5,7 +5,7 @@ include 'bootstrap.php'; try { throw new Exception('Something failed!'); } catch (Exception $e) { - $debugbar['messages']->addMessage($e, 'error'); + $debugbar['exceptions']->addException($e); } ?> diff --git a/demo/demo_pdo.php b/demo/demo_pdo.php new file mode 100644 index 0000000..91f0020 --- /dev/null +++ b/demo/demo_pdo.php @@ -0,0 +1,34 @@ +addCollector(new PDOCollector($pdo)); + +$pdo->exec('create table users (name varchar)'); +$stmt = $pdo->prepare('insert into users (name) values (?)'); +$stmt->execute(array('foo')); +$stmt->execute(array('bar')); + +$users = $pdo->query('select * from users')->fetchAll(); +$stmt = $pdo->prepare('select * from users where name=?'); +$stmt->execute(array('foo')); +$foo = $stmt->fetch(); + +$pdo->exec('delete from titi'); + +?> + + + renderHead() ?> + + +

PhpDebugBar PDO Demo

+ render(); + ?> + + diff --git a/src/DebugBar/Bridge/MonologCollector.php b/src/DebugBar/Bridge/MonologCollector.php index 28e0ac3..860371c 100644 --- a/src/DebugBar/Bridge/MonologCollector.php +++ b/src/DebugBar/Bridge/MonologCollector.php @@ -35,7 +35,10 @@ class MonologCollector extends AbstractProcessingHandler implements DataCollecto public function collect() { - return $this->records; + return array( + 'count' => count($this->records), + 'records' => $this->records + ); } public function getName() @@ -48,8 +51,12 @@ class MonologCollector extends AbstractProcessingHandler implements DataCollecto return array( "logs" => array( "widget" => "PhpDebugBar.Widgets.MessagesWidget", - "map" => "monolog", + "map" => "monolog.records", "default" => "[]" + ), + "logs:badge" => array( + "map" => "monolog.count", + "default" => "null" ) ); } diff --git a/src/DebugBar/DataCollector/DataCollector.php b/src/DebugBar/DataCollector/DataCollector.php index 21806d7..21e4204 100644 --- a/src/DebugBar/DataCollector/DataCollector.php +++ b/src/DebugBar/DataCollector/DataCollector.php @@ -21,8 +21,36 @@ abstract class DataCollector implements DataCollectorInterface * @param mixed $var * @return string */ - public function varToString($var) + public function formatVar($var) { return print_r($var, true); } + + /** + * Transforms a duration in seconds in a readable string + * + * @param float $value + * @return string + */ + public function formatDuration($seconds) + { + return round($seconds * 1000) . 'ms'; + } + + /** + * Transforms a size in bytes to a human readable string + * + * @param string $size + * @param integer $precision + * @return string + */ + public function formatBytes($size, $precision = 2) + { + if ($size === 0) { + return "0B"; + } + $base = log($size) / log(1024); + $suffixes = array('', 'KB', 'MB', 'GB', 'TB'); + return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; + } } diff --git a/src/DebugBar/DataCollector/DependencyCollector.php b/src/DebugBar/DataCollector/DependencyCollector.php deleted file mode 100644 index 9159a83..0000000 --- a/src/DebugBar/DataCollector/DependencyCollector.php +++ /dev/null @@ -1,30 +0,0 @@ -exceptions[] = $e; + } + + /** + * Returns the list of exceptions being profiled + * + * @return array[Exception] + */ + public function getExceptions() + { + return $this->exceptions; + } + + /** + * {@inheritDoc} + */ + public function collect() + { + return array( + 'count' => count($this->exceptions), + 'exceptions' => array_map(array($this, 'formatExceptionData'), $this->exceptions) + ); + } + + /** + * Returns exception data as an array + * + * @param Exception $e + * @return array + */ + public function formatExceptionData(Exception $e) + { + $lines = file($e->getFile()); + $start = $e->getLine() - 4; + $lines = array_slice($lines, $start < 0 ? 0 : $start, 7); + + return array( + 'type' => get_class($e), + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'surrounding_lines' => $lines + ); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'exceptions'; + } + + /** + * {@inheritDoc} + */ + public function getWidgets() + { + return array( + 'exceptions' => array( + 'widget' => 'PhpDebugBar.Widgets.ExceptionsWidget', + 'map' => 'exceptions.exceptions', + 'default' => '[]' + ), + 'exceptions:badge' => array( + 'map' => 'exceptions.count', + 'default' => 'null' + ) + ); + } +} diff --git a/src/DebugBar/DataCollector/MemoryCollector.php b/src/DebugBar/DataCollector/MemoryCollector.php index 197d97f..99b4d41 100644 --- a/src/DebugBar/DataCollector/MemoryCollector.php +++ b/src/DebugBar/DataCollector/MemoryCollector.php @@ -35,20 +35,6 @@ class MemoryCollector extends DataCollector implements Renderable $this->peakUsage = memory_get_peak_usage(true); } - /** - * Transforms a size in bytes to a human readable string - * - * @param string $size - * @param integer $precision - * @return string - */ - public function toReadableString($size, $precision = 2) - { - $base = log($size) / log(1024); - $suffixes = array('', 'KB', 'MB', 'GB', 'TB'); - return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; - } - /** * {@inheritDoc} */ @@ -57,7 +43,7 @@ class MemoryCollector extends DataCollector implements Renderable $this->updatePeakUsage(); return array( 'peak_usage' => $this->peakUsage, - 'peak_usage_str' => $this->toReadableString($this->peakUsage) + 'peak_usage_str' => $this->formatBytes($this->peakUsage) ); } diff --git a/src/DebugBar/DataCollector/MessagesCollector.php b/src/DebugBar/DataCollector/MessagesCollector.php index 92b88d4..3cc97c1 100644 --- a/src/DebugBar/DataCollector/MessagesCollector.php +++ b/src/DebugBar/DataCollector/MessagesCollector.php @@ -28,7 +28,7 @@ class MessagesCollector extends DataCollector implements Renderable public function addMessage($message, $label = 'info') { $this->messages[] = array( - 'message' => $this->varToString($message), + 'message' => $this->formatVar($message), 'is_string' => is_string($message), 'label' => $label, 'time' => microtime(true), @@ -52,7 +52,10 @@ class MessagesCollector extends DataCollector implements Renderable */ public function collect() { - return $this->messages; + return array( + 'count' => count($this->messages), + 'messages' => $this->messages + ); } /** @@ -71,8 +74,12 @@ class MessagesCollector extends DataCollector implements Renderable return array( "messages" => array( "widget" => "PhpDebugBar.Widgets.MessagesWidget", - "map" => "messages", + "map" => "messages.messages", "default" => "[]" + ), + "messages:badge" => array( + "map" => "messages.count", + "default" => "null" ) ); } diff --git a/src/DebugBar/DataCollector/PDO/PDOCollector.php b/src/DebugBar/DataCollector/PDO/PDOCollector.php new file mode 100644 index 0000000..e19bf4c --- /dev/null +++ b/src/DebugBar/DataCollector/PDO/PDOCollector.php @@ -0,0 +1,65 @@ +pdo = $pdo; + } + + public function collect() + { + $stmts = array(); + foreach ($this->pdo->getExecutedStatements() as $stmt) { + $stmts[] = array( + 'sql' => $stmt->getSqlWithParams(), + 'row_count' => $stmt->getRowCount(), + 'stmt_id' => $stmt->getPreparedId(), + 'prepared_stmt' => $stmt->getSql(), + 'params' => $stmt->getParameters(), + 'duration' => $stmt->getDuration(), + 'duration_str' => $this->formatDuration($stmt->getDuration()), + 'memory' => $stmt->getMemoryUsage(), + 'memory_str' => $this->formatBytes($stmt->getMemoryUsage()), + 'is_success' => $stmt->isSuccess(), + 'error_code' => $stmt->getErrorCode(), + 'error_message' => $stmt->getErrorMessage() + ); + } + + return array( + 'nb_statements' => count($stmts), + 'nb_failed_statements' => count($this->pdo->getFailedExecutedStatements()), + 'accumulated_duration' => $this->pdo->getAccumulatedStatementsDuration(), + 'accumulated_duration_str' => $this->formatDuration($this->pdo->getAccumulatedStatementsDuration()), + 'peak_memory_usage' => $this->pdo->getPeakMemoryUsage(), + 'peak_memory_usage_str' => $this->formatBytes($this->pdo->getPeakMemoryUsage()), + 'statements' => $stmts + ); + } + + public function getName() + { + return 'pdo'; + } + + public function getWidgets() + { + return array( + "database" => array( + "widget" => "PhpDebugBar.Widgets.SQLQueriesWidget", + "map" => "pdo", + "default" => "[]" + ), + "database:badge" => array( + "map" => "pdo.nb_statements", + "default" => 0 + ) + ); + } +} diff --git a/src/DebugBar/DataCollector/PDO/TraceablePDO.php b/src/DebugBar/DataCollector/PDO/TraceablePDO.php new file mode 100644 index 0000000..2af2f2f --- /dev/null +++ b/src/DebugBar/DataCollector/PDO/TraceablePDO.php @@ -0,0 +1,211 @@ +pdo = $pdo; + $this->pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DebugBar\DataCollector\PDO\TraceablePDOStatement', array($this))); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + return $this->pdo->beginTransaction(); + } + + /** + * {@inheritDoc} + */ + public function commit() + { + return $this->pdo->commit(); + } + + /** + * {@inheritDoc} + */ + public function errorCode() + { + return $this->pdo->errorCode(); + } + + /** + * {@inheritDoc} + */ + public function errorInfo() + { + return $this->errorInfo(); + } + + /** + * {@inheritDoc} + */ + public function exec($sql) + { + return $this->profileCall('exec', $sql, func_get_args()); + } + + /** + * {@inheritDoc} + */ + public function getAttribute($attr) + { + return $this->pdo->getAttribute($attr); + } + + /** + * {@inheritDoc} + */ + public function inTransaction() + { + return $this->pdo->inTransaction(); + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + return $this->pdo->lastInsertId($name); + } + + /** + * {@inheritDoc} + */ + public function prepare($sql, $driver_options = array()) + { + return $this->pdo->prepare($sql, $driver_options); + } + + /** + * {@inheritDoc} + */ + public function query($sql) + { + return $this->profileCall('query', $sql, func_get_args()); + } + + /** + * {@inheritDoc} + */ + public function quote($expr, $parameter_type = PDO::PARAM_STR) + { + return $this->pdo->quote($expr, $parameter_type); + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + return $this->pdo->rollBack(); + } + + /** + * {@inheritDoc} + */ + public function setAttribute($attr, $value) + { + return $this->pdo->setAttribute($attr, $value); + } + + /** + * Profiles a call to a PDO method + * + * @param string $method + * @param string $sql + * @param array $args + * @return mixed The result of the call + */ + protected function profileCall($method, $sql, array $args) + { + $start = microtime(true); + $ex = null; + + try { + $result = call_user_func_array(array($this->pdo, $method), $args); + } catch (PDOException $e) { + $ex = $e; + } + + $duration = microtime(true) - $start; + $memoryUsage = memory_get_peak_usage(true); + if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION && $result === false) { + $error = $this->pdo->errorInfo(); + $ex = new PDOException($error[2], $error[0]); + } + + $tracedStmt = new TracedStatement($sql, array(), null, 0, $duration, $memoryUsage, $ex); + $this->addExecutedStatement($tracedStmt); + + if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION && $ex !== null) { + throw $ex; + } + return $result; + } + + /** + * Adds an executed TracedStatement + * + * @param TracedStatement $stmt + */ + public function addExecutedStatement(TracedStatement $stmt) + { + $this->executedStatements[] = $stmt; + } + + /** + * Returns the accumulated execution time of statements + * + * @return int + */ + public function getAccumulatedStatementsDuration() + { + return array_reduce($this->executedStatements, function($v, $s) { return $v + $s->getDuration(); }); + } + + /** + * Returns the peak memory usage while performing statements + * + * @return int + */ + public function getPeakMemoryUsage() + { + return array_reduce($this->executedStatements, function($v, $s) { $m = $s->getMemoryUsage(); return $m > $v ? $m : $v; }); + } + + /** + * Returns the list of executed statements as TracedStatement objects + * + * @return array + */ + public function getExecutedStatements() + { + return $this->executedStatements; + } + + /** + * Returns the list of failed statements + * + * @return array + */ + public function getFailedExecutedStatements() + { + return array_filter($this->executedStatements, function($s) { return !$s->isSuccess(); }); + } +} diff --git a/src/DebugBar/DataCollector/PDO/TraceablePDOStatement.php b/src/DebugBar/DataCollector/PDO/TraceablePDOStatement.php new file mode 100644 index 0000000..518fbde --- /dev/null +++ b/src/DebugBar/DataCollector/PDO/TraceablePDOStatement.php @@ -0,0 +1,81 @@ +pdo = $pdo; + } + + /** + * {@inheritDoc} + */ + public function bindColumn($column, &$param) { + $this->boundParameters[$column] = $param; + $args = array_merge(array($column, &$param), array_slice(func_get_args(), 2)); + return call_user_func_array(array("parent", 'bindColumn'), $args); + } + + /** + * {@inheritDoc} + */ + public function bindParam($param, &$var) { + $this->boundParameters[$param] = $var; + $args = array_merge(array($param, &$var), array_slice(func_get_args(), 2)); + return call_user_func_array(array("parent", 'bindParam'), $args); + } + + /** + * {@inheritDoc} + */ + public function bindValue($param, $value) { + $this->boundParameters[$param] = $value; + return call_user_func_array(array("parent", 'bindValue'), func_get_args()); + } + + /** + * {@inheritDoc} + */ + public function execute($params = array()) + { + $start = microtime(true); + $ex = null; + + try { + $result = parent::execute($params); + } catch (PDOException $e) { + $ex = $e; + } + + $preparedId = spl_object_hash($this); + $boundParameters = array_merge($this->boundParameters, $params); + $duration = microtime(true) - $start; + $memoryUsage = memory_get_peak_usage(true); + if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION && $result === false) { + $error = $this->errorInfo(); + $ex = new PDOException($error[2], $error[0]); + } + + $tracedStmt = new TracedStatement($this->queryString, $boundParameters, + $preparedId, $this->rowCount(), $duration, $memoryUsage, $ex); + $this->pdo->addExecutedStatement($tracedStmt); + + if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION && $ex !== null) { + throw $ex; + } + return $result; + } +} diff --git a/src/DebugBar/DataCollector/PDO/TracedStatement.php b/src/DebugBar/DataCollector/PDO/TracedStatement.php new file mode 100644 index 0000000..e98682d --- /dev/null +++ b/src/DebugBar/DataCollector/PDO/TracedStatement.php @@ -0,0 +1,189 @@ +sql = $sql; + $this->rowCount = $rowCount; + $this->parameters = $params; + $this->preparedId = $preparedId; + $this->duration = $duration; + $this->memoryUsage = $memoryUsage; + $this->exception = $e; + } + + /** + * Returns the SQL string used for the query + * + * @return string + */ + public function getSql() + { + return $this->sql; + } + + /** + * Returns the SQL string with any parameters used embedded + * + * @return string + */ + public function getSqlWithParams() + { + $sql = $this->sql; + foreach ($this->parameters as $k => $v) { + $v = sprintf('<%s>', $v); + if (!is_numeric($k)) { + $sql = str_replace($k, $v, $sql); + } else { + $p = strpos($sql, '?'); + $sql = substr($sql, 0, $p) . $v. substr($sql, $p + 1); + } + } + return $sql; + } + + /** + * Returns the number of rows affected/returned + * + * @return int + */ + public function getRowCount() + { + return $this->rowCount; + } + + /** + * Returns an array of parameters used with the query + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Returns the prepared statement id + * + * @return string + */ + public function getPreparedId() + { + return $this->preparedId; + } + + /** + * Checks if this is a prepared statement + * + * @return boolean + */ + public function isPrepared() + { + return $this->preparedId !== null; + } + + /** + * Returns the duration in seconds of the execution + * + * @return int + */ + public function getDuration() + { + return $this->duration; + } + + /** + * Returns the peak memory usage during the execution + * + * @return int + */ + public function getMemoryUsage() + { + return $this->memoryUsage; + } + + /** + * Checks if the statement was successful + * + * @return boolean + */ + public function isSuccess() + { + return $this->exception === null; + } + + /** + * Returns the exception triggered + * + * @return \Exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Returns the exception's code + * + * @return string + */ + public function getErrorCode() + { + return $this->exception !== null ? $this->exception->getCode() : 0; + } + + /** + * Returns the exception's message + * + * @return string + */ + public function getErrorMessage() + { + return $this->exception !== null ? $this->exception->getMessage() : ''; + } +} diff --git a/src/DebugBar/DataCollector/RequestDataCollector.php b/src/DebugBar/DataCollector/RequestDataCollector.php index 1785808..82bd50e 100644 --- a/src/DebugBar/DataCollector/RequestDataCollector.php +++ b/src/DebugBar/DataCollector/RequestDataCollector.php @@ -25,7 +25,7 @@ class RequestDataCollector extends DataCollector implements Renderable foreach ($vars as $var) { if (isset($GLOBALS[$var])) { - $data["$" . $var] = $this->varToString($GLOBALS[$var]); + $data["$" . $var] = $this->formatVar($GLOBALS[$var]); } } diff --git a/src/DebugBar/DataCollector/TimeDataCollector.php b/src/DebugBar/DataCollector/TimeDataCollector.php index 93e8a5c..6775db8 100644 --- a/src/DebugBar/DataCollector/TimeDataCollector.php +++ b/src/DebugBar/DataCollector/TimeDataCollector.php @@ -64,7 +64,7 @@ class TimeDataCollector extends DataCollector implements Renderable $this->measures[$name]['end'] = $end; $this->measures[$name]['relative_end'] = $end - $this->requestEndTime; $this->measures[$name]['duration'] = $end - $this->measures[$name]['start']; - $this->measures[$name]['duration_str'] = $this->toReadableString($this->measures[$name]['duration']); + $this->measures[$name]['duration_str'] = $this->formatDuration($this->measures[$name]['duration']); } /** @@ -123,17 +123,6 @@ class TimeDataCollector extends DataCollector implements Renderable return microtime(true) - $this->requestStartTime; } - /** - * Transforms a duration in seconds in a readable string - * - * @param float $value - * @return string - */ - public function toReadableString($value) - { - return round($value * 1000) . 'ms'; - } - /** * {@inheritDoc} */ @@ -150,7 +139,7 @@ class TimeDataCollector extends DataCollector implements Renderable 'start' => $this->requestStartTime, 'end' => $this->requestEndTime, 'duration' => $this->getRequestDuration(), - 'duration_str' => $this->toReadableString($this->getRequestDuration()), + 'duration_str' => $this->formatDuration($this->getRequestDuration()), 'measures' => array_values($this->measures) ); } diff --git a/src/DebugBar/JavascriptRenderer.php b/src/DebugBar/JavascriptRenderer.php index 857dc89..a97f302 100644 --- a/src/DebugBar/JavascriptRenderer.php +++ b/src/DebugBar/JavascriptRenderer.php @@ -205,6 +205,7 @@ class JavascriptRenderer * - icon: icon name * - tooltip: string * - widget: widget class name + * - title: tab title * - map: a property name from the data to map the control to * - default: a js string, default value of the data map * @@ -375,12 +376,13 @@ class JavascriptRenderer foreach ($controls as $name => $options) { if (isset($options['widget'])) { - $js .= sprintf("%s.createTab(\"%s\", new %s());\n", + $js .= sprintf("%s.createTab(\"%s\", new %s()%s);\n", $varname, $name, - $options['widget'] + $options['widget'], + isset($options['title']) ? sprintf(', "%s"', $options['title']) : '' ); - } else { + } else if (strpos($name, ':') === false) { $js .= sprintf("%s.createIndicator(\"%s\", \"%s\", \"%s\");\n", $varname, $name, diff --git a/src/DebugBar/StandardDebugBar.php b/src/DebugBar/StandardDebugBar.php index d697b1e..f98083a 100644 --- a/src/DebugBar/StandardDebugBar.php +++ b/src/DebugBar/StandardDebugBar.php @@ -15,6 +15,7 @@ use DebugBar\DataCollector\MessagesCollector; use DebugBar\DataCollector\TimeDataCollector; use DebugBar\DataCollector\RequestDataCollector; use DebugBar\DataCollector\MemoryCollector; +use DebugBar\DataCollector\ExceptionsCollector; /** * Debug bar subclass which adds all included collectors @@ -31,5 +32,6 @@ class StandardDebugBar extends DebugBar $this->addCollector(new RequestDataCollector()); $this->addCollector(new TimeDataCollector()); $this->addCollector(new MemoryCollector()); + $this->addCollector(new ExceptionsCollector()); } } diff --git a/web/debugbar.css b/web/debugbar.css index a4f667e..a0e0a37 100644 --- a/web/debugbar.css +++ b/web/debugbar.css @@ -51,6 +51,19 @@ background-image: -ms-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%); background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.41, rgb(173,173,173)), color-stop(0.71, rgb(209,209,209))); } + .phpdebugbar-tab .badge { + display: none; + margin-left: 5px; + font-size: 11px; + padding: 1px 6px; + background: #ccc; + border-radius: 4px; + color: #555; + } + .phpdebugbar-tab .badge.important { + background: #ed6868; + color: white; + } .phpdebugbar-close-btn { display: none; @@ -126,6 +139,7 @@ .phpdebugbar-widgets-list .list-item { padding: 3px 6px; border-bottom: 1px solid #eee; + position: relative; } .phpdebugbar-widgets-list .list-item:hover { background: #fafafa; @@ -249,3 +263,90 @@ top: 5px; border-radius: 2px; } + +/* -------------------------------------- */ + +.phpdebugbar-widgets-exceptions .list-item { + cursor: pointer; +} + .phpdebugbar-widgets-exceptions .list-item .message { + display: block; + color: red; + } + + .phpdebugbar-widgets-exceptions .list-item .filename { + display: block; + font-style: italic; + color: #555; + } + + .phpdebugbar-widgets-exceptions .list-item .type { + display: block; + position: absolute; + right: 4px; + top: 4px; + font-weight: bold; + } + + .phpdebugbar-widgets-exceptions .list-item .file { + display: none; + margin: 10px; + padding: 5px; + border: 1px solid #ddd; + font-family: monospace; + } + +/* -------------------------------------- */ + +.phpdebugbar-widgets-sqlqueries .status { + font-family: monospace; + padding: 6px 6px; + border-bottom: 1px solid #ddd; + font-weight: bold; + color: #555; + background: #fafafa; +} + +.phpdebugbar-widgets-sqlqueries .list-item.error { + color: red; +} + +.phpdebugbar-widgets-sqlqueries .duration, +.phpdebugbar-widgets-sqlqueries .memory, +.phpdebugbar-widgets-sqlqueries .row-count, +.phpdebugbar-widgets-sqlqueries .stmt-id { + float: right; + margin-left: 8px; + color: #888; +} +.phpdebugbar-widgets-sqlqueries .status .duration, +.phpdebugbar-widgets-sqlqueries .status .memory, +.phpdebugbar-widgets-sqlqueries .status .row-count, +.phpdebugbar-widgets-sqlqueries .status .stmt-id { + color: #555; +} +.phpdebugbar-widgets-sqlqueries .duration:before, +.phpdebugbar-widgets-sqlqueries .memory:before, +.phpdebugbar-widgets-sqlqueries .row-count:before, +.phpdebugbar-widgets-sqlqueries .stmt-id:before { + font-family: FontAwesome; + margin-right: 4px; + font-size: 12px; +} +.phpdebugbar-widgets-sqlqueries .duration:before { + content: "\f017"; +} +.phpdebugbar-widgets-sqlqueries .memory:before { + content: "\f085"; +} +.phpdebugbar-widgets-sqlqueries .row-count:before { + content: "\f0ce"; +} +.phpdebugbar-widgets-sqlqueries .stmt-id:before { + content: "\f08d"; +} + +.phpdebugbar-widgets-sqlqueries .list-item .error { + display: block; + font-weight: bold; +} diff --git a/web/debugbar.js b/web/debugbar.js index 2955158..2e3659d 100644 --- a/web/debugbar.js +++ b/web/debugbar.js @@ -62,7 +62,9 @@ PhpDebugBar.DebugBar = (function($) { * @param {Object} widget */ var Tab = function(title, widget) { - this.tab = $('').text(title); + this.tab = $('') + this.tabText = $('').text(title).appendTo(this.tab); + this.badge = $('').appendTo(this.tab); this.panel = $('
'); this.replaceWidget(widget); }; @@ -77,6 +79,20 @@ PhpDebugBar.DebugBar = (function($) { this.tab.text(text); }; + /** + * Sets the badge value of the tab + * + * @this {Tab} + * @param {String} value + */ + Tab.prototype.setBadgeValue = function(value) { + if (value === null) { + this.badge.hide(); + } else { + this.badge.text(value).show(); + } + }; + /** * Replaces the widget inside the panel * @@ -544,7 +560,10 @@ PhpDebugBar.DebugBar = (function($) { var self = this; $.each(this.dataMap, function(key, def) { var d = getDictValue(data, def[0], def[1]); - if (self.isIndicator(key)) { + if (key.indexOf(':') != -1) { + key = key.split(':')[0]; + self.getTab(key).setBadgeValue(d); + } else if (self.isIndicator(key)) { self.getIndicator(key).setText(d); } else { self.getTab(key).widget.setData(d); diff --git a/web/widgets.js b/web/widgets.js index 523e6ae..89e67d2 100644 --- a/web/widgets.js +++ b/web/widgets.js @@ -354,6 +354,92 @@ PhpDebugBar.Widgets = (function($) { widgets.TimelineWidget = TimelineWidget; + // ------------------------------------------------------------------ + + /** + * Widget for the displaying exceptions + * + * @this {ExceptionsWidget} + * @constructor + * @param {Object} data + */ + var ExceptionsWidget = function(data) { + this.element = $('
'); + + this.list = new ListWidget(null, function(li, e) { + $('').text(e.message).appendTo(li); + $('').text(e.file + "#" + e.line).appendTo(li); + $('').text(e.type).appendTo(li); + var file = $('
').html(htmlize(e.surrounding_lines.join(""))).appendTo(li); + + li.click(function() { + if (file.is(':visible')) { + file.hide(); + } else { + file.show(); + } + }); + }); + this.element.append(this.list.element); + + if (data) { + this.setData(data); + } + }; + + ExceptionsWidget.prototype.setData = function(data) { + this.list.setData(data); + if (data.length == 1) { + this.list.element.children().first().find('.file').show(); + } + }; + + widgets.ExceptionsWidget = ExceptionsWidget; + + // ------------------------------------------------------------------ + + /** + * Widget for the displaying sql queries + * + * @this {SQLQueriesWidget} + * @constructor + * @param {Object} data + */ + var SQLQueriesWidget = function(data) { + this.element = $('
'); + this.status = $('
').appendTo(this.element); + + this.list = new ListWidget(null, function(li, stmt) { + $('').text(stmt.sql).appendTo(li); + $('').text(stmt.duration_str).appendTo(li); + $('').text(stmt.memory_str).appendTo(li); + if (!stmt.is_success) { + li.addClass('error'); + li.append($('').text("[" + stmt.error_code + "] " + stmt.error_message)); + } else { + $('').text(stmt.row_count).appendTo(li); + } + if (stmt.stmt_id) { + $('').text(stmt.stmt_id).appendTo(li); + } + }); + this.element.append(this.list.element); + + if (data) { + this.setData(data); + } + }; + + SQLQueriesWidget.prototype.setData = function(data) { + this.list.setData(data.statements); + this.status.empty() + .append($('').text(data.nb_statements + " statements were executed" + (data.nb_failed_statements > 0 ? (", " + data.nb_failed_statements + " of which failed") : ""))) + .append($('').text(data.accumulated_duration_str)) + .append($('').text(data.peak_memory_usage_str)); + }; + + widgets.SQLQueriesWidget = SQLQueriesWidget; + // ------------------------------------------------------------------ return widgets;