Inital commit

This commit is contained in:
2020-11-19 15:36:28 +01:00
parent 71f32f83d3
commit 66ce4ee218
18077 changed files with 2166122 additions and 35184 deletions

View File

@@ -0,0 +1,2 @@
composer.lock
vendor

View File

@@ -0,0 +1,6 @@
language: php
php:
- 5.3
- 5.4
before_script: composer install
script: phpunit

19
core/vendor/maximebf/debugbar/LICENSE vendored 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.

101
core/vendor/maximebf/debugbar/README.md vendored Normal file
View File

@@ -0,0 +1,101 @@
# PHP Debug Bar
[![Latest Stable Version](https://poser.pugx.org/maximebf/debugbar/v/stable.png)](https://packagist.org/packages/maximebf/debugbar) [![Build Status](https://travis-ci.org/maximebf/php-debugbar.png?branch=master)](https://travis-ci.org/maximebf/php-debugbar)
Displays a debug bar in the browser with information from php.
No more `var_dump()` in your code!
![Screenshot](https://raw.github.com/maximebf/php-debugbar/master/docs/screenshot.png)
**Features:**
- Generic debug bar
- 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
- Save and re-open previous requests
- [Very well documented](http://phpdebugbar.com/docs)
Includes collectors for:
- [PDO](http://php.net/manual/en/book.pdo.php)
- [CacheCache](http://maximebf.github.io/CacheCache/)
- [Doctrine](http://doctrine-project.org)
- [Monolog](https://github.com/Seldaek/monolog)
- [Propel](http://propelorm.org/)
- [Slim](http://slimframework.com)
- [Swift Mailer](http://swiftmailer.org/)
- [Twig](http://twig.sensiolabs.org/)
Checkout the [demo](https://github.com/maximebf/php-debugbar/tree/master/demo) for
examples and [phpdebugbar.com](http://phpdebugbar.com) for a live example.
Integrations with other frameworks:
- [Laravel](https://github.com/barryvdh/laravel-debugbar) (project by barryvdh)
*(drop me a message or submit a PR to add your DebugBar related project here)*
## Installation
The best way to install DebugBar is using [Composer](http://getcomposer.org)
with the following requirement:
```JSON
{
"require": {
"maximebf/debugbar": ">=1.0.0"
}
}
```
If you are cloning the repository, you'll need to run `composer install`.
## Quick start
DebugBar is very easy to use and you can add it to any of your projects in no time.
The easiest way is using the `render()` functions
```PHP
<?php
use DebugBar\StandardDebugBar;
use DebugBar\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>+6
```
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`:
```PHP
$debugbar["messages"]->addMessage("hello world!");
```
`StandardDebugBar` activates the following collectors:
- `MemoryCollector` (*memory*)
- `MessagesCollector` (*messages*)
- `PhpInfoCollector` (*php*)
- `RequestDataCollector` (*request*)
- `TimeDataCollector` (*time*)
- `ExceptionsCollector` (*exceptions*)
Learn more about DebugBar in the [docs](http://phpdebugbar.com/docs).

View File

@@ -0,0 +1,27 @@
{
"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",
"psr/log": "~1.0"
},
"require-dev": {
"php": ">=5.3.0"
},
"autoload": {
"psr-0": {"DebugBar": "src/"}
},
"suggest": {
"kriswallsmith/assetic": "The best way to manage assets",
"monolog/monolog": "Log using Monolog"
}
}

View File

@@ -0,0 +1,10 @@
<?php
include 'bootstrap.php';
$debugbar['messages']->addMessage('hello from ajax');
?>
hello from AJAX
<?php
echo $debugbarRenderer->render(false);
?>

View File

@@ -0,0 +1,15 @@
<?php
include 'bootstrap.php';
try {
throw new Exception('Something failed!');
} catch (Exception $e) {
$debugbar['exceptions']->addException($e);
}
?>
error from AJAX
<?php
echo $debugbarRenderer->render(false);
?>

View File

@@ -0,0 +1,45 @@
<?php
include __DIR__ . '/../tests/bootstrap.php';
use DebugBar\StandardDebugBar;
$debugbar = new StandardDebugBar();
$debugbarRenderer = $debugbar->getJavascriptRenderer()->setBaseUrl('../src/DebugBar/Resources');
//
// create a writable profiles folder in the demo directory to uncomment the following lines
//
//$debugbar->setStorage(new DebugBar\Storage\FileStorage(__DIR__ . '/profiles'));
//$debugbarRenderer->setOpenHandlerUrl('open.php');
function render_demo_page(Closure $callback = null)
{
global $debugbarRenderer;
?>
<html>
<head>
<?php echo $debugbarRenderer->renderHead() ?>
<script type="text/javascript">
$(function() {
$('.ajax').click(function() {
var container = $(this).parent().html('...');
$.get(this.href, function(data) {
container.html(data);
});
return false;
});
});
</script>
</head>
<body>
<h1>DebugBar Demo</h1>
<p>DebugBar at the bottom of the page</p>
<?php if ($callback) $callback(); ?>
<?php
echo $debugbarRenderer->render();
?>
</body>
</html>
<?php
}

View File

@@ -0,0 +1,6 @@
{
"require": {
"maximebf/cachecache": "*",
"monolog/monolog": "*"
}
}

View File

@@ -0,0 +1,16 @@
<?php
include __DIR__ . '/vendor/autoload.php';
include __DIR__ . '/../../bootstrap.php';
$debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
$cache = new CacheCache\Cache(new CacheCache\Backends\Memory());
$debugbar->addCollector(new DebugBar\Bridge\CacheCacheCollector($cache));
$cache->set('foo', 'bar');
$cache->get('foo');
$cache->get('bar');
render_demo_page();

View File

@@ -0,0 +1,22 @@
<?php
// bootstrap.php
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
require_once "vendor/autoload.php";
// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode);
// or if you prefer yaml or XML
//$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
//$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
// database configuration parameters
$conn = array(
'driver' => 'pdo_sqlite',
'path' => __DIR__ . '/db.sqlite',
);
// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);

View File

@@ -0,0 +1,3 @@
#!/bin/bash
php vendor/bin/doctrine orm:schema-tool:update --force

View File

@@ -0,0 +1,9 @@
<?php
// cli-config.php
require_once "bootstrap.php";
$em = $entityManager;
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
));

View File

@@ -0,0 +1,9 @@
{
"require": {
"doctrine/orm": "2.*",
"symfony/yaml": "2.*"
},
"autoload": {
"psr-0": {"": "src/"}
}
}

View File

@@ -0,0 +1,18 @@
<?php
include __DIR__ . '/bootstrap.php';
include __DIR__ . '/../../bootstrap.php';
$debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
$debugStack = new Doctrine\DBAL\Logging\DebugStack();
$entityManager->getConnection()->getConfiguration()->setSQLLogger($debugStack);
$debugbar->addCollector(new DebugBar\Bridge\DoctrineCollector($debugStack));
$product = new Product();
$product->setName("foobar");
$entityManager->persist($product);
$entityManager->flush();
render_demo_page();

View File

@@ -0,0 +1,27 @@
<?php
/**
* @Entity @Table(name="products")
**/
class Product
{
/** @Id @Column(type="integer") @GeneratedValue **/
protected $id;
/** @Column(type="string") **/
protected $name;
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}

View File

@@ -0,0 +1,5 @@
{
"require": {
"monolog/monolog": "*"
}
}

View File

@@ -0,0 +1,14 @@
<?php
include __DIR__ . '/vendor/autoload.php';
include __DIR__ . '/../../bootstrap.php';
$debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
$logger = new Monolog\Logger('demo');
$debugbar->addCollector(new DebugBar\Bridge\MonologCollector($logger));
$logger->info('hello world');
render_demo_page();

View File

@@ -0,0 +1,7 @@
# Database driver
propel.database = sqlite
# Project name
propel.project = demo
propel.database.url = sqlite:demo.db

View File

@@ -0,0 +1,4 @@
#!/bin/bash
vendor/bin/propel-gen
sqlite3 demo.db < build/sql/schema.sql

View File

@@ -0,0 +1,5 @@
{
"require": {
"propel/propel1": "*"
}
}

View File

@@ -0,0 +1,23 @@
<?php
include __DIR__ . '/vendor/autoload.php';
include __DIR__ . '/../../bootstrap.php';
$debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
use DebugBar\Bridge\PropelCollector;
$debugbar->addCollector(new PropelCollector());
Propel::init('build/conf/demo-conf.php');
set_include_path("build/classes" . PATH_SEPARATOR . get_include_path());
PropelCollector::enablePropelProfiling();
$user = new User();
$user->setName('foo');
$user->save();
$firstUser = UserQuery::create()->findPK(1);
render_demo_page();

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<config>
<!-- Uncomment this if you have PEAR Log installed
<log>
<type>file</type>
<name>/path/to/propel.log</name>
<ident>propel-bookstore</ident>
<level>7</level>
</log>
-->
<propel>
<datasources default="demo">
<datasource id="demo">
<adapter>sqlite</adapter> <!-- sqlite, mysql, mssql, oracle, or pgsql -->
<connection>
<classname>DebugPDO</classname>
<dsn>sqlite:demo.db</dsn>
</connection>
</datasource>
</datasources>
</propel>
</config>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<database name="demo" defaultIdMethod="native">
<table name="users" phpName="User">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
<column name="name" type="varchar" size="255" required="true" />
</table>
</database>

View File

@@ -0,0 +1,5 @@
{
"require": {
"slim/slim": "*"
}
}

View File

@@ -0,0 +1,16 @@
<?php
include __DIR__ . '/vendor/autoload.php';
include __DIR__ . '/../../bootstrap.php';
$debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
$app = new \Slim\Slim();
$app->get('/', function () use ($app) {
$app->getLog()->info('hello world');
render_demo_page();
});
$debugbar->addCollector(new DebugBar\Bridge\SlimCollector($app));
$app->run();

View File

@@ -0,0 +1,5 @@
{
"require": {
"swiftmailer/swiftmailer": "*"
}
}

View File

@@ -0,0 +1,24 @@
<?php
include __DIR__ . '/vendor/autoload.php';
include __DIR__ . '/../../bootstrap.php';
$debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
use DebugBar\Bridge\SwiftMailer\SwiftLogCollector;
use DebugBar\Bridge\SwiftMailer\SwiftMailCollector;
$mailer = Swift_Mailer::newInstance(Swift_NullTransport::newInstance());
$debugbar['messages']->aggregate(new SwiftLogCollector($mailer));
$debugbar->addCollector(new SwiftMailCollector($mailer));
$message = Swift_Message::newInstance('Wonderful Subject')
->setFrom(array('john@doe.com' => 'John Doe'))
->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
->setBody('Here is the message itself');
$mailer->send($message);
render_demo_page();

View File

@@ -0,0 +1,5 @@
{
"require": {
"twig/twig": "*"
}
}

View File

@@ -0,0 +1 @@
foobar

View File

@@ -0,0 +1,2 @@
Hello {{ name }}
{% include "foobar.html" %}

View File

@@ -0,0 +1,15 @@
<?php
include __DIR__ . '/vendor/autoload.php';
include __DIR__ . '/../../bootstrap.php';
$debugbarRenderer->setBaseUrl('../../../src/DebugBar/Resources');
$loader = new Twig_Loader_Filesystem('.');
$twig = new DebugBar\Bridge\Twig\TraceableTwigEnvironment(new Twig_Environment($loader), $debugbar['time']);
$debugbar->addCollector(new DebugBar\Bridge\Twig\TwigCollector($twig));
render_demo_page(function() use ($twig) {
echo $twig->render('hello.html', array('name' => 'peter pan'));
});

View File

@@ -0,0 +1,15 @@
<?php
include 'bootstrap.php';
if (!isset($_GET['type'])) {
$_GET['type'] = 'js';
}
if ($_GET['type'] == 'css') {
header('content-type', 'text/css');
$debugbarRenderer->dumpCssAssets();
} else if ($_GET['type'] == 'js') {
header('content-type', 'text/javascript');
$debugbarRenderer->dumpJsAssets();
}

View File

@@ -0,0 +1,44 @@
<?php
include 'bootstrap.php';
$debugbar['messages']->addMessage('hello');
$debugbar['time']->startMeasure('op1', 'sleep 500');
usleep(300);
$debugbar['time']->startMeasure('op2', 'sleep 400');
usleep(200);
$debugbar['time']->stopMeasure('op1');
usleep(200);
$debugbar['time']->stopMeasure('op2');
$debugbar['messages']->addMessage('world', 'warning');
$debugbar['messages']->addMessage(array('toto' => array('titi', 'tata')));
$debugbar['messages']->addMessage('oups', 'error');
$debugbar['time']->startMeasure('render');
render_demo_page(function() {
?>
<h2>AJAX</h2>
<ul>
<li><a href="ajax.php" class="ajax">load ajax content</a></li>
<li><a href="ajax_exception.php" class="ajax">load ajax content with exception</a></li>
</ul>
<h2>PDO</h2>
<ul>
<li><a href="pdo.php">PDO demo</a></li>
</ul>
<h2>Bridges</h2>
<p>(you need to install needed dependencies first, run <code>composer.phar install</code> in each demo folders)</p>
<ul>
<li><a href="bridge/cachecache">CacheCache</a></li>
<li><a href="bridge/doctrine">Doctrine</a></li>
<li><a href="bridge/monolog">Monolog</a></li>
<li><a href="bridge/propel">Propel</a></li>
<li><a href="bridge/slim">Slim</a></li>
<li><a href="bridge/swiftmailer">Swift mailer</a></li>
<li><a href="bridge/twig">Twig</a></li>
</ul>
<?php
});

View File

@@ -0,0 +1,6 @@
<?php
include __DIR__ . '/bootstrap.php';
$openHandler = new DebugBar\OpenHandler($debugbar);
$openHandler->handle();

View File

@@ -0,0 +1,23 @@
<?php
include 'bootstrap.php';
use DebugBar\DataCollector\PDO\TraceablePDO;
use DebugBar\DataCollector\PDO\PDOCollector;
$pdo = new TraceablePDO(new PDO('sqlite::memory:'));
$debugbar->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');
render_demo_page();

View File

@@ -0,0 +1,110 @@
# Base collectors
Collectors provided in the `DebugBar\DataCollector` namespace.
## Messages
Provides a way to log messages (compatible with [PSR-3 logger](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)).
$debugbar->addCollector(new DebugBar\DataCollector\MessagesCollector());
$debugbar['messages']->info('hello world');
You can have multiple messages collector by naming them:
$debugbar->addCollector(new MessagesCollector('io_ops'));
$debugbar['io_ops']->info('opening files');
You can aggregate messages collector into other to have a unified view:
$debugbar['messages']->aggregate($debugbar['io_ops']);
If you don't want to create a standalone tab in the debug bar but still be able
to log messages from a different collector, you don't have to add the collector
to the debug bar:
$debugbar['messages']->aggregate(new MessagesCollector('io_ops'));
## TimeData
Provides a way to log total execution time as well as taking "measures" (ie. measure the execution time of a particular operation).
$debugbar->addCollector(new DebugBar\DataCollector\TimeDataCollector());
$debugbar['time']->startMeasure('longop', 'My long operation');
sleep(2);
$debugbar['time']->stopMeasure('longop');
$debugbar['time']->measure('My long operation', function() {
sleep(2);
});
Displays the measures on a timeline
## Exceptions
Display exceptions
$debugbar->addCollector(new DebugBar\DataCollector\ExceptionsCollector());
try {
throw new Exception('foobar');
} catch (Exception $e) {
$debugbar['exceptions']->addException($e);
}
## PDO
Logs SQL queries. You need to wrap your `PDO` object into a `DebugBar\DataCollector\PDO\TraceablePDO` object.
$pdo = new DebugBar\DataCollector\PDO\TraceablePDO(new PDO('sqlite::memory:'));
$debugbar->addCollector(new DebugBar\DataCollector\PDO\PDOCollector($pdo));
You can even log queries from multiple `PDO` connections:
$pdoRead = new DebugBar\DataCollector\PDO\TraceablePDO(new PDO('sqlite::memory:'));
$pdoWrite = new DebugBar\DataCollector\PDO\TraceablePDO(new PDO('sqlite::memory:'));
$pdoCollector = new DebugBar\DataCollector\PDO\PDOCollector();
$pdoCollector->addConnection($pdoRead, 'read-db');
$pdoCollector->addConnection($pdoWrite, 'write-db');
$debugbar->addCollector($pdoCollector);
## RequestData
Collects the data of PHP's global variables
$debugbar->addCollector(new DebugBar\DataCollector\RequestDataCollector());
## Config
Used to display any key/value pairs array
$data = array('foo' => 'bar');
$debugbar->addCollector(new DebugBar\DataCollector\ConfigCollector($data));
You can provide a different name for this collector in the second argument of the constructor.
## AggregatedCollector
Aggregates multiple collectors. Do not provide any widgets, you have to add your own controls.
$debugbar->addCollector(new DebugBar\DataCollector\AggregatedCollector('all_messages', 'messages', 'time'));
$debugbar['all_messages']->addCollector($debugbar['messages']);
$debugbar['all_messages']->addCollector(new MessagesCollector('mails'));
$debugbar['all_messages']['mails']->addMessage('sending mail');
$renderer = $debugbar->getJavascriptRenderer();
$renderer->addControl('all_messages', array(
'widget' => 'PhpDebugBar.Widgets.MessagesWidget',
'map' => 'all_messages',
'default' => '[]';
));
## Others
Misc collectors which you can just register:
- `MemoryCollector` (*memory*): Display memory usage
- `PhpInfoCollector` (*php*): PHP version number

View File

@@ -0,0 +1,101 @@
# Bridge collectors
DebugBar comes with some "bridge" collectors. This collectors provides a way to integrate
other projects with the DebugBar.
## CacheCache
http://maximebf.github.io/CacheCache/
Displays cache operations using `DebugBar\Bridge\CacheCacheCollector`
$cache = new CacheCache\Cache(new CacheCache\Backends\Memory());
$debugbar->addCollector(new DebugBar\Bridge\CacheCacheCollector($cache));
CacheCache uses [Monolog](https://github.com/Seldaek/monolog) for logging,
thus it is required to collect data.
`CacheCacheCollector` subclasses `MonologCollector`, thus it can be
[aggregated in the messages view](base-collectors.html#messages).
## Doctrine
http://doctrine-project.org
Displays sql queries into an SQL queries view using `DebugBar\Bridge\DoctrineCollector`.
You will need to set a `Doctrine\DBAL\Logging\DebugStack` logger on your connection.
$debugStack = new Doctrine\DBAL\Logging\DebugStack();
$entityManager->getConnection()->getConfiguration()->setSQLLogger($debugStack);
$debugbar->addCollector(new DebugBar\Bridge\DoctrineCollector($debugStack));
`DoctrineCollector` also accepts an `Doctrine\ORM\EntityManager` as argument
provided the `SQLLogger` is a ̀DebugStack`.
## Monolog
https://github.com/Seldaek/monolog
Integrates Monolog messages into a message view using `DebugBar\Bridge\MonologCollector`.
$logger = new Monolog\Logger('mylogger');
$debugbar->addCollector(new DebugBar\Bridge\MonologCollector($logger));
Note that multiple logger can be collected:
$debugbar['monolog']->addLogger($logger);
`MonologCollector` can be [aggregated](base-collectors.html#messages) into the `MessagesCollector`.
## Propel
http://propelorm.org/
Displays propel queries into an SQL queries view using `DebugBar\Bridge\PropelCollector`.
You will need to activate Propel debug mode.
// before Propel::init()
$debugbar->addCollector(new DebugBar\Bridge\PropelCollector());
Propel::init('/path/to/config');
// after Propel::init()
// not mandatory if you set config options by yourself
DebugBar\Bridge\PropelCollector::enablePropelProfiling();
Queries can be collected on a single connection by providing the `PropelPDO` object
to the `PropelCollector` as first argument.
## Slim
http://slimframework.com
Displays message from the Slim logger into a message view using `DebugBar\Bridge\SlimCollector`.
$app = new Slim\Slim();
$debugbar->addCollector(new DebugBar\Bridge\SlimCollector($app));
## Swift Mailer
http://swiftmailer.org/
Display log messages and sent mail using `DebugBar\Bridge\SwiftMailer\SwiftLogCollector` and
`DebugBar\Bridge\SwiftMailer\SwiftMailCollector`.
$mailer = Swift_Mailer::newInstance(Swift_NullTransport::newInstance());
$debugbar['messages']->aggregate(new DebugBar\Bridge\SwiftMailer\SwiftLogCollector($mailer));
$debugbar->addCollector(new DebugBar\Bridge\SwiftMailer\SwiftMailCollector($mailer));
## Twig
http://twig.sensiolabs.org/
Collects info about rendered templates using `DebugBar\Bridge\Twig\TwigCollector`.
You need to wrap your `Twig_Environment` object into a `DebugBar\Bridge\Twig\TraceableTwigEnvironment` object.
$loader = new Twig_Loader_Filesystem('.');
$env = new DebugBar\Bridge\Twig\TraceableTwigEnvironment(new Twig_Environment($loader));
$debugbar->addCollector(new DebugBar\Bridge\Twig\TwigCollector($env));
You can provide a `DebugBar\DataCollector\TimeDataCollector` as the second argument of
`TraceableTwigEnvironment` so render operation can be measured.

View File

@@ -0,0 +1,72 @@
# 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.

View File

@@ -0,0 +1,181 @@
# Javascript Bar
**This section is here to document the inner workings of the client side debug bar.
Nothing described below is needed to run the debug bar in a normal way.**
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, visibility, 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* and *FontAwesome* (css). *FontAwesome* is
optional but is used to add nice icons!
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 be *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 switch 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
Widgets should inherit from the `PhpDebugBar.Widget` class which is used
as the base of every visual component in the debug bar.
New widgets can be created using `extend()`:
var MyWidget = PhpDebugBar.Widget.extend({
// class properties
});
The Widget class defines a `set(attr, value)` function which can be used
to set the value of attributes.
Using `bindAttr(attr, callback)`, you can trigger a callback every time
the value of the attribute is changed. `callback` can also be a `jQuery`
object and in that case it will use the `text()` function to fill the element.
Widgets should define a `render()` function which initializes the widget
elements.
`initialize(options)` will always be called after the constructor.
var MyWidget = PhpDebugBar.Widget.extend({
tagName: 'div', // optional as 'div' is the default
className: 'mywidget',
render: function() {
this.bindAttr('data', this.$el);
}
});
// ----
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`
- `ExceptionWidget`: for the `ExceptionCollector`
- `SQLQueriesWidget`: for the `PDOCollector`
- `TemplatesWidget`: for the `TwigCollector`
## Custom tabs and indicators
Behind the scene, `createTab()` and `createIndicator()` use `addTab(name, tab)` and
`addIndicator(name, indicator)`. Tabs are objects of type `PhpDebugBar.DebugBar.Tab`
and indicators of type `PhpDebugBar.DebugBar.Indicator`. These classes subclass
`PhpDebugBar.Widget` which makes it easy to create custom tabs or indicators.
var LinkIndicator = PhpDebugBar.DebugBar.Indicator.extend({
tagName: 'a',
render: function() {
LinkIndicator.__super__.render.apply(this);
this.bindAttr('href', function(href) {
this.$el.attr('href', href);
});
}
});
// ----
debugbar.addIndicator('phpdoc', new LinkIndicator({ href: 'http://doc.php.com', title: 'PHP doc' }));
## OpenHandler
An OpenHandler object can be provided using `setOpenHandler()`. The object is in charge
of loading datasets. The only requirement is to provide a `show()` method which takes
as only parameter a callback which expects an id and data parameter.
The default implementation is `PhpDebugBar.OpenHandler` which must be use in conjunction
with the server side `DebugBar\OpenHandler` (see previous section).
debugbar.setOpenHandler(new PhpDebugBar.OpenHandler({ url: "open.php" }));

View File

@@ -0,0 +1,15 @@
{
"title": "PHP Debug Bar",
"css": "style.css",
"home": "../README.md",
"files": [
"../README.md",
"data_collectors.md",
"rendering.md",
"base_collectors.md",
"bridge_collectors.md",
"storage.md",
"openhandler.md",
"javascript_bar.md"
]
}

View File

@@ -0,0 +1,23 @@
# Open handler
The debug bar can open previous sets of collected data which were stored using
a storage handler (see previous section). To do so, it needs to be provided an
url to an open handler.
An open handler must respect a very simple protocol. The default implementation
is `DebugBar\OpenHandler`.
$openHandler = new DebugBar\OpenHandler($debugbar);
$openHandler->handle();
Calling `handle()` will use data from the `$_REQUEST` array and echo the output.
The function also supports input from other source if you provide an array as
first argument. It can also return the data instead of echoing (use false as
second argument) and not send the content-type header (use false as third argument).
One you have setup your open handler, tell the `JavascriptRenderer` its url.
$renderer->setOpenHandlerUrl('open.php');
This adds a button in the top right corner of the debug bar which allows you
to browse and open previous sets of collected data.

View File

@@ -0,0 +1,137 @@
# 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. They are located in the *src/DebugBar/Resources* folder.
This can be done in four 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()`
- Dumping the assets yourself using `JavascriptRenderer::dumpCssAssets()` and
`JavascriptRenderer::dumpJsAssets()`
- Retrieving the list filenames of assets using `JavascriptRenderer::getAssets()`
and doing something with it
I would recommend using the second method as Assetic is a very powerful asset
manager but the other methods are provided to quickly integrate the debug bar
into any projects.
You can define the base url of your assets using `setBaseUrl()`. This is needed
in 99% of cases.
Using `renderHead()`:
<html>
<head>
...
<?php echo $renderer->renderHead() ?>
...
</head>
...
</html>
Using Assetic:
list($cssCollection, $jsCollection) = $renderer->getAsseticCollection();
Dumping the assets:
header('Content-Type', 'text/javascript');
$renderer->dumpJsAssets();
Retrieving the assets:
list($cssFiles, $jsFiles) = $renderer->getAssets();
Note that you can only use the debug bar assets and manage the dependencies by yourself
using `$renderer->setIncludeVendors(false)`. Instead of false, *css* or *js* may be used
to only include css or js assets of vendors.
## 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 initialization 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 initialization.
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
- *tab*: class name of the tab object (to use a custom tab object)
- *indicator*: class name of the indicator object (to use a custom indicator object)
At least *icon* or *widget* are needed (unless *tab* or *indicator* are specified). If *widget* is
specified, a tab will be created, otherwise an indicator. Any other options is also passed to the tab
or indicator.
$renderer->addControl('messages', array(
"widget" => "PhpDebugBar.Widgets.MessagesWidget",
"map" => "messages",
"default" => "[]"
));
You can disable a control using `disableControl($name)` and ignore any controls provided by
a collector using `ignoreCollector($name)`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,28 @@
# Storage
DebugBar supports storing collected data for later analysis.
You'll need to set a storage handler using `setStorage()` on your `DebugBar` instance.
$debugbar->setStorage(new DebugBar\Storage\FileStorage('/path/to/dir'));
Each time `DebugBar::collect()` is called, the data will be persisted.
## Available storage
Only file storage is provided at the moment. It will collected data as json files
under the specified directory (which as to be writable).
$storage = new DebugBar\Storage\FileStorage($directory);
## Creating your own storage
You can easily create your own storage handler by implementing the
`DebugBar\Storage\StorageInterface`.
## Request ID generator
For each request, the debug bar will generate a unique id under which to store the
collected data. This is perform using a `DebugBar\RequestIdGeneratorInterface` object.
If none are defined, the debug bar will automatically use `DebugBar\RequestIdGenerator`
which uses the `$_SERVER` array to generate the id.

View File

@@ -0,0 +1,40 @@
#page {
width: 1000px;
margin: 0 auto;
padding-top: 110px;
}
#header {
background: #4ad7ff;
position: absolute;
left: 0;
top: 0;
width: 100%;
border-bottom: 8px solid #49c5eb;
padding: 0;
}
#header h1 {
font-size: 45px;
line-height: 50px;
width: 1000px;
margin: 0 auto;
display: block;
text-shadow: none;
line-height: 92px;
}
#header h1 a {
color: #fff;
text-decoration: none;
}
#sidebar {
padding: 0;
padding-right: 20px;
}
#content {
width: 729px;
padding: 0;
padding-left: 20px;
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<testsuites>
<testsuite name="DebugBar Test Suite">
<directory>./tests/DebugBar/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/DebugBar/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,62 @@
<?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\Bridge;
use CacheCache\Cache;
use CacheCache\LoggingBackend;
use Monolog\Logger;
/**
* Collects CacheCache operations
*
* http://maximebf.github.io/CacheCache/
*
* Example:
* <code>
* $debugbar->addCollector(new CacheCacheCollector(CacheManager::get('default')));
* // or
* $debugbar->addCollector(new CacheCacheCollector());
* $debugbar['cache']->addCache(CacheManager::get('default'));
* </code>
*/
class CacheCacheCollector extends MonologCollector
{
protected $logger;
public function __construct(Cache $cache = null, Logger $logger = null, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct(null, $level, $bubble);
if ($logger === null) {
$logger = new Logger('Cache');
}
$this->logger = $logger;
if ($cache !== null) {
$this->addCache($cache);
}
}
public function addCache(Cache $cache)
{
$backend = $cache->getBackend();
if (!($backend instanceof LoggingBackend)) {
$backend = new LoggingBackend($backend, $this->logger);
}
$cache->setBackend($backend);
$this->addLogger($backend->getLogger());
}
public function getName()
{
return 'cache';
}
}

View File

@@ -0,0 +1,97 @@
<?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\Bridge;
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
use DebugBar\DebugBarException;
use Doctrine\ORM\EntityManager;
use Doctrine\DBAL\Logging\DebugStack;
/**
* Collects Doctrine queries
*
* http://doctrine-project.org
*
* Uses the DebugStack logger to collects data about queries
*
* <code>
* $debugStack = new Doctrine\DBAL\Logging\DebugStack();
* $entityManager->getConnection()->getConfiguration()->setSQLLogger($debugStack);
* $debugbar->addCollector(new DoctrineCollector($debugStack));
* </code>
*/
class DoctrineCollector extends DataCollector implements Renderable
{
protected $debugStack;
public function __construct($debugStackOrEntityManager)
{
if ($debugStackOrEntityManager instanceof EntityManager) {
$debugStackOrEntityManager = $debugStackOrEntityManager->getConnection()->getConfiguration()->getSQLLogger();
}
if (!($debugStackOrEntityManager instanceof DebugStack)) {
throw new DebugBarException("'DoctrineCollector' requires an 'EntityManager' or 'DebugStack' object");
}
$this->debugStack = $debugStackOrEntityManager;
}
/**
* {@inheritDoc}
*/
public function collect()
{
$queries = array();
$totalExecTime = 0;
foreach ($this->debugStack->queries as $q) {
$queries[] = array(
'sql' => $q['sql'],
'params' => (object) $q['params'],
'duration' => $q['executionMS'],
'duration_str' => $this->formatDuration($q['executionMS'])
);
$totalExecTime += $q['executionMS'];
}
return array(
'nb_statements' => count($queries),
'accumulated_duration' => $totalExecTime,
'accumulated_duration_str' => $this->formatDuration($totalExecTime),
'statements' => $queries
);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'doctrine';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
"database" => array(
"widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
"map" => "doctrine",
"default" => "[]"
),
"database:badge" => array(
"map" => "doctrine.nb_statements",
"default" => 0
)
);
}
}

View File

@@ -0,0 +1,117 @@
<?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\Bridge;
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
use DebugBar\DataCollector\DataCollectorInterface;
use DebugBar\DataCollector\Renderable;
use DebugBar\DataCollector\MessagesAggregateInterface;
/**
* A monolog handler as well as a data collector
*
* https://github.com/Seldaek/monolog
*
* <code>
* $debugbar->addCollector(new MonologCollector($logger));
* </code>
*/
class MonologCollector extends AbstractProcessingHandler implements DataCollectorInterface, Renderable, MessagesAggregateInterface
{
protected $name;
protected $records = array();
/**
* @param Logger $logger
* @param int $level
* @param boolean $bubble
* @param string $name
*/
public function __construct(Logger $logger = null, $level = Logger::DEBUG, $bubble = true, $name = 'monolog')
{
parent::__construct($level, $bubble);
$this->name = $name;
if ($logger !== null) {
$this->addLogger($logger);
}
}
/**
* Adds logger which messages you want to log
*
* @param Logger $logger
*/
public function addLogger(Logger $logger)
{
$logger->pushHandler($this);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$this->records[] = array(
'message' => $record['formatted'],
'is_string' => true,
'label' => strtolower($record['level_name']),
'time' => $record['datetime']->format('U')
);
}
/**
* {@inheritDoc}
*/
public function getMessages()
{
return $this->records;
}
/**
* {@inheritDoc}
*/
public function collect()
{
return array(
'count' => count($this->records),
'records' => $this->records
);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
$name = $this->getName();
return array(
$name => array(
"widget" => "PhpDebugBar.Widgets.MessagesWidget",
"map" => "$name.records",
"default" => "[]"
),
"$name:badge" => array(
"map" => "$name.count",
"default" => "null"
)
);
}
}

View File

@@ -0,0 +1,279 @@
<?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\Bridge;
use Propel;
use PropelPDO;
use PropelConfiguration;
use BasicLogger;
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* A Propel logger which acts as a data collector
*
* http://propelorm.org/
*
* Will log queries and display them using the SQLQueries widget.
* You can provide a LoggerInterface object to forward non-query related message to.
*
* Example:
* <code>
* $debugbar->addCollector(new PropelCollector($debugbar['messages']));
* PropelCollector::enablePropelProfiling();
* </code>
*/
class PropelCollector extends DataCollector implements BasicLogger, Renderable
{
protected $logger;
protected $statements = array();
protected $accumulatedTime = 0;
protected $peakMemory = 0;
/**
* Sets the needed configuration option in propel to enable query logging
*
* @param PropelConfiguration $config Apply profiling on a specific config
*/
public static function enablePropelProfiling(PropelConfiguration $config = null)
{
if ($config === null) {
$config = Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT);
}
$config->setParameter('debugpdo.logging.details.method.enabled', true);
$config->setParameter('debugpdo.logging.details.time.enabled', true);
$config->setParameter('debugpdo.logging.details.mem.enabled', true);
$allMethods = array(
'PropelPDO::__construct', // logs connection opening
'PropelPDO::__destruct', // logs connection close
'PropelPDO::exec', // logs a query
'PropelPDO::query', // logs a query
'PropelPDO::beginTransaction', // logs a transaction begin
'PropelPDO::commit', // logs a transaction commit
'PropelPDO::rollBack', // logs a transaction rollBack (watch out for the capital 'B')
'DebugPDOStatement::execute', // logs a query from a prepared statement
);
$config->setParameter('debugpdo.logging.methods', $allMethods, false);
}
/**
* @param LoggerInterface $logger A logger to forward non-query log lines to
* @param PropelPDO $conn Bound this collector to a connection only
*/
public function __construct(LoggerInterface $logger = null, PropelPDO $conn = null)
{
if ($conn) {
$conn->setLogger($this);
} else {
Propel::setLogger($this);
}
$this->logger = $logger;
$this->logQueriesToLogger = false;
}
public function setLogQueriesToLogger($enable = true)
{
$this->logQueriesToLogger = $enable;
return $this;
}
public function isLogQueriesToLogger()
{
return $this->logQueriesToLogger;
}
/**
* {@inheritDoc}
*/
public function emergency($m)
{
$this->log($m, Propel::LOG_EMERG);
}
/**
* {@inheritDoc}
*/
public function alert($m)
{
$this->log($m, Propel::LOG_ALERT);
}
/**
* {@inheritDoc}
*/
public function crit($m)
{
$this->log($m, Propel::LOG_CRIT);
}
/**
* {@inheritDoc}
*/
public function err($m)
{
$this->log($m, Propel::LOG_ERR);
}
/**
* {@inheritDoc}
*/
public function warning($m)
{
$this->log($m, Propel::LOG_WARNING);
}
/**
* {@inheritDoc}
*/
public function notice($m)
{
$this->log($m, Propel::LOG_NOTICE);
}
/**
* {@inheritDoc}
*/
public function info($m)
{
$this->log($m, Propel::LOG_INFO);
}
/**
* {@inheritDoc}
*/
public function debug($m)
{
$this->log($m, Propel::LOG_DEBUG);
}
/**
* {@inheritDoc}
*/
public function log($message, $severity = null)
{
if (strpos($message, 'DebugPDOStatement::execute') !== false) {
list($sql, $duration_str) = $this->parseAndLogSqlQuery($message);
if (!$this->logQueriesToLogger) {
return;
}
$message = "$sql ($duration_str)";
}
if ($this->logger !== null) {
$this->logger->log($this->convertLogLevel($severity), $message);
}
}
/**
* Converts Propel log levels to PSR log levels
*
* @param int $level
* @return string
*/
protected function convertLogLevel($level)
{
$map = array(
Propel::LOG_EMERG => LogLevel::EMERGENCY,
Propel::LOG_ALERT => LogLevel::ALERT,
Propel::LOG_CRIT => LogLevel::CRITICAL,
Propel::LOG_ERR => LogLevel::ERROR,
Propel::LOG_WARNING => LogLevel::WARNING,
Propel::LOG_NOTICE => LogLevel::NOTICE,
Propel::LOG_DEBUG => LogLevel::DEBUG
);
return $map[$level];
}
/**
* Parse a log line to extract query information
*
* @param string $message
*/
protected function parseAndLogSqlQuery($message)
{
$parts = explode('|', $message, 4);
$sql = trim($parts[3]);
$duration = 0;
if (preg_match('/([0-9]+\.[0-9]+)/', $parts[1], $matches)) {
$duration = (float) $matches[1];
}
$memory = 0;
if (preg_match('/([0-9]+\.[0-9]+) ([A-Z]{1,2})/', $parts[2], $matches)) {
$memory = (float) $matches[1];
if ($matches[2] == 'KB') {
$memory *= 1024;
} else if ($matches[2] == 'MB') {
$memory *= 1024 * 1024;
}
}
$this->statements[] = array(
'sql' => $sql,
'is_success' => true,
'duration' => $duration,
'duration_str' => $this->formatDuration($duration),
'memory' => $memory,
'memory_str' => $this->formatBytes($memory)
);
$this->accumulatedTime += $duration;
$this->peakMemory = max($this->peakMemory, $memory);
return array($sql, $this->formatDuration($duration));
}
/**
* {@inheritDoc}
*/
public function collect()
{
return array(
'nb_statements' => count($this->statements),
'nb_failed_statements' => 0,
'accumulated_duration' => $this->accumulatedTime,
'accumulated_duration_str' => $this->formatDuration($this->accumulatedTime),
'peak_memory_usage' => $this->peakMemory,
'peak_memory_usage_str' => $this->formatBytes($this->peakMemory),
'statements' => $this->statements
);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'propel';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
"propel" => array(
"widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
"map" => "propel",
"default" => "[]"
),
"propel:badge" => array(
"map" => "propel.nb_statements",
"default" => 0
)
);
}
}

View File

@@ -0,0 +1,62 @@
<?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\Bridge;
use DebugBar\DataCollector\MessagesCollector;
use Slim\Slim;
use Slim\Log;
use Psr\Log\LogLevel;
/**
* Collects messages from a Slim logger
*
* http://slimframework.com
*/
class SlimCollector extends MessagesCollector
{
protected $slim;
protected $originalLogWriter;
public function __construct(Slim $slim)
{
$this->slim = $slim;
if ($log = $slim->getLog()) {
$this->originalLogWriter = $log->getWriter();
$log->setWriter($this);
$log->setEnabled(true);
}
}
public function write($message, $level)
{
if ($this->originalLogWriter) {
$this->originalLogWriter->write($message, $level);
}
$this->addMessage($message, $this->getLevelName($level));
}
protected function getLevelName($level)
{
$map = array(
Log::FATAL => LogLevel::EMERGENCY,
Log::ERROR => LogLevel::ERROR,
Log::WARN => LogLevel::WARNING,
Log::INFO => LogLevel::INFO
);
return $map[$level];
}
public function getName()
{
return 'slim';
}
}

View File

@@ -0,0 +1,44 @@
<?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\Bridge\SwiftMailer;
use DebugBar\DataCollector\MessagesCollector;
use Swift_Mailer;
use Swift_Plugins_Logger;
use Swift_Plugins_LoggerPlugin;
/**
* Collects log messages
*
* http://swiftmailer.org/
*/
class SwiftLogCollector extends MessagesCollector implements Swift_Plugins_Logger
{
public function __construct(Swift_Mailer $mailer)
{
$mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($this));
}
public function add($entry)
{
$this->addMessage($entry);
}
public function dump()
{
return implode(PHP_EOL, $this->_log);
}
public function getName()
{
return 'swiftmailer_logs';
}
}

View File

@@ -0,0 +1,87 @@
<?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\Bridge\SwiftMailer;
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
use Swift_Mailer;
use Swift_Plugins_MessageLogger;
/**
* Collects data about sent mails
*
* http://swiftmailer.org/
*/
class SwiftMailCollector extends DataCollector implements Renderable
{
protected $messagesLogger;
public function __construct(Swift_Mailer $mailer)
{
$this->messagesLogger = new Swift_Plugins_MessageLogger();
$mailer->registerPlugin($this->messagesLogger);
}
/**
* {@inheritDoc}
*/
public function collect()
{
$mails = array();
foreach ($this->messagesLogger->getMessages() as $msg) {
$mails[] = array(
'to' => $this->formatTo($msg->getTo()),
'subject' => $msg->getSubject(),
'headers' => $msg->getHeaders()->toString()
);
}
return array(
'count' => count($mails),
'mails' => $mails
);
}
protected function formatTo($to)
{
$f = array();
foreach ($to as $k => $v) {
$f[] = (empty($v) ? '' : "$v ") . "<$k>";
}
return implode(', ', $f);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'swiftmailer_mails';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
'emails' => array(
'widget' => 'PhpDebugBar.Widgets.MailsWidget',
'map' => 'swiftmailer_mails.mails',
'default' => '[]',
'title' => 'Mails'
),
'emails:badge' => array(
'map' => 'swiftmailer_mails.count',
'default' => 0
)
);
}
}

View File

@@ -0,0 +1,610 @@
<?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\Bridge\Twig;
use Twig_Environment;
use Twig_LexerInterface;
use Twig_ParserInterface;
use Twig_TokenStream;
use Twig_CompilerInterface;
use Twig_NodeInterface;
use Twig_LoaderInterface;
use Twig_ExtensionInterface;
use Twig_TokenParserInterface;
use Twig_NodeVisitorInterface;
use DebugBar\DataCollector\TimeDataCollector;
/**
* Wrapped a Twig Environment to provide profiling features
*/
class TraceableTwigEnvironment extends Twig_Environment
{
protected $twig;
protected $renderedTemplates = array();
protected $timeDataCollector;
/**
* @param Twig_Environment $twig
* @param TimeDataCollector $timeDataCollector
*/
public function __construct(Twig_Environment $twig, TimeDataCollector $timeDataCollector = null)
{
$this->twig = $twig;
$this->timeDataCollector = $timeDataCollector;
}
public function getRenderedTemplates()
{
return $this->renderedTemplates;
}
public function addRenderedTemplate(array $info)
{
$this->renderedTemplates[] = $info;
}
public function getTimeDataCollector()
{
return $this->timeDataCollector;
}
/**
* {@inheritDoc}
*/
public function getBaseTemplateClass()
{
return $this->twig->getBaseTemplateClass();
}
/**
* {@inheritDoc}
*/
public function setBaseTemplateClass($class)
{
$this->twig->setBaseTemplateClass($class);
}
/**
* {@inheritDoc}
*/
public function enableDebug()
{
$this->twig->enableDebug();
}
/**
* {@inheritDoc}
*/
public function disableDebug()
{
$this->twig->disableDebug();
}
/**
* {@inheritDoc}
*/
public function isDebug()
{
return $this->twig->isDebug();
}
/**
* {@inheritDoc}
*/
public function enableAutoReload()
{
$this->twig->enableAutoReload();
}
/**
* {@inheritDoc}
*/
public function disableAutoReload()
{
$this->twig->disableAutoReload();
}
/**
* {@inheritDoc}
*/
public function isAutoReload()
{
return $this->twig->isAutoReload();
}
/**
* {@inheritDoc}
*/
public function enableStrictVariables()
{
$this->twig->enableStrictVariables();
}
/**
* {@inheritDoc}
*/
public function disableStrictVariables()
{
$this->twig->disableStrictVariables();
}
/**
* {@inheritDoc}
*/
public function isStrictVariables()
{
return $this->twig->isStrictVariables();
}
/**
* {@inheritDoc}
*/
public function getCache()
{
return $this->twig->getCache();
}
/**
* {@inheritDoc}
*/
public function setCache($cache)
{
$this->twig->setCache($cache);
}
/**
* {@inheritDoc}
*/
public function getCacheFilename($name)
{
return $this->twig->getCacheFilename($name);
}
/**
* {@inheritDoc}
*/
public function getTemplateClass($name, $index = null)
{
return $this->twig->getTemplateClass($name, $index);
}
/**
* {@inheritDoc}
*/
public function getTemplateClassPrefix()
{
return $this->twig->getTemplateClassPrefix();
}
/**
* {@inheritDoc}
*/
public function render($name, array $context = array())
{
return $this->loadTemplate($name)->render($context);
}
/**
* {@inheritDoc}
*/
public function display($name, array $context = array())
{
$this->loadTemplate($name)->display($context);
}
/**
* {@inheritDoc}
*/
public function loadTemplate($name, $index = null)
{
$cls = $this->twig->getTemplateClass($name, $index);
if (isset($this->twig->loadedTemplates[$cls])) {
return $this->twig->loadedTemplates[$cls];
}
if (!class_exists($cls, false)) {
if (false === $cache = $this->getCacheFilename($name)) {
eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name));
} else {
if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
$this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name));
}
require_once $cache;
}
}
if (!$this->twig->runtimeInitialized) {
$this->initRuntime();
}
return $this->twig->loadedTemplates[$cls] = new TraceableTwigTemplate($this, new $cls($this));
}
/**
* {@inheritDoc}
*/
public function isTemplateFresh($name, $time)
{
return $this->twig->isTemplateFresh($name, $time);
}
/**
* {@inheritDoc}
*/
public function resolveTemplate($names)
{
return $this->twig->resolveTemplate($names);
}
/**
* {@inheritDoc}
*/
public function clearTemplateCache()
{
$this->twig->clearTemplateCache();
}
/**
* {@inheritDoc}
*/
public function clearCacheFiles()
{
$this->twig->clearCacheFiles();
}
/**
* {@inheritDoc}
*/
public function getLexer()
{
return $this->twig->getLexer();
}
/**
* {@inheritDoc}
*/
public function setLexer(Twig_LexerInterface $lexer)
{
$this->twig->setLexer($lexer);
}
/**
* {@inheritDoc}
*/
public function tokenize($source, $name = null)
{
return $this->twig->tokenize($source, $name);
}
/**
* {@inheritDoc}
*/
public function getParser()
{
return $this->twig->getParser();
}
/**
* {@inheritDoc}
*/
public function setParser(Twig_ParserInterface $parser)
{
$this->twig->setParser($parser);
}
/**
* {@inheritDoc}
*/
public function parse(Twig_TokenStream $tokens)
{
return $this->twig->parse($tokens);
}
/**
* {@inheritDoc}
*/
public function getCompiler()
{
return $this->twig->getCompiler();
}
/**
* {@inheritDoc}
*/
public function setCompiler(Twig_CompilerInterface $compiler)
{
$this->twig->setCompiler($compiler);
}
/**
* {@inheritDoc}
*/
public function compile(Twig_NodeInterface $node)
{
return $this->twig->compile($node);
}
/**
* {@inheritDoc}
*/
public function compileSource($source, $name = null)
{
return $this->twig->compileSource($source, $name);
}
/**
* {@inheritDoc}
*/
public function setLoader(Twig_LoaderInterface $loader)
{
$this->twig->setLoader($loader);
}
/**
* {@inheritDoc}
*/
public function getLoader()
{
return $this->twig->getLoader();
}
/**
* {@inheritDoc}
*/
public function setCharset($charset)
{
$this->twig->setCharset($charset);
}
/**
* {@inheritDoc}
*/
public function getCharset()
{
return $this->twig->getCharset();
}
/**
* {@inheritDoc}
*/
public function initRuntime()
{
$this->twig->initRuntime();
}
/**
* {@inheritDoc}
*/
public function hasExtension($name)
{
return $this->twig->hasExtension($name);
}
/**
* {@inheritDoc}
*/
public function getExtension($name)
{
return $this->twig->getExtension($name);
}
/**
* {@inheritDoc}
*/
public function addExtension(Twig_ExtensionInterface $extension)
{
$this->twig->addExtension($extension);
}
/**
* {@inheritDoc}
*/
public function removeExtension($name)
{
$this->twig->removeExtension($name);
}
/**
* {@inheritDoc}
*/
public function setExtensions(array $extensions)
{
$this->twig->setExtensions($extensions);
}
/**
* {@inheritDoc}
*/
public function getExtensions()
{
return $this->twig->getExtensions();
}
/**
* {@inheritDoc}
*/
public function addTokenParser(Twig_TokenParserInterface $parser)
{
$this->twig->addTokenParser($parser);
}
/**
* {@inheritDoc}
*/
public function getTokenParsers()
{
return $this->twig->getTokenParsers();
}
/**
* {@inheritDoc}
*/
public function getTags()
{
return $this->twig->getTags();
}
/**
* {@inheritDoc}
*/
public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
{
$this->twig->addNodeVisitor($visitor);
}
/**
* {@inheritDoc}
*/
public function getNodeVisitors()
{
return $this->twig->getNodeVisitors();
}
/**
* {@inheritDoc}
*/
public function addFilter($name, $filter = null)
{
$this->twig->addFilter($name, $filter);
}
/**
* {@inheritDoc}
*/
public function getFilter($name)
{
return $this->twig->getFilter($name);
}
/**
* {@inheritDoc}
*/
public function registerUndefinedFilterCallback($callable)
{
$this->twig->registerUndefinedFilterCallback($callable);
}
/**
* {@inheritDoc}
*/
public function getFilters()
{
return $this->twig->getFilters();
}
/**
* {@inheritDoc}
*/
public function addTest($name, $test = null)
{
$this->twig->addTest($name, $test);
}
/**
* {@inheritDoc}
*/
public function getTests()
{
return $this->twig->getTests();
}
/**
* {@inheritDoc}
*/
public function getTest($name)
{
return $this->twig->getTest($name);
}
/**
* {@inheritDoc}
*/
public function addFunction($name, $function = null)
{
$this->twig->addFunction($name, $function);
}
/**
* {@inheritDoc}
*/
public function getFunction($name)
{
return $this->twig->getFunction($name);
}
/**
* {@inheritDoc}
*/
public function registerUndefinedFunctionCallback($callable)
{
$this->twig->registerUndefinedFunctionCallback($callable);
}
/**
* {@inheritDoc}
*/
public function getFunctions()
{
return $this->twig->getFunctions();
}
/**
* {@inheritDoc}
*/
public function addGlobal($name, $value)
{
$this->twig->addGlobal($name, $value);
}
/**
* {@inheritDoc}
*/
public function getGlobals()
{
return $this->twig->getGlobals();
}
/**
* {@inheritDoc}
*/
public function mergeGlobals(array $context)
{
return $this->twig->mergeGlobals($context);
}
/**
* {@inheritDoc}
*/
public function getUnaryOperators()
{
return $this->twig->getUnaryOperators();
}
/**
* {@inheritDoc}
*/
public function getBinaryOperators()
{
return $this->twig->getBinaryOperators();
}
/**
* {@inheritDoc}
*/
public function computeAlternatives($name, $items)
{
return $this->twig->computeAlternatives($name, $items);
}
}

View File

@@ -0,0 +1,168 @@
<?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\Bridge\Twig;
use Twig_TemplateInterface;
use Twig_Template;
/**
* Wraps a Twig_Template to add profiling features
*/
class TraceableTwigTemplate implements Twig_TemplateInterface
{
protected $template;
/**
* @param TraceableTwigEnvironment $env
* @param Twig_Template $template
*/
public function __construct(TraceableTwigEnvironment $env, Twig_Template $template)
{
$this->env = $env;
$this->template = $template;
}
/**
* {@inheritDoc}
*/
public function getTemplateName()
{
return $this->template->getTemplateName();
}
/**
* {@inheritDoc}
*/
public function getEnvironment()
{
return $this->template->getEnvironment();
}
/**
* {@inheritDoc}
*/
public function getParent(array $context)
{
return $this->template->getParent($context);
}
/**
* {@inheritDoc}
*/
public function isTraitable()
{
return $this->template->isTraitable();
}
/**
* {@inheritDoc}
*/
public function displayParentBlock($name, array $context, array $blocks = array())
{
$this->template->displayParentBlock($name, $context, $blocks);
}
/**
* {@inheritDoc}
*/
public function displayBlock($name, array $context, array $blocks = array())
{
$this->displayBlock($name, $context, $blocks);
}
/**
* {@inheritDoc}
*/
public function renderParentBlock($name, array $context, array $blocks = array())
{
return $this->template->renderParentBlock($name, $context, $blocks);
}
/**
* {@inheritDoc}
*/
public function renderBlock($name, array $context, array $blocks = array())
{
return $this->template->renderBlock($name, $context, $blocks);
}
/**
* {@inheritDoc}
*/
public function hasBlock($name)
{
return $this->template->hasBlock($name);
}
/**
* {@inheritDoc}
*/
public function getBlockNames()
{
return $this->template->getBlockNames();
}
/**
* {@inheritDoc}
*/
public function getBlocks()
{
return $this->template->getBlocks();
}
/**
* {@inheritDoc}
*/
public function display(array $context, array $blocks = array())
{
$start = microtime(true);
$this->template->display($context, $blocks);
$end = microtime(true);
if ($timeDataCollector = $this->env->getTimeDataCollector()) {
$name = sprintf("twig.render(%s)", $this->template->getTemplateName());
$timeDataCollector->addMeasure($name, $start, $end);
}
$this->env->addRenderedTemplate(array(
'name' => $this->template->getTemplateName(),
'render_time' => $end - $start
));
}
/**
* {@inheritDoc}
*/
public function render(array $context)
{
$level = ob_get_level();
ob_start();
try {
$this->display($context);
} catch (Exception $e) {
while (ob_get_level() > $level) {
ob_end_clean();
}
throw $e;
}
return ob_get_clean();
}
/**
* {@inheritDoc}
*/
public static function clearCache()
{
$this->template->clearCache();
}
}

View File

@@ -0,0 +1,82 @@
<?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\Bridge\Twig;
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
/**
* Collects data about rendered templates
*
* http://twig.sensiolabs.org/
*
* Your Twig_Environment object needs to be wrapped in a
* TraceableTwigEnvironment object
*
* <code>
* $env = new TraceableTwigEnvironment(new Twig_Environment($loader));
* $debugbar->addCollector(new TwigCollector($env));
* </code>
*/
class TwigCollector extends DataCollector implements Renderable
{
public function __construct(TraceableTwigEnvironment $twig)
{
$this->twig = $twig;
}
/**
* {@inheritDoc}
*/
public function collect()
{
$templates = array();
$accuRenderTime = 0;
foreach ($this->twig->getRenderedTemplates() as $tpl) {
$accuRenderTime += $tpl['render_time'];
$templates[] = array(
'name' => $tpl['name'],
'render_time' => $tpl['render_time'],
'render_time_str' => $this->formatDuration($tpl['render_time'])
);
}
return array(
'nb_templates' => count($templates),
'templates' => $templates,
'accumulated_render_time' => $accuRenderTime,
'accumulated_render_time_str' => $this->formatDuration($accuRenderTime)
);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'twig';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
'twig' => array(
'widget' => 'PhpDebugBar.Widgets.TemplatesWidget',
'map' => 'twig',
'default' => '[]'
)
);
}
}

View File

@@ -0,0 +1,172 @@
<?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;
use ArrayAccess;
use DebugBar\DebugBarException;
/**
* Aggregates data from multiple collectors
*
* <code>
* $aggcollector = new AggregateCollector('foobar');
* $aggcollector->addCollector(new MessagesCollector('msg1'));
* $aggcollector->addCollector(new MessagesCollector('msg2'));
* $aggcollector['msg1']->addMessage('hello world');
* </code>
*/
class AggregatedCollector implements DataCollectorInterface, ArrayAccess
{
protected $name;
protected $mergeProperty;
protected $sort;
protected $collectors = array();
/**
* @param string $name
* @param string $mergeProperty
* @param boolean $sort
*/
public function __construct($name, $mergeProperty = null, $sort = false)
{
$this->name = $name;
$this->mergeProperty = $mergeProperty;
$this->sort = $sort;
}
/**
* @param DataCollectorInterface $collector
*/
public function addCollector(DataCollectorInterface $collector)
{
$this->collectors[$collector->getName()] = $collector;
}
/**
* @return array
*/
public function getCollectors()
{
return $this->collectors;
}
/**
* Merge data from one of the key/value pair of the collected data
*
* @param string $property
*/
public function setMergeProperty($property)
{
$this->mergeProperty = $property;
}
/**
* @return string
*/
public function getMergeProperty()
{
return $this->mergeProperty;
}
/**
* Sorts the collected data
*
* If true, sorts using sort()
* If it is a string, sorts the data using the value from a key/value pair of the array
*
* @param bool|string $sort
*/
public function setSort($sort)
{
$this->sort = $sort;
}
/**
* @return bool|string
*/
public function getSort()
{
return $this->sort;
}
/**
* {@inheritDoc}
*/
public function collect()
{
$aggregate = array();
foreach ($this->collectors as $collector) {
$data = $collector->collect();
if ($this->mergeProperty !== null) {
$data = $data[$this->mergeProperty];
}
$aggregate = array_merge($aggregate, $data);
}
return $this->sort($aggregate);
}
/**
* Sorts the collected data
*
* @param array $data
* @return array
*/
protected function sort($data)
{
if (is_string($this->sort)) {
$p = $this->sort;
usort($data, function($a, $b) use ($p) {
if ($a[$p] == $b[$p]) {
return 0;
}
return $a[$p] < $b[$p] ? -1 : 1;
});
} else if ($this->sort === true) {
sort($data);
}
return $data;
}
/**
* {@inheritDoc}
*/
public function getName()
{
return $this->name;
}
// --------------------------------------------
// ArrayAccess implementation
public function offsetSet($key, $value)
{
throw new DebugBarException("AggregatedCollector[] is read-only");
}
public function offsetGet($key)
{
return $this->collectors[$key];
}
public function offsetExists($key)
{
return isset($this->collectors[$key]);
}
public function offsetUnset($key)
{
throw new DebugBarException("AggregatedCollector[] is read-only");
}
}

View File

@@ -0,0 +1,76 @@
<?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;
/**
* Collects array data
*/
class ConfigCollector extends DataCollector implements Renderable
{
protected $name;
protected $data;
/**
* @param array $data
* @param string $name
*/
public function __construct(array $data = array(), $name = 'config')
{
$this->name = $name;
$this->data = $data;
}
/**
* Sets the data
*
* @param array $data
*/
public function setData(array $data)
{
$this->data = $data;
}
/**
* {@inheritDoc}
*/
public function collect()
{
$data = array();
foreach ($this->data as $k => $v) {
$data[$k] = $this->formatVar($v);
}
return $data;
}
/**
* {@inheritDoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
$name = $this->getName();
return array(
"$name" => array(
"widget" => "PhpDebugBar.Widgets.VariableListWidget",
"map" => "$name",
"default" => "{}"
)
);
}
}

View File

@@ -0,0 +1,68 @@
<?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;
/**
* Abstract class for data collectors
*/
abstract class DataCollector implements DataCollectorInterface
{
/**
* Transforms a PHP variable to a string representation
*
* @param mixed $var
* @return string
*/
public function formatVar($var)
{
if (is_array($var)) {
foreach ($var as &$v) {
if (is_object($v)) {
$v = "Object(" . get_class($v) . ")";
}
}
} else if (is_object($var)) {
$var = "Object(" . get_class($var) . ")";
}
return print_r($var, true);
}
/**
* Transforms a duration in seconds in a readable string
*
* @param float $seconds
* @return string
*/
public function formatDuration($seconds)
{
if ($seconds < 1) {
return round($seconds * 1000) . 'ms';
}
return round($seconds, 2) . 's';
}
/**
* 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 || $size === null) {
return "0B";
}
$base = log($size) / log(1024);
$suffixes = array('', 'KB', 'MB', 'GB', 'TB');
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
}

View File

@@ -0,0 +1,31 @@
<?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;
/**
* DataCollector Interface
*/
interface DataCollectorInterface
{
/**
* Called by the DebugBar when data needs to be collected
*
* @return array Collected data
*/
function collect();
/**
* Returns the unique name of the collector
*
* @return string
*/
function getName();
}

View File

@@ -0,0 +1,100 @@
<?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;
use Exception;
/**
* Collects info about exceptions
*/
class ExceptionsCollector extends DataCollector implements Renderable
{
protected $exceptions = array();
/**
* Adds an exception to be profiled in the debug bar
*
* @param Exception $e
*/
public function addException(Exception $e)
{
$this->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'
)
);
}
}

View File

@@ -0,0 +1,72 @@
<?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;
/**
* Collects info about memory usage
*/
class MemoryCollector extends DataCollector implements Renderable
{
protected $peakUsage = 0;
/**
* Returns the peak memory usage
*
* @return integer
*/
public function getPeakUsage()
{
return $this->peakUsage;
}
/**
* Updates the peak memory usage value
*/
public function updatePeakUsage()
{
$this->peakUsage = memory_get_peak_usage(true);
}
/**
* {@inheritDoc}
*/
public function collect()
{
$this->updatePeakUsage();
return array(
'peak_usage' => $this->peakUsage,
'peak_usage_str' => $this->formatBytes($this->peakUsage)
);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'memory';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
"memory" => array(
"icon" => "cogs",
"tooltip" => "Memory Usage",
"map" => "memory.peak_usage_str",
"default" => "'0B'"
)
);
}
}

