Laravel 5.6 updates

Travis config update

Removed HHVM script as Laravel no longer support HHVM after releasing 5.3
This commit is contained in:
Manish Verma
2018-08-06 20:08:55 +05:30
parent 126fbb0255
commit 1ac0f42a58
2464 changed files with 65239 additions and 46734 deletions

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the DebugBar package.
*
* (c) 2017 Tim Riemenschneider
*
* 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\TimeDataCollector;
use Twig_Profiler_Profile;
/**
* Class TimeableTwigExtensionProfiler
*
* Extends Twig_Extension_Profiler to add rendering times to the TimeDataCollector
*
* @package DebugBar\Bridge\Twig
*/
class TimeableTwigExtensionProfiler extends \Twig_Extension_Profiler
{
/**
* @var \DebugBar\DataCollector\TimeDataCollector
*/
private $timeDataCollector;
/**
* @param \DebugBar\DataCollector\TimeDataCollector $timeDataCollector
*/
public function setTimeDataCollector(TimeDataCollector $timeDataCollector)
{
$this->timeDataCollector = $timeDataCollector;
}
public function __construct(\Twig_Profiler_Profile $profile, TimeDataCollector $timeDataCollector = null)
{
parent::__construct($profile);
$this->timeDataCollector = $timeDataCollector;
}
public function enter(Twig_Profiler_Profile $profile)
{
if ($this->timeDataCollector && $profile->isTemplate()) {
$this->timeDataCollector->startMeasure($profile->getName(), 'template ' . $profile->getName());
}
parent::enter($profile);
}
public function leave(Twig_Profiler_Profile $profile)
{
parent::leave($profile);
if ($this->timeDataCollector && $profile->isTemplate()) {
$this->timeDataCollector->stopMeasure($profile->getName());
}
}
}

View File

