1
0
mirror of https://github.com/maximebf/php-debugbar.git synced 2025-01-16 21:08:34 +01:00

huge refactoring, comments and docs

This commit is contained in:
maximebf 2013-06-13 18:48:23 +08:00
parent 2d4d840d21
commit 836050cea5
31 changed files with 2082 additions and 571 deletions

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (C) 2013 Maxime Bouroumeau-Fuseau
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,3 +1,83 @@
# Debug bar for PHP # PHP Debug Bar
Displays a debug bar in the browser with information from php Displays a debug bar in the browser with information from php.
No more `var_dump()` in your code!
![Screenshot](docs/screenshot.png)
**Features:**
- Generic debug bar with no other dependencies
- Easy to integrate with any project
- Clean, fast and easy to use interface
- Handles AJAX request
- Includes generic data collectors and collectors for well known libraries
- The client side bar is 100% coded in javascript
- Easily create your own collectors and their associated view in the bar
## Installation
The easiest way to install DebugBar is using [Composer](https://github.com/composer/composer)
with the following requirement:
{
"require": {
"maximebf/debugbar": ">=1.0.0"
}
}
Alternatively, you can [download the archive](https://github.com/maximebf/php-debugbar/zipball/master)
and add the src/ folder to PHP's include path:
set_include_path('/path/to/src' . PATH_SEPARATOR . get_include_path());
DebugBar does not provide an autoloader but follows the [PSR-0 convention](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md).
You can use the following snippet to autoload ConsoleKit classes:
spl_autoload_register(function($className) {
if (substr($className, 0, 8) === 'DebugBar') {
$filename = str_replace('\\', DIRECTORY_SEPARATOR, trim($className, '\\')) . '.php';
require_once $filename;
}
});
## Quick start
DebugBar is very easy to use and you can add it to any of your projets in no time.
The easiest way is using the `render()` functions
<?php
use DebugBar\StandardDebugBar;
use DebugBar\Renderer\JavascriptRenderer;
$debugbar = new StandardDebugBar();
$debugbarRenderer = $debugbar->getJavascriptRenderer();
$debugbar["messages"]->addMessage("hello world!");
?>
<html>
<head>
<?php echo $debugbarRenderer->renderHead() ?>
</head>
<body>
...
<?php echo $debugbarRenderer->render() ?>
</body>
</html>
The DebugBar uses DataCollectors to collect data from your PHP code. Some of them are
automated but others are manual. Use the `DebugBar` like an array where keys are the
collector names. In our previous example, we add a message to the `MessagesCollector`:
$debugbar["messages"]->addMessage("hello world!");
`StandardDebugBar` activates all bundled collectors:
- `MemoryCollector` (*memory*)
- `MessagesCollector` (*messages*)
- `PhpInfoCollector` (*php*)
- `RequestDataCollector` (*request*)
- `TimeDataCollector` (*time*)
Learn more about DebugBar in the [docs](http://maximebf.github.io/php-debugbar).

19
composer.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "maximebf/debugbar",
"description": "Debug bar in the browser for php application",
"keywords": ["debug"],
"homepage": "https://github.com/maximebf/php-debugbar",
"type": "library",
"license": "MIT",
"authors": [{
"name": "Maxime Bouroumeau-Fuseau",
"email": "maxime.bouroumeau@gmail.com",
"homepage": "http://maximebf.com"
}],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-0": {"DebugBar": "src/"}
}
}

View File

@ -3,7 +3,6 @@
include '../tests/bootstrap.php'; include '../tests/bootstrap.php';
use DebugBar\StandardDebugBar; use DebugBar\StandardDebugBar;
use DebugBar\Renderer\JavascriptRenderer;
$debugbar = new StandardDebugBar(); $debugbar = new StandardDebugBar();
$debugbarRenderer = new JavascriptRenderer($debugbar, '../web/'); $debugbarRenderer = $debugbar->getJavascriptRenderer()->setBaseUrl('../web');

View File

@ -20,7 +20,7 @@ $debugbar['time']->startMeasure('render');
?> ?>
<html> <html>
<head> <head>
<?php echo $debugbarRenderer->renderIncludes() ?> <?php echo $debugbarRenderer->renderHead() ?>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
$('.ajax').click(function() { $('.ajax').click(function() {
@ -39,8 +39,7 @@ $debugbar['time']->startMeasure('render');
<p><a href="demo_ajax_exception.php" class="ajax">load ajax content with exception</a></p> <p><a href="demo_ajax_exception.php" class="ajax">load ajax content with exception</a></p>
<?php <?php
usleep(100); usleep(100);
$debugbar->collect(); echo $debugbarRenderer->render();
echo $debugbarRenderer->renderToolbar();
?> ?>
</body> </body>
</html> </html>

View File

@ -6,6 +6,5 @@ $debugbar['messages']->addMessage('hello from ajax');
?> ?>
hello from AJAX hello from AJAX
<?php <?php
$debugbar->collect(); echo $debugbarRenderer->render(false);
echo $debugbarRenderer->renderAjaxToolbar();
?> ?>

View File

@ -11,6 +11,5 @@ try {
?> ?>
error from AJAX error from AJAX
<?php <?php
$debugbar->collect(); echo $debugbarRenderer->render(false);
echo $debugbarRenderer->renderAjaxToolbar();
?> ?>

80
docs/data_collectors.md Normal file
View File

@ -0,0 +1,80 @@
# Collecting Data
## Using collectors
Collectors can be added to your debug bar using `addCollector()`.
$debugbar = new DebugBar();
$debugbar->addCollector(new DataCollector\RequestDataCollector());
Each collector as a unique name as defined by its `getName()` method. You can
access collectors using `getCollector($name)`.
$debugbar->addCollector(new DataCollector\MessagesCollector());
$debugbar->getCollector('messages')->addMessage("foobar");
// or:
$debugbar['messages']->addMessage("foobar");
Data will be collected from them when the debug bar is rendered. You can however
collect the data earlier using `collect()`.
$debugbar->collect();
## Creating collectors
Collectors must implement the `DebugBar\DataCollector\DataCollectorInterface`. They
may subclass `DebugBar\DataCollector\DataCollector` which provides utility methods.
Collectors must provide a `getName()` function returning their unique name and a
`collect()` function returning some json-encodable data. The latter will be called at the
same time the `DebugBar::collect()` method is called.
class MyDataCollector extends DebugBar\DataCollector\DataCollector
{
public function collect()
{
return array("uniqid" => uniqid());
}
public function getName()
{
return 'mycollector';
}
}
$debugbar->addCollector(new MyDataCollector());
This however won't show anything in the debug bar as no information are provided
on how to display these data. You could do that manually as you'll see in later chapter
or implement the `DebugBar\DataSource\Renderable` interface.
To implement it, you must define a `getWidgets()` function which returns an array
of key/value pairs where key are control names and values control options as defined
in `JavascriptRenderer::addControl($name, $options)` (see Rendering chapter).
class MyDataCollector extends DebugBar\DataCollector\DataCollector implements DebugBar\DataCollector\Renderable
{
// ...
public function getWidgets()
{
return array(
"mycollector" => array(
"icon" => "cogs",
"tooltip" => "uniqid()",
"map" => "uniqid",
"default" => "''"
)
);
}
}
This will have the result of adding a new indicator to the debug bar.
## Included collectors
- `MemoryCollector` (*memory*)
- `MessagesCollector` (*messages*)
- `PhpInfoCollector` (*php*)
- `RequestDataCollector` (*request*)
- `TimeDataCollector` (*time*)

120
docs/javascript_bar.md Normal file
View File

@ -0,0 +1,120 @@
# Javascript Bar
The default client side implementation of the debug bar is made
entirely in Javascript and is located in the *debugbar.js* file.
It adds a bottom-anchored bar which can have tabs and indicators.
The bar can be in an open or close state. When open, the tab panel is
visible.
An indicator is a piece of information displayed in the always-visible
part of the bar.
The bar handles multiple datasets by displaying a select box
which allows you to switch between them.
The state of the bar (height, visibilty, active panel) can be saved
between requests (enabled in the standard bar).
Each panel is composed of a widget which is used to display the
data from a data collector. Some common widgets are provided in
the *widgets.js* file.
The `PhpDebugBar` namespace is used for all objects and the only
dependencies are *jQuery*, *jquery-drag* and *FontAwesome* (css).
The main class is `PhpDebugBar.DebugBar`. It provides the infrastructure
to manage tabs, indicators and datasets.
When initialized, the `DebugBar` class adds itself to the `<body>` of the
page. It is empty by default.
## Tabs and indicators
Controls (ie. tabs and indicators) are uniquely named. You can check if
a control exists using `isControl(name)`.
Tabs can be added using the `createTab(name, widget, title)` function.
The third argument is optional and will be computed from the name if not
provided.
var debugbar = new PhpDebugBar.DebugBar();
debugbar.createTab("messages", new PhpDebugBar.Widgets.MessagesWidget());
Indicators can be added using `createIndicator(name, icon, tooltip, position)`.
Only `name` is required in this case. `icon` should be the name of a FontAwesome
icon. `position` can either by *right* (default) or *left*.
debugbar.createIndicator("time", "cogs", "Request duration");
You may have noticed that the data to use inside these controls is not
specified at the moment. Although it could be specified when initialized, it
is better to use data mapping to support dynamically changing the data set.
## Data mapping
To enable dynamically changing the data sets, we need to specify which values
should be feed into which controls. This can be done using `setDataMap(map)`
which takes as argument an object where properties are control names. Values
should be arrays where the first item is the property from the data set and
the second a default value.
debugbar.setDataMap({
"messages": ["messages", []],
"time": ["time.duration_str", "0ms"]
});
You can notice that nested properties can also be accessed using the dot
notation.
In this mapping, `data["messages"]` will be fed to the *messages* tab
and `data["time"]["duration_str"]` will be fed to the *time* indicator.
Note: you can append mapping info using `addDataMap(map)`
## Datasets
Although you shouldn't have to do anything regarding managing datasets,
it is interesting to know a few functions related to them.
`addDataSet(data, id)` adds a dataset to the bar. The select box that
allows to swtich between sets is only displayed if more than one are added.
`id` is optional and will be auto-generated if not specified.
`showDataSet(id)` allows you to switch to a specific dataset.
## Widgets
A widget is a javascript object which must contain at least an `element`
property. `element`'s value will be appended to the tab panel.
Widgets should provide a `setData()` function so they can be used with
the data mapper.
var MyWidget = function() {
this.element = $('<div class="mywidget" />');
};
MyWidget.prototype.setData = function(text) {
this.element.text(text);
};
// ----
debugbar.createTab("mytab", new MyWidget());
debugbar.addDataMap({"mytab": ["mydata", ""]});
Widgets for bundled data collectors are included as well as more generic
widgets that you can build on top of. They are located in *widgets.js* in
the `PhpDebugBar.Widgets` namespace.
Generic widgets:
- `ListWidget`: renders an array as a UL list
- `KVListWidget`: renders a hash as a DL list
- `VariablesListWidget`: an extension of `KVListWidget` to display a list of variables
- `IFrameWidget`: renders an iframe
Data collectors related widgets:
- `MessagesWidget`: for the `MessagesCollector`
- `TimelineWidget`: for the `TimeDataCollector`

10
docs/manifest.json Normal file
View File

@ -0,0 +1,10 @@
{
"title": "PHP Debug Bar",
"home": "../README.md",
"files": [
"../README.md",
"data_collectors.md",
"rendering.md",
"javascript_bar.md"
]
}

117
docs/rendering.md Normal file
View File

@ -0,0 +1,117 @@
# Rendering
Rendering is performed using the `DebugBar\JavascriptRenderer̀ class. It contains
all the useful functions to included the needed assets and generate a debug bar.
$renderer = $debugbar->getJavascriptRenderer();
## Assets
The debug bar relies on some css and javascript files which needs to be included
into your webpage. This can be done in two ways:
- Using `JavascriptRenderer::renderHead()` which will returns a string with
the needed script and link tags
- Using [Assetic](https://github.com/kriswallsmith/assetic) and
`JavascriptRenderer::getAsseticCollection()`
I would recommend using the second method as Assetic is a very powerful asset
manager but the first method is provided to quickly integrate the debug bar
into any projets.
You can define the base url of your assets using `setBaseUrl()`. This is needed
in 99% of cases. If you are using Assetic, you may have to change the base path
of the assets if you moved the folder. This can be done using `setBasePath()`.
Using `renderHead()`:
<html>
<head>
...
<?php echo $renderer->renderHead() ?>
...
</head>
...
</html>
Using Assetic:
list($cssCollection, $jsCollection) = $renderer->getAsseticCollection();
Note that you can only use the debug bar assets and manage the dependencies by yourself
using `$renderer->setIncludeVendors(false)`.
## The javascript object
The renderer will generate all the needed code for your debug bar. This means
initializing the DebugBar js object, adding tabs and indicators, defining a data map, etc...
Data collectors can provide their own controls when implementing the
`DebugBar\DataCollector\Renderable` interface as explained in the Collecting Data chapter.
Thus in almost all cases, you should only have to use `render()` right away:
<html>
...
<body>
<?php echo $renderer->render() ?>
</body>
</html>
This will print the initilization code for the toolbar and the dataset for the request.
When you are performing AJAX requests, you do not want to initialize a new toolbar but
add the dataset to the existing one. You can disable initialization using ̀false` as
the first argument of ̀render()`.
<p>my ajax content</p>
<?php echo $renderer->render(false) ?>
### Controlling object initialization
You can further control the initialization of the javascript object using `setInitialization()`.
It takes a bitwise value made out of the constants ̀INITIALIZE_CONSTRUCTOR` and `INITIALIZE_CONTROLS`.
The first one controls whether to initialize the variable (ie. `var debugbar = new DebugBar()`). The
second one whether to initialize all the controls (ie. adding tab and indicators as well as data mapping).
You can also control the class name of the object using `setJavascriptClass()` and the name of
the instance variable using `setVariableName()`.
Let's say you have subclassed `PhpDebugBar.DebugBar` in javascript to do your own initilization.
Your new object is called `MyDebugBar`.
$renderer->setJavascriptClass("MyDebugBar");
$renderer->setInitialization(JavascriptRenderer::INITIALIZE_CONSTRUCTOR);
// ...
echo $renderer->render();
This has the result of printing:
<script type="text/javascript">
var phpdebugbar = new MyDebugBar();
phpdebugbar.addDataSet({ ... });
</script>
Using `setInitialization(0)` will only render the addDataSet part.
### Defining controls
Controls can be manually added to the debug bar using `addControl($name, $options)`. You should read
the Javascript bar chapter before this section.
`$name` will be the name of your control and `$options` is a key/value pair array with these
possible values:
- *icon*: icon name
- *tooltip*: string
- *widget*: widget class name
- *map*: a property name from the data to map the control to
- *default*: a js string, default value of the data map
At least *icon* or *widget* are needed. If *widget* is specified, a tab will be created, otherwise
an indicator.
$renderer->addControl('messages', array(
"widget" => "PhpDebugBar.Widgets.MessagesWidget",
"map" => "messages",
"default" => "[]"
));

BIN
docs/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,9 +1,26 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
/**
* Abstract class for data collectors
*/
abstract class DataCollector implements DataCollectorInterface abstract class DataCollector implements DataCollectorInterface
{ {
/**
* Transforms a PHP variable to a string representation
*
* @param mixed $var
* @return string
*/
public function varToString($var) public function varToString($var)
{ {
return print_r($var, true); return print_r($var, true);

View File

@ -1,10 +1,31 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
/**
* DataCollector Interface
*/
interface DataCollectorInterface interface DataCollectorInterface
{ {
function getName(); /**
* Called by the DebugBar when data needs to be collected
*
* @return array Collected data
*/
function collect(); function collect();
/**
* Returns the unique name of the collector
*
* @return string
*/
function getName();
} }

View File

@ -1,16 +1,30 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
class DependencyCollector extends DataCollector class DependencyCollector extends DataCollector
{ {
public function getName() /**
{ * {@inheritDoc}
return 'dependency'; */
}
public function collect() public function collect()
{ {
return array(); return array();
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return 'dependency';
}
} }

View File

@ -1,26 +1,47 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
class MemoryCollector extends DataCollector /**
* Collects info about memory usage
*/
class MemoryCollector extends DataCollector implements Renderable
{ {
protected $peakUsage = 0; protected $peakUsage = 0;
public function getName() /**
{ * Returns the peak memory usage
return 'memory'; *
} * @return integer
*/
public function getPeakUsage() public function getPeakUsage()
{ {
return $this->peakUsage; return $this->peakUsage;
} }
/**
* Updates the peak memory usage value
*/
public function updatePeakUsage() public function updatePeakUsage()
{ {
$this->peakUsage = memory_get_peak_usage(true); $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) public function toReadableString($size, $precision = 2)
{ {
$base = log($size) / log(1024); $base = log($size) / log(1024);
@ -28,6 +49,9 @@ class MemoryCollector extends DataCollector
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
} }
/**
* {@inheritDoc}
*/
public function collect() public function collect()
{ {
$this->updatePeakUsage(); $this->updatePeakUsage();
@ -36,4 +60,27 @@ class MemoryCollector extends DataCollector
'peak_usage_str' => $this->toReadableString($this->peakUsage) 'peak_usage_str' => $this->toReadableString($this->peakUsage)
); );
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return 'memory';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
"memory" => array(
"icon" => "cogs",
"tooltip" => "Memory Usage",
"map" => "peak_usage_str",
"default" => "'0B'"
)
);
}
} }

View File

@ -1,11 +1,30 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
class MessagesCollector extends DataCollector /**
* Provides a way to log messages
*/
class MessagesCollector extends DataCollector implements Renderable
{ {
protected $messages = array(); protected $messages = array();
/**
* Adds a message
*
* A message can be anything from an object to a string
*
* @param mixed $message
* @param string $label
*/
public function addMessage($message, $label = 'info') public function addMessage($message, $label = 'info')
{ {
$this->messages[] = array( $this->messages[] = array(
@ -18,18 +37,43 @@ class MessagesCollector extends DataCollector
); );
} }
/**
* Returns all messages
*
* @return array
*/
public function getMessages() public function getMessages()
{ {
return $this->messages; return $this->messages;
} }
/**
* {@inheritDoc}
*/
public function collect()
{
return $this->messages;
}
/**
* {@inheritDoc}
*/
public function getName() public function getName()
{ {
return 'messages'; return 'messages';
} }
public function collect() /**
* {@inheritDoc}
*/
public function getWidgets()
{ {
return $this->messages; return array(
"messages" => array(
"widget" => "PhpDebugBar.Widgets.MessagesWidget",
"map" => "messages",
"default" => "[]"
)
);
} }
} }

View File

@ -1,18 +1,35 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
/**
* Collects info about PHP
*/
class PhpInfoCollector extends DataCollector class PhpInfoCollector extends DataCollector
{ {
public function getName() /**
{ * {@inheritDoc}
return 'php'; */
}
public function collect() public function collect()
{ {
return array( return array(
'version' => PHP_VERSION 'version' => PHP_VERSION
); );
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return 'php';
}
} }

View File

@ -0,0 +1,25 @@
<?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector;
/**
* Indicates that a DataCollector is renderable using JavascriptRenderer
*/
interface Renderable
{
/**
* Returns a hash where keys are control names and their values
* an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
*
* @return array
*/
function getWidgets();
}

View File

@ -1,14 +1,23 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
class RequestDataCollector extends DataCollector /**
* Collects info about the current request
*/
class RequestDataCollector extends DataCollector implements Renderable
{ {
public function getName() /**
{ * {@inheritDoc}
return 'request'; */
}
public function collect() public function collect()
{ {
$vars = array('_GET', '_POST', '_SESSION', '_COOKIE', '_SERVER'); $vars = array('_GET', '_POST', '_SESSION', '_COOKIE', '_SERVER');
@ -22,4 +31,26 @@ class RequestDataCollector extends DataCollector
return $data; return $data;
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return 'request';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
"request" => array(
"widget" => "PhpDebugBar.Widgets.VariableListWidget",
"map" => "request",
"default" => "{}"
)
);
}
} }

View File

@ -1,16 +1,30 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar\DataCollector; namespace DebugBar\DataCollector;
class TimeCollector extends DataCollector /**
* Collects info about the request duration as well as providing
* a way to log duration of any operations
*/
class TimeDataCollector extends DataCollector implements Renderable
{ {
protected $requestStartTime; protected $requestStartTime;
protected $requestEndTime; protected $requestEndTime;
protected $measures = array(); protected $measures = array();
/**
* @param float $requestStartTime
*/
public function __construct($requestStartTime = null) public function __construct($requestStartTime = null)
{ {
if ($requestStartTime === null) { if ($requestStartTime === null) {
@ -23,6 +37,12 @@ class TimeCollector extends DataCollector
$this->requestStartTime = $requestStartTime; $this->requestStartTime = $requestStartTime;
} }
/**
* Starts a measure
*
* @param string $name Internal name, used to stop the measure
* @param string $label Public name
*/
public function startMeasure($name, $label = null) public function startMeasure($name, $label = null)
{ {
$start = microtime(true); $start = microtime(true);
@ -33,6 +53,11 @@ class TimeCollector extends DataCollector
); );
} }
/**
* Stops a measure
*
* @param string $name
*/
public function stopMeasure($name) public function stopMeasure($name)
{ {
$end = microtime(true); $end = microtime(true);
@ -42,16 +67,54 @@ class TimeCollector extends DataCollector
$this->measures[$name]['duration_str'] = $this->toReadableString($this->measures[$name]['duration']); $this->measures[$name]['duration_str'] = $this->toReadableString($this->measures[$name]['duration']);
} }
/**
* Utility function to measure the execution of a Closure
*
* @param Closure $closure
*/
public function measure(\Closure $closure)
{
$name = spl_object_hash($closure);
$this->startMeasure($name, $closure);
$closure();
$this->stopMeasure($name);
}
/**
* Returns an array of all measures
*
* @return array
*/
public function getMeasures()
{
return $this->measures;
}
/**
* Returns the request start time
*
* @return float
*/
public function getRequestStartTime() public function getRequestStartTime()
{ {
return $this->requestStartTime; return $this->requestStartTime;
} }
/**
* Returns the request end time
*
* @return float
*/
public function getRequestEndTime() public function getRequestEndTime()
{ {
return $this->requestEndTime; return $this->requestEndTime;
} }
/**
* Returns the duration of a request
*
* @return float
*/
public function getRequestDuration() public function getRequestDuration()
{ {
if ($this->requestEndTime !== null) { if ($this->requestEndTime !== null) {
@ -60,21 +123,20 @@ class TimeCollector extends DataCollector
return microtime(true) - $this->requestStartTime; return microtime(true) - $this->requestStartTime;
} }
public function getMeasures() /**
{ * Transforms a duration in seconds in a readable string
return $this->measures; *
} * @param float $value
* @return string
*/
public function toReadableString($value) public function toReadableString($value)
{ {
return round($value / 1000) . 'ms'; return round($value * 1000) . 'ms';
}
public function getName()
{
return 'time';
} }
/**
* {@inheritDoc}
*/
public function collect() public function collect()
{ {
$this->requestEndTime = microtime(true); $this->requestEndTime = microtime(true);
@ -92,4 +154,32 @@ class TimeCollector extends DataCollector
'measures' => array_values($this->measures) 'measures' => array_values($this->measures)
); );
} }
/**
* {@inheritDoc}
*/
public function getName()
{
return 'time';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
"time" => array(
"icon" => "time",
"tooltip" => "Request Duration",
"map" => "time.duration_str",
"default" => "'0ms'"
),
"timeline" => array(
"widget" => "PhpDebugBar.Widgets.TimelineWidget",
"map" => "time",
"default" => "{}"
)
);
}
} }

View File

@ -1,28 +1,69 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar; namespace DebugBar;
use ArrayAccess; use ArrayAccess;
use DebugBar\DataCollector\DataCollector; use DebugBar\DataCollector\DataCollector;
use DebugBar\Renderer\Renderer;
use DebugBar\Renderer\JavascriptRenderer;
/**
* Main DebugBar object
*
* Mananges data collectors. DebugBar provides an array-like access
* to collectors by name.
*
* <code>
* $debugbar = new DebugBar();
* $debugbar->addCollector(new DataCollector\MessagesCollector());
* $debugbar['messages']->addMessage("foobar");
* </code>
*/
class DebugBar implements ArrayAccess class DebugBar implements ArrayAccess
{ {
protected $collectors = array(); protected $collectors = array();
protected $data; protected $data;
protected $jsRenderer;
/**
* Adds a data collector
*
* @param DataCollector $collector
*/
public function addCollector(DataCollector $collector) public function addCollector(DataCollector $collector)
{ {
if (isset($this->collectors[$collector->getName()])) {
throw new DebugBarException("'$name' is already a registered collector");
}
$this->collectors[$collector->getName()] = $collector; $this->collectors[$collector->getName()] = $collector;
return $this;
} }
/**
* Checks if a data collector has been added
*
* @param string $name
* @return boolean
*/
public function hasCollector($name) public function hasCollector($name)
{ {
return isset($this->collectors[$name]); return isset($this->collectors[$name]);
} }
/**
* Returns a data collector
*
* @param string $name
* @return DataCollector
*/
public function getCollector($name) public function getCollector($name)
{ {
if (!isset($this->collectors[$name])) { if (!isset($this->collectors[$name])) {
@ -31,11 +72,61 @@ class DebugBar implements ArrayAccess
return $this->collectors[$name]; return $this->collectors[$name];
} }
/**
* Returns an array of all data collectors
*
* @return array[DataCollector]
*/
public function getCollectors() public function getCollectors()
{ {
return $this->collectors; return $this->collectors;
} }
/**
* Collects the data from the collectors
*
* @return array
*/
public function collect()
{
$this->data = array();
foreach ($this->collectors as $name => $collector) {
$this->data[$name] = $collector->collect();
}
return $this->data;
}
/**
* Returns collected data
*
* Will collect the data if none have been collected yet
*
* @return array
*/
public function getData()
{
if ($this->data === null) {
$this->collect();
}
return $this->data;
}
/**
* Returns a JavascriptRenderer for this instance
*
* @return JavascriptRenderer
*/
public function getJavascriptRenderer()
{
if ($this->jsRenderer === null) {
$this->jsRenderer = new JavascriptRenderer($this);
}
return $this->jsRenderer;
}
// --------------------------------------------
// ArrayAccess implementation
public function offsetSet($key, $value) public function offsetSet($key, $value)
{ {
throw new DebugBarException("DebugBar[] is read-only"); throw new DebugBarException("DebugBar[] is read-only");
@ -55,18 +146,4 @@ class DebugBar implements ArrayAccess
{ {
throw new DebugBarException("DebugBar[] is read-only"); throw new DebugBarException("DebugBar[] is read-only");
} }
public function collect()
{
$this->data = array();
foreach ($this->collectors as $name => $collector) {
$this->data[$name] = $collector->collect();
}
return $this->data;
}
public function getData()
{
return $this->data;
}
} }

View File

@ -1,4 +1,12 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar; namespace DebugBar;

View File

@ -0,0 +1,407 @@
<?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar;
use DebugBar\DataCollector\Renderable;
/**
* Renders the debug bar using the client side javascript implementation
*
* Generates all the needed initialization code of controls
*/
class JavascriptRenderer
{
const INITIALIZE_CONSTRUCTOR = 2;
const INITIALIZE_CONTROLS = 4;
protected $debugBar;
protected $baseUrl;
protected $basePath;
protected $cssVendors = array('vendor/font-awesome/css/font-awesome.css');
protected $jsVendors = array('vendor/jquery-1.8.3.min.js', 'vendor/jquery.event.drag-2.2.js');
protected $includeVendors = true;
protected $cssFiles = array('debugbar.css');
protected $jsFiles = array('debugbar.js', 'widgets.js');
protected $javascriptClass = 'PhpDebugBar.DebugBar';
protected $variableName = 'phpdebugbar';
protected $initialization;
protected $controls = array();
/**
* @param DebugBar $debugBar
* @param string $baseUrl
* @param string $basePath
*/
public function __construct(DebugBar $debugBar, $baseUrl = '/debugbar', $basePath = null)
{
$this->debugBar = $debugBar;
$this->baseUrl = $baseUrl;
if ($basePath === null) {
$basePath = __DIR__ . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, array('..', '..', '..', 'web'));
}
$this->basePath = $basePath;
// bitwise operations cannot be done in class definition :(
$this->initialization = self::INITIALIZE_CONSTRUCTOR | self::INITIALIZE_CONTROLS;
}
/**
* Sets the path which assets are relative to
*
* @param string $path
*/
public function setBasePath($path)
{
$this->basePath = $path;
return $this;
}
/**
* Returns the path which assets are relative to
*
* @return string
*/
public function getBasePath()
{
return $this->basePath;
}
/**
* Sets the base URL from which assets will be served
*
* @param string $url
*/
public function setBaseUrl($url)
{
$this->baseUrl = $url;
return $this;
}
/**
* Returns the base URL from which assets will be served
*
* @return string
*/
public function getBaseUrl()
{
return $this->baseUrl;
}
/**
* Whether to include vendor assets
*
* @param boolean $enabled
*/
public function setIncludeVendors($enabled = true)
{
$this->includeVendors = $enabled;
return $this;
}
/**
* Checks if vendors assets are included
*
* @return boolean
*/
public function areVendorsIncluded()
{
return $this->includeVendors;
}
/**
* Sets the javascript class name
*
* @param string $className
*/
public function setJavascriptClass($className)
{
$this->javascriptClass = $className;
return $this;
}
/**
* Returns the javascript class name
*
* @return string
*/
public function getJavascriptClass()
{
return $this->javascriptClass;
}
/**
* Sets the variable name of the class instance
*
* @param string $name
*/
public function setVariableName($name)
{
$this->variableName = $name;
return $this;
}
/**
* Returns the variable name of the class instance
*
* @return string
*/
public function getVariableName()
{
return $this->variableName;
}
/**
* Sets what should be initialized
*
* - INITIALIZE_CONSTRUCTOR: only initializes the instance
* - INITIALIZE_CONTROLS: initializes the controls and data mapping
* - INITIALIZE_CONSTRUCTOR | INITIALIZE_CONTROLS: initialize everything (default)
*
* @param integer $init
*/
public function setInitialization($init)
{
$this->initialization = $init;
return $this;
}
/**
* Returns what should be initialized
*
* @return integer
*/
public function getInitialization()
{
return $this->initialization;
}
/**
* Adds a control to initialize
*
* Possible options:
* - icon: icon name
* - tooltip: string
* - widget: widget class name
* - map: a property name from the data to map the control to
* - default: a js string, default value of the data map
*
* "icon" or "widget" are at least needed
*
* @param string $name
* @param array $options
*/
public function addControl($name, $options)
{
if (!isset($options['icon']) || !isset($options['widget'])) {
throw new DebugBarException("Missing 'icon' or 'widget' option for control '$name'");
}
$this->controls[$name] = $options;
return $this;
}
/**
* Returns the list of asset files
*
* @return array
*/
protected function getAssetFiles()
{
$cssFiles = $this->cssFiles;
$jsFiles = $this->jsFiles;
if ($this->includeVendors) {
$cssFiles = array_merge($this->cssVendors, $cssFiles);
$jsFiles = array_merge($this->jsVendors, $jsFiles);
}
return array($cssFiles, $jsFiles);
}
/**
* Returns a tuple where the both items are Assetic AssetCollection,
* the first one being css files and the second js files
*
* @return array or \Assetic\Asset\AssetCollection
*/
public function getAsseticCollection()
{
list($cssFiles, $jsFiles) = $this->getAssetFiles();
return array(
$this->createAsseticCollection($cssFiles),
$this->createAsseticCollection($jsFiles)
);
}
/**
* Create an Assetic AssetCollection with the given files.
* Filenames will be converted to absolute path using
* the base path.
*
* @param array $files
* @return \Assetic\Asset\AssetCollection
*/
protected function createAsseticCollection($files)
{
$assets = array();
foreach ($files as $file) {
$assets[] = new \Assetic\Asset\FileAsset($this->makeUriRelativeTo($file, $this->basePath));
}
return new \Assetic\Asset\AssetCollection($assets);
}
/**
* Renders the html to include needed assets
*
* Only useful if Assetic is not used
*
* @return string
*/
public function renderHead()
{
list($cssFiles, $jsFiles) = $this->getAssetFiles();
$html = '';
foreach ($cssFiles as $file) {
$html .= sprintf('<link rel="stylesheet" type="text/css" href="%s">' . "\n",
$this->makeUriRelativeTo($file, $this->baseUrl));
}
foreach ($jsFiles as $file) {
$html .= sprintf('<script type"text/javascript" src="%s"></script>' . "\n",
$this->makeUriRelativeTo($file, $this->baseUrl));
}
return $html;
}
/**
* Makes a URI relative to another
*
* @param string $uri
* @param string $root
* @return string
*/
protected function makeUriRelativeTo($uri, $root)
{
if (substr($uri, 0, 1) === '/' || preg_match('/^([a-z]+:\/\/|[a-zA-Z]:\/)/', $uri)) {
return $uri;
}
return rtrim($root, '/') . "/$uri";
}
/**
* Returns the code needed to display the debug bar
*
* AJAX request should not render the initialization code.
*
* @param boolean $initialize Whether to render the de bug bar initialization code
* @return string
*/
public function render($initialize = true)
{
$js = '';
if ($initialize) {
$js = $this->getJsInitializationCode();
}
$js .= sprintf("%s.addDataSet(%s);\n", $this->variableName, json_encode($this->debugBar->getData()));
return "<script type=\"text/javascript\">\n$js\n</script>\n";
}
/**
* Returns the js code needed to initialize the debug bar
*
* @return string
*/
protected function getJsInitializationCode()
{
$js = '';
if (($this->initialization & self::INITIALIZE_CONSTRUCTOR) === self::INITIALIZE_CONSTRUCTOR) {
$js = sprintf("var %s = new %s();\n", $this->variableName, $this->javascriptClass);
}
if (($this->initialization & self::INITIALIZE_CONTROLS) === self::INITIALIZE_CONTROLS) {
$js .= $this->getJsControlsDefinitionCode($this->variableName);
}
return $js;
}
/**
* Returns the js code needed to initialized the controls and data mapping of the debug bar
*
* Controls can be defined by collectors themselves or using {@see addControl()}
*
* @param string $varname Debug bar's variable name
* @return string
*/
protected function getJsControlsDefinitionCode($varname)
{
$js = '';
$dataMap = array();
$controls = $this->controls;
// finds controls provided by collectors
foreach ($this->debugBar->getCollectors() as $collector) {
if ($collector instanceof Renderable) {
$controls = array_merge($controls, $collector->getWidgets());
}
}
foreach ($controls as $name => $options) {
if (isset($options['widget'])) {
$js .= sprintf("%s.createTab(\"%s\", new %s());\n",
$varname,
$name,
$options['widget']
);
} else {
$js .= sprintf("%s.createIndicator(\"%s\", \"%s\", \"%s\");\n",
$varname,
$name,
isset($options['icon']) ? $options['icon'] : 'null',
isset($options['tooltip']) ? $options['tooltip'] : 'null'
);
}
if (isset($options['map']) && isset($options['default'])) {
$dataMap[$name] = array($options['map'], $options['default']);
}
}
// creates the data mapping object
$mapJson = array();
foreach ($dataMap as $name => $values) {
$mapJson[] = sprintf('"%s": ["%s", %s]', $name, $values[0], $values[1]);
}
$js .= sprintf("%s.setDataMap({\n%s\n});\n", $varname, implode(",\n", $mapJson));
// activate state restauration
$js .= sprintf("%s.restoreState();\n", $varname);
return $js;
}
}

View File

@ -1,159 +0,0 @@
<?php
namespace DebugBar\Renderer;
use DebugBar\DebugBar;
class JavascriptRenderer
{
protected $baseUrl = '/';
protected $cssVendors = array('vendor/font-awesome/css/font-awesome.css');
protected $jsVendors = array('vendor/jquery-1.8.3.min.js', 'vendor/jquery.event.drag-2.2.js');
protected $cssFiles = array('debugbar.css');
protected $jsFiles = array('debugbar.js', 'widgets.js');
protected $includeVendors = true;
protected $includeFiles = true;
protected $toolbarFile = 'standard-debugbar.js';
protected $toolbarClass = 'StandardPhpDebugBar';
protected $toolbarVariableName = 'phpdebugbar';
/**
* @param \DebugBar\DebugBar $debugBar
* @param string $baseUrl
*
* @Inject(debugBar="debugBar", baseUrl="$[debugBarRenderer][baseUrl]")
*/
public function __construct(DebugBar $debugBar, $baseUrl = '/')
{
$this->debugBar = $debugBar;
$this->setBaseUrl($baseUrl);
}
public function setBaseUrl($url)
{
$this->baseUrl = $url;
return $this;
}
public function getBaseUrl()
{
return $this->baseUrl;
}
public function setIncludeVendors($enabled = true)
{
$this->includeVendors = $enabled;
return $this;
}
public function areVendorsIncluded()
{
return $this->includeVendors;
}
public function setIncludeFiles($enabled = true)
{
$this->includeFiles = $enabled;
return $this;
}
public function areFilesIncluded()
{
return $this->includeFiles;
}
public function setToolbarFile($file)
{
$this->toolbarFile = $file;
return $this;
}
public function getToolbarFile()
{
return $this->toolbarFile;
}
public function setToolbarClass($className)
{
$this->toolbarClass = $className;
return $this;
}
public function getToolbarClass()
{
return $this->toolbarClass;
}
public function setToolbarVariableName($name)
{
$this->toolbarVariableName = $name;
}
public function getToolbarVariableName()
{
return $this->toolbarVariableName;
}
public function renderIncludes()
{
$cssFiles = array();
$jsFiles = array();
if ($this->includeVendors) {
$cssFiles = array_merge($cssFiles, $this->cssVendors);
$jsFiles = array_merge($jsFiles, $this->jsVendors);
}
if ($this->includeFiles) {
$cssFiles = array_merge($cssFiles, $this->cssFiles);
$jsFiles = array_merge($jsFiles, $this->jsFiles);
}
$jsFiles[] = $this->toolbarFile;
$html = '';
foreach ($cssFiles as $file) {
$html .= sprintf('<link rel="stylesheet" type="text/css" href="%s">' . "\n",
$this->makeUrlRelativeTo($file, $this->baseUrl));
}
foreach ($jsFiles as $file) {
$html .= sprintf('<script type"text/javascript" src="%s"></script>' . "\n",
$this->makeUrlRelativeTo($file, $this->baseUrl));
}
return $html;
}
public function renderToolbar()
{
return sprintf('<script type="text/javascript">var %s = new %s(%s);</script>' . "\n",
$this->toolbarVariableName, $this->toolbarClass, json_encode($this->debugBar->getData()));
}
public function renderAjaxToolbar()
{
return sprintf('<script type="text/javascript">%s.addDataStack(%s);</script>' . "\n",
$this->toolbarVariableName, json_encode($this->debugBar->getData()));
}
public function renderAll()
{
return $this->renderIncludes() . $this->renderToolbar();
}
protected function makeUrlRelativeTo($url, $root)
{
if (substr($url, 0, 1) === '/' || preg_match('/^[a-z]+:\/\//', $url)) {
return $url;
}
return rtrim($root, '/') . "/$url";
}
}

View File

@ -1,21 +1,35 @@
<?php <?php
/*
* This file is part of the DebugBar package.
*
* (c) 2013 Maxime Bouroumeau-Fuseau
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace DebugBar; namespace DebugBar;
use DebugBar\DataCollector\PhpInfoCollector; use DebugBar\DataCollector\PhpInfoCollector;
use DebugBar\DataCollector\MessagesCollector; use DebugBar\DataCollector\MessagesCollector;
use DebugBar\DataCollector\TimeCollector; use DebugBar\DataCollector\TimeDataCollector;
use DebugBar\DataCollector\RequestDataCollector; use DebugBar\DataCollector\RequestDataCollector;
use DebugBar\DataCollector\MemoryCollector; use DebugBar\DataCollector\MemoryCollector;
/**
* Debug bar subclass which adds all included collectors
*/
class StandardDebugBar extends DebugBar class StandardDebugBar extends DebugBar
{ {
/**
* {@inheritDoc}
*/
public function __construct() public function __construct()
{ {
$this->addCollector(new PhpInfoCollector()); $this->addCollector(new PhpInfoCollector());
$this->addCollector(new MessagesCollector()); $this->addCollector(new MessagesCollector());
$this->addCollector(new TimeCollector());
$this->addCollector(new RequestDataCollector()); $this->addCollector(new RequestDataCollector());
$this->addCollector(new TimeDataCollector());
$this->addCollector(new MemoryCollector()); $this->addCollector(new MemoryCollector());
} }
} }

View File

@ -0,0 +1,8 @@
<?php
namespace DebugBar\Tests;
abstract class DebugBarTestCase extends \PHPUnit_Framework_TestCase
{
}

View File

@ -1,27 +1,27 @@
.phpdebugbar { .phpdebugbar {
position: absolute; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
font-family: arial; font-family: arial;
background: #fff; background: #fff;
} }
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-header { .phpdebugbar-header {
background: #efefef url(php-icon.png) no-repeat 5px 4px; background: #efefef url(php-icon.png) no-repeat 5px 4px;
padding-left: 29px; padding-left: 29px;
min-height: 26px; min-height: 26px;
} }
.phpdebugbar-header:before, .phpdebugbar-header:after { .phpdebugbar-header:before, .phpdebugbar-header:after {
display: table; display: table;
line-height: 0; line-height: 0;
content: ""; content: "";
} }
.phpdebugbar-header:after { .phpdebugbar-header:after {
clear: both; clear: both;
} }
/* -------------------------------------- */ /* -------------------------------------- */
@ -29,79 +29,70 @@
.phpdebugbar-tab, .phpdebugbar-tab,
.phpdebugbar-indicator, .phpdebugbar-indicator,
.phpdebugbar-close-btn { .phpdebugbar-close-btn {
float: left; float: left;
padding: 5px 8px; padding: 5px 8px;
font-size: 14px; font-size: 14px;
color: #555; color: #555;
text-decoration: none; text-decoration: none;
} }
.phpdebugbar-indicator, .phpdebugbar-indicator,
.phpdebugbar-close-btn { .phpdebugbar-close-btn {
float: right; float: right;
border-right: 1px solid #ddd; border-right: 1px solid #ddd;
} }
.phpdebugbar-tab.active { .phpdebugbar-tab.active {
background: #ccc; background: #ccc;
color: #444; color: #444;
background-image: linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%); background-image: linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%);
background-image: -o-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%); background-image: -o-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%);
background-image: -moz-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%); background-image: -moz-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%);
background-image: -webkit-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%); background-image: -webkit-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%);
background-image: -ms-linear-gradient(bottom, rgb(173,173,173) 41%, rgb(209,209,209) 71%); 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)));
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-close-btn { .phpdebugbar-close-btn {
display: none; display: none;
} }
.phpdebugbar-indicator { .phpdebugbar-indicator {
position: relative; position: relative;
} }
.phpdebugbar-indicator .text { .phpdebugbar-indicator .text {
margin-left: 5px; margin-left: 5px;
} }
.phpdebugbar-indicator .tooltip { .phpdebugbar-indicator .tooltip {
display: none;
position: absolute;
top: -30px;
background: #efefef;
opacity: .7;
border: 1px solid #ccc;
color: #555;
font-size: 11px;
padding: 2px 3px;
z-index: 1000;
}
.phpdebugbar-indicator:hover .tooltip {
display: block;
}
.phpdebugbar-data-stacks {
float: right;
display: none; display: none;
margin: 2px 0 0 7px; position: absolute;
top: -30px;
background: #efefef;
opacity: .7;
border: 1px solid #ccc;
color: #555;
font-size: 11px;
padding: 2px 3px;
z-index: 1000;
}
.phpdebugbar-indicator:hover .tooltip {
display: block;
}
.phpdebugbar-datasets-switcher {
float: right;
display: none;
margin: 2px 0 0 7px;
} }
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-body { .phpdebugbar-body {
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
display: none; display: none;
position: relative; position: relative;
height: 300px; height: 300px;
} }
.phpdebugbar-resize-handle {
.phpdebugbar-resize-handle {
display: none; display: none;
height: 4px; height: 4px;
width: 100%; width: 100%;
@ -110,148 +101,151 @@
cursor: move; cursor: move;
position: absolute; position: absolute;
top: -33px; top: -33px;
} }
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-panel { .phpdebugbar-panel {
display: none; display: none;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
width: 100%; width: 100%;
} }
.phpdebugbar-panel.active { .phpdebugbar-panel.active {
display: block; display: block;
} }
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-widgets-list { .phpdebugbar-widgets-list {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
font-family: monospace; font-family: monospace;
} }
.phpdebugbar-widgets-list .list-item { .phpdebugbar-widgets-list .list-item {
padding: 3px 6px; padding: 3px 6px;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
.phpdebugbar-widgets-list .list-item:hover { .phpdebugbar-widgets-list .list-item:hover {
background: #fafafa; background: #fafafa;
} }
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-widgets-messages { .phpdebugbar-widgets-messages {
position: relative; position: relative;
height: 100%; height: 100%;
} }
.phpdebugbar-widgets-messages .list-item .value.warn:before { .phpdebugbar-widgets-messages .list-item .value.warn:before {
font-family: FontAwesome; font-family: FontAwesome;
content: "\f071"; content: "\f071";
margin-right: 8px; margin-right: 8px;
font-size: 11px; font-size: 11px;
color: #ecb03d; color: #ecb03d;
}
.phpdebugbar-widgets-messages .list-item .value.error {
color: red;
}
.phpdebugbar-widgets-messages .list-item .value.error:before {
font-family: FontAwesome;
content: "\f057";
margin-right: 8px;
font-size: 11px;
color: red;
}
.phpdebugbar-widgets-messages .list-item .backtrace,
.phpdebugbar-widgets-messages .list-item .label {
float: right;
font-size: 12px;
padding: 2px 4px;
color: #888;
margin: 0 2px;
text-decoration: none;
}
.phpdebugbar-widgets-messages .toolbar {
position: absolute;
bottom: 0;
width: 100%;
background: #fff;
}
.phpdebugbar-widgets-messages .toolbar input {
border: 0;
margin-left: 7px;
width: 50%;
} }
.phpdebugbar-widgets-messages .list-item .value.error { .phpdebugbar-widgets-messages .toolbar input:focus {
color: red; outline: none;
} }
.phpdebugbar-widgets-messages .list-item .value.error:before { .phpdebugbar-widgets-messages .toolbar .filter {
font-family: FontAwesome;
content: "\f057";
margin-right: 8px;
font-size: 11px;
color: red;
}
.phpdebugbar-widgets-messages .list-item .backtrace,
.phpdebugbar-widgets-messages .list-item .label {
float: right; float: right;
font-size: 12px; font-size: 12px;
padding: 2px 4px; padding: 2px 4px;
color: #888; background: #7cacd5;
margin: 0 2px; margin: 0 2px;
border-radius: 4px;
color: #fff;
text-decoration: none; text-decoration: none;
} }
.phpdebugbar-widgets-messages .toolbar { .phpdebugbar-widgets-messages .toolbar .filter.disabled {
position: absolute; background: #eee;
bottom: 0; color: #888;
width: 100%; }
background: #fff;
}
.phpdebugbar-widgets-messages .toolbar input {
border: 0;
margin-left: 7px;
width: 50%;
}
.phpdebugbar-widgets-messages .toolbar input:focus {
outline: none;
}
.phpdebugbar-widgets-messages .toolbar .filter {
float: right;
font-size: 12px;
padding: 2px 4px;
background: #7cacd5;
margin: 0 2px;
border-radius: 4px;
color: #fff;
text-decoration: none;
}
.phpdebugbar-widgets-messages .toolbar .filter.disabled {
background: #eee;
color: #888;
}
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-widgets-kvlist { .phpdebugbar-widgets-kvlist {
margin: 0; margin: 0;
} }
.phpdebugbar-widgets-kvlist dt { .phpdebugbar-widgets-kvlist dt {
float: left; float: left;
width: 140px; width: 140px;
padding: 5px; padding: 5px;
border-top: 1px solid #eee; border-top: 1px solid #eee;
font-weight: bold; font-weight: bold;
clear: both; clear: both;
} }
.phpdebugbar-widgets-kvlist dd { .phpdebugbar-widgets-kvlist dd {
margin-left: 150px; margin-left: 150px;
padding: 5px; padding: 5px;
border-top: 1px solid #eee; border-top: 1px solid #eee;
cursor: pointer; cursor: pointer;
} }
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-widgets-varlist { .phpdebugbar-widgets-varlist {
font-family: monospace; font-family: monospace;
} }
/* -------------------------------------- */ /* -------------------------------------- */
.phpdebugbar-widgets-timeline { .phpdebugbar-widgets-timeline {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
.phpdebugbar-widgets-timeline li { .phpdebugbar-widgets-timeline li {
height: 20px; height: 20px;
position: relative; position: relative;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
}
.phpdebugbar-widgets-timeline li:hover {
background: #fafafa;
}
.phpdebugbar-widgets-timeline li .label {
position: absolute;
font-size: 12px;
font-family: monospace;
color: #555;
top: 4px;
left: 5px;
}
.phpdebugbar-widgets-timeline li .value {
display: block;
position: absolute;
height: 10px;
background: #3db9ec;
top: 5px;
border-radius: 2px;
} }
.phpdebugbar-widgets-timeline li .label {
position: absolute;
font-size: 12px;
font-family: monospace;
color: #555;
top: 4px;
left: 5px;
}
.phpdebugbar-widgets-timeline li .value {
display: block;
position: absolute;
height: 10px;
background: #3db9ec;
top: 5px;
border-radius: 2px;
}

View File

@ -1,41 +1,176 @@
if (typeof(PhpDebugBar) == 'undefined') { if (typeof(PhpDebugBar) == 'undefined') {
// namespace
var PhpDebugBar = {}; var PhpDebugBar = {};
} }
if (typeof(localStorage) == 'undefined') { if (typeof(localStorage) == 'undefined') {
// provide mock localStorage object for dumb browsers
var localStorage = { var localStorage = {
setItem: function(key, value) {}, setItem: function(key, value) {},
getItem: function(key) { return null; } getItem: function(key) { return null; }
}; };
} }
/**
* DebugBar
*
* Creates a bar that appends itself to the body of your page
* and sticks to the bottom.
*
* The bar can be customized by adding tabs and indicators.
* A data map is used to fill those controls with data provided
* from datasets.
*
* @constructor
*/
PhpDebugBar.DebugBar = (function($) { PhpDebugBar.DebugBar = (function($) {
function getDictValue(dict, key, def) { /**
* Returns the value from an object property.
* Using dots in the key, it is possible to retreive nested property values
*
* @param {Object} dict
* @param {String} key
* @param {Object} default_value
* @return {Object}
*/
function getDictValue(dict, key, default_value) {
var d = dict, parts = key.split('.'); var d = dict, parts = key.split('.');
for (var i = 0; i < parts.length; i++) { for (var i = 0; i < parts.length; i++) {
if (!d[parts[i]]) { if (!d[parts[i]]) {
return def; return default_value;
} }
d = d[parts[i]]; d = d[parts[i]];
} }
return d; return d;
} }
// ------------------------------------------------------------------
/**
* Tab
*
* A tab is composed of a tab label which is always visible and
* a tab panel which is visible only when the tab is active.
*
* The panel must contain a widget. A widget is an object which has
* an element property containing something appendable to a jQuery object.
*
* @this {Tab}
* @constructor
* @param {String} title
* @param {Object} widget
*/
var Tab = function(title, widget) {
this.tab = $('<a href="javascript:" class="phpdebugbar-tab" />').text(title);
this.panel = $('<div class="phpdebugbar-panel" />');
this.replaceWidget(widget);
};
/**
* Sets the title of the tab
*
* @this {Tab}
* @param {String} text
*/
Tab.prototype.setTitle = function(text) {
this.tab.text(text);
};
/**
* Replaces the widget inside the panel
*
* @this {Tab}
* @param {Object} new_widget
*/
Tab.prototype.replaceWidget = function(new_widget) {
this.panel.empty().append(new_widget.element);
this.widget = new_widget;
};
// ------------------------------------------------------------------
/**
* Indicator
*
* An indicator is a text and an icon to display single value information
* right inside the always visible part of the debug bar
*
* @this {Indicator}
* @constructor
* @param {String} icon
* @param {String} tooltip
* @param {String} position "right" or "left", default is "right"
*/
var Indicator = function(icon, tooltip, position) {
if (!position) {
position = 'right'
}
this.position = position;
this.element = $('<span class="phpdebugbar-indicator" />').css('float', position);
this.label = $('<span class="text" />').appendTo(this.element);
if (icon) {
$('<i class="icon-' + icon + '" />').insertBefore(this.label);
}
if (tooltip) {
this.element.append($('<span class="tooltip" />').text(tooltip));
}
};
/**
* Sets the text of the indicator
*
* @this {Indicator}
* @param {String} text
*/
Indicator.prototype.setText = function(text) {
this.element.find('.text').text(text);
};
/**
* Sets the tooltip of the indicator
*
* @this {Indicator}
* @param {String} text
*/
Indicator.prototype.setTooltip = function(text) {
this.element.find('.tooltip').text(text);
};
// ------------------------------------------------------------------
/**
* DebugBar
*
* @this {DebugBar}
* @constructor
*/
var DebugBar = function() { var DebugBar = function() {
this.controls = {}; this.controls = {};
this.dataMap = {}; this.dataMap = {};
this.datasets = {};
this.initUI(); this.initUI();
this.init(); this.init();
this.restoreState();
}; };
/**
* Initialiazes the UI
*
* @this {DebugBar}
*/
DebugBar.prototype.initUI = function() { DebugBar.prototype.initUI = function() {
var self = this;
this.element = $('<div class="phpdebugbar" />').appendTo('body'); this.element = $('<div class="phpdebugbar" />').appendTo('body');
this.header = $('<div class="phpdebugbar-header" />').appendTo(this.element); this.header = $('<div class="phpdebugbar-header" />').appendTo(this.element);
this.body = $('<div class="phpdebugbar-body" />').appendTo(this.element); this.body = $('<div class="phpdebugbar-body" />').appendTo(this.element);
this.resizeHandle = $('<div class="phpdebugbar-resize-handle" />').appendTo(this.body); this.resizeHandle = $('<div class="phpdebugbar-resize-handle" />').appendTo(this.body);
this.firstPanelName = null;
this.activePanelName = null;
// allow resizing by dragging handle
this.body.drag('start', function(e, dd) { this.body.drag('start', function(e, dd) {
dd.height = $(this).height(); dd.height = $(this).height();
}).drag(function(e, dd) { }).drag(function(e, dd) {
@ -44,21 +179,35 @@ PhpDebugBar.DebugBar = (function($) {
localStorage.setItem('phpdebugbar-height', h); localStorage.setItem('phpdebugbar-height', h);
}, {handle: '.phpdebugbar-resize-handle'}); }, {handle: '.phpdebugbar-resize-handle'});
// close button
this.closeButton = $('<a class="phpdebugbar-close-btn" href="javascript:"><i class="icon-remove"></i></a>').appendTo(this.header); this.closeButton = $('<a class="phpdebugbar-close-btn" href="javascript:"><i class="icon-remove"></i></a>').appendTo(this.header);
var self = this;
this.closeButton.click(function() { this.closeButton.click(function() {
self.hidePanels(); self.hidePanels();
}); });
this.stackSelectBox = $('<select class="phpdebugbar-data-stacks" />').appendTo(this.header); // select box for data sets
this.stackSelectBox.change(function() { this.datasetSelectBox = $('<select class="phpdebugbar-datasets-switcher" />').appendTo(this.header);
self.dataChangeHandler(self.stacks[this.value]); this.datasetSelectBox.change(function() {
self.dataChangeHandler(self.datasets[this.value]);
}); });
}; };
/**
* Custom initialiaze function for subsclasses
*
* @this {DebugBar}
*/
DebugBar.prototype.init = function() {}; DebugBar.prototype.init = function() {};
/**
* Restores the state of the DebugBar using localStorage
* This is not called by default in the constructor and
* needs to be called by subclasses in their init() method
*
* @this {DebugBar}
*/
DebugBar.prototype.restoreState = function() { DebugBar.prototype.restoreState = function() {
// bar height
var height = localStorage.getItem('phpdebugbar-height'); var height = localStorage.getItem('phpdebugbar-height');
if (height) { if (height) {
this.body.css('height', height); this.body.css('height', height);
@ -66,86 +215,171 @@ PhpDebugBar.DebugBar = (function($) {
localStorage.setItem('phpdebugbar-height', this.body.height()); localStorage.setItem('phpdebugbar-height', this.body.height());
} }
// bar visibility
var visible = localStorage.getItem('phpdebugbar-visible'); var visible = localStorage.getItem('phpdebugbar-visible');
if (visible && visible == '1') { if (visible && visible == '1') {
this.showPanel(localStorage.getItem('phpdebugbar-panel')); this.showPanel(localStorage.getItem('phpdebugbar-panel'));
} }
}; };
/**
* Creates and adds a new tab
*
* @this {DebugBar}
* @param {String} name Internal name
* @param {Object} widget A widget object with an element property
* @param {String} title The text in the tab, if not specified, name will be used
* @return {Tab}
*/
DebugBar.prototype.createTab = function(name, widget, title) { DebugBar.prototype.createTab = function(name, widget, title) {
var tab = new Tab(title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)), widget);
return this.addTab(name, tab);
};
/**
* Adds a new tab
*
* @this {DebugBar}
* @param {String} name Internal name
* @param {Tab} tab Tab object
* @return {Tab}
*/
DebugBar.prototype.addTab = function(name, tab) {
if (this.isControl(name)) { if (this.isControl(name)) {
throw new Exception(name + ' already exists'); throw new Exception(name + ' already exists');
} }
var tab = $('<a href="javascript:" class="phpdebugbar-tab" />').text(title || (name.charAt(0).toUpperCase() + name.slice(1))), var self = this;
panel = $('<div class="phpdebugbar-panel" data-id="' + name + '" />'), tab.tab.appendTo(this.header).click(function() { self.showPanel(name); });
self = this; tab.panel.appendTo(this.body);
tab.appendTo(this.header).click(function() { self.showPanel(name); }); this.controls[name] = tab;
panel.appendTo(this.body).append(widget.element); if (this.firstPanelName == null) {
this.firstPanelName = name;
this.controls[name] = {type: "tab", tab: tab, panel: panel, widget: widget}; }
return widget; return tab;
}; };
DebugBar.prototype.setTabTitle = function(name, title) { /**
* Returns a Tab object
*
* @this {DebugBar}
* @param {String} name
* @return {Tab}
*/
DebugBar.prototype.getTab = function(name) {
if (this.isTab(name)) { if (this.isTab(name)) {
this.controls[name].tab.text(title); return this.controls[name];
}
};
DebugBar.prototype.getTabWidget = function(name) {
if (this.isTab(name)) {
return this.controls[name].widget;
} }
}; };
/**
* Creates and adds an indicator
*
* @this {DebugBar}
* @param {String} name Internal name
* @param {String} icon
* @param {String} tooltip
* @param {String} position "right" or "left", default is "right"
* @return {Indicator}
*/
DebugBar.prototype.createIndicator = function(name, icon, tooltip, position) { DebugBar.prototype.createIndicator = function(name, icon, tooltip, position) {
var indicator = new Indicator(icon, tooltip, position);
return this.addIndicator(name, indicator);
};
/**
* Adds an indicator
*
* @this {DebugBar}
* @param {String} name Internal name
* @param {Indicator} indicator Indicator object
* @return {Indicator}
*/
DebugBar.prototype.addIndicator = function(name, indicator) {
if (this.isControl(name)) { if (this.isControl(name)) {
throw new Exception(name + ' already exists'); throw new Exception(name + ' already exists');
} }
if (!position) {
position = 'right'
}
var indicator = $('<span class="phpdebugbar-indicator" />').css('float', position), if (indicator.position == 'right') {
text = $('<span class="text" />').appendTo(indicator); indicator.element.appendTo(this.header);
if (icon) {
$('<i class="icon-' + icon + '" />').insertBefore(text);
}
if (tooltip) {
indicator.append($('<span class="tooltip" />').text(tooltip));
}
if (position == 'right') {
indicator.appendTo(this.header);
} else { } else {
indicator.insertBefore(this.header.children().first()) indicator.element.insertBefore(this.header.children().first())
} }
this.controls[name] = {type: "indicator", indicator: indicator}; this.controls[name] = indicator;
return text; return indicator;
}; };
DebugBar.prototype.setIndicatorText = function(name, text) { /**
* Returns an Indicator object
*
* @this {DebugBar}
* @param {String} name
* @return {Indicator}
*/
DebugBar.prototype.getIndicator = function(name) {
if (this.isIndicator(name)) { if (this.isIndicator(name)) {
this.controls[name].indicator.find('.text').text(text); return this.controls[name];
} }
}; };
/**
* Adds a control
*
* @param {String} name
* @param {Object} control
* @return {Object}
*/
DebugBar.prototype.addControl = function(name, control) {
if (control instanceof Tab) {
this.addTab(name, control);
} else if (control instanceof Indicator) {
this.addIndicator(name, control);
} else {
throw new Exception("Unknown type of control");
}
return control;
};
/**
* Checks if there's a control under the specified name
*
* @this {DebugBar}
* @param {String} name
* @return {Boolean}
*/
DebugBar.prototype.isControl = function(name) { DebugBar.prototype.isControl = function(name) {
return typeof(this.controls[name]) != 'undefined'; return typeof(this.controls[name]) != 'undefined';
}; };
/**
* Checks if a tab with the specified name exists
*
* @this {DebugBar}
* @param {String} name
* @return {Boolean}
*/
DebugBar.prototype.isTab = function(name) { DebugBar.prototype.isTab = function(name) {
return typeof(this.controls[name]) != 'undefined' && this.controls[name].type === 'tab'; return this.isControl(name) && this.controls[name] instanceof Tab;
}; };
/**
* Checks if an indicator with the specified name exists
*
* @this {DebugBar}
* @param {String} name
* @return {Boolean}
*/
DebugBar.prototype.isIndicator = function(name) { DebugBar.prototype.isIndicator = function(name) {
return this.isControl(name) && this.controls[name].type === 'indicator'; return this.isControl(name) && this.controls[name] instanceof Indicator;
}; };
/**
* Removes all tabs and indicators from the debug bar and hides it
*
* @this {DebugBar}
*/
DebugBar.prototype.reset = function() { DebugBar.prototype.reset = function() {
this.hidePanels(); this.hidePanels();
this.body.find('.phpdebugbar-panel').remove(); this.body.find('.phpdebugbar-panel').remove();
@ -153,17 +387,22 @@ PhpDebugBar.DebugBar = (function($) {
this.controls = {}; this.controls = {};
}; };
/**
* Open the debug bar and display a specified panel
*
* @this {DebugBar}
* @param {String} name If not specified, display the first panel
*/
DebugBar.prototype.showPanel = function(name) { DebugBar.prototype.showPanel = function(name) {
this.resizeHandle.show(); this.resizeHandle.show();
this.body.show(); this.body.show();
this.closeButton.show(); this.closeButton.show();
if (!name) { if (!name) {
activePanel = this.body.find('.phpdebugbar-panel.active'); if (this.activePanelName) {
if (activePanel.length > 0) { name = this.activePanelName;
name = activePanel.data('id');
} else { } else {
name = this.body.find('.phpdebugbar-panel').first().data('id'); name = this.firstPanelName;
} }
} }
@ -173,15 +412,26 @@ PhpDebugBar.DebugBar = (function($) {
if (this.isTab(name)) { if (this.isTab(name)) {
this.controls[name].tab.addClass('active'); this.controls[name].tab.addClass('active');
this.controls[name].panel.addClass('active').show(); this.controls[name].panel.addClass('active').show();
this.activePanelName = name;
} }
localStorage.setItem('phpdebugbar-visible', '1'); localStorage.setItem('phpdebugbar-visible', '1');
localStorage.setItem('phpdebugbar-panel', name); localStorage.setItem('phpdebugbar-panel', name);
}; };
/**
* Shows the first panel
*
* @this {DebugBar}
*/
DebugBar.prototype.showFirstPanel = function() { DebugBar.prototype.showFirstPanel = function() {
this.showPanel(this.body.find('.phpdebugbar-panel').first().data('id')); this.showPanel(this.firstPanelName);
}; };
/**
* Hide panels and "close" the debug bar
*
* @this {DebugBar}
*/
DebugBar.prototype.hidePanels = function() { DebugBar.prototype.hidePanels = function() {
this.header.find('.phpdebugbar-tab.active').removeClass('active'); this.header.find('.phpdebugbar-tab.active').removeClass('active');
this.body.hide(); this.body.hide();
@ -190,40 +440,121 @@ PhpDebugBar.DebugBar = (function($) {
localStorage.setItem('phpdebugbar-visible', '0'); localStorage.setItem('phpdebugbar-visible', '0');
}; };
DebugBar.prototype.setData = function(data) { /**
this.stacks = {}; * Sets the data map used by dataChangeHandler to populate
this.addDataStack(data); * indicators and widgets
*
* A data map is an object where properties are control names.
* The value of each property should be an array where the first
* item is the name of a property from the data object (nested properties
* can be specified) and the second item the default value.
*
* Example:
* {"memory": ["memory.peak_usage_str", "0B"]}
*
* @this {DebugBar}
* @param {Object} map
*/
DebugBar.prototype.setDataMap = function(map) {
this.dataMap = map;
}; };
DebugBar.prototype.addDataStack = function(data, id) { /**
id = id || ("Request #" + (Object.keys(this.stacks).length + 1)); * Same as setDataMap() but appends to the existing map
this.stacks[id] = data; * rather than replacing it
*
* @this {DebugBar}
* @param {Object} map
*/
DebugBar.prototype.addDataMap = function(map) {
$.extend(this.dataMap, map);
};
this.stackSelectBox.append($('<option value="' + id + '">' + id + '</option>')); /**
if (Object.keys(this.stacks).length > 1) { * Resets datasets and add one set of data
this.stackSelectBox.show(); *
* For this method to be usefull, you need to specify
* a dataMap using setDataMap()
*
* @this {DebugBar}
* @param {Object} data
* @return {String} Dataset's id
*/
DebugBar.prototype.setData = function(data) {
this.datasets = {};
return this.addDataSet(data);
};
/**
* Adds a dataset
*
* If more than one dataset are added, the dataset selector
* will be displayed.
*
* For this method to be usefull, you need to specify
* a dataMap using setDataMap()
*
* @this {DebugBar}
* @param {Object} data
* @param {String} id The name of this set, optional
* @return {String} Dataset's id
*/
DebugBar.prototype.addDataSet = function(data, id) {
id = id || ("Request #" + (Object.keys(this.datasets).length + 1));
this.datasets[id] = data;
this.datasetSelectBox.append($('<option value="' + id + '">' + id + '</option>'));
if (Object.keys(this.datasets).length > 1) {
this.datasetSelectBox.show();
} }
this.switchDataStack(id); this.showDataSet(id);
return id;
}; };
DebugBar.prototype.switchDataStack = function(id) { /**
this.dataChangeHandler(this.stacks[id]); * Returns the data from a dataset
this.stackSelectBox.val(id); *
* @this {DebugBar}
* @param {String} id
* @return {Object}
*/
DebugBar.prototype.getDataSet = function(id) {
return this.datasets[id];
}; };
/**
* Switch the currently displayed dataset
*
* @this {DebugBar}
* @param {String} id
*/
DebugBar.prototype.showDataSet = function(id) {
this.dataChangeHandler(this.datasets[id]);
this.datasetSelectBox.val(id);
};
/**
* Called when the current dataset is modified.
*
* @this {DebugBar}
* @param {Object} data
*/
DebugBar.prototype.dataChangeHandler = function(data) { DebugBar.prototype.dataChangeHandler = function(data) {
var self = this; var self = this;
$.each(this.dataMap, function(key, def) { $.each(this.dataMap, function(key, def) {
var d = getDictValue(data, def[0], def[1]); var d = getDictValue(data, def[0], def[1]);
if (self.isIndicator(key)) { if (self.isIndicator(key)) {
self.setIndicatorText(key, d); self.getIndicator(key).setText(d);
} else { } else {
self.getTabWidget(key).setData(d); self.getTab(key).widget.setData(d);
} }
}); });
}; };
DebugBar.Tab = Tab;
DebugBar.Indicator = Indicator;
return DebugBar; return DebugBar;
})(jQuery); })(jQuery);

View File

@ -1,30 +0,0 @@
var StandardPhpDebugBar = (function() {
var DebugBar = function(data) {
PhpDebugBar.DebugBar.apply(this);
this.setData(data);
};
DebugBar.prototype = new PhpDebugBar.DebugBar();
DebugBar.prototype.init = function() {
this.createIndicator('memory', 'cogs', 'Memory Usage');
this.createIndicator('time', 'time', 'Request duration');
this.createTab('messages', new PhpDebugBar.Widgets.MessagesWidget());
this.createTab('request', new PhpDebugBar.Widgets.VariableListWidget());
this.createTab('timeline', new PhpDebugBar.Widgets.TimelineWidget());
this.dataMap = {
"memory": ["memory.peak_usage_str", "0B"],
"time": ["time.duration_str", "0ms"],
"messages": ["messages", []],
"request": ["request", {}],
"timeline": ["time", {}]
};
};
return DebugBar;
})();

View File

@ -1,15 +1,35 @@
if (typeof(PhpDebugBar) == 'undefined') { if (typeof(PhpDebugBar) == 'undefined') {
// namespace
var PhpDebugBar = {}; var PhpDebugBar = {};
} }
/**
* @namespace
*/
PhpDebugBar.Widgets = (function($) { PhpDebugBar.Widgets = (function($) {
var widgets = {}; var widgets = {};
/**
* Replaces spaces with &nbsp; and line breaks with <br>
*
* @param {String} text
* @return {String}
*/
var htmlize = function(text) { var htmlize = function(text) {
return text.replace(/\n/g, '<br>').replace(/\s/g, "&nbsp;") return text.replace(/\n/g, '<br>').replace(/\s/g, "&nbsp;")
}; };
widgets.htmlize = htmlize;
/**
* Returns a string representation of value, using JSON.stringify
* if it's an object.
*
* @param {Object} value
* @param {Boolean} prettify Uses htmlize() if true
* @return {String}
*/
var renderValue = function(value, prettify) { var renderValue = function(value, prettify) {
if (typeof(value) !== 'string') { if (typeof(value) !== 'string') {
if (prettify) { if (prettify) {
@ -20,8 +40,21 @@ PhpDebugBar.Widgets = (function($) {
return value; return value;
}; };
// ------------------------------------------ widgets.renderValue = renderValue;
// ------------------------------------------------------------------
// Generic widgets
// ------------------------------------------------------------------
/**
* Displays array element in a <ul> list
*
* @this {ListWidget}
* @constructor
* @param {Array} data
* @param {Function} itemRenderer Optional
*/
var ListWidget = function(data, itemRenderer) { var ListWidget = function(data, itemRenderer) {
this.element = $('<ul class="phpdebugbar-widgets-list" />'); this.element = $('<ul class="phpdebugbar-widgets-list" />');
if (itemRenderer) { if (itemRenderer) {
@ -32,6 +65,12 @@ PhpDebugBar.Widgets = (function($) {
} }
}; };
/**
* Sets the data and updates the list
*
* @this {ListWidget}
* @param {Array} data
*/
ListWidget.prototype.setData = function(data) { ListWidget.prototype.setData = function(data) {
this.element.empty(); this.element.empty();
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
@ -40,14 +79,154 @@ PhpDebugBar.Widgets = (function($) {
} }
}; };
/**
* Renders the content of a <li> element
*
* @this {ListWidget}
* @param {jQuery} li The <li> element as a jQuery Object
* @param {Object} value An item from the data array
*/
ListWidget.prototype.itemRenderer = function(li, value) { ListWidget.prototype.itemRenderer = function(li, value) {
li.html(renderValue(value)); li.html(renderValue(value));
}; };
widgets.ListWidget = ListWidget; widgets.ListWidget = ListWidget;
// ------------------------------------------ // ------------------------------------------------------------------
/**
* Displays object property/value paris in a <dl> list
*
* @this {KVListWidget}
* @constructor
* @param {Object} data
* @param {Function} itemRenderer Optional
*/
var KVListWidget = function(data, itemRenderer) {
this.element = $('<dl class="phpdebugbar-widgets-kvlist" />');
if (itemRenderer) {
this.itemRenderer = itemRenderer;
}
if (data) {
this.setData(data);
}
};
/**
* Sets the data and updates the list
*
* @this {KVListWidget}
* @param {Object} data
*/
KVListWidget.prototype.setData = function(data) {
var self = this;
this.element.empty();
$.each(data, function(key, value) {
var dt = $('<dt class="key" />').appendTo(self.element);
var dd = $('<dd class="value" />').appendTo(self.element);
self.itemRenderer(dt, dd, key, value);
});
};
/**
* Renders the content of the <dt> and <dd> elements
*
* @this {KVListWidget}
* @param {jQuery} dt The <dt> element as a jQuery Object
* @param {jQuery} dd The <dd> element as a jQuery Object
* @param {String} key Property name
* @param {Object} value Property value
*/
KVListWidget.prototype.itemRenderer = function(dt, dd, key, value) {
dt.text(key);
dd.html(htmlize(value));
};
widgets.KVListWidget = KVListWidget;
// ------------------------------------------------------------------
/**
* An extension of KVListWidget where the data represents a list
* of variables
*
* @this {VariableListWidget}
* @constructor
* @param {Object} data
*/
var VariableListWidget = function(data) {
KVListWidget.apply(this, [data]);
this.element.addClass('phpdebugbar-widgets-varlist');
};
VariableListWidget.prototype = new KVListWidget();
VariableListWidget.constructor = VariableListWidget;
VariableListWidget.prototype.itemRenderer = function(dt, dd, key, value) {
dt.text(key);
var v = value;
if (v.length > 100) {
v = v.substr(0, 100) + "...";
}
dd.text(v).click(function() {
if (dd.hasClass('pretty')) {
dd.text(v).removeClass('pretty');
} else {
dd.html(htmlize(value)).addClass('pretty');
}
});
};
widgets.VariableListWidget = VariableListWidget;
// ------------------------------------------------------------------
/**
* Iframe widget
*
* @this {IFrameWidget}
* @constructor
* @param {String} url
*/
var IFrameWidget = function(url) {
this.element = $('<iframe src="" class="phpdebugbar-widgets-iframe" seamless="seamless" border="0" width="100%" height="100%" />');
if (url) {
this.setData(url);
}
};
/**
* Sets the iframe url
*
* @this {IFrameWidget}
* @param {String} url
*/
IFrameWidget.prototype.setUrl = function(url) {
this.element.attr('src', url);
};
// for compatibility with data mapping
IFrameWidget.prototype.setData = function(url) {
this.setUrl(url);
};
widgets.IFrameWidget = IFrameWidget;
// ------------------------------------------------------------------
// Collector specific widgets
// ------------------------------------------------------------------
/**
* Widget for the MessagesCollector
*
* Uses ListWidget under the hood
*
* @this {MessagesWidget}
* @constructor
* @param {Array} data
*/
var MessagesWidget = function(data) { var MessagesWidget = function(data) {
this.element = $('<div class="phpdebugbar-widgets-messages" />'); this.element = $('<div class="phpdebugbar-widgets-messages" />');
@ -142,80 +321,15 @@ PhpDebugBar.Widgets = (function($) {
widgets.MessagesWidget = MessagesWidget; widgets.MessagesWidget = MessagesWidget;
// ------------------------------------------ // ------------------------------------------------------------------
var KVListWidget = function(data, itemRenderer) {
this.element = $('<dl class="phpdebugbar-widgets-kvlist" />');
if (itemRenderer) {
this.itemRenderer = itemRenderer;
}
if (data) {
this.setData(data);
}
};
KVListWidget.prototype.setData = function(data) {
var self = this;
this.element.empty();
$.each(data, function(key, value) {
var dt = $('<dt class="key" />').appendTo(self.element);
var dd = $('<dd class="value" />').appendTo(self.element);
self.itemRenderer(dt, dd, key, value);
});
};
KVListWidget.prototype.itemRenderer = function(dt, dd, key, value) {
dt.text(key);
dd.html(htmlize(value));
};
widgets.KVListWidget = KVListWidget;
// ------------------------------------------
var VariableListWidget = function(data) {
KVListWidget.apply(this, [data]);
this.element.addClass('phpdebugbar-widgets-varlist');
};
VariableListWidget.prototype = new KVListWidget();
VariableListWidget.constructor = VariableListWidget;
VariableListWidget.prototype.itemRenderer = function(dt, dd, key, value) {
dt.text(key);
var v = value;
if (v.length > 100) {
v = v.substr(0, 100) + "...";
}
dd.text(v).click(function() {
if (dd.hasClass('pretty')) {
dd.text(v).removeClass('pretty');
} else {
dd.html(htmlize(value)).addClass('pretty');
}
});
};
widgets.VariableListWidget = VariableListWidget;
// ------------------------------------------
var IFrameWidget = function(url) {
this.element = $('<iframe src="" class="phpdebugbar-widgets-iframe" seamless="seamless" border="0" width="100%" height="100%" />');
if (url) {
this.setData(url);
}
};
IFrameWidget.prototype.setData = function(url) {
this.element.attr('src', url);
};
widgets.IFrameWidget = IFrameWidget;
// ------------------------------------------
/**
* Widget for the TimeDataCollector
*
* @this {TimelineWidget}
* @constructor
* @param {Object} data
*/
var TimelineWidget = function(data) { var TimelineWidget = function(data) {
this.element = $('<ul class="phpdebugbar-widgets-timeline" />'); this.element = $('<ul class="phpdebugbar-widgets-timeline" />');
if (data) { if (data) {
@ -240,7 +354,7 @@ PhpDebugBar.Widgets = (function($) {
widgets.TimelineWidget = TimelineWidget; widgets.TimelineWidget = TimelineWidget;
// ------------------------------------------ // ------------------------------------------------------------------
return widgets; return widgets;