View File

@@ -0,0 +1,21 @@
<?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;
interface MessagesAggregateInterface
{
/**
* Returns collected messages
*
* @return array
*/
public function getMessages();
}

View File

@@ -0,0 +1,141 @@
<?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;
use Psr\Log\AbstractLogger;
/**
* Provides a way to log messages
*/
class MessagesCollector extends AbstractLogger implements DataCollectorInterface, MessagesAggregateInterface, Renderable
{
protected $name;
protected $messages = array();
protected $aggregates = array();
/**
* @param string $name
*/
public function __construct($name = 'messages')
{
$this->name = $name;
}
/**
* 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')
{
$this->messages[] = array(
'message' => print_r($message, true),
'is_string' => is_string($message),
'label' => $label,
'time' => microtime(true)
);
}
/**
* Aggregates messages from other collectors
*
* @param MessagesAggregateInterface $messages
*/
public function aggregate(MessagesAggregateInterface $messages)
{
$this->aggregates[] = $messages;
}
/**
* {@inheritDoc}
*/
public function getMessages()
{
$messages = $this->messages;
foreach ($this->aggregates as $collector) {
$msgs = array_map(function($m) use ($collector) {
$m['collector'] = $collector->getName();
return $m;
}, $collector->getMessages());
$messages = array_merge($messages, $msgs);
}
// sort messages by their timestamp
usort($messages, function($a, $b) {
if ($a['time'] === $b['time']) {
return 0;
}
return $a['time'] < $b['time'] ? -1 : 1;
});
return $messages;
}
/**
* {@inheritDoc}
*/
public function log($level, $message, array $context = array())
{
$this->addMessage($message, $level);
}
/**
* Deletes all messages
*/
public function clear()
{
$this->messages = array();
}
/**
* {@inheritDoc}
*/
public function collect()
{
$messages = $this->getMessages();
return array(
'count' => count($messages),
'messages' => $messages
);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
$name = $this->getName();
return array(
"$name" => array(
"widget" => "PhpDebugBar.Widgets.MessagesWidget",
"map" => "$name.messages",
"default" => "[]"
),
"$name:badge" => array(
"map" => "$name.count",
"default" => "null"
)
);
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace DebugBar\DataCollector\PDO;
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
use DebugBar\DataCollector\TimeDataCollector;
/**
* Collects data about SQL statements executed with PDO
*/
class PDOCollector extends DataCollector implements Renderable
{
protected $connections = array();
protected $timeCollector;
/**
* @param TraceablePDO $pdo
* @param TimeDataCollector $timeCollector
*/
public function __construct(TraceablePDO $pdo = null, TimeDataCollector $timeCollector = null)
{
$this->timeCollector = $timeCollector;
if ($pdo !== null) {
$this->addConnection($pdo, 'default');
}
}
/**
* Adds a new PDO instance to be collector
*
* @param TraceablePDO $pdo
* @param string $name Optional connection name
*/
public function addConnection(TraceablePDO $pdo, $name = null)
{
if ($name === null) {
$name = spl_object_hash($pdo);
}
$this->connections[$name] = $pdo;
}
/**
* Returns PDO instances to be collected
*
* @return array
*/
public function getConnections()
{
return $this->connections;
}
/**
* {@inheritDoc}
*/
public function collect()
{
$data = array(
'nb_statements' => 0,
'nb_failed_statements' => 0,
'accumulated_duration' => 0,
'peak_memory_usage' => 0,
'statements' => array()
);
foreach ($this->connections as $name => $pdo) {
$pdodata = $this->collectPDO($pdo, $this->timeCollector);
$data['nb_statements'] += $pdodata['nb_statements'];
$data['nb_failed_statements'] += $pdodata['nb_failed_statements'];
$data['accumulated_duration'] += $pdodata['accumulated_duration'];
$data['peak_memory_usage'] = max($data['peak_memory_usage'], $pdodata['peak_memory_usage']);
$data['statements'] = array_merge($data['statements'],
array_map(function($s) use ($name) { $s['connection'] = $name; return $s; }, $pdodata['statements']));
}
$data['accumulated_duration_str'] = $this->formatDuration($data['accumulated_duration']);
$data['peak_memory_usage_str'] = $this->formatBytes($data['peak_memory_usage']);
return $data;
}
/**
* Collects data from a single TraceablePDO instance
*
* @param TraceablePDO $pdo
* @param TimeDataCollector $timeCollector
* @return array
*/
protected function collectPDO(TraceablePDO $pdo, TimeDataCollector $timeCollector = null)
{
$stmts = array();
foreach ($pdo->getExecutedStatements() as $stmt) {
$stmts[] = array(
'sql' => $stmt->getSql(),
'row_count' => $stmt->getRowCount(),
'stmt_id' => $stmt->getPreparedId(),
'prepared_stmt' => $stmt->getSql(),
'params' => (object) $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()
);
if ($timeCollector !== null) {
$timeCollector->addMeasure($stmt->getSql(), $stmt->getStartTime(), $stmt->getEndTime());
}
}
return array(
'nb_statements' => count($stmts),
'nb_failed_statements' => count($pdo->getFailedExecutedStatements()),
'accumulated_duration' => $pdo->getAccumulatedStatementsDuration(),
'accumulated_duration_str' => $this->formatDuration($pdo->getAccumulatedStatementsDuration()),
'peak_memory_usage' => $pdo->getPeakMemoryUsage(),
'peak_memory_usage_str' => $this->formatBytes($pdo->getPeakMemoryUsage()),
'statements' => $stmts
);
}
/**
* {@inheritDoc}
*/
public function getName()
{
return 'pdo';
}
/**
* {@inheritDoc}
*/
public function getWidgets()
{
return array(
"database" => array(
"widget" => "PhpDebugBar.Widgets.SQLQueriesWidget",
"map" => "pdo",
"default" => "[]"
),
"database:badge" => array(
"map" => "pdo.nb_statements",
"default" => 0
)
);
}
}

View File

@@ -0,0 +1,211 @@
<?php
namespace DebugBar\DataCollector\PDO;
use PDO;
use PDOException;
/**
* A PDO proxy which traces statements
*/
class TraceablePDO extends PDO
{
protected $pdo;
protected $executedStatements = array();
public function __construct(PDO $pdo)
{
$this->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;
}
$end = microtime(true);
$memoryUsage = memory_get_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, $start, $end, $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(); });
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace DebugBar\DataCollector\PDO;
use PDO;
use PDOStatement;
use PDOException;
/**
* A traceable PDO statement to use with Traceablepdo
*/
class TraceablePDOStatement extends PDOStatement
{
protected $pdo;
protected $boundParameters = array();
protected function __construct(TraceablePDO $pdo)
{
$this->pdo = $pdo;
}
/**
* {@inheritDoc}
*/
public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null) {
$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, $data_type = PDO::PARAM_STR, $length = null, $driver_options = null) {
$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, $data_type = PDO::PARAM_STR) {
$this->boundParameters[$param] = $value;
return call_user_func_array(array("parent", 'bindValue'), func_get_args());
}
/**
* {@inheritDoc}
*/
public function execute($params = null)
{
$start = microtime(true);
$ex = null;
try {
$result = parent::execute($params);
} catch (PDOException $e) {
$ex = $e;
}
$preparedId = spl_object_hash($this);
$boundParameters = $this->boundParameters;
if (is_array($params)) {
$boundParameters = array_merge($boundParameters, $params);
}
$end = microtime(true);
$memoryUsage = memory_get_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(), $start, $end, $memoryUsage, $ex);
$this->pdo->addExecutedStatement($tracedStmt);
if ($this->pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION && $ex !== null) {
throw $ex;
}
return $result;
}
}