@@ -16,7 +16,7 @@ use Twig_TemplateInterface;
/**
* Wraps a Twig_Template to add profiling features
*/
class TraceableTwigTemplate implements Twig_TemplateInterface
class TraceableTwigTemplate extends Twig_Template implements Twig_TemplateInterface
{
protected $template;
@@ -35,6 +35,11 @@ class TraceableTwigTemplate implements Twig_TemplateInterface
return call_user_func_array(array($this->template, $name), $arguments);
}
public function doDisplay(array $context, array $blocks = array())
{
return $this->template->doDisplay($context, $blocks);
}
public function getTemplateName()
{
return $this->template->getTemplateName();

View File

@@ -0,0 +1,197 @@
<?php
/*
* This file is part of the DebugBar package.
*
* (c) 2017 Tim Riemenschneider
*
* 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\AssetProvider;
use DebugBar\DataCollector\DataCollector;
use DebugBar\DataCollector\Renderable;
/**
* Collects data about rendered templates
*
* http://twig.sensiolabs.org/
*
* A Twig_Extension_Profiler should be added to your Twig_Environment
* The root-Twig_Profiler_Profile-object should then be injected into this collector
*
* you can optionally provide the Twig_Environment or the Twig_Loader to also create
* debug-links.
*
* @see \Twig_Extension_Profiler, \Twig_Profiler_Profile
*
* <code>
* $env = new Twig_Environment($loader); // Or from a PSR11-container
* $profile = new Twig_Profiler_Profile();
* $env->addExtension(new Twig_Extension_Profile($profile));
* $debugbar->addCollector(new TwigProfileCollector($profile, $env));
* // or: $debugbar->addCollector(new TwigProfileCollector($profile, $loader));
* </code>
*/
class TwigProfileCollector extends DataCollector implements Renderable, AssetProvider
{
/**
* @var \Twig_Profiler_Profile
*/
private $profile;
/**
* @var \Twig_LoaderInterface
*/
private $loader;
/** @var int */
private $templateCount;
/** @var int */
private $blockCount;
/** @var int */
private $macroCount;
/**
* @var array[] {
* @var string $name
* @var int $render_time
* @var string $render_time_str
* @var string $memory_str
* @var string $xdebug_link
* }
*/
private $templates;
/**
* TwigProfileCollector constructor.
*
* @param \Twig_Profiler_Profile $profile
* @param \Twig_LoaderInterface|\Twig_Environment $loaderOrEnv
*/
public function __construct(\Twig_Profiler_Profile $profile, $loaderOrEnv = null)
{
$this->profile = $profile;
if ($loaderOrEnv instanceof \Twig_Environment) {
$loaderOrEnv = $loaderOrEnv->getLoader();
}
$this->loader = $loaderOrEnv;
}
/**
* Returns a hash where keys are control names and their values
* an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
*
* @return array
*/
public function getWidgets()
{
return array(
'twig' => array(
'icon' => 'leaf',
'widget' => 'PhpDebugBar.Widgets.TemplatesWidget',
'map' => 'twig',
'default' => json_encode(array('templates' => array())),
),
'twig:badge' => array(
'map' => 'twig.badge',
'default' => 0,
),
);
}
/**
* @return array
*/
public function getAssets()
{
return array(
'css' => 'widgets/templates/widget.css',
'js' => 'widgets/templates/widget.js',
);
}
/**
* Called by the DebugBar when data needs to be collected
*
* @return array Collected data
*/
public function collect()
{
$this->templateCount = $this->blockCount = $this->macroCount = 0;
$this->templates = array();
$this->computeData($this->profile);
return array(
'nb_templates' => $this->templateCount,
'nb_blocks' => $this->blockCount,
'nb_macros' => $this->macroCount,
'templates' => $this->templates,
'accumulated_render_time' => $this->profile->getDuration(),
'accumulated_render_time_str' => $this->getDataFormatter()->formatDuration($this->profile->getDuration()),
'memory_usage_str' => $this->getDataFormatter()->formatBytes($this->profile->getMemoryUsage()),
'callgraph' => $this->getHtmlCallGraph(),
'badge' => implode(
'/',
array(
$this->templateCount,
$this->blockCount,
$this->macroCount,
)
),
);
}
/**
* Returns the unique name of the collector
*
* @return string
*/
public function getName()
{
return 'twig';
}
public function getHtmlCallGraph()
{
$dumper = new \Twig_Profiler_Dumper_Html();
return $dumper->dump($this->profile);
}
/**
* Get an Xdebug Link to a file
*
* @return array {
* @var string url
* @var bool ajax
* }
*/
public function getXdebugLink($template, $line = 1)
{
if (is_null($this->loader)) {
return null;
}
$file = $this->loader->getSourceContext($template)->getPath();
return parent::getXdebugLink($file, $line);
}
private function computeData(\Twig_Profiler_Profile $profile)
{
$this->templateCount += ($profile->isTemplate() ? 1 : 0);
$this->blockCount += ($profile->isBlock() ? 1 : 0);
$this->macroCount += ($profile->isMacro() ? 1 : 0);
if ($profile->isTemplate()) {
$this->templates[] = array(
'name' => $profile->getName(),
'render_time' => $profile->getDuration(),
'render_time_str' => $this->getDataFormatter()->formatDuration($profile->getDuration()),
'memory_str' => $this->getDataFormatter()->formatBytes($profile->getMemoryUsage()),
'xdebug_link' => $this->getXdebugLink($profile->getTemplate()),
);
}
foreach ($profile as $p) {
$this->computeData($p);
}
}
}

View File

@@ -21,6 +21,21 @@ interface AssetProvider
* - base_url
* - css: an array of filenames
* - js: an array of filenames
* - inline_css: an array map of content ID to inline CSS content (not including <style> tag)
* - inline_js: an array map of content ID to inline JS content (not including <script> tag)
* - inline_head: an array map of content ID to arbitrary inline HTML content (typically
* <style>/<script> tags); it must be embedded within the <head> element
*
* All keys are optional.
*
* Ideally, you should store static assets in filenames that are returned via the normal css/js
* keys. However, the inline asset elements are useful when integrating with 3rd-party
* libraries that require static assets that are only available in an inline format.
*
* The inline content arrays require special string array keys: the caller of this function
* will use them to deduplicate content. This is particularly useful if multiple instances of
* the same asset provider are used. Inline assets from all collectors are merged together into
* the same array, so these content IDs effectively deduplicate the inline assets.
*
* @return array
*/

View File

@@ -13,12 +13,40 @@ namespace DebugBar\DataCollector;
/**
* Collects array data
*/
class ConfigCollector extends DataCollector implements Renderable
class ConfigCollector extends DataCollector implements Renderable, AssetProvider
{
protected $name;
protected $data;
// The HTML var dumper requires debug bar users to support the new inline assets, which not all
// may support yet - so return false by default for now.
protected $useHtmlVarDumper = false;
/**
* Sets a flag indicating whether the Symfony HtmlDumper will be used to dump variables for
* rich variable rendering.
*
* @param bool $value
* @return $this
*/
public function useHtmlVarDumper($value = true)
{
$this->useHtmlVarDumper = $value;
return $this;
}
/**
* Indicates whether the Symfony HtmlDumper will be used to dump variables for rich variable
* rendering.
*
* @return mixed
*/
public function isHtmlVarDumperUsed()
{
return $this->useHtmlVarDumper;
}
/**
* @param array $data
* @param string $name
@@ -46,7 +74,9 @@ class ConfigCollector extends DataCollector implements Renderable
{
$data = array();
foreach ($this->data as $k => $v) {
if (!is_string($v)) {
if ($this->isHtmlVarDumperUsed()) {
$v = $this->getVarDumper()->renderVar($v);
} else if (!is_string($v)) {
$v = $this->getDataFormatter()->formatVar($v);
}
$data[$k] = $v;
@@ -62,16 +92,26 @@ class ConfigCollector extends DataCollector implements Renderable
return $this->name;
}
/**
* @return array
*/
public function getAssets() {
return $this->isHtmlVarDumperUsed() ? $this->getVarDumper()->getAssets() : array();
}
/**
* @return array
*/
public function getWidgets()
{
$name = $this->getName();
$widget = $this->isHtmlVarDumperUsed()
? "PhpDebugBar.Widgets.HtmlVariableListWidget"
: "PhpDebugBar.Widgets.VariableListWidget";
return array(
"$name" => array(
"icon" => "gear",
"widget" => "PhpDebugBar.Widgets.VariableListWidget",
"widget" => $widget,
"map" => "$name",
"default" => "{}"
)

View File

@@ -12,6 +12,7 @@ namespace DebugBar\DataCollector;
use DebugBar\DataFormatter\DataFormatter;
use DebugBar\DataFormatter\DataFormatterInterface;
use DebugBar\DataFormatter\DebugBarVarDumper;
/**
* Abstract class for data collectors
@@ -19,8 +20,13 @@ use DebugBar\DataFormatter\DataFormatterInterface;
abstract class DataCollector implements DataCollectorInterface
{
private static $defaultDataFormatter;
private static $defaultVarDumper;
protected $dataFormater;
protected $varDumper;
protected $xdebugLinkTemplate = '';
protected $xdebugShouldUseAjax = false;
protected $xdebugReplacements = array();
/**
* Sets the default data formater instance used by all collectors subclassing this class
@@ -68,6 +74,78 @@ abstract class DataCollector implements DataCollectorInterface
return $this->dataFormater;
}
/**
* Get an Xdebug Link to a file
*
* @param string $file
* @param int $line
*
* @return array {
* @var string $url
* @var bool $ajax should be used to open the url instead of a normal links
* }
*/
public function getXdebugLink($file, $line = 1)
{
if (count($this->xdebugReplacements)) {
$file = strtr($file, $this->xdebugReplacements);
}
$url = strtr($this->getXdebugLinkTemplate(), ['%f' => $file, '%l' => $line]);
if ($url) {
return ['url' => $url, 'ajax' => $this->getXdebugShouldUseAjax()];
}
}
/**
* Sets the default variable dumper used by all collectors subclassing this class
*
* @param DebugBarVarDumper $varDumper
*/
public static function setDefaultVarDumper(DebugBarVarDumper $varDumper)
{
self::$defaultVarDumper = $varDumper;
}
/**
* Returns the default variable dumper
*
* @return DebugBarVarDumper
*/
public static function getDefaultVarDumper()
{
if (self::$defaultVarDumper === null) {
self::$defaultVarDumper = new DebugBarVarDumper();
}
return self::$defaultVarDumper;
}
/**
* Sets the variable dumper instance used by this collector
*
* @param DebugBarVarDumper $varDumper
* @return $this
*/
public function setVarDumper(DebugBarVarDumper $varDumper)
{
$this->varDumper = $varDumper;
return $this;
}
/**
* Gets the variable dumper instance used by this collector; note that collectors using this
* instance need to be sure to return the static assets provided by the variable dumper.
*
* @return DebugBarVarDumper
*/
public function getVarDumper()
{
if ($this->varDumper === null) {
$this->varDumper = self::getDefaultVarDumper();
}
return $this->varDumper;
}
/**
* @deprecated
*/
@@ -91,4 +169,66 @@ abstract class DataCollector implements DataCollectorInterface
{
return $this->getDataFormatter()->formatBytes($size, $precision);
}
/**
* @return string
*/
public function getXdebugLinkTemplate()
{
if (empty($this->xdebugLinkTemplate) && !empty(ini_get('xdebug.file_link_format'))) {
$this->xdebugLinkTemplate = ini_get('xdebug.file_link_format');
}
return $this->xdebugLinkTemplate;
}
/**
* @param string $xdebugLinkTemplate
* @param bool $shouldUseAjax
*/
public function setXdebugLinkTemplate($xdebugLinkTemplate, $shouldUseAjax = false)
{
if ($xdebugLinkTemplate === 'idea') {
$this->xdebugLinkTemplate = 'http://localhost:63342/api/file/?file=%f&line=%l';
$this->xdebugShouldUseAjax = true;
} else {
$this->xdebugLinkTemplate = $xdebugLinkTemplate;
$this->xdebugShouldUseAjax = $shouldUseAjax;
}
}
/**
* @return bool
*/
public function getXdebugShouldUseAjax()
{
return $this->xdebugShouldUseAjax;
}
/**
* returns an array of filename-replacements
*
* this is useful f.e. when using vagrant or remote servers,
* where the path of the file is different between server and
* development environment
*
* @return array key-value-pairs of replacements, key = path on server, value = replacement
*/
public function getXdebugReplacements()
{
return $this->xdebugReplacements;
}
/**
* @param array $xdebugReplacements
*/
public function setXdebugReplacements($xdebugReplacements)
{
$this->xdebugReplacements = $xdebugReplacements;
}
public function setXdebugReplacement($serverPath, $replacement)
{
$this->xdebugReplacements[$serverPath] = $replacement;
}
}

View File

@@ -108,7 +108,8 @@ class ExceptionsCollector extends DataCollector implements Renderable
'code' => $e->getCode(),
'file' => $filePath,
'line' => $e->getLine(),
'surrounding_lines' => $lines
'surrounding_lines' => $lines,
'xdebug_link' => $this->getXdebugLink($filePath, $e->getLine())
);
}

View File

@@ -15,8 +15,32 @@ namespace DebugBar\DataCollector;
*/
class MemoryCollector extends DataCollector implements Renderable
{
protected $realUsage = false;
protected $peakUsage = 0;
/**
* Returns whether total allocated memory page size is used instead of actual used memory size
* by the application. See $real_usage parameter on memory_get_peak_usage for details.
*
* @return bool
*/
public function getRealUsage()
{
return $this->realUsage;
}
/**
* Sets whether total allocated memory page size is used instead of actual used memory size
* by the application. See $real_usage parameter on memory_get_peak_usage for details.
*
* @param bool $realUsage
*/
public function setRealUsage($realUsage)
{
$this->realUsage = $realUsage;
}
/**
* Returns the peak memory usage
*
@@ -32,7 +56,7 @@ class MemoryCollector extends DataCollector implements Renderable
*/
public function updatePeakUsage()
{
$this->peakUsage = memory_get_peak_usage(true);
$this->peakUsage = memory_get_peak_usage($this->realUsage);
}
/**

View File

@@ -12,11 +12,12 @@ namespace DebugBar\DataCollector;
use Psr\Log\AbstractLogger;
use DebugBar\DataFormatter\DataFormatterInterface;
use DebugBar\DataFormatter\DebugBarVarDumper;
/**
* Provides a way to log messages
*/
class MessagesCollector extends AbstractLogger implements DataCollectorInterface, MessagesAggregateInterface, Renderable
class MessagesCollector extends AbstractLogger implements DataCollectorInterface, MessagesAggregateInterface, Renderable, AssetProvider
{
protected $name;
@@ -26,6 +27,12 @@ class MessagesCollector extends AbstractLogger implements DataCollectorInterface
protected $dataFormater;
protected $varDumper;
// The HTML var dumper requires debug bar users to support the new inline assets, which not all
// may support yet - so return false by default for now.
protected $useHtmlVarDumper = false;
/**
* @param string $name
*/
@@ -57,6 +64,56 @@ class MessagesCollector extends AbstractLogger implements DataCollectorInterface
return $this->dataFormater;
}
/**
* Sets the variable dumper instance used by this collector
*
* @param DebugBarVarDumper $varDumper
* @return $this
*/
public function setVarDumper(DebugBarVarDumper $varDumper)
{
$this->varDumper = $varDumper;
return $this;
}
/**
* Gets the variable dumper instance used by this collector
*
* @return DebugBarVarDumper
*/
public function getVarDumper()
{
if ($this->varDumper === null) {
$this->varDumper = DataCollector::getDefaultVarDumper();
}
return $this->varDumper;
}
/**
* Sets a flag indicating whether the Symfony HtmlDumper will be used to dump variables for
* rich variable rendering. Be sure to set this flag before logging any messages for the
* first time.
*
* @param bool $value
* @return $this
*/
public function useHtmlVarDumper($value = true)
{
$this->useHtmlVarDumper = $value;
return $this;
}
/**
* Indicates whether the Symfony HtmlDumper will be used to dump variables for rich variable
* rendering.
*
* @return mixed
*/
public function isHtmlVarDumperUsed()
{
return $this->useHtmlVarDumper;
}
/**
* Adds a message
*
@@ -67,12 +124,19 @@ class MessagesCollector extends AbstractLogger implements DataCollectorInterface
*/
public function addMessage($message, $label = 'info', $isString = true)
{
$messageText = $message;
$messageHtml = null;
if (!is_string($message)) {
$message = $this->getDataFormatter()->formatVar($message);
// Send both text and HTML representations; the text version is used for searches
$messageText = $this->getDataFormatter()->formatVar($message);
if ($this->isHtmlVarDumperUsed()) {
$messageHtml = $this->getVarDumper()->renderVar($message);
}
$isString = false;
}
$this->messages[] = array(
'message' => $message,
'message' => $messageText,
'message_html' => $messageHtml,
'is_string' => $isString,
'label' => $label,
'time' => microtime(true)
@@ -152,6 +216,13 @@ class MessagesCollector extends AbstractLogger implements DataCollectorInterface
return $this->name;
}
/**
* @return array
*/
public function getAssets() {
return $this->isHtmlVarDumperUsed() ? $this->getVarDumper()->getAssets() : array();
}
/**
* @return array
*/

View File

@@ -46,7 +46,7 @@ class TracedStatement
public function start($startTime = null, $startMemory = null)
{
$this->startTime = $startTime ?: microtime(true);
$this->startMemory = $startMemory ?: memory_get_usage(true);
$this->startMemory = $startMemory ?: memory_get_usage(false);
}
/**
@@ -59,7 +59,7 @@ class TracedStatement
{
$this->endTime = $endTime ?: microtime(true);
$this->duration = $this->endTime - $this->startTime;
$this->endMemory = $endMemory ?: memory_get_usage(true);
$this->endMemory = $endMemory ?: memory_get_usage(false);
$this->memoryDelta = $this->endMemory - $this->startMemory;
$this->exception = $exception;
$this->rowCount = $rowCount;
@@ -110,7 +110,7 @@ class TracedStatement
foreach ($this->parameters as $k => $v) {
$v = "$quoteLeft$v$quoteRight";
if (!is_numeric($k)) {
$sql = str_replace($k, $v, $sql);
$sql = preg_replace("/{$k}\b/", $v, $sql, 1);
} else {
$p = strpos($sql, '?');
$sql = substr($sql, 0, $p) . $v. substr($sql, $p + 1);

View File

@@ -13,8 +13,16 @@ namespace DebugBar\DataCollector;
/**
* Collects info about PHP
*/
class PhpInfoCollector extends DataCollector
class PhpInfoCollector extends DataCollector implements Renderable
{
/**
* @return string
*/
public function getName()
{
return 'php';
}
/**
* @return array
*/
@@ -27,10 +35,17 @@ class PhpInfoCollector extends DataCollector
}
/**
* @return string
* {@inheritDoc}
*/
public function getName()
public function getWidgets()
{
return 'php';
return array(
"php_version" => array(
"icon" => "code",
"tooltip" => "Version",
"map" => "php.version",
"default" => ""
),
);
}
}

View File

@@ -13,8 +13,36 @@ namespace DebugBar\DataCollector;
/**
* Collects info about the current request
*/
class RequestDataCollector extends DataCollector implements Renderable
class RequestDataCollector extends DataCollector implements Renderable, AssetProvider
{
// The HTML var dumper requires debug bar users to support the new inline assets, which not all
// may support yet - so return false by default for now.
protected $useHtmlVarDumper = false;
/**
* Sets a flag indicating whether the Symfony HtmlDumper will be used to dump variables for
* rich variable rendering.
*
* @param bool $value
* @return $this
*/
public function useHtmlVarDumper($value = true)
{
$this->useHtmlVarDumper = $value;
return $this;
}
/**
* Indicates whether the Symfony HtmlDumper will be used to dump variables for rich variable
* rendering.
*
* @return mixed
*/
public function isHtmlVarDumperUsed()
{
return $this->useHtmlVarDumper;
}
/**
* @return array
*/
@@ -25,7 +53,12 @@ class RequestDataCollector extends DataCollector implements Renderable
foreach ($vars as $var) {
if (isset($GLOBALS[$var])) {
$data["$" . $var] = $this->getDataFormatter()->formatVar($GLOBALS[$var]);
$key = "$" . $var;
if ($this->isHtmlVarDumperUsed()) {
$data[$key] = $this->getVarDumper()->renderVar($GLOBALS[$var]);
} else {
$data[$key] = $this->getDataFormatter()->formatVar($GLOBALS[$var]);
}
}
}
@@ -40,15 +73,25 @@ class RequestDataCollector extends DataCollector implements Renderable
return 'request';
}
/**
* @return array
*/
public function getAssets() {
return $this->isHtmlVarDumperUsed() ? $this->getVarDumper()->getAssets() : array();
}
/**
* @return array
*/
public function getWidgets()
{
$widget = $this->isHtmlVarDumperUsed()
? "PhpDebugBar.Widgets.HtmlVariableListWidget"
: "PhpDebugBar.Widgets.VariableListWidget";
return array(
"request" => array(
"icon" => "tags",
"widget" => "PhpDebugBar.Widgets.VariableListWidget",
"widget" => $widget,
"map" => "request",
"default" => "{}"
)

View File

@@ -50,7 +50,7 @@ class TimeDataCollector extends DataCollector implements Renderable
$requestStartTime = microtime(true);
}
}
$this->requestStartTime = $requestStartTime;
$this->requestStartTime = (float)$requestStartTime;
}
/**

View File

@@ -0,0 +1,315 @@
<?php
namespace DebugBar\DataFormatter;
use DebugBar\DataCollector\AssetProvider;
use DebugBar\DataFormatter\VarDumper\DebugBarHtmlDumper;
use DebugBar\DataFormatter\VarDumper\SeekingData;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/**
* Clones and renders variables in HTML format using the Symfony VarDumper component.
*
* Cloning is decoupled from rendering, so that dumper users can have the fastest possible cloning
* performance, while delaying rendering until it is actually needed.
*/
class DebugBarVarDumper implements AssetProvider
{
protected static $defaultClonerOptions = array();
protected static $defaultDumperOptions = array(
'expanded_depth' => 0,
'styles' => array(
// NOTE: 'default' CSS is also specified in debugbar.css
'default' => 'word-wrap: break-word; white-space: pre-wrap; word-break: normal',
'num' => 'font-weight:bold; color:#1299DA',
'const' => 'font-weight:bold',
'str' => 'font-weight:bold; color:#3A9B26',
'note' => 'color:#1299DA',
'ref' => 'color:#7B7B7B',
'public' => 'color:#000000',
'protected' => 'color:#000000',
'private' => 'color:#000000',
'meta' => 'color:#B729D9',
'key' => 'color:#3A9B26',
'index' => 'color:#1299DA',
'ellipsis' => 'color:#A0A000',
),
);
protected $clonerOptions;
protected $dumperOptions;
/** @var VarCloner */
protected $cloner;
/** @var DebugBarHtmlDumper */
protected $dumper;
/**
* Gets the VarCloner instance with configuration options set.
*
* @return VarCloner
*/
protected function getCloner()
{
if (!$this->cloner) {
$clonerOptions = $this->getClonerOptions();
if (isset($clonerOptions['casters'])) {
$this->cloner = new VarCloner($clonerOptions['casters']);
} else {
$this->cloner = new VarCloner();
}
if (isset($clonerOptions['additional_casters'])) {
$this->cloner->addCasters($clonerOptions['additional_casters']);
}
if (isset($clonerOptions['max_items'])) {
$this->cloner->setMaxItems($clonerOptions['max_items']);
}
if (isset($clonerOptions['max_string'])) {
$this->cloner->setMaxString($clonerOptions['max_string']);
}
// setMinDepth was added to Symfony 3.4:
if (isset($clonerOptions['min_depth']) && method_exists($this->cloner, 'setMinDepth')) {
$this->cloner->setMinDepth($clonerOptions['min_depth']);
}
}
return $this->cloner;
}
/**
* Gets the DebugBarHtmlDumper instance with configuration options set.
*
* @return DebugBarHtmlDumper
*/
protected function getDumper()
{
if (!$this->dumper) {
$this->dumper = new DebugBarHtmlDumper();
$dumperOptions = $this->getDumperOptions();
if (isset($dumperOptions['styles'])) {
$this->dumper->setStyles($dumperOptions['styles']);
}
}
return $this->dumper;
}
/**
* Gets the array of non-default VarCloner configuration options.
*
* @return array
*/
public function getClonerOptions()
{
if ($this->clonerOptions === null) {
$this->clonerOptions = self::$defaultClonerOptions;
}
return $this->clonerOptions;
}
/**
* Merges an array of non-default VarCloner configuration options with the existing non-default
* options.
*
* Configuration options are:
* - casters: a map of VarDumper Caster objects to use instead of the default casters.
* - additional_casters: a map of VarDumper Caster objects to use in addition to the default
* casters.
* - max_items: maximum number of items to clone beyond the minimum depth.
* - max_string: maximum string size
* - min_depth: minimum tree depth to clone before counting items against the max_items limit.
* (Requires Symfony 3.4; ignored on older versions.)
*
* @param array $options
*/
public function mergeClonerOptions($options)
{
$this->clonerOptions = $options + $this->getClonerOptions();
$this->cloner = null;
}
/**
* Resets the array of non-default VarCloner configuration options without retaining any of the
* existing non-default options.
*
* Configuration options are:
* - casters: a map of VarDumper Caster objects to use instead of the default casters.
* - additional_casters: a map of VarDumper Caster objects to use in addition to the default
* casters.
* - max_items: maximum number of items to clone beyond the minimum depth.
* - max_string: maximum string size
* - min_depth: minimum tree depth to clone before counting items against the max_items limit.
* (Requires Symfony 3.4; ignored on older versions.)
*
* @param array $options
*/
public function resetClonerOptions($options = null)
{
$this->clonerOptions = ($options ?: array()) + self::$defaultClonerOptions;
$this->cloner = null;
}
/**
* Gets the array of non-default HtmlDumper configuration options.
*
* @return array
*/
public function getDumperOptions()
{
if ($this->dumperOptions === null) {
$this->dumperOptions = self::$defaultDumperOptions;
}
return $this->dumperOptions;
}
/**
* Merges an array of non-default HtmlDumper configuration options with the existing non-default
* options.
*
* Configuration options are:
* - styles: a map of CSS styles to include in the assets, as documented in
* HtmlDumper::setStyles.
* - expanded_depth: the tree depth to initially expand.
* (Requires Symfony 3.2; ignored on older versions.)
* - max_string: maximum string size.
* (Requires Symfony 3.2; ignored on older versions.)
* - file_link_format: link format for files; %f expanded to file and %l expanded to line
* (Requires Symfony 3.2; ignored on older versions.)
*
* @param array $options
*/
public function mergeDumperOptions($options)
{
$this->dumperOptions = $options + $this->getDumperOptions();
$this->dumper = null;
}
/**
* Resets the array of non-default HtmlDumper configuration options without retaining any of the
* existing non-default options.
*
* Configuration options are:
* - styles: a map of CSS styles to include in the assets, as documented in
* HtmlDumper::setStyles.
* - expanded_depth: the tree depth to initially expand.
* (Requires Symfony 3.2; ignored on older versions.)
* - max_string: maximum string size.
* (Requires Symfony 3.2; ignored on older versions.)
* - file_link_format: link format for files; %f expanded to file and %l expanded to line
* (Requires Symfony 3.2; ignored on older versions.)
*
* @param array $options
*/
public function resetDumperOptions($options = null)
{
$this->dumperOptions = ($options ?: array()) + self::$defaultDumperOptions;
$this->dumper = null;
}
/**
* Captures the data from a variable and serializes it for later rendering.
*
* @param mixed $data The variable to capture.
* @return string Serialized variable data.
*/
public function captureVar($data)
{
return serialize($this->getCloner()->cloneVar($data));
}
/**
* Gets the display options for the HTML dumper.
*
* @return array
*/
protected function getDisplayOptions()
{
$displayOptions = array();
$dumperOptions = $this->getDumperOptions();
// Only used by Symfony 3.2 and newer:
if (isset($dumperOptions['expanded_depth'])) {
$displayOptions['maxDepth'] = $dumperOptions['expanded_depth'];
}
// Only used by Symfony 3.2 and newer:
if (isset($dumperOptions['max_string'])) {
$displayOptions['maxStringLength'] = $dumperOptions['max_string'];
}
// Only used by Symfony 3.2 and newer:
if (isset($dumperOptions['file_link_format'])) {
$displayOptions['fileLinkFormat'] = $dumperOptions['file_link_format'];
}
return $displayOptions;
}
/**
* Renders previously-captured data from captureVar to HTML and returns it as a string.
*
* @param string $capturedData Captured data from captureVar.
* @param array $seekPath Pass an array of keys to traverse if you only want to render a subset
* of the data.
* @return string HTML rendering of the variable.
*/
public function renderCapturedVar($capturedData, $seekPath = array())
{
$data = unserialize($capturedData);
// The seek method was added in Symfony 3.2; emulate the behavior via SeekingData for older
// Symfony versions.
if (!method_exists($data, 'seek')) {
$data = new SeekingData($data->getRawData());
}
foreach ($seekPath as $key) {
$data = $data->seek($key);
}
return $this->dump($data);
}
/**
* Captures and renders the data from a variable to HTML and returns it as a string.
*
* @param mixed $data The variable to capture and render.
* @return string HTML rendering of the variable.
*/
public function renderVar($data)
{
return $this->dump($this->getCloner()->cloneVar($data));
}
/**
* Returns assets required for rendering variables.
*
* @return array
*/
public function getAssets() {
$dumper = $this->getDumper();
$dumper->setDumpHeader(null); // this will cause the default dump header to regenerate
return array(
'inline_head' => array(
'html_var_dumper' => $dumper->getDumpHeaderByDebugBar(),
),
);
}
/**
* Helper function to dump a Data object to HTML.
*
* @param Data $data
* @return string
*/
protected function dump(Data $data)
{
$dumper = $this->getDumper();
$output = fopen('php://memory', 'r+b');
$dumper->setOutput($output);
$dumper->setDumpHeader(''); // we don't actually want a dump header
// NOTE: Symfony 3.2 added the third $extraDisplayOptions parameter. Older versions will
// safely ignore it.
$dumper->dump($data, null, $this->getDisplayOptions());
$result = stream_get_contents($output, -1, 0);
fclose($output);
return $result;
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace DebugBar\DataFormatter\VarDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/**
* We have to extend the base HtmlDumper class in order to get access to the protected-only
* getDumpHeader function.
*/
class DebugBarHtmlDumper extends HtmlDumper
{
public function getDumpHeaderByDebugBar() {
// getDumpHeader is protected:
return str_replace('pre.sf-dump', '.phpdebugbar pre.sf-dump', $this->getDumpHeader());
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace DebugBar\DataFormatter\VarDumper;
use Symfony\Component\VarDumper\Cloner\Cursor;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\DumperInterface;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* This class backports the seek() function from Symfony 3.2 to older versions - up to v2.6. The
* class should not be used with newer Symfony versions that provide the seek function, as it relies
* on a lot of undocumented functionality.
*/
class SeekingData extends Data
{
// Because the class copies/pastes the seek() implementation from Symfony 3.2, we reproduce its
// copyright here; this class is subject to the following additional copyright:
/*
* Copyright (c) 2014-2017 Fabien Potencier
*
* 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.
*/
private $position = 0;
private $key = 0;
/**
* Seeks to a specific key in nested data structures.
*
* @param string|int $key The key to seek to
*
* @return self|null A clone of $this of null if the key is not set
*/
public function seek($key)
{
$thisData = $this->getRawData();
$item = $thisData[$this->position][$this->key];
if (!$item instanceof Stub || !$item->position) {
return;
}
$keys = array($key);
switch ($item->type) {
case Stub::TYPE_OBJECT:
$keys[] = "\0+\0".$key;
$keys[] = "\0*\0".$key;
$keys[] = "\0~\0".$key;
$keys[] = "\0$item->class\0$key";
case Stub::TYPE_ARRAY:
case Stub::TYPE_RESOURCE:
break;
default:
return;
}
$data = null;
$children = $thisData[$item->position];
foreach ($keys as $key) {
if (isset($children[$key]) || array_key_exists($key, $children)) {
$data = clone $this;
$data->key = $key;
$data->position = $item->position;
break;
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function dump(DumperInterface $dumper)
{
// Override the base class dump to use the position and key
$refs = array(0);
$class = new \ReflectionClass($this);
$dumpItem = $class->getMethod('dumpItem');
$dumpItem->setAccessible(true);
$data = $this->getRawData();
$args = array($dumper, new Cursor(), &$refs, $data[$this->position][$this->key]);
$dumpItem->invokeArgs($this, $args);
}
}

View File

@@ -204,14 +204,33 @@ class DebugBar implements ArrayAccess
*/
public function collect()
{
$this->data = array(
'__meta' => array(
'id' => $this->getCurrentRequestId(),
'datetime' => date('Y-m-d H:i:s'),
'utime' => microtime(true),
if (php_sapi_name() === 'cli') {
$ip = gethostname();
if ($ip) {
$ip = gethostbyname($ip);
} else {
$ip = '127.0.0.1';
}
$request_variables = array(
'method' => 'CLI',
'uri' => isset($_SERVER['SCRIPT_FILENAME']) ? realpath($_SERVER['SCRIPT_FILENAME']) : null,
'ip' => $ip
);
} else {
$request_variables = array(
'method' => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null,
'uri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null,
'ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null
);
}
$this->data = array(
'__meta' => array_merge(
array(
'id' => $this->getCurrentRequestId(),
'datetime' => date('Y-m-d H:i:s'),
'utime' => microtime(true)
),
$request_variables
)
);

View File

@@ -74,6 +74,8 @@ class JavascriptRenderer
protected $ajaxHandlerBindToXHR = false;
protected $ajaxHandlerAutoShow = true;
protected $openHandlerClass = 'PhpDebugBar.OpenHandler';
protected $openHandlerUrl;
@@ -117,6 +119,7 @@ class JavascriptRenderer
* - ignore_collectors
* - ajax_handler_classname
* - ajax_handler_bind_to_jquery
* - ajax_handler_auto_show
* - open_handler_classname
* - open_handler_url
*
@@ -169,6 +172,9 @@ class JavascriptRenderer
if (array_key_exists('ajax_handler_bind_to_jquery', $options)) {
$this->setBindAjaxHandlerToJquery($options['ajax_handler_bind_to_jquery']);
}
if (array_key_exists('ajax_handler_auto_show', $options)) {
$this->setAjaxHandlerAutoShow($options['ajax_handler_auto_show']);
}
if (array_key_exists('open_handler_classname', $options)) {
$this->setOpenHandlerClass($options['open_handler_classname']);
}
@@ -513,6 +519,28 @@ class JavascriptRenderer
return $this->ajaxHandlerBindToXHR;
}
/**
* Sets whether new ajax debug data will be immediately shown. Setting to false could be useful
* if there are a lot of tracking events cluttering things.
*
* @param boolean $autoShow
*/
public function setAjaxHandlerAutoShow($autoShow = true)
{
$this->ajaxHandlerAutoShow = $autoShow;
return $this;
}
/**
* Checks whether the ajax handler will immediately show new ajax requests.
*
* @return boolean
*/
public function isAjaxHandlerAutoShow()
{
return $this->ajaxHandlerAutoShow;
}
/**
* Sets the class name of the js open handler
*
@@ -556,12 +584,13 @@ class JavascriptRenderer
}
/**
* Add assets to render in the head
* Add assets stored in files to render in the head
*
* @param array $cssFiles An array of filenames
* @param array $jsFiles An array of filenames
* @param string $basePath Base path of those files
* @param string $baseUrl Base url of those files
* @return $this
*/
public function addAssets($cssFiles, $jsFiles, $basePath = null, $baseUrl = null)
{
@@ -574,10 +603,37 @@ class JavascriptRenderer
return $this;
}
/**
* Add inline assets to render inline in the head. Ideally, you should store static assets in
* files that you add with the addAssets function. However, adding inline assets is useful when
* integrating with 3rd-party libraries that require static assets that are only available in an
* inline format.
*
* The inline content arrays require special string array keys: they are used to deduplicate
* content. This is particularly useful if multiple instances of the same asset end up being
* added. Inline assets from all collectors are merged together into the same array, so these
* content IDs effectively deduplicate the inline assets.
*
* @param array $inlineCss An array map of content ID to inline CSS content (not including <style> tag)
* @param array $inlineJs An array map of content ID to inline JS content (not including <script> tag)
* @param array $inlineHead An array map of content ID to arbitrary inline HTML content (typically
* <style>/<script> tags); it must be embedded within the <head> element
* @return $this
*/
public function addInlineAssets($inlineCss, $inlineJs, $inlineHead)
{
$this->additionalAssets[] = array(
'inline_css' => (array) $inlineCss,
'inline_js' => (array) $inlineJs,
'inline_head' => (array) $inlineHead
);
return $this;
}
/**
* Returns the list of asset files
*
* @param string $type Only return css or js files
* @param string $type 'css', 'js', 'inline_css', 'inline_js', 'inline_head', or null for all
* @param string $relativeTo The type of path to which filenames must be relative (path, url or null)
* @return array
*/
@@ -585,6 +641,9 @@ class JavascriptRenderer
{
$cssFiles = $this->cssFiles;
$jsFiles = $this->jsFiles;
$inlineCss = array();
$inlineJs = array();
$inlineHead = array();
if ($this->includeVendors !== false) {
if ($this->includeVendors === true || in_array('css', $this->includeVendors)) {
@@ -615,11 +674,29 @@ class JavascriptRenderer
$root = $this->getRelativeRoot($relativeTo,
$this->makeUriRelativeTo($basePath, $this->basePath),
$this->makeUriRelativeTo($baseUrl, $this->baseUrl));
$cssFiles = array_merge($cssFiles, $this->makeUriRelativeTo((array) $assets['css'], $root));
$jsFiles = array_merge($jsFiles, $this->makeUriRelativeTo((array) $assets['js'], $root));
if (isset($assets['css'])) {
$cssFiles = array_merge($cssFiles, $this->makeUriRelativeTo((array) $assets['css'], $root));
}
if (isset($assets['js'])) {
$jsFiles = array_merge($jsFiles, $this->makeUriRelativeTo((array) $assets['js'], $root));
}
if (isset($assets['inline_css'])) {
$inlineCss = array_merge($inlineCss, (array) $assets['inline_css']);
}
if (isset($assets['inline_js'])) {
$inlineJs = array_merge($inlineJs, (array) $assets['inline_js']);
}
if (isset($assets['inline_head'])) {
$inlineHead = array_merge($inlineHead, (array) $assets['inline_head']);
}
}
return $this->filterAssetArray(array($cssFiles, $jsFiles), $type);
// Deduplicate files
$cssFiles = array_unique($cssFiles);
$jsFiles = array_unique($jsFiles);
return $this->filterAssetArray(array($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead), $type);
}
/**
@@ -669,53 +746,64 @@ class JavascriptRenderer
}
/**
* Filters a tuple of (css, js) assets according to $type
* Filters a tuple of (css, js, inline_css, inline_js, inline_head) assets according to $type
*
* @param array $array
* @param string $type 'css', 'js' or null for both
* @param string $type 'css', 'js', 'inline_css', 'inline_js', 'inline_head', or null for all
* @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;
$types = array('css', 'js', 'inline_css', 'inline_js', 'inline_head');
$typeIndex = array_search(strtolower($type), $types);
return $typeIndex !== false ? $array[$typeIndex] : $array;
}
/**
* Returns a tuple where the both items are Assetic AssetCollection,
* the first one being css files and the second js files
* Returns an array where all items are Assetic AssetCollection:
* - The first one contains the CSS files
* - The second one contains the JS files
* - The third one contains arbitrary inline HTML (typically composed of <script>/<style>
* elements); it must be embedded within the <head> element
*
* @param string $type Only return css or js collection
* @return array or \Assetic\Asset\AssetCollection
* @param string $type Optionally return only 'css', 'js', or 'inline_head' collection
* @return array|\Assetic\Asset\AssetCollection
*/
public function getAsseticCollection($type = null)
{
list($cssFiles, $jsFiles) = $this->getAssets();
return $this->filterAssetArray(array(
$this->createAsseticCollection($cssFiles),
$this->createAsseticCollection($jsFiles)
), $type);
$types = array('css', 'js', 'inline_head');
$typeIndex = array_search(strtolower($type), $types);
list($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead) = $this->getAssets();
$collections = array(
$this->createAsseticCollection($cssFiles, $inlineCss),
$this->createAsseticCollection($jsFiles, $inlineJs),
$this->createAsseticCollection(null, $inlineHead)
);
return $typeIndex !== false ? $collections[$typeIndex] : $collections;
}
/**
* Create an Assetic AssetCollection with the given files.
* Create an Assetic AssetCollection with the given content.
* Filenames will be converted to absolute path using
* the base path.
*
* @param array $files
* @param array|null $files Array of asset filenames.
* @param array|null $content Array of inline asset content.
* @return \Assetic\Asset\AssetCollection
*/
protected function createAsseticCollection($files)
protected function createAsseticCollection($files = null, $content = null)
{
$assets = array();
foreach ($files as $file) {
$assets[] = new \Assetic\Asset\FileAsset($file);
if ($files) {
foreach ($files as $file) {
$assets[] = new \Assetic\Asset\FileAsset($file);
}
}
if ($content) {
foreach ($content as $item) {
$assets[] = new \Assetic\Asset\StringAsset($item);
}
}
return new \Assetic\Asset\AssetCollection($assets);
}
@@ -727,7 +815,7 @@ class JavascriptRenderer
*/
public function dumpCssAssets($targetFilename = null)
{
$this->dumpAssets($this->getAssets('css'), $targetFilename);
$this->dumpAssets($this->getAssets('css'), $this->getAssets('inline_css'), $targetFilename);
}
/**
@@ -737,29 +825,48 @@ class JavascriptRenderer
*/
public function dumpJsAssets($targetFilename = null)
{
$this->dumpAssets($this->getAssets('js'), $targetFilename, $this->useRequireJs);
$this->dumpAssets($this->getAssets('js'), $this->getAssets('inline_js'), $targetFilename, $this->useRequireJs);
}
/**
* Write all inline HTML header assets to standard output or in a file (only returns assets not
* already returned by dumpCssAssets or dumpJsAssets)
*
* @param string $targetFilename
*/
public function dumpHeadAssets($targetFilename = null)
{
$this->dumpAssets(null, $this->getAssets('inline_head'), $targetFilename);
}
/**
* Write assets to standard output or in a file
*
* @param array $files
* @param array|null $files Filenames containing assets
* @param array|null $content Inline content to dump
* @param string $targetFilename
* @param bool $useRequireJs
*/
protected function dumpAssets($files, $targetFilename = null, $useRequireJs = false)
protected function dumpAssets($files = null, $content = null, $targetFilename = null, $useRequireJs = false)
{
$content = '';
foreach ($files as $file) {
$content .= file_get_contents($file) . "\n";
$dumpedContent = '';
if ($files) {
foreach ($files as $file) {
$dumpedContent .= file_get_contents($file) . "\n";
}
}
if ($content) {
foreach ($content as $item) {
$dumpedContent .= $item . "\n";
}
}
if ($useRequireJs) {
$content = "define('debugbar', ['jquery'], function($){\r\n" . $content . "\r\n return PhpDebugBar; \r\n});";
$dumpedContent = "define('debugbar', ['jquery'], function($){\r\n" . $dumpedContent . "\r\n return PhpDebugBar; \r\n});";
}
if ($targetFilename !== null) {
file_put_contents($targetFilename, $content);
file_put_contents($targetFilename, $dumpedContent);
} else {
echo $content;
echo $dumpedContent;
}
}
@@ -772,17 +879,29 @@ class JavascriptRenderer
*/
public function renderHead()
{
list($cssFiles, $jsFiles) = $this->getAssets(null, self::RELATIVE_URL);
list($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead) = $this->getAssets(null, self::RELATIVE_URL);
$html = '';
foreach ($cssFiles as $file) {
$html .= sprintf('<link rel="stylesheet" type="text/css" href="%s">' . "\n", $file);
}
foreach ($inlineCss as $content) {
$html .= sprintf('<style type="text/css">%s</style>' . "\n", $content);
}
foreach ($jsFiles as $file) {
$html .= sprintf('<script type="text/javascript" src="%s"></script>' . "\n", $file);
}
foreach ($inlineJs as $content) {
$html .= sprintf('<script type="text/javascript">%s</script>' . "\n", $content);
}
foreach ($inlineHead as $content) {
$html .= $content . "\n";
}
if ($this->enableJqueryNoConflict && !$this->useRequireJs) {
$html .= '<script type="text/javascript">jQuery.noConflict(true);</script>' . "\n";
}
@@ -897,7 +1016,12 @@ class JavascriptRenderer
}
if ($this->ajaxHandlerClass) {
$js .= sprintf("%s.ajaxHandler = new %s(%s);\n", $this->variableName, $this->ajaxHandlerClass, $this->variableName);
$js .= sprintf("%s.ajaxHandler = new %s(%s, undefined, %s);\n",
$this->variableName,
$this->ajaxHandlerClass,
$this->variableName,
$this->ajaxHandlerAutoShow ? 'true' : 'false'
);
if ($this->ajaxHandlerBindToXHR) {
$js .= sprintf("%s.ajaxHandler.bindToXHR();\n", $this->variableName);
} elseif ($this->ajaxHandlerBindToJquery) {

View File

@@ -11,15 +11,33 @@
namespace DebugBar;
/**
* Request id generator based on the $_SERVER array
* Basic request ID generator
*/
class RequestIdGenerator implements RequestIdGeneratorInterface
{
protected $index = 0;
/**
* @return string
*/
public function generate()
{
return md5(serialize($_SERVER) . microtime());
if (function_exists('random_bytes')) {
// PHP 7 only
return 'X' . bin2hex(random_bytes(16));
} else if (function_exists('openssl_random_pseudo_bytes')) {
// PHP >= 5.3.0, but OpenSSL may not always be available
return 'X' . bin2hex(openssl_random_pseudo_bytes(16));
} else {
// Fall back to a rudimentary ID generator:
// * $_SERVER array will make the ID unique to this request.
// * spl_object_hash($this) will make the ID unique to this object instance.
// (note that object hashes can be reused, but the other data here should prevent issues here).
// * uniqid('', true) will use the current microtime(), plus additional random data.
// * $this->index guarantees the uniqueness of IDs from the current object.
$this->index++;
$entropy = serialize($_SERVER) . uniqid('', true) . spl_object_hash($this) . $this->index;
return 'X' . md5($entropy);
}
}
}

View File

@@ -13,7 +13,11 @@ namespace DebugBar;
interface RequestIdGeneratorInterface
{
/**
* Generates a unique id for the current request
* Generates a unique id for the current request. If called repeatedly, a new unique id must
* always be returned on each call to generate() - even across different object instances.
*
* To avoid any potential confusion in ID --> value maps, the returned value must be
* guaranteed to not be all-numeric.
*
* @return string
*/

View File

@@ -11,7 +11,7 @@ div.phpdebugbar {
left: 0;
width: 100%;
border-top: 0;
font-family: arial, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
background: #fff;
z-index: 10000;
font-size: 14px;
@@ -64,15 +64,34 @@ div.phpdebugbar table {
border-spacing: 0;
}
div.phpdebugbar code, div.phpdebugbar pre {
div.phpdebugbar input[type='text'], div.phpdebugbar input[type='password'] {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
background: #fff;
font-size: 14px;
color: #000;
border: 0;
padding: 0;
margin: 0;
}
div.phpdebugbar code, div.phpdebugbar pre, div.phpdebugbar samp {
background: none;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 1em;
border: 0;
padding: 0;
margin: 0;
}
div.phpdebugbar code, div.phpdebugbar pre {
color: #000;
}
div.phpdebugbar pre.sf-dump {
color: #a0a000;
outline: 0;
}
a.phpdebugbar-restore-btn {
float: left;
padding: 5px 8px;
@@ -158,7 +177,7 @@ a.phpdebugbar-tab.phpdebugbar-active {
margin-left: 5px;
font-size: 11px;
line-height: 14px;
padding: 0px 6px;
padding: 0 6px;
background: #ccc;
border-radius: 4px;
color: #555;
@@ -170,6 +189,9 @@ a.phpdebugbar-tab.phpdebugbar-active {
display: none;
vertical-align: middle;
}
a.phpdebugbar-tab span.phpdebugbar-badge.phpdebugbar-visible {
display: inline;
}
a.phpdebugbar-tab span.phpdebugbar-badge.phpdebugbar-important {
background: #ed6868;
color: white;
@@ -181,7 +203,7 @@ a.phpdebugbar-close-btn, a.phpdebugbar-open-btn, a.phpdebugbar-restore-btn, a.ph
}
a.phpdebugbar-minimize-btn , a.phpdebugbar-maximize-btn {
padding-right: 0px !important;
padding-right: 0 !important;
}
a.phpdebugbar-maximize-btn { display: none}

View File

@@ -272,9 +272,9 @@ if (typeof(PhpDebugBar) == 'undefined') {
this.bindAttr('badge', function(value) {
if (value !== null) {
this.$badge.text(value);
this.$badge.show();
this.$badge.addClass(csscls('visible'));
} else {
this.$badge.hide();
this.$badge.removeClass(csscls('visible'));
}
});
@@ -370,7 +370,15 @@ if (typeof(PhpDebugBar) == 'undefined') {
return "#" + nb + suffix;
}
var filename = data['__meta']['uri'].substr(data['__meta']['uri'].lastIndexOf('/') + 1);
var uri = data['__meta']['uri'], filename;
if (uri.length && uri.charAt(uri.length - 1) === '/') {
// URI ends in a trailing /: get the portion before then to avoid returning an empty string
filename = uri.substr(0, uri.length - 1); // strip trailing '/'
filename = filename.substr(filename.lastIndexOf('/') + 1); // get last path segment
filename += '/'; // add the trailing '/' back
} else {
filename = uri.substr(uri.lastIndexOf('/') + 1);
}
var label = "#" + nb + " " + filename + suffix + ' (' + data['__meta']['datetime'].split(' ')[1] + ')';
return label;
}
@@ -895,9 +903,10 @@ if (typeof(PhpDebugBar) == 'undefined') {
* @param {Object} data
* @param {String} id The name of this set, optional
* @param {String} suffix
* @param {Bool} show Whether to show the new dataset, optional (default: true)
* @return {String} Dataset's id
*/
addDataSet: function(data, id, suffix) {
addDataSet: function(data, id, suffix, show) {
var label = this.datesetTitleFormater.format(id, data, suffix);
id = id || (getObjectSize(this.datasets) + 1);
this.datasets[id] = data;
@@ -907,7 +916,9 @@ if (typeof(PhpDebugBar) == 'undefined') {
this.$datasets.show();
}
this.showDataSet(id);
if (typeof(show) == 'undefined' || show) {
this.showDataSet(id);
}
return id;
},
@@ -915,14 +926,15 @@ if (typeof(PhpDebugBar) == 'undefined') {
* Loads a dataset using the open handler
*
* @param {String} id
* @param {Bool} show Whether to show the new dataset, optional (default: true)
*/
loadDataSet: function(id, suffix, callback) {
loadDataSet: function(id, suffix, callback, show) {
if (!this.openHandler) {
throw new Error('loadDataSet() needs an open handler');
}
var self = this;
this.openHandler.load(id, function(data) {
self.addDataSet(data, id, suffix);
self.addDataSet(data, id, suffix, show);
callback && callback(data);
});
},
@@ -1004,10 +1016,13 @@ if (typeof(PhpDebugBar) == 'undefined') {
* AjaxHandler
*
* Extract data from headers of an XMLHttpRequest and adds a new dataset
*
* @param {Bool} autoShow Whether to immediately show new datasets, optional (default: true)
*/
var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName) {
var AjaxHandler = PhpDebugBar.AjaxHandler = function(debugbar, headerName, autoShow) {
this.debugbar = debugbar;
this.headerName = headerName || 'phpdebugbar';
this.autoShow = typeof(autoShow) == 'undefined' ? true : autoShow;
};
$.extend(AjaxHandler.prototype, {
@@ -1039,7 +1054,7 @@ if (typeof(PhpDebugBar) == 'undefined') {
loadFromId: function(xhr) {
var id = this.extractIdFromHeaders(xhr);
if (id && this.debugbar.openHandler) {
this.debugbar.loadDataSet(id, "(ajax)");
this.debugbar.loadDataSet(id, "(ajax)", undefined, this.autoShow);
return true;
}
return false;
@@ -1071,7 +1086,7 @@ if (typeof(PhpDebugBar) == 'undefined') {
if (data.error) {
throw new Error('Error loading debugbar data: ' + data.error);
} else if(data.data) {
this.debugbar.addDataSet(data.data, data.id, "(ajax)");
this.debugbar.addDataSet(data.data, data.id, "(ajax)", this.autoShow);
}
return true;
},

View File

@@ -22,7 +22,7 @@ div.phpdebugbar-openhandler {
border: 2px solid #888;
overflow: auto;
z-index: 20001;
font-family: arial;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
padding-bottom: 10px;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 434 KiB

View File

@@ -4,122 +4,122 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
.hljs {
div.phpdebugbar .hljs {
display: block; padding: 0.5em;
color: #333;
background: #f8f8f8
}
.hljs-comment,
.hljs-template_comment,
.diff .hljs-header,
.hljs-javadoc {
div.phpdebugbar .hljs-comment,
div.phpdebugbar .hljs-template_comment,
div.phpdebugbar .diff .hljs-header,
div.phpdebugbar .hljs-javadoc {
color: #998;
font-style: italic
}
.hljs-keyword,
.css .rule .hljs-keyword,
.hljs-winutils,
.javascript .hljs-title,
.nginx .hljs-title,
.hljs-subst,
.hljs-request,
.hljs-status {
div.phpdebugbar .hljs-keyword,
div.phpdebugbar .css .rule .hljs-keyword,
div.phpdebugbar .hljs-winutils,
div.phpdebugbar .javascript .hljs-title,
div.phpdebugbar .nginx .hljs-title,
div.phpdebugbar .hljs-subst,
div.phpdebugbar .hljs-request,
div.phpdebugbar .hljs-status {
color: #333;
font-weight: bold
}
.hljs-number,
.hljs-hexcolor,
.ruby .hljs-constant {
div.phpdebugbar .hljs-number,
div.phpdebugbar .hljs-hexcolor,
div.phpdebugbar .ruby .hljs-constant {
color: #099;
}
.hljs-string,
.hljs-tag .hljs-value,
.hljs-phpdoc,
.tex .hljs-formula {
div.phpdebugbar .hljs-string,
div.phpdebugbar .hljs-tag .hljs-value,
div.phpdebugbar .hljs-phpdoc,
div.phpdebugbar .tex .hljs-formula {
color: #d14
}
.hljs-title,
.hljs-id,
.coffeescript .hljs-params,
.scss .hljs-preprocessor {
div.phpdebugbar .hljs-title,
div.phpdebugbar .hljs-id,
div.phpdebugbar .coffeescript .hljs-params,
div.phpdebugbar .scss .hljs-preprocessor {
color: #900;
font-weight: bold
}
.javascript .hljs-title,
.lisp .hljs-title,
.clojure .hljs-title,
.hljs-subst {
div.phpdebugbar .javascript .hljs-title,
div.phpdebugbar .lisp .hljs-title,
div.phpdebugbar .clojure .hljs-title,
div.phpdebugbar .hljs-subst {
font-weight: normal
}
.hljs-class .hljs-title,
.haskell .hljs-type,
.vhdl .hljs-literal,
.tex .hljs-command {
div.phpdebugbar .hljs-class .hljs-title,
div.phpdebugbar .haskell .hljs-type,
div.phpdebugbar .vhdl .hljs-literal,
div.phpdebugbar .tex .hljs-command {
color: #458;
font-weight: bold
}
.hljs-tag,
.hljs-tag .hljs-title,
.hljs-rules .hljs-property,
.django .hljs-tag .hljs-keyword {
div.phpdebugbar .hljs-tag,
div.phpdebugbar .hljs-tag .hljs-title,
div.phpdebugbar .hljs-rules .hljs-property,
div.phpdebugbar .django .hljs-tag .hljs-keyword {
color: #000080;
font-weight: normal
}
.hljs-attribute,
.hljs-variable,
.lisp .hljs-body {
div.phpdebugbar .hljs-attribute,
div.phpdebugbar .hljs-variable,
div.phpdebugbar .lisp .hljs-body {
color: #008080
}
.hljs-regexp {
div.phpdebugbar .hljs-regexp {
color: #009926
}
.hljs-symbol,
.ruby .hljs-symbol .hljs-string,
.lisp .hljs-keyword,
.tex .hljs-special,
.hljs-prompt {
div.phpdebugbar .hljs-symbol,
div.phpdebugbar .ruby .hljs-symbol .hljs-string,
div.phpdebugbar .lisp .hljs-keyword,
div.phpdebugbar .tex .hljs-special,
div.phpdebugbar .hljs-prompt {
color: #990073
}
.hljs-built_in,
.lisp .hljs-title,
.clojure .hljs-built_in {
div.phpdebugbar .hljs-built_in,
div.phpdebugbar .lisp .hljs-title,
div.phpdebugbar .clojure .hljs-built_in {
color: #0086b3
}
.hljs-preprocessor,
.hljs-pragma,
.hljs-pi,
.hljs-doctype,
.hljs-shebang,
.hljs-cdata {
div.phpdebugbar .hljs-preprocessor,
div.phpdebugbar .hljs-pragma,
div.phpdebugbar .hljs-pi,
div.phpdebugbar .hljs-doctype,
div.phpdebugbar .hljs-shebang,
div.phpdebugbar .hljs-cdata {
color: #999;
font-weight: bold
}
.hljs-deletion {
div.phpdebugbar .hljs-deletion {
background: #fdd
}
.hljs-addition {
div.phpdebugbar .hljs-addition {
background: #dfd
}
.diff .hljs-change {
div.phpdebugbar .diff .hljs-change {
background: #0086b3
}
.hljs-chunk {
div.phpdebugbar .hljs-chunk {
color: #aaa
}

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,41 @@
pre.phpdebugbar-widgets-code-block {
white-space: pre;
word-wrap: normal;
overflow: hidden;
}
pre.phpdebugbar-widgets-code-block code {
display: block;
overflow-x: auto;
overflow-y: hidden;
}
pre.phpdebugbar-widgets-code-block code.phpdebugbar-widgets-numbered-code {
padding: 5px;
}
pre.phpdebugbar-widgets-code-block code span.phpdebugbar-widgets-highlighted-line {
background: #800000;
color: #fff;
display: inline-block;
min-width: 100%;
}
pre.phpdebugbar-widgets-code-block code span.phpdebugbar-widgets-highlighted-line span {
background: none !important;
color: inherit !important;
}
pre.phpdebugbar-widgets-code-block ul {
float: left;
padding: 5px;
background: #cacaca;
border-right: 1px solid #aaa;
text-align: right;
}
/* -------------------------------------- */
ul.phpdebugbar-widgets-list {
margin: 0;
padding: 0;
list-style: none;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item {
padding: 3px 6px;
@@ -20,7 +53,7 @@ div.phpdebugbar-widgets-messages {
position: relative;
height: 100%;
}
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-list {
div.phpdebugbar-widgets-messages ul.phpdebugbar-widgets-list {
padding-bottom: 20px;
}
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-warning:before {
@@ -40,6 +73,9 @@ div.phpdebugbar-widgets-messages {
font-size: 11px;
color: red;
}
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item pre.sf-dump {
display: inline;
}
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-collector,
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-label {
float: right;
@@ -113,9 +149,13 @@ dl.phpdebugbar-widgets-kvlist {
/* -------------------------------------- */
dl.phpdebugbar-widgets-varlist {
font-family: monospace;
dl.phpdebugbar-widgets-varlist,
dl.phpdebugbar-widgets-htmlvarlist {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
dl.phpdebugbar-widgets-htmlvarlist dd {
cursor: initial;
}
/* -------------------------------------- */
@@ -137,7 +177,7 @@ ul.phpdebugbar-widgets-timeline {
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-collector {
position: absolute;
font-size: 12px;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
color: #555;
top: 4px;
left: 5px;
@@ -151,7 +191,7 @@ ul.phpdebugbar-widgets-timeline {
right: 5px;
}
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-value {
display: block;
display: block;
position: absolute;
height: 10px;
background: #3db9ec;
@@ -164,7 +204,7 @@ ul.phpdebugbar-widgets-timeline {
width: 70%;
margin: 10px;
border: 1px solid #ddd;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
border-collapse: collapse;
}
ul.phpdebugbar-widgets-timeline table.phpdebugbar-widgets-params td {
@@ -205,5 +245,17 @@ div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item {
margin: 10px;
padding: 5px;
border: 1px solid #ddd;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
div.phpdebugbar-widgets-exceptions a.phpdebugbar-widgets-editor-link:before {
font-family: PhpDebugbarFontAwesome;
margin-right: 4px;
font-size: 12px;
font-style: normal;
}
div.phpdebugbar-widgets-exceptions a.phpdebugbar-widgets-editor-link:before {
content: "\f08e";
margin-left: 4px;
}

View File

@@ -11,6 +11,8 @@ if (typeof(PhpDebugBar) == 'undefined') {
*/
PhpDebugBar.Widgets = {};
var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
/**
* Replaces spaces with &nbsp; and line breaks with <br>
*
@@ -68,21 +70,54 @@ if (typeof(PhpDebugBar) == 'undefined') {
*
* @param {String} code
* @param {String} lang
* @param {Number} [firstLineNumber] If provided, shows line numbers beginning with the given value.
* @param {Number} [highlightedLine] If provided, the given line number will be highlighted.
* @return {String}
*/
var createCodeBlock = PhpDebugBar.Widgets.createCodeBlock = function(code, lang) {
var pre = $('<pre />');
$('<code />').text(code).appendTo(pre);
var createCodeBlock = PhpDebugBar.Widgets.createCodeBlock = function(code, lang, firstLineNumber, highlightedLine) {
var pre = $('<pre />').addClass(csscls('code-block'));
// Add a newline to prevent <code> element from vertically collapsing too far if the last
// code line was empty: that creates problems with the horizontal scrollbar being
// incorrectly positioned - most noticeable when line numbers are shown.
var codeElement = $('<code />').text(code + '\n').appendTo(pre);
// Add a span with a special class if we are supposed to highlight a line. highlight.js will
// still correctly format code even with existing markup in it.
if ($.isNumeric(highlightedLine)) {
if ($.isNumeric(firstLineNumber)) {
highlightedLine = highlightedLine - firstLineNumber + 1;
}
codeElement.html(function (index, html) {
var currentLine = 1;
return html.replace(/^.*$/gm, function(line) {
if (currentLine++ == highlightedLine) {
return '<span class="' + csscls('highlighted-line') + '">' + line + '</span>';
} else {
return line;
}
});
});
}
// Format the code
if (lang) {
pre.addClass("language-" + lang);
}
highlight(pre);
// Show line numbers in a list
if ($.isNumeric(firstLineNumber)) {
var lineCount = code.split('\n').length;
var $lineNumbers = $('<ul />').prependTo(pre);
pre.children().addClass(csscls('numbered-code'));
for (var i = firstLineNumber; i < firstLineNumber + lineCount; i++) {
$('<li />').text(i).appendTo($lineNumbers);
}
}
return pre;
};
var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
// ------------------------------------------------------------------
// Generic widgets
// ------------------------------------------------------------------
@@ -214,7 +249,28 @@ if (typeof(PhpDebugBar) == 'undefined') {
});
// ------------------------------------------------------------------
/**
* An extension of KVListWidget where the data represents a list
* of variables whose contents are HTML; this is useful for showing
* variable output from VarDumper's HtmlDumper.
*
* Options:
* - data
*/
var HtmlVariableListWidget = PhpDebugBar.Widgets.HtmlVariableListWidget = KVListWidget.extend({
className: csscls('kvlist htmlvarlist'),
itemRenderer: function(dt, dd, key, value) {
$('<span />').attr('title', key).text(key).appendTo(dt);
dd.html(value);
}
});
// ------------------------------------------------------------------
/**
* Iframe widget
*
@@ -260,33 +316,37 @@ if (typeof(PhpDebugBar) == 'undefined') {
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 />').addClass(csscls('value')).text(m).appendTo(li);
if (!value.is_string || value.message.length > 100) {
var prettyVal = value.message;
if (!value.is_string) {
prettyVal = null;
if (value.message_html) {
var val = $('<span />').addClass(csscls('value')).html(value.message_html).appendTo(li);
} else {
var m = value.message;
if (m.length > 100) {
m = m.substr(0, 100) + "...";
}
li.css('cursor', 'pointer').click(function() {
if (val.hasClass(csscls('pretty'))) {
val.text(m).removeClass(csscls('pretty'));
} else {
prettyVal = prettyVal || createCodeBlock(value.message, 'php');
val.addClass(csscls('pretty')).empty().append(prettyVal);
var val = $('<span />').addClass(csscls('value')).text(m).appendTo(li);
if (!value.is_string || value.message.length > 100) {
var prettyVal = value.message;
if (!value.is_string) {
prettyVal = null;
}
});
li.css('cursor', 'pointer').click(function () {
if (val.hasClass(csscls('pretty'))) {
val.text(m).removeClass(csscls('pretty'));
} else {
prettyVal = prettyVal || createCodeBlock(value.message, 'php');
val.addClass(csscls('pretty')).empty().append(prettyVal);
}
});
}
}
if (value.collector) {
$('<span />').addClass(csscls('collector')).text(value.collector).prependTo(li);
}
if (value.label) {
val.addClass(csscls(value.label));
$('<span />').addClass(csscls('label')).text(value.label).appendTo(li);
}
if (value.collector) {
$('<span />').addClass(csscls('collector')).text(value.collector).appendTo(li);
$('<span />').addClass(csscls('label')).text(value.label).prependTo(li);
}
}});
@@ -430,7 +490,17 @@ if (typeof(PhpDebugBar) == 'undefined') {
this.$list = new ListWidget({ itemRenderer: function(li, e) {
$('<span />').addClass(csscls('message')).text(e.message).appendTo(li);
if (e.file) {
$('<span />').addClass(csscls('filename')).text(e.file + "#" + e.line).appendTo(li);
var header = $('<span />').addClass(csscls('filename')).text(e.file + "#" + e.line);
if (e.xdebug_link) {
if (e.xdebug_link.ajax) {
$('<a title="' + e.xdebug_link.url + '"></a>').on('click', function () {
$.ajax(e.xdebug_link.url);
}).addClass(csscls('editor-link')).appendTo(header);
} else {
$('<a href="' + e.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(header);
}
}
header.appendTo(li);
}
if (e.type) {
$('<span />').addClass(csscls('type')).text(e.type).appendTo(li);

View File

@@ -8,5 +8,5 @@ div.phpdebugbar-widgets-mails li.phpdebugbar-widgets-list-item pre.phpdebugbar-w
margin: 10px;
padding: 5px;
border: 1px solid #ddd;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
}

View File

@@ -1,5 +1,5 @@
div.phpdebugbar-widgets-sqlqueries .phpdebugbar-widgets-status {
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
padding: 6px 6px;
border-bottom: 1px solid #ddd;
font-weight: bold;
@@ -15,6 +15,7 @@ div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-database,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-copy-clipboard,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id {
float: right;
margin-left: 8px;
@@ -24,6 +25,7 @@ div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugb
div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-duration,
div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-memory,
div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-row-count,
div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-copy-clipboard,
div.phpdebugbar-widgets-sqlqueries div.phpdebugbar-widgets-status span.phpdebugbar-widgets-stmt-id {
color: #555;
}
@@ -31,6 +33,7 @@ div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-database:before,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-duration:before,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-memory:before,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count:before,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-copy-clipboard:before,
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id:before {
font-family: PhpDebugbarFontAwesome;
margin-right: 4px;
@@ -51,12 +54,15 @@ div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-row-count:before {
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-stmt-id:before {
content: "\f08d";
}
div.phpdebugbar-widgets-sqlqueries span.phpdebugbar-widgets-copy-clipboard:before {
content: "\f0c5";
}
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params {
display: none;
width: 70%;
margin: 10px;
border: 1px solid #ddd;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
border-collapse: collapse;
}
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td {

View File

@@ -24,7 +24,32 @@
this.set('exclude', excludedLabels);
},
onCopyToClipboard: function (el) {
var code = $(el).parent('li').find('code').get(0);
var copy = function () {
try {
document.execCommand('copy');
alert('Query copied to the clipboard');
} catch (err) {
console.log('Oops, unable to copy');
}
};
var select = function (node) {
if (document.selection) {
var range = document.body.createTextRange();
range.moveToElementText(node);
range.select();
} else if (window.getSelection) {
var range = document.createRange();
range.selectNodeContents(node);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
}
copy();
window.getSelection().removeAllRanges();
};
select(code);
},
render: function() {
this.$status = $('<div />').addClass(csscls('status')).appendTo(this.$el);
@@ -67,6 +92,14 @@
li.addClass(csscls('error'));
li.append($('<span />').addClass(csscls('error')).text("[" + stmt.error_code + "] " + stmt.error_message));
}
$('<span title="Copy to clipboard" />')
.addClass(csscls('copy-clipboard'))
.css('cursor', 'pointer')
.on('click', function (event) {
self.onCopyToClipboard(this);
event.stopPropagation();
})
.appendTo(li);
if (stmt.params && !$.isEmptyObject(stmt.params)) {
var table = $('<table><tr><th colspan="2">Params</th></tr></table>').addClass(csscls('params')).appendTo(li);
for (var key in stmt.params) {
@@ -87,11 +120,15 @@
this.$list.$el.appendTo(this.$el);
this.bindAttr('data', function(data) {
// the PDO collector maybe is empty
if (data.length <= 0) {
return false;
}
this.$list.set('data', data.statements);
this.$status.empty();
// Search for duplicate statements.
for (var sql = {}, unique = 0, i = 0; i < data.statements.length; i++) {
for (var sql = {}, unique = 0, duplicate = 0, i = 0; i < data.statements.length; i++) {
var stmt = data.statements[i].sql;
if (data.statements[i].params && !$.isEmptyObject(data.statements[i].params)) {
stmt += ' {' + $.param(data.statements[i].params, false) + '}';
@@ -102,11 +139,13 @@
// Add classes to all duplicate SQL statements.
for (var stmt in sql) {
if (sql[stmt].keys.length > 1) {
unique++;
duplicate += sql[stmt].keys.length;
for (var i = 0; i < sql[stmt].keys.length; i++) {
this.$list.$el.find('.' + csscls('list-item')).eq(sql[stmt].keys[i])
.addClass(csscls('sql-duplicate')).addClass(csscls('sql-duplicate-'+unique));
.addClass(csscls('sql-duplicate'));
}
} else {
unique++;
}
}
@@ -114,8 +153,8 @@
if (data.nb_failed_statements) {
t.append(", " + data.nb_failed_statements + " of which failed");
}
if (unique) {
t.append(", " + (data.nb_statements - unique) + " of which were duplicated");
if (duplicate) {
t.append(", " + duplicate + " of which were duplicates");
t.append(", " + unique + " unique");
}
if (data.accumulated_duration_str) {

View File

@@ -1,6 +1,6 @@
div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status {
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
padding: 6px 6px;
border-bottom: 1px solid #ddd;
font-weight: bold;
@@ -25,7 +25,9 @@ div.phpdebugbar-widgets-templates div.phpdebugbar-widgets-status span.phpdebugba
div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-render-time:before,
div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-memory:before,
div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count:before,
div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-type:before {
div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-type:before,
div.phpdebugbar-widgets-templates a.phpdebugbar-widgets-editor-link:before
{
font-family: PhpDebugbarFontAwesome;
margin-right: 4px;
font-size: 12px;
@@ -42,12 +44,16 @@ div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count:before {
div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-type:before {
content: "\f121";
}
div.phpdebugbar-widgets-templates a.phpdebugbar-widgets-editor-link:before {
content: "\f08e";
margin-left: 4px;
}
div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params {
display: none;
width: 70%;
margin: 10px;
border: 1px solid #ddd;
font-family: monospace;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
border-collapse: collapse;
}
div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params td {

View File

@@ -17,6 +17,16 @@
this.$list = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function(li, tpl) {
$('<span />').addClass(csscls('name')).text(tpl.name).appendTo(li);
if (typeof tpl.xdebug_link !== 'undefined') {
if (tpl.xdebug_link.ajax) {
$('<a title="' + tpl.xdebug_link.url + '"></a>').on('click', function () {
$.ajax(tpl.xdebug_link.url);
}).addClass(csscls('editor-link')).appendTo(li);
} else {
$('<a href="' + tpl.xdebug_link.url + '"></a>').addClass(csscls('editor-link')).appendTo(li);
}
}
if (tpl.render_time_str) {
$('<span title="Render time" />').addClass(csscls('render-time')).text(tpl.render_time_str).appendTo(li);
}
@@ -47,13 +57,15 @@
}
}});
this.$list.$el.appendTo(this.$el);
this.$callgraph = $('<div />').addClass(csscls('callgraph')).appendTo(this.$el);
this.bindAttr('data', function(data) {
this.$list.set('data', data.templates);
this.$status.empty();
this.$callgraph.empty();
var sentence = data.sentence || "templates were rendered";
$('<span />').text(data.templates.length + " " + sentence).appendTo(this.$status);
$('<span />').text(data.nb_templates + " " + sentence).appendTo(this.$status);
if (data.accumulated_render_time_str) {
this.$status.append($('<span title="Accumulated render time" />').addClass(csscls('render-time')).text(data.accumulated_render_time_str));
@@ -61,6 +73,15 @@
if (data.memory_usage_str) {
this.$status.append($('<span title="Memory usage" />').addClass(csscls('memory')).text(data.memory_usage_str));
}
if (data.nb_blocks > 0) {
$('<div />').text(data.nb_blocks + " blocks were rendered").appendTo(this.$status);
}
if (data.nb_macros > 0) {
$('<div />').text(data.nb_macros + " macros were rendered").appendTo(this.$status);
}
if (typeof data.callgraph !== 'undefined') {
this.$callgraph.html(data.callgraph);
}
});
}

View File

@@ -11,6 +11,7 @@
namespace DebugBar\Storage;
use Memcached;
use ReflectionMethod;
/**
* Stores collected data into Memcache using the Memcached extension
@@ -21,13 +22,20 @@ class MemcachedStorage implements StorageInterface
protected $keyNamespace;
protected $expiration;
protected $newGetMultiSignature;
/**
* @param Memcached $memcached
* @param string $keyNamespace Namespace for Memcached key names (to avoid conflict with other Memcached users).
* @param int $expiration Expiration for Memcached entries (see Expiration Times in Memcached documentation).
*/
public function __construct(Memcached $memcached, $keyNamespace = 'phpdebugbar')
public function __construct(Memcached $memcached, $keyNamespace = 'phpdebugbar', $expiration = 0)
{
$this->memcached = $memcached;
$this->keyNamespace = $keyNamespace;
$this->expiration = $expiration;
}
/**
@@ -36,9 +44,12 @@ class MemcachedStorage implements StorageInterface
public function save($id, $data)
{
$key = $this->createKey($id);
$this->memcached->set($key, $data);
$this->memcached->set($key, $data, $this->expiration);
if (!$this->memcached->append($this->keyNamespace, "|$key")) {
$this->memcached->set($this->keyNamespace, $key);
$this->memcached->set($this->keyNamespace, $key, $this->expiration);
} else if ($this->expiration) {
// append doesn't support updating expiration, so do it here:
$this->memcached->touch($this->keyNamespace, $this->expiration);
}
}
@@ -60,15 +71,32 @@ class MemcachedStorage implements StorageInterface
}
$results = array();
foreach (explode('|', $keys) as $key) {
if ($data = $this->memcached->get($key)) {
$meta = $data['__meta'];
if ($this->filter($meta, $filters)) {
$results[] = $meta;
$keys = array_reverse(explode('|', $keys)); // Reverse so newest comes first
$keyPosition = 0; // Index in $keys to try to get next items from
$remainingItems = $max + $offset; // Try to obtain this many remaining items
// Loop until we've found $remainingItems matching items or no more items may exist.
while ($remainingItems > 0 && $keyPosition < count($keys)) {
// Consume some keys from $keys:
$itemsToGet = array_slice($keys, $keyPosition, $remainingItems);
$keyPosition += $remainingItems;
// Try to get them, and filter them:
$newItems = $this->memcachedGetMulti($itemsToGet, Memcached::GET_PRESERVE_ORDER);
if ($newItems) {
foreach ($newItems as $data) {
$meta = $data['__meta'];
if ($this->filter($meta, $filters)) {
$remainingItems--;
// Keep the result only if we've discarded $offset items first
if ($offset <= 0) {
$results[] = $meta;
} else {
$offset--;
}
}
}
}
}
return array_slice($results, $offset, $max);
return $results;
}
/**
@@ -108,4 +136,23 @@ class MemcachedStorage implements StorageInterface
{
return md5("{$this->keyNamespace}.$id");
}
/**
* The memcached getMulti function changed in version 3.0.0 to only have two parameters.
*
* @param array $keys
* @param int $flags
*/
protected function memcachedGetMulti($keys, $flags)
{
if ($this->newGetMultiSignature === null) {
$this->newGetMultiSignature = (new ReflectionMethod('Memcached', 'getMulti'))->getNumberOfParameters() === 2;
}
if ($this->newGetMultiSignature) {
return $this->memcached->getMulti($keys, $flags);
} else {
$null = null;
return $this->memcached->getMulti($keys, $null, $flags);
}
}
}

View File

@@ -10,8 +10,6 @@
namespace DebugBar\Storage;
use Predis\Client;
/**
* Stores collected data into Redis
*/
@@ -25,7 +23,7 @@ class RedisStorage implements StorageInterface
* @param \Predis\Client $redis Redis Client
* @param string $hash
*/
public function __construct(Client $redis, $hash = 'phpdebugbar')
public function __construct($redis, $hash = 'phpdebugbar')
{
$this->redis = $redis;
$this->hash = $hash;
@@ -36,7 +34,9 @@ class RedisStorage implements StorageInterface
*/
public function save($id, $data)
{
$this->redis->hset($this->hash, $id, serialize($data));
$this->redis->hset("$this->hash:meta", $id, serialize($data['__meta']));
unset($data['__meta']);
$this->redis->hset("$this->hash:data", $id, serialize($data));
}
/**
@@ -44,7 +44,8 @@ class RedisStorage implements StorageInterface
*/
public function get($id)
{
return unserialize($this->redis->hget($this->hash, $id));
return array_merge(unserialize($this->redis->hget("$this->hash:data", $id)),
array('__meta' => unserialize($this->redis->hget("$this->hash:meta", $id))));
}
/**
@@ -53,14 +54,23 @@ class RedisStorage implements StorageInterface
public function find(array $filters = array(), $max = 20, $offset = 0)
{
$results = array();
foreach ($this->redis->hgetall($this->hash) as $id => $data) {
if ($data = unserialize($data)) {
$meta = $data['__meta'];
if ($this->filter($meta, $filters)) {
$results[] = $meta;
$cursor = "0";
do {
list($cursor, $data) = $this->redis->hscan("$this->hash:meta", $cursor);
foreach ($data as $meta) {
if ($meta = unserialize($meta)) {
if ($this->filter($meta, $filters)) {
$results[] = $meta;
}
}
}
}
} while($cursor);
usort($results, function ($a, $b) {
return $a['utime'] < $b['utime'];
});
return array_slice($results, $offset, $max);
}