View File

@@ -0,0 +1,202 @@
<?php
namespace DebugBar\DataCollector\PDO;
/**
* Holds information about a statement
*/
class TracedStatement
{
protected $sql;
protected $rowCount;
protected $parameters;
protected $duration;
protected $memoryUsage;
protected $exception;
/**
* Traces a call and returns a TracedStatement
*
* @param callback $callback
* @param array $args Callback args
* @param string $sql The SQL query string
* @return TracedStatement
*/
public static function traceCall($callback, array $args, $sql = '')
{
$start = microtime(true);
$result = call_user_func_array($callback, $args);
$duration = microtime(true) - $start;
$memoryUsage = memory_get_peak_usage(true);
$tracedStmt = new TracedStatement($sql, array(), null, 0, $duration, $memoryUsage);
return array($tracedStmt, $result);
}
/**
* @param string $sql
* @param array $params
* @param string $preparedId
* @param integer $rowCount
* @param integer $startTime
* @param integer $endTime
* @param integer $memoryUsage
* @param \Exception $e
*/
public function __construct($sql, array $params = array(), $preparedId = null, $rowCount = 0, $startTime = 0, $endTime = 0, $memoryUsage = 0, \Exception $e = null)
{
$this->sql = $sql;
$this->rowCount = $rowCount;
$this->parameters = $params;
$this->preparedId = $preparedId;
$this->startTime = $startTime;
$this->endTime = $endTime;
$this->duration = $endTime - $startTime;
$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;
}
public function getStartTime()
{
return $this->startTime;
}
public function getEndTime()
{
return $this->endTime;
}
/**
* 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() : '';
}
}

View File

@@ -0,0 +1,35 @@
<?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;
/**
* Collects info about PHP
*/
class PhpInfoCollector extends DataCollector
{
/**
* {@inheritDoc}
*/
public function collect()
{
return array(
'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

@@ -0,0 +1,56 @@
<?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;
/**
* Collects info about the current request
*/
class RequestDataCollector extends DataCollector implements Renderable
{
/**
* {@inheritDoc}
*/
public function collect()
{
$vars = array('_GET', '_POST', '_SESSION', '_COOKIE', '_SERVER');
$data = array();
foreach ($vars as $var) {
if (isset($GLOBALS[$var])) {
$data["$" . $var] = $this->formatVar($GLOBALS[$var]);
}
}
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

@@ -0,0 +1,197 @@
<?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;
use DebugBar\DebugBarException;
/**
* 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 $requestEndTime;
protected $startedMeasures = array();
protected $measures = array();
/**
* @param float $requestStartTime
*/
public function __construct($requestStartTime = null)
{
if ($requestStartTime === null) {
if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
$requestStartTime = $_SERVER['REQUEST_TIME_FLOAT'];
} else {
$requestStartTime = microtime(true);
}
}
$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)
{
$start = microtime(true);
$this->startedMeasures[$name] = array(
'label' => $label ?: $name,
'start' => $start
);
}
/**
* Stops a measure
*
* @param string $name
*/
public function stopMeasure($name)
{
$end = microtime(true);
if (!isset($this->startedMeasures[$name])) {
throw new DebugBarException("Failed stopping measure '$name' because it hasn't been started");
}
$this->addMeasure($this->startedMeasures[$name]['label'], $this->startedMeasures[$name]['start'], $end);
unset($this->startedMeasures[$name]);
}
/**
* Adds a measure
*
* @param string $label
* @param float $start
* @param float $end
*/
public function addMeasure($label, $start, $end)
{
$this->measures[] = array(
'label' => $label,
'start' => $start,
'relative_start' => $start - $this->requestStartTime,
'end' => $end,
'relative_end' => $end - $this->requestEndTime,
'duration' => $end - $start,
'duration_str' => $this->formatDuration($end - $start)
);
}
/**
* Utility function to measure the execution of a Closure
*
* @param string $label
* @param Closure $closure
*/
public function measure($label, \Closure $closure)
{
$name = spl_object_hash($closure);
$this->startMeasure($name, $label);
$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()
{
return $this->requestStartTime;
}
/**
* Returns the request end time
*
* @return float
*/
public function getRequestEndTime()
{
return $this->requestEndTime;
}
/**
* Returns the duration of a request
*
* @return float
*/
public function getRequestDuration()
{
if ($this->requestEndTime !== null) {
return $this->requestEndTime - $this->requestStartTime;
}
return microtime(true) - $this->requestStartTime;
}
/**
* {@inheritDoc}
*/
public function collect()
{
$this->requestEndTime = microtime(true);
foreach (array_keys($this->startedMeasures) as $name) {
$this->stopMeasure($name);
}
return array(
'start' => $this->requestStartTime,
'end' => $this->requestEndTime,
'duration' => $this->getRequestDuration(),
'duration_str' => $this->formatDuration($this->getRequestDuration()),
'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

@@ -0,0 +1,240 @@
<?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 ArrayAccess;
use DebugBar\DataCollector\DataCollectorInterface;
use DebugBar\Storage\StorageInterface;
/**
* Main DebugBar object
*
* Manages 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
{
protected $collectors = array();
protected $data;
protected $jsRenderer;
protected $requestIdGenerator;
protected $requestId;
protected $storage;
/**
* Adds a data collector
*
* @param DataCollectorInterface $collector
*
* @throws DebugBarException
* @return $this
*/
public function addCollector(DataCollectorInterface $collector)
{
if ($collector->getName() === '__meta') {
throw new DebugBarException("'__meta' is a reserved name and cannot be used as a collector name");
}
if (isset($this->collectors[$collector->getName()])) {
throw new DebugBarException("'{$collector->getName()}' is already a registered 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)
{
return isset($this->collectors[$name]);
}
/**
* Returns a data collector
*
* @param string $name
* @return DataCollectorInterface
*/
public function getCollector($name)
{
if (!isset($this->collectors[$name])) {
throw new DebugBarException("'$name' is not a registered collector");
}
return $this->collectors[$name];
}
/**
* Returns an array of all data collectors
*
* @return array[DataCollectorInterface]
*/
public function getCollectors()
{
return $this->collectors;
}
/**
* Sets the request id generator
*
* @param RequestIdGeneratorInterface $generator
*/
public function setRequestIdGenerator(RequestIdGeneratorInterface $generator)
{
$this->requestIdGenerator = $generator;
return $this;
}
/**
* @return RequestIdGeneratorInterface
*/
public function getRequestIdGenerator()
{
if ($this->requestIdGenerator === null) {
$this->requestIdGenerator = new RequestIdGenerator();
}
return $this->requestIdGenerator;
}
/**
* Returns the id of the current request
*
* @return string
*/
public function getCurrentRequestId()
{
if ($this->requestId === null) {
$this->requestId = $this->getRequestIdGenerator()->generate();
}
return $this->requestId;
}
/**
* Sets the storage backend to use to store the collected data
*
* @param StorageInterface $storage
*/
public function setStorage(StorageInterface $storage = null)
{
$this->storage = $storage;
return $this;
}
/**
* @return StorageInterface
*/
public function getStorage()
{
return $this->storage;
}
/**
* Checks if the data will be persisted
*
* @return boolean
*/
public function isDataPersisted()
{
return $this->storage !== null;
}
/**
* Collects the data from the collectors
*
* @return array
*/
public function collect()
{
$this->data = array(
'__meta' => array(
'id' => $this->getCurrentRequestId(),
'datetime' => date('Y-m-d H:i:s'),
'utime' => microtime(true),
'uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null,
'ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null
)
);
foreach ($this->collectors as $name => $collector) {
$this->data[$name] = $collector->collect();
}
if ($this->storage !== null) {
$this->storage->save($this->getCurrentRequestId(), $this->data);
}
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)
{
throw new DebugBarException("DebugBar[] is read-only");
}
public function offsetGet($key)
{
return $this->getCollector($key);
}
public function offsetExists($key)
{
return $this->hasCollector($key);
}
public function offsetUnset($key)
{
throw new DebugBarException("DebugBar[] is read-only");
}
}

View File

@@ -0,0 +1,16 @@
<?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;
class DebugBarException extends \Exception
{
}

View File

@@ -0,0 +1,676 @@
<?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');
protected $includeVendors = true;
protected $cssFiles = array('debugbar.css', 'widgets.css', 'openhandler.css');
protected $jsFiles = array('debugbar.js', 'widgets.js', 'openhandler.js');
protected $javascriptClass = 'PhpDebugBar.DebugBar';
protected $variableName = 'phpdebugbar';
protected $initialization;
protected $controls = array();
protected $ignoredCollectors = array();
protected $openHandlerClass = 'PhpDebugBar.OpenHandler';
protected $openHandlerUrl;
/**
* @param \DebugBar\DebugBar $debugBar
* @param string $baseUrl
* @param string $basePath
*/
public function __construct(DebugBar $debugBar, $baseUrl = null, $basePath = null)
{
$this->debugBar = $debugBar;
if ($baseUrl === null) {
$baseUrl = '/vendor/maximebf/debugbar/src/DebugBar/Resources';
}
$this->baseUrl = $baseUrl;
if ($basePath === null) {
$basePath = __DIR__ . DIRECTORY_SEPARATOR . 'Resources';
}
$this->basePath = $basePath;
// bitwise operations cannot be done in class definition :(
$this->initialization = self::INITIALIZE_CONSTRUCTOR | self::INITIALIZE_CONTROLS;
}
/**
* Sets options from an array
*
* Options:
* - base_path
* - base_url
* - include_vendors
* - javascript_class
* - variable_name
* - controls
* - disable_controls
* - ignore_collectors
*
* @param array $options [description]
*/
public function setOptions(array $options)
{
if (array_key_exists('base_path', $options)) {
$this->setBasePath($options['base_path']);
}
if (array_key_exists('base_url', $options)) {
$this->setBaseUrl($options['base_url']);
}
if (array_key_exists('include_vendors', $options)) {
$this->setIncludeVendors($options['include_vendors']);
}
if (array_key_exists('javascript_class', $options)) {
$this->setJavascriptClass($options['javascript_class']);
}
if (array_key_exists('variable_name', $options)) {
$this->setVariableName($options['variable_name']);
}
if (array_key_exists('initialization', $options)) {
$this->setInitialization($options['initialization']);
}
if (array_key_exists('controls', $options)) {
foreach ($options['controls'] as $name => $control) {
$this->addControl($name, $control);
}
}
if (array_key_exists('disable_controls', $options)) {
foreach ((array) $options['disable_controls'] as $name) {
$this->disableControl($name);
}
}
if (array_key_exists('ignore_collectors', $options)) {
foreach ((array) $options['ignore_collectors'] as $name) {
$this->ignoreCollector($name);
}
}
if (array_key_exists('open_handler_classname', $options)) {
$this->setOpenHandlerClass($options['open_handler_classname']);
}
if (array_key_exists('open_handler_url', $options)) {
$this->setOpenHandlerUrl($options['open_handler_url']);
}
}
/**
* 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
*
* You can only include js or css vendors using
* setIncludeVendors('css') or setIncludeVendors('js')
*
* @param boolean $enabled
*/
public function setIncludeVendors($enabled = true)
{
if (is_string($enabled)) {
$enabled = array($enabled);
}
$this->includeVendors = $enabled;
return $this;
}
/**
* Checks if vendors assets are included
*
* @return boolean
*/
public function areVendorsIncluded()
{
return $this->includeVendors !== false;
}
/**
* 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
* - 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
*
* "icon" or "widget" are at least needed
*
* @param string $name
* @param array $options
*/
public function addControl($name, array $options)
{
if (count(array_intersect(array_keys($options), array('icon', 'widget', 'tab', 'indicator'))) === 0) {
throw new DebugBarException("Not enough options for control '$name'");
}
$this->controls[$name] = $options;
return $this;
}
/**
* Disables a control
*
* @param string $name
*/
public function disableControl($name)
{
$this->controls[$name] = null;
return $this;
}
/**
* Returns the list of controls
*
* This does not include controls provided by collectors
*
* @return array
*/
public function getControls()
{
return $this->controls;
}
/**
* Ignores widgets provided by a collector
*
* @param string $name
*/
public function ignoreCollector($name)
{
$this->ignoredCollectors[] = $name;
return $this;
}
/**
* Returns the list of ignored collectors
*
* @return array
*/
public function getIgnoredCollectors()
{
return $this->ignoredCollectors;
}
/**
* Returns needed asset files relative to the base path
*
* @param string $type 'css', 'js' or null for both
* @return array
*/
public function getAssets($type = null)
{
list($cssFiles, $jsFiles) = $this->getAssetFiles();
return $this->filterAssetArray(array(
$this->makeUriRelativeTo($cssFiles, $this->basePath),
$this->makeUriRelativeTo($jsFiles, $this->basePath)
), $type);
}
/**
* Sets the class name of the js open handler
*
* @param string $className
*/
public function setOpenHandlerClass($className)
{
$this->openHandlerClass = $className;
return $this;
}
/**
* Returns the class name of the js open handler
*
* @return string
*/
public function getOpenHandlerClass()
{
return $this->openHandlerClass;
}
/**
* Sets the url of the open handler
*
* @param string $url
*/
public function setOpenHandlerUrl($url)
{
$this->openHandlerUrl = $url;
return $this;
}
/**
* Returns the url for the open handler
*
* @return string
*/
public function getOpenHandlerUrl()
{
return $this->openHandlerUrl;
}
/**
* Returns the list of asset files
*
* @param string $type Only return css or js files
* @return array
*/
protected function getAssetFiles($type = null)
{
$cssFiles = $this->cssFiles;
$jsFiles = $this->jsFiles;
if ($this->includeVendors !== false) {
if ($this->includeVendors === true || in_array('css', $this->includeVendors)) {
$cssFiles = array_merge($this->cssVendors, $cssFiles);
}
if ($this->includeVendors === true || in_array('js', $this->includeVendors)) {
$jsFiles = array_merge($this->jsVendors, $jsFiles);
}
}
return $this->filterAssetArray(array($cssFiles, $jsFiles), $type);
}
/**
* Filters a tuple of (css, js) assets according to $type
*
* @param array $array
* @param string $type 'css', 'js' or null for both
* @return array
*/
protected function filterAssetArray($array, $type = null)
{
$type = strtolower($type);
if ($type === 'css') {
return $array[0];
}
if ($type === 'js') {
return $array[1];
}
return $array;
}
/**
* Returns a tuple where the both items are Assetic AssetCollection,
* the first one being css files and the second js files
*
* @param string $type Only return css or js collection
* @return array or \Assetic\Asset\AssetCollection
*/
public function getAsseticCollection($type = null)
{
list($cssFiles, $jsFiles) = $this->getAssetFiles();
return $this->filterAssetArray(array(
$this->createAsseticCollection($cssFiles),
$this->createAsseticCollection($jsFiles)
), $type);
}
/**
* 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);
}
/**
* Write all CSS assets to standard output or in a file
*
* @param string $targetFilename
*/
public function dumpCssAssets($targetFilename = null)
{
$this->dumpAssets($this->getAssets('css'), $targetFilename);
}
/**
* Write all JS assets to standard output or in a file
*
* @param string $targetFilename
*/
public function dumpJsAssets($targetFilename = null)
{
$this->dumpAssets($this->getAssets('js'), $targetFilename);
}
/**
* Write assets to standard output or in a file
*
* @param array $files
* @param string $targetFilename
*/
protected function dumpAssets($files, $targetFilename = null)
{
$content = '';
foreach ($files as $file) {
$content .= file_get_contents($file) . "\n";
}
if ($targetFilename !== null) {
file_put_contents($targetFilename, $content);
} else {
echo $content;
}
}
/**
* 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|array $uri
* @param string $root
* @return string
*/
protected function makeUriRelativeTo($uri, $root)
{
if (is_array($uri)) {
$uris = array();
foreach ($uri as $u) {
$uris[] = $this->makeUriRelativeTo($u, $root);
}
return $uris;
}
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, \"%s\");\n",
$this->variableName,
json_encode($this->debugBar->getData()),
$this->debugBar->getCurrentRequestId());
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);
}
if ($this->openHandlerUrl !== null) {
$js .= sprintf("%s.setOpenHandler(new %s(%s));\n", $this->variableName,
$this->openHandlerClass,
json_encode(array("url" => $this->openHandlerUrl)));
}
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();
$excludedOptions = array('indicator', 'tab', 'map', 'default', 'widget');
// finds controls provided by collectors
$widgets = array();
foreach ($this->debugBar->getCollectors() as $collector) {
if (($collector instanceof Renderable) && !in_array($collector->getName(), $this->ignoredCollectors)) {
if ($w = $collector->getWidgets()) {
$widgets = array_merge($widgets, $w);
}
}
}
$controls = array_merge($widgets, $this->controls);
foreach (array_filter($controls) as $name => $options) {
$opts = array_diff_key($options, array_flip($excludedOptions));
if (isset($options['tab']) || isset($options['widget'])) {
if (!isset($opts['title'])) {
$opts['title'] = ucfirst(str_replace('_', ' ', $name));
}
$js .= sprintf("%s.addTab(\"%s\", new %s({%s%s}));\n",
$varname,
$name,
isset($options['tab']) ? $options['tab'] : 'PhpDebugBar.DebugBar.Tab',
substr(json_encode($opts, JSON_FORCE_OBJECT), 1, -1),
isset($options['widget']) ? sprintf('%s"widget": new %s()', count($opts) ? ', ' : '', $options['widget']) : ''
);
} else if (isset($options['indicator']) || isset($options['icon'])) {
$js .= sprintf("%s.addIndicator(\"%s\", new %s(%s));\n",
$varname,
$name,
isset($options['indicator']) ? $options['indicator'] : 'PhpDebugBar.DebugBar.Indicator',
json_encode($opts, JSON_FORCE_OBJECT)
);
}
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 restoration
$js .= sprintf("%s.restoreState();\n", $varname);
return $js;
}
}

View File

@@ -0,0 +1,103 @@
<?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;
/**
* Handler to list and open saved dataset
*/
class OpenHandler
{
protected $debugBar;
/**
* @param DebugBar $debugBar
*/
public function __construct(DebugBar $debugBar)
{
if (!$debugBar->isDataPersisted()) {
throw new DebugBarException("DebugBar must have a storage backend to use OpenHandler");
}
$this->debugBar = $debugBar;
}
/**
* Handles the current request
*
* @param array $request Request data
*/
public function handle(array $request = null, $echo = true, $sendHeader = true)
{
if ($request === null) {
$request = $_REQUEST;
}
if (!isset($request['op'])) {
$request['op'] = 'find';
}
if (!in_array($request['op'], array('find', 'get', 'clear'))) {
throw new DebugBarException("Invalid operation '{$request['op']}'");
}
if ($sendHeader) {
header('Content-Type: application/json');
}
$response = json_encode(call_user_func(array($this, $request['op']), $request));
if ($echo) {
echo $response;
}
return $response;
}
/**
* Find operation
*/
protected function find(array $request)
{
$max = 20;
if (isset($request['max'])) {
$max = $request['max'];
}
$offset = 0;
if (isset($request['offset'])) {
$offset = $request['offset'];
}
$filters = array();
foreach (array('utime', 'datetime', 'ip', 'uri') as $key) {
if (isset($request[$key])) {
$filters[$key] = $request[$key];
}
}
return $this->debugBar->getStorage()->find($filters, $max, $offset);
}
/**
* Get operation
*/
protected function get(array $request)
{
if (!isset($request['id'])) {
throw new DebugBarException("Missing 'id' parameter in 'get' operation");
}
return $this->debugBar->getStorage()->get($request['id']);
}
/**
* Clear operation
*/
protected function clear(array $request)
{
$this->debugBar->getStorage()->clear();
return array('success' => true);
}
}

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;
/**
* Request id generator based on the $_SERVER array
*/
class RequestIdGenerator implements RequestIdGeneratorInterface
{
/**
* {@inheritDoc}
*/
public function generate()
{
return md5(serialize($_SERVER) . microtime());
}
}

View File

@@ -0,0 +1,21 @@
<?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;
interface RequestIdGeneratorInterface
{
/**
* Generates a unique id for the current request
*
* @return string
*/
function generate();
}

View File

@@ -0,0 +1,138 @@
div.phpdebugbar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
border-top: 1px solid #ccc;
font-family: arial;
background: #fff;
z-index: 10000;
font-size: 14px;
}
/* -------------------------------------- */
div.phpdebugbar-header {
background: #efefef url(php-icon.png) no-repeat 5px 4px;
padding-left: 29px;
min-height: 26px;
line-height: 16px;
}
div.phpdebugbar-header:before, div.phpdebugbar-header:after {
display: table;
line-height: 0;
content: "";
}
div.phpdebugbar-header:after {
clear: both;
}
/* -------------------------------------- */
a.phpdebugbar-tab,
span.phpdebugbar-indicator,
a.phpdebugbar-indicator,
a.phpdebugbar-open-btn,
a.phpdebugbar-minimize-btn {
float: left;
padding: 5px 8px;
font-size: 14px;
color: #555;
text-decoration: none;
}
span.phpdebugbar-indicator,
a.phpdebugbar-indicator,
a.phpdebugbar-open-btn,
a.phpdebugbar-minimize-btn {
float: right;
border-right: 1px solid #ddd;
}
a.phpdebugbar-tab.active {
background: #ccc;
color: #444;
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: -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: -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)));
}
a.phpdebugbar-tab span.badge {
display: none;
margin-left: 5px;
font-size: 11px;
padding: 1px 6px;
background: #ccc;
border-radius: 4px;
color: #555;
font-weight: normal;
text-shadow: none;
}
a.phpdebugbar-tab span.badge.important {
background: #ed6868;
color: white;
}
a.phpdebugbar-minimize-btn {
display: none;
}
.phpdebugbar-indicator {
position: relative;
}
.phpdebugbar-indicator span.text {
margin-left: 5px;
}
.phpdebugbar-indicator span.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 span.tooltip:not(.disabled) {
display: block;
}
select.phpdebugbar-datasets-switcher {
float: right;
display: none;
margin: 2px 0 0 7px;
}
/* -------------------------------------- */
div.phpdebugbar-body {
border-top: 1px solid #ccc;
display: none;
position: relative;
height: 300px;
}
div.phpdebugbar-resize-handle {
display: none;
height: 4px;
width: 100%;
background: #fff;
border-top: 1px solid #ccc;
cursor: move;
position: absolute;
top: -33px;
}
/* -------------------------------------- */
div.phpdebugbar-panel {
display: none;
height: 100%;
overflow: auto;
width: 100%;
}
div.phpdebugbar-panel.active {
display: block;
}

View File

@@ -0,0 +1,732 @@
if (typeof(PhpDebugBar) == 'undefined') {
// namespace
var PhpDebugBar = {};
}
if (typeof(localStorage) == 'undefined') {
// provide mock localStorage object for dumb browsers
localStorage = {
setItem: function(key, value) {},
getItem: function(key) { return null; }
};
}
(function($) {
/**
* Returns the value from an object property.
* Using dots in the key, it is possible to retrieve 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('.');
for (var i = 0; i < parts.length; i++) {
if (!d[parts[i]]) {
return default_value;
}
d = d[parts[i]];
}
return d;
}
// ------------------------------------------------------------------
/**
* Base class for all elements with a visual component
*
* @param {Object} options
* @constructor
*/
var Widget = PhpDebugBar.Widget = function(options) {
this._attributes = $.extend({}, this.defaults);
this._boundAttributes = {};
this.$el = $('<' + this.tagName + ' />');
if (this.className) {
this.$el.addClass(this.className);
}
this.initialize.apply(this, [options || {}]);
this.render.apply(this);
};
$.extend(Widget.prototype, {
tagName: 'div',
className: null,
defaults: {},
/**
* Called after the constructor
*
* @param {Object} options
*/
initialize: function(options) {
this.set(options);
},
/**
* Called after the constructor to render the element
*/
render: function() {},
/**
* Sets the value of an attribute
*
* @param {String} attr Can also be an object to set multiple attributes at once
* @param {Object} value
*/
set: function(attr, value) {
if (typeof(attr) != 'string') {
for (var k in attr) {
this.set(k, attr[k]);
}
return;
}
this._attributes[attr] = value;
if (typeof(this._boundAttributes[attr]) !== 'undefined') {
for (var i = 0, c = this._boundAttributes[attr].length; i < c; i++) {
this._boundAttributes[attr][i].apply(this, [value]);
}
}
},
/**
* Checks if an attribute exists and is not null
*
* @param {String} attr
* @return {[type]} [description]
*/
has: function(attr) {
return typeof(this._attributes[attr]) !== 'undefined' && this._attributes[attr] !== null;
},
/**
* Returns the value of an attribute
*
* @param {String} attr
* @return {Object}
*/
get: function(attr) {
return this._attributes[attr];
},
/**
* Registers a callback function that will be called whenever the value of the attribute changes
*
* If cb is a jQuery element, text() will be used to fill the element
*
* @param {String} attr
* @param {Function} cb
*/
bindAttr: function(attr, cb) {
if ($.isArray(attr)) {
for (var i = 0, c = attr.length; i < c; i++) {
this.bindAttr(attr[i], cb);
}
return;
}
if (typeof(this._boundAttributes[attr]) == 'undefined') {
this._boundAttributes[attr] = [];
}
if (typeof(cb) == 'object') {
var el = cb;
cb = function(value) { el.text(value || ''); };
}
this._boundAttributes[attr].push(cb);
if (this.has(attr)) {
cb.apply(this, [this._attributes[attr]]);
}
}
});
/**
* Creates a subclass
*
* Code from Backbone.js
*
* @param {Array} props Prototype properties
* @return {Function}
*/
Widget.extend = function(props) {
var parent = this;
var child = function() { return parent.apply(this, arguments); };
$.extend(child, parent);
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate;
$.extend(child.prototype, props);
child.__super__ = parent.prototype;
return child;
};
// ------------------------------------------------------------------
/**
* 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.
*
* Options:
* - title
* - badge
* - widget
* - data: forward data to widget data
*/
var Tab = Widget.extend({
className: 'phpdebugbar-panel',
render: function() {
this.$tab = $('<a href="javascript:" class="phpdebugbar-tab" />');
this.bindAttr('title', $('<span class="text" />').appendTo(this.$tab));
this.$badge = $('<span class="badge" />').appendTo(this.$tab);
this.bindAttr('badge', function(value) {
if (value !== null) {
this.$badge.text(value);
this.$badge.show();
} else {
this.$badge.hide();
}
});
this.bindAttr('widget', function(widget) {
this.$el.empty().append(widget.$el);
});
this.bindAttr('data', function(data) {
if (this.has('widget')) {
this.get('widget').set('data', data);
}
})
}
});
// ------------------------------------------------------------------
/**
* Indicator
*
* An indicator is a text and an icon to display single value information
* right inside the always visible part of the debug bar
*
* Options:
* - icon
* - title
* - tooltip
* - position: "right" or "left"
* - data: alias of title
*/
var Indicator = Widget.extend({
tagName: 'span',
className: 'phpdebugbar-indicator',
defaults: {
position: "right"
},
render: function() {
this.bindAttr('position', function(pos) { this.$el.css('float', pos); });
this.$icon = $('<i />').appendTo(this.$el);
this.bindAttr('icon', function(icon) {
if (icon) {
this.$icon.attr('class', 'icon-' + icon);
} else {
this.$icon.attr('class', '');
}
});
this.bindAttr(['title', 'data'], $('<span class="text" />').appendTo(this.$el));
this.$tooltip = $('<span class="tooltip disabled" />').appendTo(this.$el);
this.bindAttr('tooltip', function(tooltip) {
if (tooltip) {
this.$tooltip.text(tooltip).removeClass('disabled');
} else {
this.$tooltip.addClass('disabled');
}
});
}
});
// ------------------------------------------------------------------
/**
* 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.
*/
var DebugBar = PhpDebugBar.DebugBar = Widget.extend({
className: "phpdebugbar",
options: {
bodyPaddingBottom: true
},
initialize: function() {
this.controls = {};
this.dataMap = {};
this.datasets = {};
this.firstTabName = null;
this.activePanelName = null;
},
/**
* Initialiazes the UI
*
* @this {DebugBar}
*/
render: function() {
var self = this;
this.$el.appendTo('body');
this.$header = $('<div class="phpdebugbar-header" />').appendTo(this.$el);
var $body = this.$body = $('<div class="phpdebugbar-body" />').appendTo(this.$el);
this.$resizehdle = $('<div class="phpdebugbar-resize-handle" />').appendTo(this.$body);
this.recomputeBottomOffset();
// dragging of resize handle
var dragging = false;
this.$resizehdle.on('mousedown', function(e) {
var orig_h = $body.height(), pos_y = e.pageY;
dragging = true;
$body.parents().on('mousemove', function(e) {
if (dragging) {
var h = orig_h + (pos_y - e.pageY);
$body.css('height', h);
localStorage.setItem('phpdebugbar-height', h);
self.recomputeBottomOffset();
}
}).on('mouseup', function() {
dragging = false;
});
e.preventDefault();
});
// minimize button
this.$minimizebtn = $('<a class="phpdebugbar-minimize-btn" href="javascript:"><i class="icon-remove"></i></a>').appendTo(this.$header);
this.$minimizebtn.click(function() {
self.minimize();
});
// open button
this.$openbtn = $('<a class="phpdebugbar-open-btn" href="javascript:"><i class="icon-folder-open"></i></a>').appendTo(this.$header).hide();
this.$openbtn.click(function() {
self.openHandler.show(function(id, dataset) {
self.addDataSet(dataset, id, id + " (opened)");
});
});
// select box for data sets
this.$datasets = $('<select class="phpdebugbar-datasets-switcher" />').appendTo(this.$header);
this.$datasets.change(function() {
self.dataChangeHandler(self.datasets[this.value]);
});
},
/**
* 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}
*/
restoreState: function() {
// bar height
var height = localStorage.getItem('phpdebugbar-height');
if (height) {
this.$body.css('height', height);
} else {
localStorage.setItem('phpdebugbar-height', this.$body.height());
}
// bar visibility
var visible = localStorage.getItem('phpdebugbar-visible');
if (visible && visible == '1') {
var tab = localStorage.getItem('phpdebugbar-tab');
if (this.isTab(tab)) {
this.showTab(tab);
}
}
},
/**
* 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}
*/
createTab: function(name, widget, title) {
var tab = new Tab({
title: title || (name.replace(/[_\-]/g, ' ').charAt(0).toUpperCase() + name.slice(1)),
widget: widget
});
return this.addTab(name, tab);
},
/**
* Adds a new tab
*
* @this {DebugBar}
* @param {String} name Internal name
* @param {Tab} tab Tab object
* @return {Tab}
*/
addTab: function(name, tab) {
if (this.isControl(name)) {
throw new Error(name + ' already exists');
}
var self = this;
tab.$tab.appendTo(this.$header).click(function() { self.showTab(name); });
tab.$el.appendTo(this.$body);
this.controls[name] = tab;
if (this.firstTabName == null) {
this.firstTabName = name;
}
return tab;
},
/**
* 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}
*/
createIndicator: function(name, icon, tooltip, position) {
var indicator = new Indicator({
icon: icon,
tooltip: tooltip,
position: position || 'right'
});
return this.addIndicator(name, indicator);
},
/**
* Adds an indicator
*
* @this {DebugBar}
* @param {String} name Internal name
* @param {Indicator} indicator Indicator object
* @return {Indicator}
*/
addIndicator: function(name, indicator) {
if (this.isControl(name)) {
throw new Error(name + ' already exists');
}
if (indicator.get('position') == 'right') {
indicator.$el.appendTo(this.$header);
} else {
indicator.$el.insertBefore(this.$header.children().first())
}
this.controls[name] = indicator;
return indicator;
},
/**
* Returns a control
*
* @param {String} name
* @return {Object}
*/
getControl: function(name) {
if (this.isControl(name)) {
return this.controls[name];
}
},
/**
* Checks if there's a control under the specified name
*
* @this {DebugBar}
* @param {String} name
* @return {Boolean}
*/
isControl: function(name) {
return typeof(this.controls[name]) != 'undefined';
},
/**
* Checks if a tab with the specified name exists
*
* @this {DebugBar}
* @param {String} name
* @return {Boolean}
*/
isTab: function(name) {
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}
*/
isIndicator: function(name) {
return this.isControl(name) && this.controls[name] instanceof Indicator;
},
/**
* Removes all tabs and indicators from the debug bar and hides it
*
* @this {DebugBar}
*/
reset: function() {
this.minimize();
var self = this;
$.each(this.controls, function(name, control) {
if (self.isTab(name)) {
control.$tab.remove();
}
control.$el.remove();
});
this.controls = {};
},
/**
* Open the debug bar and display the specified tab
*
* @this {DebugBar}
* @param {String} name If not specified, display the first tab
*/
showTab: function(name) {
if (!name) {
if (this.activePanelName) {
name = this.activePanelName;
} else {
name = this.firstTabName;
}
}
if (!this.isTab(name)) {
throw new Error("Unknown tab '" + name + "'");
}
this.$resizehdle.show();
this.$body.show();
this.$minimizebtn.show();
this.recomputeBottomOffset();
$(this.$header).find('> .active').removeClass('active');
$(this.$body).find('> .active').removeClass('active');
this.controls[name].$tab.addClass('active');
this.controls[name].$el.addClass('active');
this.activePanelName = name;
localStorage.setItem('phpdebugbar-visible', '1');
localStorage.setItem('phpdebugbar-tab', name);
},
/**
* Hide panels and "close" the debug bar
*
* @this {DebugBar}
*/
minimize: function() {
this.$header.find('> .active').removeClass('active');
this.$body.hide();
this.$minimizebtn.hide();
this.$resizehdle.hide();
this.recomputeBottomOffset();
localStorage.setItem('phpdebugbar-visible', '0');
},
/**
* Recomputes the padding-bottom css property of the body so
* that the debug bar never hides any content
*/
recomputeBottomOffset: function() {
if (this.options.bodyPaddingBottom) {
$('body').css('padding-bottom', this.$el.height());
}
},
/**
* Sets the data map used by dataChangeHandler to populate
* 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
*/
setDataMap: function(map) {
this.dataMap = map;
},
/**
* Same as setDataMap() but appends to the existing map
* rather than replacing it
*
* @this {DebugBar}
* @param {Object} map
*/
addDataMap: function(map) {
$.extend(this.dataMap, map);
},
/**
* Resets datasets and add one set of data
*
* For this method to be usefull, you need to specify
* a dataMap using setDataMap()
*
* @this {DebugBar}
* @param {Object} data
* @return {String} Dataset's id
*/
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
* @param {String} label
* @return {String} Dataset's id
*/
addDataSet: function(data, id, label) {
id = id || ("Request #" + (Object.keys(this.datasets).length + 1));
label = label || id;
this.datasets[id] = data;
this.$datasets.append($('<option value="' + id + '">' + label + '</option>'));
if (Object.keys(this.datasets).length > 1) {
this.$datasets.show();
}
this.showDataSet(id);
return id;
},
/**
* Returns the data from a dataset
*
* @this {DebugBar}
* @param {String} id
* @return {Object}
*/
getDataSet: function(id) {
return this.datasets[id];
},
/**
* Switch the currently displayed dataset
*
* @this {DebugBar}
* @param {String} id
*/
showDataSet: function(id) {
this.dataChangeHandler(this.datasets[id]);
this.$datasets.val(id);
},
/**
* Called when the current dataset is modified.
*
* @this {DebugBar}
* @param {Object} data
*/
dataChangeHandler: function(data) {
var self = this;
$.each(this.dataMap, function(key, def) {
var d = getDictValue(data, def[0], def[1]);
if (key.indexOf(':') != -1) {
key = key.split(':');
self.getControl(key[0]).set(key[1], d);
} else {
self.getControl(key).set('data', d);
}
});
},
/**
* Sets the handler to open past dataset
*
* @this {DebugBar}
* @param {object} handler
*/
setOpenHandler: function(handler) {
this.openHandler = handler;
if (handler !== null) {
this.$openbtn.show();
} else {
this.$openbtn.hide();
}
},
/**
* Returns the handler to open past dataset
*
* @this {DebugBar}
* @return {object}
*/
getOpenHandler: function() {
return this.openHandler;
}
});
DebugBar.Tab = Tab;
DebugBar.Indicator = Indicator;
})(jQuery);

View File

@@ -0,0 +1,60 @@
div.phpdebugbar-openhandler-overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #000;
opacity: .3;
z-index: 20000;
}
div.phpdebugbar-openhandler {
position: absolute;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 70%;
height: 70%;
background: #fff;
border: 2px solid #888;
overflow: auto;
z-index: 20001;
font-family: arial;
font-size: 14px;
padding-bottom: 10px;
}
div.phpdebugbar-openhandler .header {
background: #efefef url(php-icon.png) no-repeat 5px 4px;
padding-left: 29px;
min-height: 26px;
line-height: 25px;
color: #555;
margin-bottom: 10px;
}
div.phpdebugbar-openhandler .header a {
font-size: 14px;
color: #555;
text-decoration: none;
float: right;
padding: 5px 8px;
}
div.phpdebugbar-openhandler table {
width: 100%;
font-size: 14px;
}
div.phpdebugbar-openhandler table td {
padding: 6px 3px;
text-align: center;
border-bottom: 1px solid #ddd;
}
div.phpdebugbar-openhandler .actions {
text-align: center;
padding: 7px 0;
}
div.phpdebugbar-openhandler .actions a {
margin: 0 10px;
color: #555;
}

View File

@@ -0,0 +1,122 @@
if (typeof(PhpDebugBar) == 'undefined') {
// namespace
var PhpDebugBar = {};
}
(function($) {
PhpDebugBar.OpenHandler = PhpDebugBar.Widget.extend({
className: 'phpdebugbar-openhandler',
defaults: {
items_per_page: 20
},
render: function() {
var self = this;
this.$el.appendTo('body').hide();
this.$closebtn = $('<a href="javascript:"><i class="icon-remove"></i></a>');
this.$table = $('<tbody />');
$('<div class="header">PHP DebugBar | Open</div>').append(this.$closebtn).appendTo(this.$el);
$('<table><thead><tr><th>ID</th><th>URL</th><th>Date</th><th>IP</th></tr></thead></table>').append(this.$table).appendTo(this.$el);
this.$actions = $('<div class="actions" />').appendTo(this.$el);
this.$closebtn.on('click', function() {
self.hide();
});
this.$loadmorebtn = $('<a href="javascript:">Load more</a>')
.appendTo(this.$actions)
.on('click', function() {
self.find(self.last_find_request, self.last_find_request.offset + self.get('items_per_page'), self.handleFind.bind(self));
});
this.$showonlycurrentbtn = $('<a href="javascript:">Show only current URL</a>')
.appendTo(this.$actions)
.on('click', function() {
self.$table.empty();
self.find({uri: window.location.pathname}, 0, self.handleFind.bind(self));
});
this.$showallbtn = $('<a href="javascript:">Show all</a>')
.appendTo(this.$actions)
.on('click', function() {
self.refresh();
});
this.$clearbtn = $('<a href="javascript:">Delete all</a>')
.appendTo(this.$actions)
.on('click', function() {
self.clear(function() {
self.hide();
});
});
this.$overlay = $('<div class="phpdebugbar-openhandler-overlay" />').hide().appendTo('body');
this.$overlay.on('click', function() {
self.hide();
});
},
refresh: function() {
this.$table.empty();
this.$loadmorebtn.show();
this.find({}, 0, this.handleFind.bind(this));
},
handleFind: function(data) {
var self = this;
$.each(data, function(i, meta) {
var a = $('<a href="javascript:" />')
.text(meta['id'])
.on('click', function(e) {
self.hide();
self.load(meta['id'], function(data) {
self.callback(meta['id'], data);
});
e.preventDefault();
});
$('<tr />')
.append($('<td />').append(a))
.append('<td>' + meta['uri'] + '</td>')
.append('<td>' + meta['datetime'] + '</td>')
.append('<td>' + meta['ip'] + '</td>')
.appendTo(self.$table);
});
if (data.length < this.get('items_per_page')) {
this.$loadmorebtn.hide();
}
},
show: function(callback) {
this.callback = callback;
this.$el.show();
this.$overlay.show();
this.refresh();
},
hide: function() {
this.$el.hide();
this.$overlay.hide();
},
find: function(filters, offset, callback) {
var data = $.extend({}, filters, {max: this.get('items_per_page'), offset: offset || 0});
this.last_find_request = data;
$.getJSON(this.get('url'), data, callback);
},
load: function(id, callback) {
$.getJSON(this.get('url'), {op: "get", id: id}, callback);
},
clear: function(callback) {
$.getJSON(this.get('url'), {op: "clear"}, callback);
}
});
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,292 @@
ul.phpdebugbar-widgets-list {
margin: 0;
padding: 0;
list-style: none;
font-family: monospace;
}
ul.phpdebugbar-widgets-list li.list-item {
padding: 3px 6px;
border-bottom: 1px solid #eee;
position: relative;
}
ul.phpdebugbar-widgets-list li.list-item:hover {
background: #fafafa;
}
/* -------------------------------------- */
div.phpdebugbar-widgets-messages {
position: relative;
height: 100%;
}
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-list {
padding-bottom: 20px;
}
div.phpdebugbar-widgets-messages li.list-item span.value.warning:before {
font-family: FontAwesome;
content: "\f071";
margin-right: 8px;
font-size: 11px;
color: #ecb03d;
}
div.phpdebugbar-widgets-messages li.list-item span.value.error {
color: red;
}
div.phpdebugbar-widgets-messages li.list-item span.value.error:before {
font-family: FontAwesome;
content: "\f057";
margin-right: 8px;
font-size: 11px;
color: red;
}
div.phpdebugbar-widgets-messages li.list-item span.collector,
div.phpdebugbar-widgets-messages li.list-item span.label {
float: right;
font-size: 12px;
padding: 2px 4px;
color: #888;
margin: 0 2px;
text-decoration: none;
text-shadow: none;
background: none;
font-weight: normal;
}
div.phpdebugbar-widgets-messages li.list-item span.collector {
color: #555;
font-style: italic;
}
div.phpdebugbar-widgets-messages div.toolbar {
position: fixed;
bottom: 0;
width: 100%;
background: #fff;
}
div.phpdebugbar-widgets-messages div.toolbar input {
border: 0;
margin: 0;
margin-left: 7px;
width: 50%;
box-shadow: none;
}
div.phpdebugbar-widgets-messages div.toolbar input:focus {
outline: none;
}
div.phpdebugbar-widgets-messages div.toolbar a.filter {
float: right;
font-size: 12px;
padding: 2px 4px;
background: #7cacd5;
margin: 0 2px;
border-radius: 4px;
color: #fff;
text-decoration: none;
}
div.phpdebugbar-widgets-messages div.toolbar a.filter.excluded {
background: #eee;
color: #888;
}
/* -------------------------------------- */
dl.phpdebugbar-widgets-kvlist {
margin: 0;
}
dl.phpdebugbar-widgets-kvlist dt {
float: left;
width: 140px;
padding: 5px;
border-top: 1px solid #eee;
font-weight: bold;
clear: both;
}
dl.phpdebugbar-widgets-kvlist dd {
margin-left: 150px;
padding: 5px;
border-top: 1px solid #eee;
cursor: pointer;
}
/* -------------------------------------- */
dl.phpdebugbar-widgets-varlist {
font-family: monospace;
}
/* -------------------------------------- */
ul.phpdebugbar-widgets-timeline {
margin: 0;
padding: 0;
list-style: none;
}
ul.phpdebugbar-widgets-timeline li {
height: 20px;
position: relative;
border-bottom: 1px solid #eee;
}
ul.phpdebugbar-widgets-timeline li:hover {
background: #fafafa;
}
ul.phpdebugbar-widgets-timeline li span.label {
position: absolute;
font-size: 12px;
font-family: monospace;
color: #555;
top: 4px;
left: 5px;
background: none;
text-shadow: none;
font-weight: normal;
}
ul.phpdebugbar-widgets-timeline li span.value {
display: block;
position: absolute;
height: 10px;
background: #3db9ec;
top: 5px;
border-radius: 2px;
}
/* -------------------------------------- */
div.phpdebugbar-widgets-exceptions li.list-item {
cursor: pointer;
}
div.phpdebugbar-widgets-exceptions li.list-item span.message {
display: block;
color: red;
}
div.phpdebugbar-widgets-exceptions li.list-item span.filename {
display: block;
font-style: italic;
color: #555;
}
div.phpdebugbar-widgets-exceptions li.list-item span.type {
display: block;
position: absolute;
right: 4px;
top: 4px;
font-weight: bold;
}
div.phpdebugbar-widgets-exceptions li.list-item span.file {
display: none;
margin: 10px;
padding: 5px;
border: 1px solid #ddd;
font-family: monospace;
}
/* -------------------------------------- */
div.phpdebugbar-widgets-sqlqueries .status {
font-family: monospace;
padding: 6px 6px;
border-bottom: 1px solid #ddd;
font-weight: bold;
color: #555;
background: #fafafa;
}
div.phpdebugbar-widgets-sqlqueries li.list-item.error {
color: red;
}
div.phpdebugbar-widgets-sqlqueries span.duration,
div.phpdebugbar-widgets-sqlqueries span.memory,
div.phpdebugbar-widgets-sqlqueries span.row-count,
div.phpdebugbar-widgets-sqlqueries span.stmt-id {
float: right;
margin-left: 8px;
color: #888;
}
div.phpdebugbar-widgets-sqlqueries div.status span.duration,
div.phpdebugbar-widgets-sqlqueries div.status span.memory,
div.phpdebugbar-widgets-sqlqueries div.status span.row-count,
div.phpdebugbar-widgets-sqlqueries div.status span.stmt-id {
color: #555;
}
div.phpdebugbar-widgets-sqlqueries span.duration:before,
div.phpdebugbar-widgets-sqlqueries span.memory:before,
div.phpdebugbar-widgets-sqlqueries span.row-count:before,
div.phpdebugbar-widgets-sqlqueries span.stmt-id:before {
font-family: FontAwesome;
margin-right: 4px;
font-size: 12px;
}
div.phpdebugbar-widgets-sqlqueries span.duration:before {
content: "\f017";
}
div.phpdebugbar-widgets-sqlqueries span.memory:before {
content: "\f085";
}
div.phpdebugbar-widgets-sqlqueries span.row-count:before {
content: "\f0ce";
}
div.phpdebugbar-widgets-sqlqueries span.stmt-id:before {
content: "\f08d";
}
div.phpdebugbar-widgets-sqlqueries table.params {
display: none;
width: 70%;
margin: 10px;
border: 1px solid #ddd;
font-family: monospace;
border-collapse: collapse;
}
div.phpdebugbar-widgets-sqlqueries table.params td {
border: 1px solid #ddd;
text-align: center;
}
div.phpdebugbar-widgets-sqlqueries table.params .name {
width: 20%;
font-weight: bold;
}
div.phpdebugbar-widgets-sqlqueries li.list-item span.error {
display: block;
font-weight: bold;
}
/* -------------------------------------- */
div.phpdebugbar-widgets-templates div.status {
font-family: monospace;
padding: 6px 6px;
border-bottom: 1px solid #ddd;
font-weight: bold;
color: #555;
background: #fafafa;
}
div.phpdebugbar-widgets-templates span.render_time {
float: right;
}
div.phpdebugbar-widgets-templates span.render_time:before {
content: "\f017";
font-family: FontAwesome;
font-size: 12px;
margin-right: 4px;
}
div.phpdebugbar-widgets-templates div.status span.render_time {
color: #555;
}
/* -------------------------------------- */
div.phpdebugbar-widgets-mails span.subject {
display: block;
}
div.phpdebugbar-widgets-mails li.list-item pre.headers {
display: none;
margin: 10px;
padding: 5px;
border: 1px solid #ddd;
font-family: monospace;
}

View File

@@ -0,0 +1,514 @@
if (typeof(PhpDebugBar) == 'undefined') {
// namespace
var PhpDebugBar = {};
}
(function($) {
/**
* @namespace
*/
PhpDebugBar.Widgets = {};
/**
* Replaces spaces with &nbsp; and line breaks with <br>
*
* @param {String} text
* @return {String}
*/
var htmlize = PhpDebugBar.Widgets.htmlize = function(text) {
return text.replace(/\n/g, '<br>').replace(/\s/g, "&nbsp;")
};
/**
* 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 = PhpDebugBar.Widgets.renderValue = function(value, prettify) {
if (typeof(value) !== 'string') {
if (prettify) {
return htmlize(JSON.stringify(value, undefined, 2));
}
return JSON.stringify(value);
}
return value;
};
// ------------------------------------------------------------------
// Generic widgets
// ------------------------------------------------------------------
/**
* Displays array element in a <ul> list
*
* Options:
* - data
* - itemRenderer: a function used to render list items (optional)
*/
var ListWidget = PhpDebugBar.Widgets.ListWidget = PhpDebugBar.Widget.extend({
tagName: 'ul',
className: 'phpdebugbar-widgets-list',
initialize: function(options) {
if (!options['itemRenderer']) {
options['itemRenderer'] = this.itemRenderer;
}
this.set(options);
},
render: function() {
this.bindAttr(['itemRenderer', 'data'], function() {
this.$el.empty();
if (!this.has('data')) {
return;
}
var data = this.get('data');
for (var i = 0; i < data.length; i++) {
var li = $('<li class="list-item" />').appendTo(this.$el);
this.get('itemRenderer')(li, data[i]);
}
});
},
/**
* Renders the content of a <li> element
*
* @param {jQuery} li The <li> element as a jQuery Object
* @param {Object} value An item from the data array
*/
itemRenderer: function(li, value) {
li.html(renderValue(value));
}
});
// ------------------------------------------------------------------
/**
* Displays object property/value paris in a <dl> list
*
* Options:
* - data
* - itemRenderer: a function used to render list items (optional)
*/
var KVListWidget = PhpDebugBar.Widgets.KVListWidget = ListWidget.extend({
tagName: 'dl',
className: 'phpdebugbar-widgets-kvlist',
render: function() {
this.bindAttr(['itemRenderer', 'data'], function() {
this.$el.empty();
if (!this.has('data')) {
return;
}
var self = this;
$.each(this.get('data'), function(key, value) {
var dt = $('<dt class="key" />').appendTo(self.$el);
var dd = $('<dd class="value" />').appendTo(self.$el);
self.get('itemRenderer')(dt, dd, key, value);
});
});
},
/**
* Renders the content of the <dt> and <dd> elements
*
* @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
*/
itemRenderer: function(dt, dd, key, value) {
dt.text(key);
dd.html(htmlize(value));
}
});
// ------------------------------------------------------------------
/**
* An extension of KVListWidget where the data represents a list
* of variables
*
* Options:
* - data
*/
var VariableListWidget = PhpDebugBar.Widgets.VariableListWidget = KVListWidget.extend({
className: 'phpdebugbar-widgets-kvlist phpdebugbar-widgets-varlist',
itemRenderer: function(dt, dd, key, value) {
dt.text(key);
var v = value;
if (v && 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');
}
});
}
});
// ------------------------------------------------------------------
/**
* Iframe widget
*
* Options:
* - data
*/
var IFrameWidget = PhpDebugBar.Widgets.IFrameWidget = PhpDebugBar.Widget.extend({
tagName: 'iframe',
className: 'phpdebugbar-widgets-iframe',
render: function() {
this.$el.attr({
seamless: "seamless",
border: "0",
width: "100%",
height: "100%"
});
this.bindAttr('data', function(url) { this.$el.attr('src', url); });
}
});
// ------------------------------------------------------------------
// Collector specific widgets
// ------------------------------------------------------------------
/**
* Widget for the MessagesCollector
*
* Uses ListWidget under the hood
*
* Options:
* - data
*/
var MessagesWidget = PhpDebugBar.Widgets.MessagesWidget = PhpDebugBar.Widget.extend({
className: 'phpdebugbar-widgets-messages',
render: function() {
var self = this;
this.$list = new ListWidget({ itemRenderer: function(li, value) {
var m = value.message;
if (m.length > 100) {
m = m.substr(0, 100) + "...";
}
var val = $('<span class="value" />').text(m).appendTo(li);
if (!value.is_string || value.message.length > 100) {
li.css('cursor', 'pointer').click(function() {
if (val.hasClass('pretty')) {
val.text(m).removeClass('pretty');
} else {
val.html(htmlize(value.message)).addClass('pretty');
}
});
}
if (value.label) {
val.addClass(value.label);
$('<span class="label" />').text(value.label).appendTo(li);
}
if (value.collector) {
$('<span class="collector" />').text(value.collector).appendTo(li);
}
}});
this.$list.$el.appendTo(this.$el);
this.$toolbar = $('<div class="toolbar"><i class="icon-search"></i></div>').appendTo(this.$el);
$('<input type="text" />')
.on('change', function() { self.set('search', this.value); })
.appendTo(this.$toolbar);
this.bindAttr('data', function(data) {
this.set({ exclude: [], search: '' });
this.$toolbar.find('.filter').remove();
var filters = [], self = this;
for (var i = 0; i < data.length; i++) {
if (!data[i].label || $.inArray(data[i].label, filters) > -1) {
continue;
}
filters.push(data[i].label);
$('<a class="filter" href="javascript:" />')
.text(data[i].label)
.attr('rel', data[i].label)
.on('click', function() { self.onFilterClick(this); })
.appendTo(this.$toolbar);
}
});
this.bindAttr(['exclude', 'search'], function() {
var data = this.get('data'),
exclude = this.get('exclude'),
search = this.get('search'),
fdata = [];
for (var i = 0; i < data.length; i++) {
if ((!data[i].label || $.inArray(data[i].label, exclude) === -1) && (!search || data[i].message.indexOf(search) > -1)) {
fdata.push(data[i]);
}
}
this.$list.set('data', fdata);
});
},
onFilterClick: function(el) {
$(el).toggleClass('excluded');
var excludedLabels = [];
this.$toolbar.find('.filter.excluded').each(function() {
excludedLabels.push(this.rel);
});
this.set('exclude', excludedLabels);
}
});
// ------------------------------------------------------------------
/**
* Widget for the TimeDataCollector
*
* Options:
* - data
*/
var TimelineWidget = PhpDebugBar.Widgets.TimelineWidget = PhpDebugBar.Widget.extend({
tagName: 'ul',
className: 'phpdebugbar-widgets-timeline',
render: function() {
this.bindAttr('data', function(data) {
this.$el.empty();
if (data.measures) {
for (var i = 0; i < data.measures.length; i++) {
var li = $('<li class="measure" />');
li.append($('<span class="label" />').text(data.measures[i].label + " (" + data.measures[i].duration_str + ")"));
li.append($('<span class="value" />').css({
left: Math.round(data.measures[i].relative_start * 100 / data.duration) + "%",
width: Math.round(data.measures[i].duration * 100 / data.duration) + "%"
}));
this.$el.append(li);
}
}
});
}
});
// ------------------------------------------------------------------
/**
* Widget for the displaying exceptions
*
* Options:
* - data
*/
var ExceptionsWidget = PhpDebugBar.Widgets.ExceptionsWidget = PhpDebugBar.Widget.extend({
className: 'phpdebugbar-widgets-exceptions',
render: function() {
this.$list = new ListWidget({ itemRenderer: function(li, e) {
$('<span class="message" />').text(e.message).appendTo(li);
if (e.file) {
$('<span class="filename" />').text(e.file + "#" + e.line).appendTo(li);
}
if (e.type) {
$('<span class="type" />').text(e.type).appendTo(li);
}
if (e.surrounding_lines) {
var file = $('<div class="file" />').html(htmlize(e.surrounding_lines.join(""))).appendTo(li);
li.click(function() {
if (file.is(':visible')) {
file.hide();
} else {
file.show();
}
});
}
}});
this.$list.$el.appendTo(this.$el);
this.bindAttr('data', function(data) {
this.$list.set('data', data);
if (data.length == 1) {
this.$list.$el.children().first().find('.file').show();
}
});
}
});
// ------------------------------------------------------------------
/**
* Widget for the displaying sql queries
*
* Options:
* - data
*/
var SQLQueriesWidget = PhpDebugBar.Widgets.SQLQueriesWidget = PhpDebugBar.Widget.extend({
className: 'phpdebugbar-widgets-sqlqueries',
render: function() {
this.$status = $('<div class="status" />').appendTo(this.$el);
this.$list = new ListWidget({ itemRenderer: function(li, stmt) {
$('<span class="sql" />').text(stmt.sql).appendTo(li);
if (stmt.duration_str) {
$('<span class="duration" title="Duration" />').text(stmt.duration_str).appendTo(li);
}
if (stmt.memory_str) {
$('<span class="memory" title="Peak memory usage" />').text(stmt.memory_str).appendTo(li);
}
if (typeof(stmt.is_success) != 'undefined' && !stmt.is_success) {
li.addClass('error');
li.append($('<span class="error" />').text("[" + stmt.error_code + "] " + stmt.error_message));
} else if (typeof(stmt.row_count) != 'undefined') {
$('<span class="row-count" title="Row count" />').text(stmt.row_count).appendTo(li);
}
if (typeof(stmt.stmt_id) != 'undefined' && stmt.stmt_id) {
$('<span class="stmt-id" title="Prepared statement ID" />').text(stmt.stmt_id).appendTo(li);
}
if (stmt.params && !$.isEmptyObject(stmt.params)) {
var table = '<table class="params"><tr><th colspan="2">Params</th></tr>';
for (var key in stmt.params) {
table += '<tr><td class="name">' + key + '</td><td class="value">' + stmt.params[key] + '</td></tr>';
}
table += '</table>';
table = $(table).appendTo(li);
li.css('cursor', 'pointer').click(function() {
if (table.is(':visible')) {
table.hide();
} else {
table.show();
}
});
}
}});
this.$list.$el.appendTo(this.$el);
this.bindAttr('data', function(data) {
this.$list.set('data', data.statements);
this.$status.empty();
var t = $('<span />').text(data.nb_statements + " statements were executed").appendTo(this.$status);
if (data.nb_failed_statements) {
t.append(", " + data.nb_failed_statements + " of which failed");
}
if (data.accumulated_duration_str) {
this.$status.append($('<span class="duration" title="Accumulated duration" />').text(data.accumulated_duration_str));
}
if (data.peak_memory_usage_str) {
this.$status.append($('<span class="memory" title="Peak memory usage" />').text(data.peak_memory_usage_str));
}
});
}
});
// ------------------------------------------------------------------
/**
* Widget for the displaying templates data
*
* Options:
* - data
*/
var TemplatesWidget = PhpDebugBar.Widgets.TemplatesWidget = PhpDebugBar.Widget.extend({
className: 'phpdebugbar-widgets-templates',
render: function() {
this.$status = $('<div class="status" />').appendTo(this.$el);
this.$list = new ListWidget({ itemRenderer: function(li, tpl) {
$('<span class="name" />').text(tpl.name).appendTo(li);
if (tpl.render_time_str) {
$('<span class="render_time" title="Render time" />').text(tpl.render_time_str).appendTo(li);
}
}});
this.$list.$el.appendTo(this.$el);
this.bindAttr('data', function(data) {
this.$list.set('data', data.templates);
this.$status.empty().append($('<span />').text(data.templates.length + " templates were rendered"));
if (data.accumulated_render_time_str) {
this.$status.append($('<span class="render_time" title="Accumulated render time" />').text(data.accumulated_render_time_str));
}
});
}
});
// ------------------------------------------------------------------
/**
* Widget for the displaying mails data
*
* Options:
* - data
*/
var MailsWidget = PhpDebugBar.Widgets.MailsWidget = PhpDebugBar.Widget.extend({
className: 'phpdebugbar-widgets-mails',
render: function() {
this.$list = new ListWidget({ itemRenderer: function(li, mail) {
$('<span class="subject" />').text(mail.subject).appendTo(li);
$('<span class="to" />').text(mail.to).appendTo(li);
if (mail.headers) {
var headers = $('<pre class="headers" />').appendTo(li);
$('<code />').text(mail.headers).appendTo(headers);
li.click(function() {
if (headers.is(':visible')) {
headers.hide();
} else {
headers.show();
}
});
}
}});
this.$list.$el.appendTo(this.$el);
this.bindAttr('data', function(data) {
this.$list.set('data', data);
});
}
});
})(jQuery);

View File

@@ -0,0 +1,37 @@
<?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\PhpInfoCollector;
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
*/
class StandardDebugBar extends DebugBar
{
/**
* {@inheritDoc}
*/
public function __construct()
{
$this->addCollector(new PhpInfoCollector());
$this->addCollector(new MessagesCollector());
$this->addCollector(new RequestDataCollector());
$this->addCollector(new TimeDataCollector());
$this->addCollector(new MemoryCollector());
$this->addCollector(new ExceptionsCollector());
}
}

View File

@@ -0,0 +1,80 @@
<?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\Storage;
/**
* Stores collected data into files
*/
class FileStorage implements StorageInterface
{
protected $dirname;
/**
* @param string $dirname Directories where to store files
*/
public function __construct($dirname)
{
$this->dirname = rtrim($dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
/**
* {@inheritDoc}
*/
public function save($id, $data)
{
file_put_contents($this->makeFilename($id), json_encode($data));
}
/**
* {@inheritDoc}
*/
public function get($id)
{
return json_decode(file_get_contents($this->makeFilename($id)), true);
}
/**
* {@inheritDoc}
*/
public function find(array $filters = array(), $max = 20, $offset = 0)
{
$results = array();
foreach (new \DirectoryIterator($this->dirname) as $file) {
if (substr($file->getFilename(), 0, 1) !== '.') {
$id = substr($file->getFilename(), 0, strpos($file->getFilename(), '.'));
$data = $this->get($id);
$meta = $data['__meta'];
unset($data);
if (array_keys(array_intersect($meta, $filters)) == array_keys($filters)) {
$results[] = $meta;
}
}
}
return array_slice($results, $offset, $max);
}
/**
* {@inheritDoc}
*/
public function clear()
{
foreach (new \DirectoryIterator($this->dirname) as $file) {
if (substr($file->getFilename(), 0, 1) !== '.') {
unlink($file->getPathname());
}
}
}
public function makeFilename($id)
{
return $this->dirname . "$id.json";
}
}

View File

@@ -0,0 +1,45 @@
<?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\Storage;
interface StorageInterface
{
/**
* Saves collected data
*
* @param string $id
* @param string $data
*/
function save($id, $data);
/**
* Returns collected data with the specified id
*
* @param string $id
* @return array
*/
function get($id);
/**
* Returns a metadata about collected data
*
* @param array $filters
* @param integer $max
* @param integer $offset
* @return array
*/
function find(array $filters = array(), $max = 20, $offset = 0);
/**
* Clears all the collected data
*/
function clear();
}

View File

@@ -0,0 +1,58 @@
<?php
namespace DebugBar\Tests\DataCollector;
use DebugBar\Tests\DebugBarTestCase;
use DebugBar\DataCollector\AggregatedCollector;
class AggregatedCollectorTest extends DebugBarTestCase
{
public function setUp()
{
$this->c = new AggregatedCollector('test');
}
public function testAddCollector()
{
$this->c->addCollector($c = new MockCollector());
$this->assertContains($c, $this->c->getCollectors());
$this->assertEquals($c, $this->c['mock']);
$this->assertTrue(isset($this->c['mock']));
}
public function testCollect()
{
$this->c->addCollector(new MockCollector(array('foo' => 'bar'), 'm1'));
$this->c->addCollector(new MockCollector(array('bar' => 'foo'), 'm2'));
$data = $this->c->collect();
$this->assertCount(2, $data);
$this->assertArrayHasKey('foo', $data);
$this->assertEquals('bar', $data['foo']);
$this->assertArrayHasKey('bar', $data);
$this->assertEquals('foo', $data['bar']);
}
public function testMergeProperty()
{
$this->c->addCollector(new MockCollector(array('foo' => array('a' => 'b')), 'm1'));
$this->c->addCollector(new MockCollector(array('foo' => array('c' => 'd')), 'm2'));
$this->c->setMergeProperty('foo');
$data = $this->c->collect();
$this->assertCount(2, $data);
$this->assertArrayHasKey('a', $data);
$this->assertEquals('b', $data['a']);
$this->assertArrayHasKey('c', $data);
$this->assertEquals('d', $data['c']);
}
public function testSort()
{
$this->c->addCollector(new MockCollector(array(array('foo' => 2, 'id' => 1)), 'm1'));
$this->c->addCollector(new MockCollector(array(array('foo' => 1, 'id' => 2)), 'm2'));
$this->c->setSort('foo');
$data = $this->c->collect();
$this->assertCount(2, $data);
$this->assertEquals(2, $data[0]['id']);
$this->assertEquals(1, $data[1]['id']);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace DebugBar\Tests\DataCollector;
use DebugBar\Tests\DebugBarTestCase;
use DebugBar\DebugBar;
use DebugBar\DataCollector\ConfigCollector;
class ConfigCollectorTest extends DebugBarTestCase
{
public function testCollect()
{
$c = new ConfigCollector(array('s' => 'bar', 'a' => array(), 'o' => new \stdClass()));
$data = $c->collect();
$this->assertArrayHasKey('s', $data);
$this->assertEquals('bar', $data['s']);
$this->assertArrayHasKey('a', $data);
$this->assertEquals("Array\n(\n)\n", $data['a']);
$this->assertArrayHasKey('o', $data);
$this->assertEquals('Object(stdClass)', $data['o']);
}
public function testName()
{
$c = new ConfigCollector(array(), 'foo');
$this->assertEquals('foo', $c->getName());
$this->assertArrayHasKey('foo', $c->getWidgets());
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace DebugBar\Tests\DataCollector;
use DebugBar\Tests\DebugBarTestCase;
use DebugBar\DataCollector\MessagesCollector;
class MessagesCollectorTest extends DebugBarTestCase
{
public function testAddMessageAndLog()
{
$c = new MessagesCollector();
$c->addMessage('foobar');
$msgs = $c->getMessages();
$this->assertCount(1, $msgs);
$c->log('notice', 'hello');
$this->assertCount(2, $c->getMessages());
}
public function testAggregate()
{
$a = new MessagesCollector('a');
$c = new MessagesCollector('c');
$c->aggregate($a);
$c->addMessage('message from c');
$a->addMessage('message from a');
$msgs = $c->getMessages();
$this->assertCount(2, $msgs);
$this->assertArrayHasKey('collector', $msgs[1]);
$this->assertEquals('a', $msgs[1]['collector']);
}
public function testCollect()
{
$c = new MessagesCollector();
$c->addMessage('foo');
$data = $c->collect();
$this->assertEquals(1, $data['count']);
$this->assertEquals($c->getMessages(), $data['messages']);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace DebugBar\Tests\DataCollector;
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
class MockCollector extends DataCollector implements Renderable
{
protected $data;
protected $name;
protected $widgets;
public function __construct($data = array(), $name = 'mock', $widgets = array())
{
$this->data = $data;
$this->name = $name;
$this->widgets = $widgets;
}
public function collect()
{
return $this->data;
}
public function getName()
{
return $this->name;
}
public function getWidgets()
{
return $this->widgets;
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace DebugBar\Tests\DataCollector;
use DebugBar\Tests\DebugBarTestCase;
use DebugBar\DataCollector\TimeDataCollector;
class TimeDataCollectorTest extends DebugBarTestCase
{
public function setUp()
{
$this->s = microtime(true);
$this->c = new TimeDataCollector($this->s);
}
public function testAddMeasure()
{
$this->c->addMeasure('foo', $this->s, $this->s + 10);
$m = $this->c->getMeasures();
$this->assertCount(1, $m);
$this->assertEquals('foo', $m[0]['label']);
$this->assertEquals(10, $m[0]['duration']);
}
public function testStartStopMeasure()
{
$this->c->startMeasure('foo');
$this->c->stopMeasure('foo');
$m = $this->c->getMeasures();
$this->assertCount(1, $m);
$this->assertEquals('foo', $m[0]['label']);
$this->assertTrue($m[0]['start'] < $m[0]['end']);
}
public function testCollect()
{
$this->c->addMeasure('foo', 0, 10);
$this->c->addMeasure('bar', 10, 20);
$data = $this->c->collect();
$this->assertTrue($data['end'] > $this->s);
$this->assertTrue($data['duration'] > 0);
$this->assertCount(2, $data['measures']);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace DebugBar\Tests;
use DebugBar\DebugBar;
use DebugBar\Tests\DataCollector\MockCollector;
use DebugBar\Tests\Storage\MockStorage;
use DebugBar\RandomRequestIdGenerator;
class DebugBarTest extends DebugBarTestCase
{
public function testAddCollector()
{
$this->debugbar->addCollector($c = new MockCollector());
$this->assertTrue($this->debugbar->hasCollector('mock'));
$this->assertEquals($c, $this->debugbar->getCollector('mock'));
$this->assertContains($c, $this->debugbar->getCollectors());
}
/**
* @expectedException \DebugBar\DebugBarException
*/
public function testAddCollectorWithSameName()
{
$this->debugbar->addCollector(new MockCollector());
$this->debugbar->addCollector(new MockCollector());
}
public function testCollect()
{
$data = array('foo' => 'bar');
$this->debugbar->addCollector(new MockCollector($data));
$datac = $this->debugbar->collect();
$this->assertArrayHasKey('mock', $datac);
$this->assertEquals($datac['mock'], $data);
$this->assertEquals($datac, $this->debugbar->getData());
}
public function testArrayAccess()
{
$this->debugbar->addCollector($c = new MockCollector());
$this->assertEquals($c, $this->debugbar['mock']);
$this->assertTrue(isset($this->debugbar['mock']));
$this->assertFalse(isset($this->debugbar['foo']));
}
public function testStorage()
{
$s = new MockStorage();
$this->debugbar->setStorage($s);
$this->debugbar->addCollector(new MockCollector(array('foo')));
$data = $this->debugbar->collect();
$this->assertEquals($s->data[$this->debugbar->getCurrentRequestId()], $data);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace DebugBar\Tests;
use DebugBar\DebugBar;
use DebugBar\RandomRequestIdGenerator;
abstract class DebugBarTestCase extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->debugbar = new DebugBar();
}
public function assertJsonIsArray($json)
{
$data = json_decode($json);
$this->assertTrue(is_array($data));
}
public function assertJsonIsObject($json)
{
$data = json_decode($json);
$this->assertTrue(is_object($data));
}
public function assertJsonArrayNotEmpty($json)
{
$data = json_decode($json, true);
$this->assertTrue(is_array($data) && !empty($data));
}
public function assertJsonHasProperty($json, $property)
{
$data = json_decode($json, true);
$this->assertTrue(array_key_exists($property, $data));
}
public function assertJsonPropertyEquals($json, $property, $expected)
{
$data = json_decode($json, true);
$this->assertTrue(array_key_exists($property, $data));
$this->assertEquals($expected, $data[$property]);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace DebugBar\Tests;
use DebugBar\JavascriptRenderer;
use DebugBar\DebugBar;
use DebugBar\StandardDebugBar;
class JavascriptRendererTest extends DebugBarTestCase
{
public function setUp()
{
parent::setUp();
$this->r = new JavascriptRenderer($this->debugbar);
}
public function testOptions()
{
$this->r->setOptions(array(
'base_path' => '/foo',
'base_url' => '/foo',
'include_vendors' => false,
'javascript_class' => 'Foobar',
'variable_name' => 'foovar',
'initialization' => JavascriptRenderer::INITIALIZE_CONTROLS,
'controls' => array(
'memory' => array(
"icon" => "cogs",
"map" => "memory.peak_usage_str",
"default" => "'0B'"
)
),
'disable_controls' => array('messages'),
'ignore_collectors' => 'config'
));
$this->assertEquals('/foo', $this->r->getBasePath());
$this->assertEquals('/foo', $this->r->getBaseUrl());
$this->assertFalse($this->r->areVendorsIncluded());
$this->assertEquals('Foobar', $this->r->getJavascriptClass());
$this->assertEquals('foovar', $this->r->getVariableName());
$this->assertEquals(JavascriptRenderer::INITIALIZE_CONTROLS, $this->r->getInitialization());
$controls = $this->r->getControls();
$this->assertCount(2, $controls);
$this->assertArrayHasKey('memory', $controls);
$this->assertArrayHasKey('messages', $controls);
$this->assertNull($controls['messages']);
$this->assertContains('config', $this->r->getIgnoredCollectors());
}
public function testGetAssets()
{
$this->r->setBasePath('/foo');
list($css, $js) = $this->r->getAssets();
$this->assertContains('/foo/debugbar.css', $css);
$this->assertContains('/foo/widgets.js', $js);
$this->assertContains('/foo/vendor/jquery-1.8.3.min.js', $js);
$this->r->setIncludeVendors(false);
$js = $this->r->getAssets('js');
$this->assertContains('/foo/debugbar.js', $js);
$this->assertNotContains('/foo/vendor/jquery-1.8.3.min.js', $js);
}
public function testRenderHead()
{
$this->r->setBaseUrl('/foo');
$html = $this->r->renderHead();
$this->assertTag(array('tag' => 'script', 'attributes' => array('src' => '/foo/debugbar.js')), $html);
}
public function testRenderFullInitialization()
{
$this->debugbar->addCollector(new \DebugBar\DataCollector\MessagesCollector());
$this->r->addControl('time', array('icon' => 'time', 'map' => 'time', 'default' => '"0s"'));
$expected = file_get_contents(__DIR__ . '/full_init.html');
$this->assertStringStartsWith($expected, $this->r->render());
}
public function testRenderConstructorOnly()
{
$this->r->setInitialization(JavascriptRenderer::INITIALIZE_CONSTRUCTOR);
$this->r->setJavascriptClass('Foobar');
$this->r->setVariableName('foovar');
$this->assertStringStartsWith("<script type=\"text/javascript\">\nvar foovar = new Foobar();\nfoovar.addDataSet(", $this->r->render());
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace DebugBar\Tests;
use DebugBar\DebugBar;
use DebugBar\OpenHandler;
use DebugBar\Tests\Storage\MockStorage;
class OpenHandlerTest extends DebugBarTestCase
{
public function setUp()
{
parent::setUp();
$this->debugbar->setStorage(new MockStorage(array('foo' => array('__meta' => array('id' => 'foo')))));
$this->openHandler = new OpenHandler($this->debugbar);
}
public function testFind()
{
$request = array();
$result = $this->openHandler->handle($request, false, false);
$this->assertJsonArrayNotEmpty($result);
}
public function testGet()
{
$request = array('op' => 'get', 'id' => 'foo');
$result = $this->openHandler->handle($request, false, false);
$this->assertJsonIsObject($result);
$this->assertJsonHasProperty($result, '__meta');
$data = json_decode($result, true);
$this->assertEquals('foo', $data['__meta']['id']);
}
/**
* @expectedException \DebugBar\DebugBarException
*/
public function testGetMissingId()
{
$this->openHandler->handle(array('op' => 'get'), false, false);
}
public function testClear()
{
$result = $this->openHandler->handle(array('op' => 'clear'), false, false);
$this->assertJsonPropertyEquals($result, 'success', true);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace DebugBar\Tests\Storage;
use DebugBar\Tests\DebugBarTestCase;
use DebugBar\Storage\FileStorage;
class FileStorageTest extends DebugBarTestCase
{
public function setUp()
{
$this->dirname = '/tmp/debugbar';
if (!file_exists($this->dirname)) {
mkdir($this->dirname, 0777);
}
$this->s = new FileStorage($this->dirname);
$this->data = array('__meta' => array('id' => 'foo'));
}
public function testSave()
{
$this->s->save('foo', $this->data);
$this->assertFileExists($this->dirname . '/foo.json');
$this->assertJsonStringEqualsJsonFile($this->dirname . '/foo.json', json_encode($this->data));
}
public function testGet()
{
$data = $this->s->get('foo');
$this->assertEquals($this->data, $data);
}
public function testFind()
{
$results = $this->s->find();
$this->assertContains($this->data['__meta'], $results);
}
public function testClear()
{
$this->s->clear();
$this->assertFileNotExists($this->dirname . '/foo.json');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace DebugBar\Tests\Storage;
use DebugBar\Storage\StorageInterface;
class MockStorage implements StorageInterface
{
public $data;
public function __construct(array $data = array())
{
$this->data = $data;
}
public function save($id, $data)
{
$this->data[$id] = $data;
}
public function get($id)
{
return $this->data[$id];
}
public function find(array $filters = array(), $max = 20, $offset = 0)
{
return array_slice($this->data, $offset, $max);
}
public function clear()
{
$this->data = array();
}
}

Some files were not shown because too many files have changed in this diff Show More