Save
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Bootstrap;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use WebmanTech\Debugbar\DataCollector\LaravelQueryCollector;
|
||||
use WebmanTech\Debugbar\DebugBar;
|
||||
use support\Db;
|
||||
use Webman\Bootstrap;
|
||||
|
||||
class LaravelQuery implements Bootstrap
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function start($worker)
|
||||
{
|
||||
if (!class_exists(Capsule::class)) {
|
||||
return;
|
||||
}
|
||||
$connections = array_keys(config('database.connections'));
|
||||
if (!$connections) {
|
||||
return;
|
||||
}
|
||||
|
||||
$collectorName = (new LaravelQueryCollector())->getName();
|
||||
$debugBar = DebugBar::instance();
|
||||
$debugBar->boot();
|
||||
if (!$debugBar->hasCollector($collectorName)) {
|
||||
return;
|
||||
}
|
||||
/** @var LaravelQueryCollector $collector */
|
||||
$collector = $debugBar->getCollector($collectorName);
|
||||
|
||||
foreach ($connections as $connection) {
|
||||
$collector->addListener(Db::connection($connection));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Bootstrap;
|
||||
|
||||
use Illuminate\Redis\RedisManager;
|
||||
use WebmanTech\Debugbar\DataCollector\LaravelRedisCollector;
|
||||
use WebmanTech\Debugbar\DebugBar;
|
||||
use support\Redis;
|
||||
use Webman\Bootstrap;
|
||||
|
||||
class LaravelRedisExec implements Bootstrap
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function start($worker)
|
||||
{
|
||||
if (!class_exists(RedisManager::class)) {
|
||||
return;
|
||||
}
|
||||
$connections = array_keys(config('redis'));
|
||||
if (!$connections) {
|
||||
return;
|
||||
}
|
||||
|
||||
$collectorName = (new LaravelRedisCollector())->getName();
|
||||
$debugBar = DebugBar::instance();
|
||||
$debugBar->boot();
|
||||
if (!$debugBar->hasCollector($collectorName)) {
|
||||
return;
|
||||
}
|
||||
/** @var LaravelRedisCollector $collector */
|
||||
$collector = $debugBar->getCollector($collectorName);
|
||||
|
||||
foreach ($connections as $connection) {
|
||||
try {
|
||||
$connection = Redis::connection($connection);
|
||||
$collector->addRedisListener($connection);
|
||||
} catch (\Throwable $e) {
|
||||
// 忽略错误的 redis connection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\TimeDataCollector as DebugBarTimeDataCollector;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\Events\QueryExecuted;
|
||||
use Illuminate\Database\Events\TransactionBeginning;
|
||||
use Illuminate\Database\Events\TransactionCommitted;
|
||||
use Illuminate\Database\Events\TransactionRolledBack;
|
||||
use WebmanTech\Debugbar\DebugBar;
|
||||
use WebmanTech\Debugbar\Helper\ArrayHelper;
|
||||
use WebmanTech\Debugbar\Laravel\DataCollector\QueryCollector;
|
||||
use WebmanTech\Debugbar\Laravel\DataFormatter\QueryFormatter;
|
||||
|
||||
class LaravelQueryCollector extends QueryCollector
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'with_params' => true, // 将参数绑定上
|
||||
'backtrace' => true, // 显示 sql 来源
|
||||
'backtrace_exclude_paths' => [], // 排除 sql 来源
|
||||
'timeline' => true, // 将 sql 显示到 timeline 上
|
||||
'duration_background' => true, // 相对于执行所需的时间,在每个查询上显示阴影背景
|
||||
'explain' => [
|
||||
'enabled' => false,
|
||||
'types' => ['SELECT'], // 废弃的设置,目前只支持 SELECT
|
||||
],
|
||||
'hints' => false, // 显示常见错误提示
|
||||
'show_copy' => true, // 显示复制
|
||||
'slow_threshold' => false, // 仅显示慢sql,设置毫秒时间来启用
|
||||
];
|
||||
|
||||
public function __construct(array $config = [], DebugBarTimeDataCollector $timeCollector = null)
|
||||
{
|
||||
$this->config = ArrayHelper::merge($this->config, $config);
|
||||
|
||||
if (!$this->config['timeline']) {
|
||||
$timeCollector = null;
|
||||
}
|
||||
|
||||
parent::__construct($timeCollector);
|
||||
|
||||
$this->setConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
$assets = parent::getAssets();
|
||||
$assets['js'] = 'laravel/sqlqueries/widget.js';
|
||||
return $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'laravelDB';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function getWidgets()
|
||||
{
|
||||
$name = $this->getName();
|
||||
return [
|
||||
$name => [
|
||||
"icon" => "database",
|
||||
"widget" => "PhpDebugBar.Widgets.LaravelSQLQueriesWidget",
|
||||
"map" => $name,
|
||||
"default" => "[]"
|
||||
],
|
||||
"{$name}:badge" => [
|
||||
"map" => "{$name}.nb_statements",
|
||||
"default" => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function setConfig()
|
||||
{
|
||||
$this->mergeBacktraceExcludePaths([
|
||||
'/vendor/webman-tech/debugbar',
|
||||
'/vendor/illuminate/support',
|
||||
'/vendor/illuminate/database',
|
||||
'/vendor/illuminate/events',
|
||||
'/vendor/jenssegers/mongodb', // jenssegers/mongodb
|
||||
]);
|
||||
|
||||
$this->setDataFormatter(new QueryFormatter());
|
||||
|
||||
if ($this->config['with_params']) {
|
||||
$this->setRenderSqlWithParams(true);
|
||||
}
|
||||
|
||||
if ($this->config['backtrace']) {
|
||||
$middleware = [];
|
||||
$this->setFindSource(true, $middleware);
|
||||
}
|
||||
|
||||
if ($this->config['backtrace_exclude_paths']) {
|
||||
$excludePaths = $this->config['backtrace_exclude_paths'];
|
||||
$this->mergeBacktraceExcludePaths($excludePaths);
|
||||
}
|
||||
|
||||
$this->setDurationBackground($this->config['duration_background']);
|
||||
|
||||
if ($this->config['explain']['enabled']) {
|
||||
$types = $this->config['explain']['types'];
|
||||
$this->setExplainSource(true, $types);
|
||||
}
|
||||
|
||||
if ($this->config['hints']) {
|
||||
$this->setShowHints(true);
|
||||
}
|
||||
|
||||
if ($this->config['show_copy']) {
|
||||
$this->setShowCopyButton(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function addQuery($query, $bindings, $time, $connection)
|
||||
{
|
||||
parent::addQuery($query, $bindings, $time, $connection);
|
||||
|
||||
// 给 connection 添加 connectionName
|
||||
$lastIndex = count($this->queries) - 1;
|
||||
$lastQuery = $this->queries[$lastIndex];
|
||||
$lastQuery['connection'] = $connection->getName() . '__' . $lastQuery['connection']; // 目前只能用 __ 分隔,其他字符比如冒号、斜杠等会导致js错误
|
||||
$this->queries[$lastIndex] = $lastQuery;
|
||||
}
|
||||
|
||||
public function addListener(Connection $db)
|
||||
{
|
||||
$db->listen(function (QueryExecuted $event) use ($db) {
|
||||
$connection = $event->connection;
|
||||
if (!$this->isEventConnectionEqual($db, $connection)) {
|
||||
return;
|
||||
}
|
||||
$bindings = $event->bindings;
|
||||
$time = $event->time;
|
||||
$query = $event->sql;
|
||||
|
||||
$threshold = $this->config['slow_threshold'];
|
||||
if (!$threshold || $time > $threshold) {
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->addQuery($query, $bindings, $time, $connection);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$db->getEventDispatcher()->listen(TransactionBeginning::class, function (TransactionBeginning $transaction) use ($db) {
|
||||
if (!$this->isEventConnectionEqual($db, $transaction->connection)) {
|
||||
return;
|
||||
}
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->collectTransactionEvent('Begin Transaction', $transaction->connection);
|
||||
}
|
||||
});
|
||||
|
||||
$db->getEventDispatcher()->listen(TransactionCommitted::class, function (TransactionCommitted $transaction) use ($db) {
|
||||
if (!$this->isEventConnectionEqual($db, $transaction->connection)) {
|
||||
return;
|
||||
}
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->collectTransactionEvent('Commit Transaction', $transaction->connection);
|
||||
}
|
||||
});
|
||||
|
||||
$db->getEventDispatcher()->listen(TransactionRolledBack::class, function (TransactionRolledBack $transaction) use ($db) {
|
||||
if (!$this->isEventConnectionEqual($db, $transaction->connection)) {
|
||||
return;
|
||||
}
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->collectTransactionEvent('Rollback Transaction', $transaction->connection);
|
||||
}
|
||||
});
|
||||
|
||||
$db->getEventDispatcher()->listen('connection.*.beganTransaction', function ($event, $params) {
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->collectTransactionEvent('Begin Transaction', $params[0]);
|
||||
}
|
||||
});
|
||||
|
||||
$db->getEventDispatcher()->listen('connection.*.committed', function ($event, $params) {
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->collectTransactionEvent('Commit Transaction', $params[0]);
|
||||
}
|
||||
});
|
||||
|
||||
$db->getEventDispatcher()->listen('connection.*.rollingBack', function ($event, $params) {
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->collectTransactionEvent('Rollback Transaction', $params[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 request 下每次新的当前 collector 对象
|
||||
* @return $this|null
|
||||
*/
|
||||
protected function getRequestThisCollector(): ?self
|
||||
{
|
||||
$debugBar = DebugBar::instance();
|
||||
if (!$debugBar->hasCollector($this->getName())) {
|
||||
return null;
|
||||
}
|
||||
/** @var static $collector */
|
||||
$collector = $debugBar->getCollector($this->getName());
|
||||
return $collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* 由于 event 可能是 Container 下的,则事件会重复绑定
|
||||
* 因此粗腰判断事件的 connection 是否是当前 DB 的
|
||||
* @param Connection $connection
|
||||
* @param Connection $eventConnection
|
||||
* @return bool
|
||||
*/
|
||||
protected function isEventConnectionEqual(Connection $connection, Connection $eventConnection): bool
|
||||
{
|
||||
return $connection->getName() === $eventConnection->getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use Illuminate\Redis\Connections\Connection;
|
||||
use Illuminate\Redis\Events\CommandExecuted;
|
||||
use Illuminate\Support\Str;
|
||||
use WebmanTech\Debugbar\Laravel\DataFormatter\QueryFormatter;
|
||||
|
||||
class LaravelRedisCollector extends LaravelQueryCollector
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'with_params' => true, // 将参数绑定上
|
||||
'backtrace' => true, // 显示 sql 来源
|
||||
'backtrace_exclude_paths' => [], // 排除 sql 来源
|
||||
'timeline' => true, // 将 sql 显示到 timeline 上
|
||||
'duration_background' => true, // 相对于执行所需的时间,在每个查询上显示阴影背景
|
||||
'show_copy' => true, // 显示复制
|
||||
'slow_threshold' => false, // 仅显示慢执行,设置毫秒时间来启用
|
||||
];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function getName()
|
||||
{
|
||||
return 'laravelRedis';
|
||||
}
|
||||
|
||||
protected function setConfig()
|
||||
{
|
||||
$this->mergeBacktraceExcludePaths([
|
||||
'/webman-debugbar/',
|
||||
'/vendor/webman-tech/debugbar',
|
||||
'/vendor/illuminate/support',
|
||||
'/vendor/illuminate/redis',
|
||||
'/vendor/illuminate/events',
|
||||
'/support/Redis.php',
|
||||
]);
|
||||
|
||||
$this->setDataFormatter(new QueryFormatter());
|
||||
|
||||
if ($this->config['with_params']) {
|
||||
$this->setRenderSqlWithParams(true);
|
||||
}
|
||||
|
||||
if ($this->config['backtrace']) {
|
||||
$middleware = [];
|
||||
$this->setFindSource(true, $middleware);
|
||||
}
|
||||
|
||||
if ($this->config['backtrace_exclude_paths']) {
|
||||
$excludePaths = $this->config['backtrace_exclude_paths'];
|
||||
$this->mergeBacktraceExcludePaths($excludePaths);
|
||||
}
|
||||
|
||||
$this->setDurationBackground($this->config['duration_background']);
|
||||
|
||||
$this->setExplainSource(false, null);
|
||||
|
||||
$this->setShowHints(false);
|
||||
|
||||
if ($this->config['show_copy']) {
|
||||
$this->setShowCopyButton(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function addRedisListener(Connection $connection): void
|
||||
{
|
||||
$connection->listen(function (CommandExecuted $event) {
|
||||
$command = $event->command;
|
||||
$connection = $event->connection;
|
||||
$parameters = $event->parameters;
|
||||
$time = $event->time;
|
||||
|
||||
if ($collector = $this->getRequestThisCollector()) {
|
||||
$collector->addExec($command, $parameters, $time, $connection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $command
|
||||
* @param array $parameters
|
||||
* @param float $time
|
||||
* @param Connection $connection
|
||||
* @see addQuery
|
||||
*/
|
||||
protected function addExec(string $command, array $parameters, float $time, Connection $connection)
|
||||
{
|
||||
$explainResults = [];
|
||||
$time = $time / 1000;
|
||||
$endTime = microtime(true);
|
||||
$startTime = $endTime - $time;
|
||||
|
||||
if (!empty($parameters) && $this->renderSqlWithParams) {
|
||||
foreach ($parameters as &$item) {
|
||||
if (is_array($item)) {
|
||||
$item = implode('\', \'', $item);
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
$command .= '(' . implode(', ', $parameters) . ')';
|
||||
}
|
||||
|
||||
$source = [];
|
||||
|
||||
if ($this->findSource) {
|
||||
try {
|
||||
$source = array_slice($this->findSource(), 0, 5);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$this->queries[] = [
|
||||
'query' => $command,
|
||||
'type' => 'query',
|
||||
'bindings' => $this->getDataFormatter()->escapeBindings($parameters),
|
||||
'time' => $time,
|
||||
'source' => $source,
|
||||
'explain' => $explainResults,
|
||||
'connection' => $connection->getName(),
|
||||
'driver' => get_class($connection->client()),
|
||||
'hints' => null,
|
||||
'show_copy' => $this->showCopyButton,
|
||||
];
|
||||
|
||||
if ($this->timeCollector !== null) {
|
||||
$this->timeCollector->addMeasure(Str::limit($command, 100), $startTime, $endTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\MemoryCollector as OriginMemoryCollector;
|
||||
|
||||
class MemoryCollector extends OriginMemoryCollector
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getWidgets()
|
||||
{
|
||||
return array(
|
||||
"memory" => array(
|
||||
"icon" => "cogs",
|
||||
"tooltip" => "内存使用: 仅表示本次启动后的最大内存",
|
||||
"map" => "memory.peak_usage_str",
|
||||
"default" => "'0B'"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\PhpInfoCollector as OriginPhpInfoCollector;
|
||||
|
||||
class PhpInfoCollector extends OriginPhpInfoCollector
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWidgets()
|
||||
{
|
||||
$widgets = parent::getWidgets();
|
||||
$widgets['php_version']['tooltip'] = 'PHP Version';
|
||||
return $widgets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\RequestDataCollector as OriginRequestDataCollector;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class RequestDataCollector extends OriginRequestDataCollector
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
public function __construct(Request $request, Response $response)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
$request = $this->request;
|
||||
$response = $this->response;
|
||||
$data = [
|
||||
'app' => $request->app,
|
||||
'controller' => $request->controller,
|
||||
'action' => $request->action,
|
||||
'method' => $request->method(),
|
||||
'host' => $request->host(),
|
||||
'uri' => $request->uri(),
|
||||
'path' => $request->path(),
|
||||
'get' => $request->get(),
|
||||
'post' => $request->post(),
|
||||
'header' => $request->header(),
|
||||
'cookie' => $request->cookie(),
|
||||
'session' => $request->session()->all(),
|
||||
'server' => $_SERVER,
|
||||
'response_status_code' => $response->getStatusCode(),
|
||||
'response_reason_phrase' => $response->getReasonPhrase(),
|
||||
'response_headers' => $response->getHeaders(),
|
||||
];
|
||||
return array_map(function ($item) {
|
||||
if ($this->isHtmlVarDumperUsed()) {
|
||||
$item = $this->getVarDumper()->renderVar($item);
|
||||
} else {
|
||||
$item = $this->getDataFormatter()->formatVar($item);
|
||||
}
|
||||
return $item;
|
||||
}, $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\DataCollector;
|
||||
use DebugBar\DataCollector\DataCollectorInterface;
|
||||
use DebugBar\DataCollector\Renderable;
|
||||
|
||||
class RouteCollector extends DataCollector implements DataCollectorInterface, Renderable
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function collect()
|
||||
{
|
||||
$request = request();
|
||||
$data = [];
|
||||
if ($route = $request->route) {
|
||||
$data = [
|
||||
'uri' => $request->method() . ' ' . $request->uri(),
|
||||
'controller' => $request->controller,
|
||||
'action' => $request->action,
|
||||
'name' => $route->getName(),
|
||||
'path' => $route->getPath(),
|
||||
'methods' => $route->getMethods(),
|
||||
'middleware' => $route->getMiddleware(),
|
||||
];
|
||||
}
|
||||
foreach ($data as &$value) {
|
||||
if (!is_string($value)) {
|
||||
$value = $this->getDataFormatter()->formatVar($value);
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function getName()
|
||||
{
|
||||
return 'route';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function getWidgets()
|
||||
{
|
||||
return [
|
||||
"route" => [
|
||||
"icon" => "share",
|
||||
"widget" => "PhpDebugBar.Widgets.VariableListWidget",
|
||||
"map" => "route",
|
||||
"default" => "{}"
|
||||
],
|
||||
"currentRoute" => [
|
||||
"icon" => "share",
|
||||
"tooltip" => "Route",
|
||||
"map" => "route.uri",
|
||||
"default" => ""
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\DataCollector;
|
||||
use DebugBar\DataCollector\DataCollectorInterface;
|
||||
use DebugBar\DataCollector\Renderable;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class SessionCollector extends DataCollector implements DataCollectorInterface, Renderable
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function collect()
|
||||
{
|
||||
$data = [];
|
||||
foreach ($this->request->session()->all() as $key => $value) {
|
||||
$data[$key] = is_string($value) ? $value : $this->getDataFormatter()->formatVar($value);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function getName()
|
||||
{
|
||||
return 'session';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function getWidgets()
|
||||
{
|
||||
return [
|
||||
"session" => [
|
||||
"icon" => "archive",
|
||||
"widget" => "PhpDebugBar.Widgets.VariableListWidget",
|
||||
"map" => "session",
|
||||
"default" => "{}"
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\TimeDataCollector as OriginTimeDataCollector;
|
||||
|
||||
class TimeDataCollector extends OriginTimeDataCollector
|
||||
{
|
||||
public const REQUEST_KEY = '_debugbar_request_time';
|
||||
|
||||
public function __construct($requestStartTime = null)
|
||||
{
|
||||
if ($requestStartTime === null) {
|
||||
if ($request = request()) {
|
||||
if (!$request->{static::REQUEST_KEY}) {
|
||||
$request->{static::REQUEST_KEY} = microtime(true);
|
||||
}
|
||||
$requestStartTime = $request->{static::REQUEST_KEY};
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($requestStartTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\DataCollector;
|
||||
use DebugBar\DataCollector\Renderable;
|
||||
|
||||
class WebmanCollector extends DataCollector implements Renderable
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
return [
|
||||
"version" => defined('WEBMAN_VERSION') ? WEBMAN_VERSION : 'Undefined',
|
||||
"environment" => config('app.debug', false) ? 'debug' : 'prod',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'webman';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWidgets()
|
||||
{
|
||||
return [
|
||||
"version" => [
|
||||
"icon" => "github",
|
||||
"tooltip" => "Webman Version",
|
||||
"map" => "webman.version",
|
||||
"default" => ""
|
||||
],
|
||||
"environment" => [
|
||||
"icon" => "desktop",
|
||||
"tooltip" => "Environment",
|
||||
"map" => "webman.environment",
|
||||
"default" => ""
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar;
|
||||
|
||||
use Closure;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* @method static void addThrowable(Throwable $e)
|
||||
* @method static void addMessage($message, string $type = 'info')
|
||||
* @method static void startMeasure(string $name, string $label = null)
|
||||
* @method static void stopMeasure(string $name)
|
||||
* @method static void addMeasure(string $label, float $start, float $end)
|
||||
* @method static mixed measure(string $label, Closure $closure)
|
||||
*/
|
||||
class DebugBar
|
||||
{
|
||||
public const REQUEST_KEY = '_debugbar_request_instance';
|
||||
|
||||
/**
|
||||
* @var null|WebmanDebugBar
|
||||
*/
|
||||
protected static $_instance = null;
|
||||
|
||||
public static function instance(): WebmanDebugBar
|
||||
{
|
||||
$request = request();
|
||||
// 非 request 请求使用一个实例
|
||||
if (!$request) {
|
||||
if (!static::$_instance) {
|
||||
$config = config('plugin.webman-tech.debugbar.app.debugbar', []);
|
||||
static::$_instance = static::createDebugBar($config);
|
||||
}
|
||||
return static::$_instance;
|
||||
}
|
||||
|
||||
// 每个 request 请求单独创建一个实例
|
||||
if (!$request->{static::REQUEST_KEY}) {
|
||||
$config = config('plugin.webman-tech.debugbar.app.debugbar', []);
|
||||
$request->{static::REQUEST_KEY} = static::createDebugBar($config);
|
||||
}
|
||||
return $request->{static::REQUEST_KEY};
|
||||
}
|
||||
|
||||
protected static function createDebugBar(array $config): WebmanDebugBar
|
||||
{
|
||||
return new WebmanDebugBar($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public static function __callStatic($name, $arguments)
|
||||
{
|
||||
return static::instance()->{$name}(... $arguments);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Ext;
|
||||
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class HttpExt
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
public function __construct(Request $request, Response $response)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是重定向
|
||||
* @return bool
|
||||
*/
|
||||
public function isRedirection(): bool
|
||||
{
|
||||
return $this->response->getStatusCode() >= 300 && $this->response->getStatusCode() < 400;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否 accept html
|
||||
* @return bool
|
||||
*/
|
||||
public function isHtmlAccepted(): bool
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$header = $this->request->header('Accept');
|
||||
if (is_array($header)) {
|
||||
$header = implode(', ', $header);
|
||||
}
|
||||
$is = strpos($header, 'text/html') !== false;
|
||||
if ($is) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应的是否是 html
|
||||
* @return bool
|
||||
*/
|
||||
public function isHtmlResponse(): bool
|
||||
{
|
||||
if ($header = $this->response->getHeader('Content-Type')) {
|
||||
if (is_array($header)) {
|
||||
$header = implode(';', $header);
|
||||
}
|
||||
$is = strpos($header, 'text/html') !== false;
|
||||
if ($is) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (strpos($this->response->rawBody(), '<html') === 0 || strpos($this->response->rawBody(), '<body') !== false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Helper;
|
||||
|
||||
class ArrayHelper
|
||||
{
|
||||
/**
|
||||
* 合并数组
|
||||
* @link https://github.com/yiisoft/arrays/blob/master/src/ArrayHelper.php::merge
|
||||
* @param ...$arrays
|
||||
* @return array
|
||||
*/
|
||||
public static function merge(...$arrays): array
|
||||
{
|
||||
$result = array_shift($arrays) ?: [];
|
||||
while (!empty($arrays)) {
|
||||
/** @var mixed $value */
|
||||
foreach (array_shift($arrays) as $key => $value) {
|
||||
if (is_int($key)) {
|
||||
if (array_key_exists($key, $result)) {
|
||||
if ($result[$key] !== $value) {
|
||||
/** @var mixed */
|
||||
$result[] = $value;
|
||||
}
|
||||
} else {
|
||||
/** @var mixed */
|
||||
$result[$key] = $value;
|
||||
}
|
||||
} elseif (isset($result[$key]) && is_array($value) && is_array($result[$key])) {
|
||||
$result[$key] = static::merge($result[$key], $value);
|
||||
} else {
|
||||
/** @var mixed */
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Helper;
|
||||
|
||||
class StringHelper
|
||||
{
|
||||
/**
|
||||
* 通配符匹配
|
||||
*
|
||||
* - `\` 用于转译
|
||||
* - `*` 匹配所有字符,包括空格
|
||||
* - `?` 匹配任意单个字符
|
||||
* - `[seq]` 匹配所有在中括号内的字符
|
||||
* - `[a-z]` 匹配从 a 到 z
|
||||
* - `[!seq]` 匹配所有不在中括号内的字符
|
||||
*
|
||||
* @param string $pattern 匹配规则
|
||||
* @param string $string 匹配内容
|
||||
* @param array $options 选项
|
||||
*
|
||||
* - caseSensitive: bool, 大小写敏感,默认为 true
|
||||
* - escape: bool, 允许 \ 转译,默认为 true
|
||||
*
|
||||
* @return bool 是否匹配成功
|
||||
*/
|
||||
public static function matchWildcard(string $pattern, string $string, array $options = []): bool
|
||||
{
|
||||
if ($pattern === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$replacements = [
|
||||
'\\\\\\\\' => '\\\\',
|
||||
'\\\\\\*' => '[*]',
|
||||
'\\\\\\?' => '[?]',
|
||||
'\*' => '.*',
|
||||
'\?' => '.',
|
||||
'\[\!' => '[^',
|
||||
'\[' => '[',
|
||||
'\]' => ']',
|
||||
'\-' => '-',
|
||||
];
|
||||
|
||||
if (isset($options['escape']) && !$options['escape']) {
|
||||
unset($replacements['\\\\\\\\'], $replacements['\\\\\\*'], $replacements['\\\\\\?']);
|
||||
}
|
||||
|
||||
$pattern = strtr(preg_quote($pattern, '#'), $replacements);
|
||||
$pattern = '#^' . $pattern . '$#us';
|
||||
|
||||
if (isset($options['caseSensitive']) && !$options['caseSensitive']) {
|
||||
$pattern .= 'i';
|
||||
}
|
||||
|
||||
return preg_match($pattern, $string) === 1;
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
namespace WebmanTech\Debugbar;
|
||||
|
||||
class Install
|
||||
{
|
||||
const WEBMAN_PLUGIN = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $pathRelation = array (
|
||||
'config/plugin/webman-tech/debugbar' => 'config/plugin/webman-tech/debugbar',
|
||||
);
|
||||
|
||||
/**
|
||||
* Install
|
||||
* @return void
|
||||
*/
|
||||
public static function install()
|
||||
{
|
||||
static::installByRelation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall
|
||||
* @return void
|
||||
*/
|
||||
public static function uninstall()
|
||||
{
|
||||
self::uninstallByRelation();
|
||||
}
|
||||
|
||||
/**
|
||||
* installByRelation
|
||||
* @return void
|
||||
*/
|
||||
public static function installByRelation()
|
||||
{
|
||||
foreach (static::$pathRelation as $source => $dest) {
|
||||
if ($pos = strrpos($dest, '/')) {
|
||||
$parent_dir = base_path().'/'.substr($dest, 0, $pos);
|
||||
if (!is_dir($parent_dir)) {
|
||||
mkdir($parent_dir, 0777, true);
|
||||
}
|
||||
}
|
||||
//symlink(__DIR__ . "/$source", base_path()."/$dest");
|
||||
copy_dir(__DIR__ . "/$source", base_path()."/$dest");
|
||||
echo "Create $dest
|
||||
";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* uninstallByRelation
|
||||
* @return void
|
||||
*/
|
||||
public static function uninstallByRelation()
|
||||
{
|
||||
foreach (static::$pathRelation as $source => $dest) {
|
||||
$path = base_path()."/$dest";
|
||||
if (!is_dir($path) && !is_file($path)) {
|
||||
continue;
|
||||
}
|
||||
echo "Remove $dest
|
||||
";
|
||||
if (is_file($path) || is_link($path)) {
|
||||
unlink($path);
|
||||
continue;
|
||||
}
|
||||
remove_dir($path);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Laravel\DataCollector;
|
||||
|
||||
use DebugBar\DataCollector\PDO\PDOCollector;
|
||||
use DebugBar\DataCollector\TimeDataCollector;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @link https://github.com/barryvdh/laravel-debugbar/blob/master/src/DataCollector/QueryCollector.php
|
||||
*
|
||||
* Collects data about SQL statements executed with PDO
|
||||
*/
|
||||
class QueryCollector extends PDOCollector
|
||||
{
|
||||
protected $timeCollector;
|
||||
protected $queries = [];
|
||||
protected $renderSqlWithParams = false;
|
||||
protected $findSource = false;
|
||||
protected $middleware = [];
|
||||
protected $durationBackground = true;
|
||||
protected $explainQuery = false;
|
||||
protected $explainTypes = ['SELECT']; // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+
|
||||
protected $showHints = false;
|
||||
protected $showCopyButton = false;
|
||||
protected $reflection = [];
|
||||
protected $backtraceExcludePaths = [
|
||||
'/vendor/laravel/framework/src/Illuminate/Support',
|
||||
'/vendor/laravel/framework/src/Illuminate/Database',
|
||||
'/vendor/laravel/framework/src/Illuminate/Events',
|
||||
'/vendor/october/rain',
|
||||
'/vendor/barryvdh/laravel-debugbar',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param TimeDataCollector $timeCollector
|
||||
*/
|
||||
public function __construct(TimeDataCollector $timeCollector = null)
|
||||
{
|
||||
$this->timeCollector = $timeCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the SQL of traced statements with params embedded
|
||||
*
|
||||
* @param boolean $enabled
|
||||
* @param string $quotationChar NOT USED
|
||||
*/
|
||||
public function setRenderSqlWithParams($enabled = true, $quotationChar = "'")
|
||||
{
|
||||
$this->renderSqlWithParams = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide the hints in the parameters
|
||||
*
|
||||
* @param boolean $enabled
|
||||
*/
|
||||
public function setShowHints($enabled = true)
|
||||
{
|
||||
$this->showHints = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide copy button next to the queries
|
||||
*
|
||||
* @param boolean $enabled
|
||||
*/
|
||||
public function setShowCopyButton($enabled = true)
|
||||
{
|
||||
$this->showCopyButton = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable finding the source
|
||||
*
|
||||
* @param bool $value
|
||||
* @param array $middleware
|
||||
*/
|
||||
public function setFindSource($value, array $middleware)
|
||||
{
|
||||
$this->findSource = (bool) $value;
|
||||
$this->middleware = $middleware;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set additional paths to exclude from the backtrace
|
||||
*
|
||||
* @param array $excludePaths Array of file paths to exclude from backtrace
|
||||
*/
|
||||
public function mergeBacktraceExcludePaths(array $excludePaths)
|
||||
{
|
||||
$this->backtraceExcludePaths = array_merge($this->backtraceExcludePaths, $excludePaths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable the shaded duration background on queries
|
||||
*
|
||||
* @param bool $enabled
|
||||
*/
|
||||
public function setDurationBackground($enabled)
|
||||
{
|
||||
$this->durationBackground = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable the EXPLAIN queries
|
||||
*
|
||||
* @param bool $enabled
|
||||
* @param array|null $types Array of types to explain queries (select/insert/update/delete)
|
||||
*/
|
||||
public function setExplainSource($enabled, $types)
|
||||
{
|
||||
$this->explainQuery = $enabled;
|
||||
// workaround ['SELECT'] only. https://github.com/barryvdh/laravel-debugbar/issues/888
|
||||
// if($types){
|
||||
// $this->explainTypes = $types;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $query
|
||||
* @param array $bindings
|
||||
* @param float $time
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
*/
|
||||
public function addQuery($query, $bindings, $time, $connection)
|
||||
{
|
||||
$explainResults = [];
|
||||
$time = $time / 1000;
|
||||
$endTime = microtime(true);
|
||||
$startTime = $endTime - $time;
|
||||
$hints = $this->performQueryAnalysis($query);
|
||||
|
||||
$pdo = null;
|
||||
try {
|
||||
$pdo = $connection->getPdo();
|
||||
} catch (\Exception $e) {
|
||||
// ignore error for non-pdo laravel drivers
|
||||
}
|
||||
$bindings = $connection->prepareBindings($bindings);
|
||||
|
||||
// Run EXPLAIN on this query (if needed)
|
||||
if ($this->explainQuery && $pdo && preg_match('/^\s*(' . implode('|', $this->explainTypes) . ') /i', $query)) {
|
||||
$statement = $pdo->prepare('EXPLAIN ' . $query);
|
||||
$statement->execute($bindings);
|
||||
$explainResults = $statement->fetchAll(\PDO::FETCH_CLASS);
|
||||
}
|
||||
|
||||
$bindings = $this->getDataFormatter()->checkBindings($bindings);
|
||||
if (!empty($bindings) && $this->renderSqlWithParams) {
|
||||
foreach ($bindings as $key => $binding) {
|
||||
// This regex matches placeholders only, not the question marks,
|
||||
// nested in quotes, while we iterate through the bindings
|
||||
// and substitute placeholders by suitable values.
|
||||
$regex = is_numeric($key)
|
||||
? "/(?<!\?)\?(?=(?:[^'\\\']*'[^'\\']*')*[^'\\\']*$)(?!\?)/"
|
||||
: "/:{$key}(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/";
|
||||
|
||||
// Mimic bindValue and only quote non-integer and non-float data types
|
||||
if (!is_int($binding) && !is_float($binding)) {
|
||||
if ($pdo) {
|
||||
try {
|
||||
$binding = $pdo->quote((string) $binding);
|
||||
} catch (\Exception $e) {
|
||||
$binding = $this->emulateQuote($binding);
|
||||
}
|
||||
} else {
|
||||
$binding = $this->emulateQuote($binding);
|
||||
}
|
||||
}
|
||||
|
||||
$query = preg_replace($regex, addcslashes($binding, '$'), $query, 1);
|
||||
}
|
||||
}
|
||||
|
||||
$source = [];
|
||||
|
||||
if ($this->findSource) {
|
||||
try {
|
||||
$source = array_slice($this->findSource(), 0, 5);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$this->queries[] = [
|
||||
'query' => $query,
|
||||
'type' => 'query',
|
||||
'bindings' => $this->getDataFormatter()->escapeBindings($bindings),
|
||||
'time' => $time,
|
||||
'source' => $source,
|
||||
'explain' => $explainResults,
|
||||
'connection' => $connection->getDatabaseName(),
|
||||
'driver' => $connection->getConfig('driver'),
|
||||
'hints' => $this->showHints ? $hints : null,
|
||||
'show_copy' => $this->showCopyButton,
|
||||
];
|
||||
|
||||
if ($this->timeCollector !== null) {
|
||||
$this->timeCollector->addMeasure(Str::limit($query, 100), $startTime, $endTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimic mysql_real_escape_string
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
protected function emulateQuote($value)
|
||||
{
|
||||
$search = ["\\", "\x00", "\n", "\r", "'", '"', "\x1a"];
|
||||
$replace = ["\\\\","\\0","\\n", "\\r", "\'", '\"', "\\Z"];
|
||||
|
||||
return "'" . str_replace($search, $replace, (string) $value) . "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Explainer::performQueryAnalysis()
|
||||
*
|
||||
* Perform simple regex analysis on the code
|
||||
*
|
||||
* @package xplain (https://github.com/rap2hpoutre/mysql-xplain-xplain)
|
||||
* @author e-doceo
|
||||
* @copyright 2014
|
||||
* @version $Id$
|
||||
* @access public
|
||||
* @param string $query
|
||||
* @return string[]
|
||||
*/
|
||||
protected function performQueryAnalysis($query)
|
||||
{
|
||||
// @codingStandardsIgnoreStart
|
||||
$hints = [];
|
||||
if (preg_match('/^\\s*SELECT\\s*`?[a-zA-Z0-9]*`?\\.?\\*/i', $query)) {
|
||||
$hints[] = 'Use <code>SELECT *</code> only if you need all columns from table';
|
||||
}
|
||||
if (preg_match('/ORDER BY RAND()/i', $query)) {
|
||||
$hints[] = '<code>ORDER BY RAND()</code> is slow, try to avoid if you can.
|
||||
You can <a href="http://stackoverflow.com/questions/2663710/how-does-mysqls-order-by-rand-work" target="_blank">read this</a>
|
||||
or <a href="http://stackoverflow.com/questions/1244555/how-can-i-optimize-mysqls-order-by-rand-function" target="_blank">this</a>';
|
||||
}
|
||||
if (strpos($query, '!=') !== false) {
|
||||
$hints[] = 'The <code>!=</code> operator is not standard. Use the <code><></code> operator to test for inequality instead.';
|
||||
}
|
||||
if (stripos($query, 'WHERE') === false && preg_match('/^(SELECT) /i', $query)) {
|
||||
$hints[] = 'The <code>SELECT</code> statement has no <code>WHERE</code> clause and could examine many more rows than intended';
|
||||
}
|
||||
if (preg_match('/LIMIT\\s/i', $query) && stripos($query, 'ORDER BY') === false) {
|
||||
$hints[] = '<code>LIMIT</code> without <code>ORDER BY</code> causes non-deterministic results, depending on the query execution plan';
|
||||
}
|
||||
if (preg_match('/LIKE\\s[\'"](%.*?)[\'"]/i', $query, $matches)) {
|
||||
$hints[] = 'An argument has a leading wildcard character: <code>' . $matches[1] . '</code>.
|
||||
The predicate with this argument is not sargable and cannot use an index if one exists.';
|
||||
}
|
||||
return $hints;
|
||||
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a backtrace to search for the origins of the query.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function findSource()
|
||||
{
|
||||
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT, 50);
|
||||
|
||||
$sources = [];
|
||||
|
||||
foreach ($stack as $index => $trace) {
|
||||
$sources[] = $this->parseTrace($index, $trace);
|
||||
}
|
||||
|
||||
return array_filter($sources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a trace element from the backtrace stack.
|
||||
*
|
||||
* @param int $index
|
||||
* @param array $trace
|
||||
* @return object|bool
|
||||
*/
|
||||
protected function parseTrace($index, array $trace)
|
||||
{
|
||||
$frame = (object) [
|
||||
'index' => $index,
|
||||
'namespace' => null,
|
||||
'name' => null,
|
||||
'line' => isset($trace['line']) ? $trace['line'] : '?',
|
||||
];
|
||||
|
||||
if (isset($trace['function']) && $trace['function'] == 'substituteBindings') {
|
||||
$frame->name = 'Route binding';
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
if (
|
||||
isset($trace['class']) &&
|
||||
isset($trace['file']) &&
|
||||
!$this->fileIsInExcludedPath($trace['file'])
|
||||
) {
|
||||
$file = $trace['file'];
|
||||
|
||||
if (isset($trace['object']) && is_a($trace['object'], 'Twig_Template')) {
|
||||
list($file, $frame->line) = $this->getTwigInfo($trace);
|
||||
} elseif (strpos($file, base_path() . '/storage') !== false) {
|
||||
$hash = pathinfo($file, PATHINFO_FILENAME);
|
||||
|
||||
if (! $frame->name = $this->findViewFromHash($hash)) {
|
||||
$frame->name = $hash;
|
||||
}
|
||||
|
||||
$frame->namespace = 'view';
|
||||
|
||||
return $frame;
|
||||
} elseif (strpos($file, 'Middleware') !== false) {
|
||||
$frame->name = $this->findMiddlewareFromFile($file);
|
||||
|
||||
if ($frame->name) {
|
||||
$frame->namespace = 'middleware';
|
||||
} else {
|
||||
$frame->name = $this->normalizeFilename($file);
|
||||
}
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
$frame->name = $this->normalizeFilename($file);
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given file is to be excluded from analysis
|
||||
*
|
||||
* @param string $file
|
||||
* @return bool
|
||||
*/
|
||||
protected function fileIsInExcludedPath($file)
|
||||
{
|
||||
$normalizedPath = str_replace('\\', '/', $file);
|
||||
|
||||
foreach ($this->backtraceExcludePaths as $excludedPath) {
|
||||
if (strpos($normalizedPath, $excludedPath) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the middleware alias from the file.
|
||||
*
|
||||
* @param string $file
|
||||
* @return string|null
|
||||
*/
|
||||
protected function findMiddlewareFromFile($file)
|
||||
{
|
||||
$filename = pathinfo($file, PATHINFO_FILENAME);
|
||||
|
||||
foreach ($this->middleware as $alias => $class) {
|
||||
if (strpos($class, $filename) !== false) {
|
||||
return $alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the template name from the hash.
|
||||
*
|
||||
* @param string $hash
|
||||
* @return null|string
|
||||
*/
|
||||
protected function findViewFromHash($hash)
|
||||
{
|
||||
return null;
|
||||
|
||||
$finder = app('view')->getFinder();
|
||||
|
||||
if (isset($this->reflection['viewfinderViews'])) {
|
||||
$property = $this->reflection['viewfinderViews'];
|
||||
} else {
|
||||
$reflection = new \ReflectionClass($finder);
|
||||
$property = $reflection->getProperty('views');
|
||||
$property->setAccessible(true);
|
||||
$this->reflection['viewfinderViews'] = $property;
|
||||
}
|
||||
|
||||
foreach ($property->getValue($finder) as $name => $path) {
|
||||
if (sha1($path) == $hash || md5($path) == $hash) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename/line from a Twig template trace
|
||||
*
|
||||
* @param array $trace
|
||||
* @return array The file and line
|
||||
*/
|
||||
protected function getTwigInfo($trace)
|
||||
{
|
||||
$file = $trace['object']->getTemplateName();
|
||||
|
||||
if (isset($trace['line'])) {
|
||||
foreach ($trace['object']->getDebugInfo() as $codeLine => $templateLine) {
|
||||
if ($codeLine <= $trace['line']) {
|
||||
return [$file, $templateLine];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$file, -1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten the path by removing the relative links and base dir
|
||||
*
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeFilename($path)
|
||||
{
|
||||
if (file_exists($path)) {
|
||||
$path = realpath($path);
|
||||
}
|
||||
return str_replace(base_path(), '', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect a database transaction event.
|
||||
* @param string $event
|
||||
* @param \Illuminate\Database\Connection $connection
|
||||
* @return array
|
||||
*/
|
||||
public function collectTransactionEvent($event, $connection)
|
||||
{
|
||||
$source = [];
|
||||
|
||||
if ($this->findSource) {
|
||||
try {
|
||||
$source = $this->findSource();
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$this->queries[] = [
|
||||
'query' => $event,
|
||||
'type' => 'transaction',
|
||||
'bindings' => [],
|
||||
'time' => 0,
|
||||
'source' => $source,
|
||||
'explain' => [],
|
||||
'connection' => $connection->getDatabaseName(),
|
||||
'driver' => $connection->getConfig('driver'),
|
||||
'hints' => null,
|
||||
'show_copy' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the queries.
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->queries = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
$totalTime = 0;
|
||||
$queries = $this->queries;
|
||||
|
||||
$statements = [];
|
||||
foreach ($queries as $query) {
|
||||
$totalTime += $query['time'];
|
||||
|
||||
$statements[] = [
|
||||
'sql' => $this->getDataFormatter()->formatSql($query['query']),
|
||||
'type' => $query['type'],
|
||||
'params' => [],
|
||||
'bindings' => $query['bindings'],
|
||||
'hints' => $query['hints'],
|
||||
'show_copy' => $query['show_copy'],
|
||||
'backtrace' => array_values($query['source']),
|
||||
'duration' => $query['time'],
|
||||
'duration_str' => ($query['type'] == 'transaction') ? '' : $this->formatDuration($query['time']),
|
||||
'stmt_id' => $this->getDataFormatter()->formatSource(reset($query['source'])),
|
||||
'connection' => $query['connection'],
|
||||
];
|
||||
|
||||
// Add the results from the explain as new rows
|
||||
if ($query['driver'] === 'pgsql') {
|
||||
$explainer = trim(implode("\n", array_map(function ($explain) {
|
||||
return $explain->{'QUERY PLAN'};
|
||||
}, $query['explain'])));
|
||||
|
||||
if ($explainer) {
|
||||
$statements[] = [
|
||||
'sql' => " - EXPLAIN: {$explainer}",
|
||||
'type' => 'explain',
|
||||
];
|
||||
}
|
||||
} else {
|
||||
foreach ($query['explain'] as $explain) {
|
||||
$statements[] = [
|
||||
'sql' => " - EXPLAIN # {$explain->id}: `{$explain->table}` ({$explain->select_type})",
|
||||
'type' => 'explain',
|
||||
'params' => $explain,
|
||||
'row_count' => $explain->rows,
|
||||
'stmt_id' => $explain->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->durationBackground) {
|
||||
if ($totalTime > 0) {
|
||||
// For showing background measure on Queries tab
|
||||
$start_percent = 0;
|
||||
|
||||
foreach ($statements as $i => $statement) {
|
||||
if (!isset($statement['duration'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$width_percent = $statement['duration'] / $totalTime * 100;
|
||||
|
||||
$statements[$i] = array_merge($statement, [
|
||||
'start_percent' => round($start_percent, 3),
|
||||
'width_percent' => round($width_percent, 3),
|
||||
]);
|
||||
|
||||
$start_percent += $width_percent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$nb_statements = array_filter($queries, function ($query) {
|
||||
return $query['type'] === 'query';
|
||||
});
|
||||
|
||||
$data = [
|
||||
'nb_statements' => count($nb_statements),
|
||||
'nb_failed_statements' => 0,
|
||||
'accumulated_duration' => $totalTime,
|
||||
'accumulated_duration_str' => $this->formatDuration($totalTime),
|
||||
'statements' => $statements
|
||||
];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'queries';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getWidgets()
|
||||
{
|
||||
return [
|
||||
"queries" => [
|
||||
"icon" => "database",
|
||||
"widget" => "PhpDebugBar.Widgets.LaravelSQLQueriesWidget",
|
||||
"map" => "queries",
|
||||
"default" => "[]"
|
||||
],
|
||||
"queries:badge" => [
|
||||
"map" => "queries.nb_statements",
|
||||
"default" => 0
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Laravel\DataFormatter;
|
||||
|
||||
use DebugBar\DataFormatter\DataFormatter;
|
||||
|
||||
/**
|
||||
* @link https://github.com/barryvdh/laravel-debugbar/blob/master/src/DataFormatter/QueryFormatter.php
|
||||
*/
|
||||
class QueryFormatter extends DataFormatter
|
||||
{
|
||||
/**
|
||||
* Removes extra spaces at the beginning and end of the SQL query and its lines.
|
||||
*
|
||||
* @param string $sql
|
||||
* @return string
|
||||
*/
|
||||
public function formatSql($sql)
|
||||
{
|
||||
$sql = preg_replace("/\?(?=(?:[^'\\\']*'[^'\\']*')*[^'\\\']*$)(?:\?)/", '?', $sql);
|
||||
$sql = trim(preg_replace("/\s*\n\s*/", "\n", $sql));
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check bindings for illegal (non UTF-8) strings, like Binary data.
|
||||
*
|
||||
* @param $bindings
|
||||
* @return mixed
|
||||
*/
|
||||
public function checkBindings($bindings)
|
||||
{
|
||||
foreach ($bindings as &$binding) {
|
||||
if (is_string($binding) && !mb_check_encoding($binding, 'UTF-8')) {
|
||||
$binding = '[BINARY DATA]';
|
||||
}
|
||||
|
||||
if (is_array($binding)) {
|
||||
$binding = $this->checkBindings($binding);
|
||||
$binding = '[' . implode(',', $binding) . ']';
|
||||
}
|
||||
|
||||
if (is_object($binding)) {
|
||||
$binding = json_encode($binding);
|
||||
}
|
||||
}
|
||||
|
||||
return $bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the bindings safe for outputting.
|
||||
*
|
||||
* @param array $bindings
|
||||
* @return array
|
||||
*/
|
||||
public function escapeBindings($bindings)
|
||||
{
|
||||
foreach ($bindings as &$binding) {
|
||||
$binding = htmlentities((string) $binding, ENT_QUOTES, 'UTF-8', false);
|
||||
}
|
||||
|
||||
return $bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a source object.
|
||||
*
|
||||
* @param object|null $source If the backtrace is disabled, the $source will be null.
|
||||
* @return string
|
||||
*/
|
||||
public function formatSource($source)
|
||||
{
|
||||
if (! is_object($source)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
|
||||
if ($source->namespace) {
|
||||
$parts['namespace'] = $source->namespace . '::';
|
||||
}
|
||||
|
||||
$parts['name'] = $source->name;
|
||||
$parts['line'] = ':' . $source->line;
|
||||
|
||||
return implode($parts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
该目录下的内容来自于 https://github.com/barryvdh/laravel-debugbar
|
||||
并针对性的做一些调整(不适用于 webman 的地方)
|
||||
@@ -0,0 +1,274 @@
|
||||
(function ($) {
|
||||
|
||||
var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');
|
||||
|
||||
/**
|
||||
* Widget for the displaying sql queries
|
||||
*
|
||||
* Options:
|
||||
* - data
|
||||
*/
|
||||
var LaravelSQLQueriesWidget = PhpDebugBar.Widgets.LaravelSQLQueriesWidget = PhpDebugBar.Widget.extend({
|
||||
|
||||
className: csscls('sqlqueries'),
|
||||
|
||||
onFilterClick: function (el) {
|
||||
$(el).toggleClass(csscls('excluded'));
|
||||
|
||||
var excludedLabels = [];
|
||||
this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function () {
|
||||
excludedLabels.push(this.rel);
|
||||
});
|
||||
|
||||
this.$list.$el.find("li[connection=" + $(el).attr("rel") + "]").toggle();
|
||||
|
||||
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);
|
||||
|
||||
this.$toolbar = $('<div></div>').addClass(csscls('toolbar')).appendTo(this.$el);
|
||||
|
||||
var filters = [], self = this;
|
||||
|
||||
this.$list = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function (li, stmt) {
|
||||
if (stmt.type === 'transaction') {
|
||||
$('<strong />').addClass(csscls('sql')).addClass(csscls('name')).text(stmt.sql).appendTo(li);
|
||||
} else {
|
||||
$('<code />').addClass(csscls('sql')).html(PhpDebugBar.Widgets.highlight(stmt.sql, 'sql')).appendTo(li);
|
||||
}
|
||||
if (stmt.width_percent) {
|
||||
$('<div></div>').addClass(csscls('bg-measure')).append(
|
||||
$('<div></div>').addClass(csscls('value')).css({
|
||||
left: stmt.start_percent + '%',
|
||||
width: Math.max(stmt.width_percent, 0.01) + '%',
|
||||
})
|
||||
).appendTo(li);
|
||||
}
|
||||
if (stmt.duration_str) {
|
||||
$('<span title="Duration" />').addClass(csscls('duration')).text(stmt.duration_str).appendTo(li);
|
||||
}
|
||||
if (stmt.memory_str) {
|
||||
$('<span title="Memory usage" />').addClass(csscls('memory')).text(stmt.memory_str).appendTo(li);
|
||||
}
|
||||
if (typeof(stmt.row_count) != 'undefined') {
|
||||
$('<span title="Row count" />').addClass(csscls('row-count')).text(stmt.row_count).appendTo(li);
|
||||
}
|
||||
if (typeof(stmt.stmt_id) != 'undefined' && stmt.stmt_id) {
|
||||
$('<span title="Prepared statement ID" />').addClass(csscls('stmt-id')).text(stmt.stmt_id).appendTo(li);
|
||||
}
|
||||
if (stmt.connection) {
|
||||
$('<span title="Connection" />').addClass(csscls('database')).text(stmt.connection).appendTo(li);
|
||||
li.attr("connection",stmt.connection);
|
||||
if ( $.inArray(stmt.connection, filters) == -1 ) {
|
||||
filters.push(stmt.connection);
|
||||
$('<a />')
|
||||
.addClass(csscls('filter'))
|
||||
.text(stmt.connection)
|
||||
.attr('rel', stmt.connection)
|
||||
.on('click', function () {
|
||||
self.onFilterClick(this); })
|
||||
.appendTo(self.$toolbar);
|
||||
if (filters.length > 1) {
|
||||
self.$toolbar.show();
|
||||
self.$list.$el.css("margin-bottom","20px");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof(stmt.is_success) != 'undefined' && !stmt.is_success) {
|
||||
li.addClass(csscls('error'));
|
||||
li.append($('<span />').addClass(csscls('error')).text("[" + stmt.error_code + "] " + stmt.error_message));
|
||||
}
|
||||
if (stmt.show_copy) {
|
||||
$('<span title="Copy to clipboard" />')
|
||||
.addClass(csscls('copy-clipboard'))
|
||||
.css('cursor', 'pointer')
|
||||
.on('click', function (event) {
|
||||
self.onCopyToClipboard(this);
|
||||
event.stopPropagation();
|
||||
})
|
||||
.appendTo(li);
|
||||
}
|
||||
|
||||
var table = $('<table><tr><th colspan="2">Metadata</th></tr></table>').addClass(csscls('params')).appendTo(li);
|
||||
|
||||
if (stmt.bindings && stmt.bindings.length) {
|
||||
table.append(function () {
|
||||
var icon = 'thumb-tack';
|
||||
var $icon = '<i class="phpdebugbar-fa phpdebugbar-fa-' + icon + ' phpdebugbar-text-muted"></i>';
|
||||
var $name = $('<td />').addClass(csscls('name')).html('Bindings ' + $icon);
|
||||
var $value = $('<td />').addClass(csscls('value'));
|
||||
var $span = $('<span />').addClass('phpdebugbar-text-muted');
|
||||
|
||||
var index = 0;
|
||||
var $bindings = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function (li, binding) {
|
||||
var $index = $span.clone().text(index++ + '.');
|
||||
li.append($index, ' ', binding).removeClass(csscls('list-item')).addClass(csscls('table-list-item'));
|
||||
}});
|
||||
|
||||
$bindings.set('data', stmt.bindings);
|
||||
|
||||
$bindings.$el
|
||||
.removeClass(csscls('list'))
|
||||
.addClass(csscls('table-list'))
|
||||
.appendTo($value);
|
||||
|
||||
return $('<tr />').append($name, $value);
|
||||
});
|
||||
}
|
||||
|
||||
if (stmt.hints && stmt.hints.length) {
|
||||
table.append(function () {
|
||||
var icon = 'question-circle';
|
||||
var $icon = '<i class="phpdebugbar-fa phpdebugbar-fa-' + icon + ' phpdebugbar-text-muted"></i>';
|
||||
var $name = $('<td />').addClass(csscls('name')).html('Hints ' + $icon);
|
||||
var $value = $('<td />').addClass(csscls('value'));
|
||||
|
||||
var $hints = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function (li, hint) {
|
||||
li.append(hint).removeClass(csscls('list-item')).addClass(csscls('table-list-item'));
|
||||
}});
|
||||
|
||||
$hints.set('data', stmt.hints);
|
||||
$hints.$el
|
||||
.removeClass(csscls('list'))
|
||||
.addClass(csscls('table-list'))
|
||||
.appendTo($value);
|
||||
|
||||
return $('<tr />').append($name, $value);
|
||||
});
|
||||
}
|
||||
|
||||
if (stmt.backtrace && stmt.backtrace.length) {
|
||||
table.append(function () {
|
||||
var icon = 'list-ul';
|
||||
var $icon = '<i class="phpdebugbar-fa phpdebugbar-fa-' + icon + ' phpdebugbar-text-muted"></i>';
|
||||
var $name = $('<td />').addClass(csscls('name')).html('Backtrace ' + $icon);
|
||||
var $value = $('<td />').addClass(csscls('value'));
|
||||
var $span = $('<span />').addClass('phpdebugbar-text-muted');
|
||||
|
||||
var $backtrace = new PhpDebugBar.Widgets.ListWidget({ itemRenderer: function (li, source) {
|
||||
var $parts = [
|
||||
$span.clone().text(source.index + '.'),
|
||||
' ',
|
||||
];
|
||||
|
||||
if (source.namespace) {
|
||||
$parts.push(source.namespace + '::');
|
||||
}
|
||||
|
||||
$parts.push(source.name);
|
||||
$parts.push($span.clone().text(':' + source.line));
|
||||
|
||||
li.append($parts).removeClass(csscls('list-item')).addClass(csscls('table-list-item'));
|
||||
}});
|
||||
|
||||
$backtrace.set('data', stmt.backtrace);
|
||||
|
||||
$backtrace.$el
|
||||
.removeClass(csscls('list'))
|
||||
.addClass(csscls('table-list'))
|
||||
.appendTo($value);
|
||||
|
||||
return $('<tr />').append($name, $value);
|
||||
});
|
||||
}
|
||||
|
||||
if (stmt.params && !$.isEmptyObject(stmt.params)) {
|
||||
for (var key in stmt.params) {
|
||||
if (typeof stmt.params[key] !== 'function') {
|
||||
table.append('<tr><td class="' + csscls('name') + '">' + key + '</td><td class="' + csscls('value') +
|
||||
'">' + stmt.params[key] + '</td></tr>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 stmt;
|
||||
|
||||
// Search for duplicate statements.
|
||||
for (var sql = {}, duplicate = 0, i = 0; i < data.statements.length; i++) {
|
||||
if (data.statements[i].type === 'query') {
|
||||
stmt = data.statements[i].sql;
|
||||
if (data.statements[i].bindings && data.statements[i].bindings.length) {
|
||||
stmt += JSON.stringify(data.statements[i].bindings);
|
||||
}
|
||||
if (data.statements[i].connection) {
|
||||
stmt += '@' + data.statements[i].connection;
|
||||
}
|
||||
sql[stmt] = sql[stmt] || { keys: [] };
|
||||
sql[stmt].keys.push(i);
|
||||
}
|
||||
}
|
||||
// Add classes to all duplicate SQL statements.
|
||||
for (stmt in sql) {
|
||||
if (sql[stmt].keys.length > 1) {
|
||||
duplicate += sql[stmt].keys.length;
|
||||
|
||||
for (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-' + duplicate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (duplicate) {
|
||||
t.append(", " + duplicate + " of which were duplicated");
|
||||
t.append(", " + (data.nb_statements - duplicate) + " unique");
|
||||
}
|
||||
if (data.accumulated_duration_str) {
|
||||
this.$status.append($('<span title="Accumulated duration" />').addClass(csscls('duration')).text(data.accumulated_duration_str));
|
||||
}
|
||||
if (data.memory_usage_str) {
|
||||
this.$status.append($('<span title="Memory usage" />').addClass(csscls('memory')).text(data.memory_usage_str));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(PhpDebugBar.$);
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Middleware;
|
||||
|
||||
use WebmanTech\Debugbar\DebugBar;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
use Webman\MiddlewareInterface;
|
||||
|
||||
class DebugBarMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function process(Request $request, callable $handler): Response
|
||||
{
|
||||
$debugBar = DebugBar::instance();
|
||||
if ($debugBar->isSkipRequest($request)) {
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
$debugBar->boot();
|
||||
$debugBar->startMeasure(static::class, 'Application');
|
||||
|
||||
/** @var Response $response */
|
||||
$response = $handler($request);
|
||||
|
||||
$debugBar->stopMeasure(static::class);
|
||||
|
||||
return $debugBar->modifyResponse($request, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,796 @@
|
||||
/** 参考:https://github.com/barryvdh/laravel-debugbar/blob/master/src/Resources/laravel-debugbar.css */
|
||||
/* Force Laravel Whoops exception handler to be displayed under the debug bar */
|
||||
.Whoops.container {
|
||||
z-index: 5999999;
|
||||
}
|
||||
|
||||
div.phpdebugbar {
|
||||
font-size: 13px;
|
||||
font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
z-index: 6000000;
|
||||
}
|
||||
|
||||
div.phpdebugbar * {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler-overlay {
|
||||
z-index: 6000001;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler {
|
||||
border: 1px solid #aaa;
|
||||
border-top: 3px solid #2468f2;
|
||||
width: 80%;
|
||||
height: 70%;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
overflow-y: scroll;
|
||||
z-index: 6000002;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > a,
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin: 5px;
|
||||
padding: 0px 12px 2px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: #f5f5f5;
|
||||
color: #000;
|
||||
text-shadow: 1px 1px #fff;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form {
|
||||
margin: 15px 0px 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form > b {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input,
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select {
|
||||
margin: 0px 10px 10px 2px;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 3px;
|
||||
padding: 3px 6px 2px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select + br,
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form input[name="uri"] + br {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions > form select {
|
||||
padding: 2px 5px 1px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions input[name="uri"] {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions input[name="ip"] {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-actions button {
|
||||
outline: none;
|
||||
margin: 0px 0px 10px 2px;
|
||||
padding: 5px 15px 4px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table {
|
||||
margin: 15px 0px 10px;
|
||||
table-layout: auto;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table td,
|
||||
div.phpdebugbar-openhandler table th {
|
||||
width: auto!important;
|
||||
text-align: left;
|
||||
border: 0px solid #bbb;
|
||||
padding: 2px 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table th {
|
||||
text-shadow: 1px 1px #fff;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table th,
|
||||
div.phpdebugbar-openhandler table tr:nth-child(2n) {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table th:nth-child(1), div.phpdebugbar-openhandler table td:nth-child(1), /* Date */
|
||||
div.phpdebugbar-openhandler table th:nth-child(2), div.phpdebugbar-openhandler table td:nth-child(2), /* Method */
|
||||
div.phpdebugbar-openhandler table th:nth-child(4), div.phpdebugbar-openhandler table td:nth-child(4), /* IP */
|
||||
div.phpdebugbar-openhandler table th:nth-child(5), div.phpdebugbar-openhandler table td:nth-child(5) { /* Filter */
|
||||
width: 5%!important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table th:nth-child(2), div.phpdebugbar-openhandler table td:nth-child(2), /* Method */
|
||||
div.phpdebugbar-openhandler table th:nth-child(4), div.phpdebugbar-openhandler table td:nth-child(4), /* IP */
|
||||
div.phpdebugbar-openhandler table th:nth-child(5), div.phpdebugbar-openhandler table td:nth-child(5) { /* Filter */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table th:nth-child(3) { /* URL */
|
||||
width: calc(100vw - 100% - 196px)!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler table td a {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.phpdebugbar-resize-handle {
|
||||
display: block!important;
|
||||
height: 3px;
|
||||
margin-top: -4px;
|
||||
width: 100%;
|
||||
background: none;
|
||||
cursor: ns-resize;
|
||||
border-top: none;
|
||||
border-bottom: 0px;
|
||||
background-color: #2468f2;
|
||||
}
|
||||
|
||||
.phpdebugbar.phpdebugbar-minimized div.phpdebugbar-resize-handle {
|
||||
cursor: default!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-closed,
|
||||
div.phpdebugbar-minimized {
|
||||
border-top-color: #ddd;
|
||||
}
|
||||
|
||||
div.phpdebugbar code, div.phpdebugbar pre, div.phpdebugbar samp {
|
||||
background: none;
|
||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 1em;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.phpdebugbar .hljs {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.phpdebugbar .phpdebugbar-widgets-messages .hljs > code {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
div.phpdebugbar code, div.phpdebugbar pre {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-exceptions .phpdebugbar-widgets-filename {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item pre.phpdebugbar-widgets-file {
|
||||
border: 1px solid #d2d2d2;
|
||||
border-left: 2px solid #d2d2d2;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item pre.phpdebugbar-widgets-file[style="display: block;"] ~ div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.phpdebugbar pre.sf-dump {
|
||||
color: #000;
|
||||
outline: none;
|
||||
padding: 0px!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-body {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
div.phpdebugbar-header {
|
||||
min-height: 30px;
|
||||
line-height: 20px;
|
||||
padding-left: 39px;
|
||||
text-shadow: 1px 1px #FFF;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-header {
|
||||
background-size: 20px;
|
||||
padding: 2px 0px 3px 36px;
|
||||
background-position: 9px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-openhandler .phpdebugbar-openhandler-header a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a.phpdebugbar-close-btn {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuM4zml1AAAADDSURBVDhPxZCxCsIwFEUzuGdwCvQD7BIIcehUXDqVfGM/wsG/iG4ifkzMlRuSPLo4eeFBue8c6Iv6b4wxW557Hs0KnWa3seqDxTiOyVqbhmF4UND4Rofdruyce3rvE6bIRSo9GOI1McbLPM/vVm4l7MAQr0kpHaQsJTDE+6zrepym6SVFdNgR69M+hBTLzWCI10gJvydvBkO8ZlmWayvhJnkzGOI1+fBTCOHWPkT7YNiBId4HizxnCKy+r81uX/otSn0A7dioI/vYX+8AAAAASUVORK5CYII=) no-repeat 9px 6px;
|
||||
color : #555;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
a.phpdebugbar-open-btn {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAOCAYAAADJ7fe0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfdCgYULwwNKp3GAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4wLjOM5pdQAAAA1UlEQVQ4T2OgKpCUlOQH4vdA/B8Jv4dKEwYgDdLS0v8NDQ3/GxsbwzGIj2YoGEO1oQJkjcRgqDZUAJKwsrJ6/v//fwdiMFQbKgAZkpGR0QR0ajy60wlgRJhBXSGhpqb2CNnZhHBkZORcqBEMDFBX2BsYGGBVjAv39vZaQ41gYIC6Ygs2hbiwr6/vdqA+DqgR4CiW19bWxqoYF87Ly4uFaocAZWXlydgU4sJ2dna3ga4QgGqHAC0trY/YFOPCKSkpDVCtCAA01QaIsaYJHFgCqpVagIEBACGlF2c3r4ViAAAAAElFTkSuQmCC) no-repeat 8px 6px;
|
||||
}
|
||||
|
||||
|
||||
div.phpdebugbar-header,
|
||||
div.phpdebugbar-openhandler-header {
|
||||
background-size: 21px auto;
|
||||
background-position: 9px center;
|
||||
}
|
||||
|
||||
a.phpdebugbar-restore-btn {
|
||||
border-right-color: #ddd!important;
|
||||
height: 20px;
|
||||
width: 23px;
|
||||
background-position: center;
|
||||
background-size: 21px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-header > div > * {
|
||||
font-size: 13px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-header .phpdebugbar-tab {
|
||||
padding: 5px 8px;
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
div.phpdebugbar-header .phpdebugbar-header-left {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
div.phpdebugbar .phpdebugbar-header select {
|
||||
margin: 4px 0px 0px 8px;
|
||||
padding: 2px 3px 3px 3px;
|
||||
border-radius: 3px;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
dl.phpdebugbar-widgets-kvlist dt,
|
||||
dl.phpdebugbar-widgets-kvlist dd {
|
||||
min-height: 20px;
|
||||
line-height: 20px;
|
||||
padding: 4px 5px 5px;
|
||||
border-top: 0px;
|
||||
}
|
||||
|
||||
dl.phpdebugbar-widgets-kvlist dd.phpdebugbar-widgets-value.phpdebugbar-widgets-pretty .phpdebugbar-widgets-code-block {
|
||||
padding: 0px 0px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
dl.phpdebugbar-widgets-kvlist dt {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
dl.phpdebugbar-widgets-kvlist dd {
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline .phpdebugbar-widgets-measure {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-value {
|
||||
height: 16px;
|
||||
background-color: #63abca;
|
||||
border-bottom: 2px solid #477e96;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label,
|
||||
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-collector {
|
||||
top: 0px;
|
||||
color: #000;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline li .phpdebugbar-widgets-value span.phpdebugbar-widgets-label {
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px #000;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-timeline table.phpdebugbar-widgets-params {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar {
|
||||
width: calc(100% - 20px);
|
||||
padding: 4px 0px 4px;
|
||||
height: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-bottom: 0px;
|
||||
background-color: #e8e8e8;
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar input {
|
||||
width: calc(100% - 48px);
|
||||
margin-left: 0px;
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-label,
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-collector {
|
||||
padding: 1px 0px 0px 10px;
|
||||
margin: 0px;
|
||||
text-transform: uppercase;
|
||||
font-style: normal;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-toolbar i.phpdebugbar-fa.phpdebugbar-fa-search {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter,
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded {
|
||||
position: relative;
|
||||
top: -48px;
|
||||
display: inline-block;
|
||||
background-color: #6d6d6d;
|
||||
margin-left: 3px;
|
||||
border-radius: 3px;
|
||||
padding: 5px 8px 4px;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
text-shadow: 1px 1px #585858;
|
||||
transition: background-color .25s linear 0s, color .25s linear 0s;
|
||||
color: #FFF;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="info"],
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="info"] {
|
||||
background-color: #5896e2;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="error"],
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="error"] {
|
||||
background-color: #2468f2;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter[rel="warning"],
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded[rel="warning"] {
|
||||
background-color: #f99400;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter:hover {
|
||||
color: #FFF;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages div.phpdebugbar-widgets-toolbar a.phpdebugbar-widgets-filter.phpdebugbar-widgets-excluded {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
a.phpdebugbar-tab:hover,
|
||||
span.phpdebugbar-indicator:hover,
|
||||
a.phpdebugbar-indicator:hover,
|
||||
a.phpdebugbar-close-btn:hover,
|
||||
a.phpdebugbar-minimize-btn:hover,
|
||||
a.phpdebugbar-maximize-btn:hover,
|
||||
a.phpdebugbar-open-btn:hover {
|
||||
background-color: #ebebeb;
|
||||
/* transition: background-color .25s linear 0s, color .25s linear 0s; */
|
||||
}
|
||||
|
||||
a.phpdebugbar-minimize-btn,
|
||||
a.phpdebugbar-maximize-btn {
|
||||
width: 28px!important;
|
||||
}
|
||||
|
||||
a.phpdebugbar-tab.phpdebugbar-active {
|
||||
background: #242ef2;
|
||||
background-image: none;
|
||||
color: #fff !important;
|
||||
text-shadow: 1px 1px #3124f2;
|
||||
}
|
||||
|
||||
a.phpdebugbar-tab.phpdebugbar-active span.phpdebugbar-badge {
|
||||
background-color: white;
|
||||
color: #2468f2;
|
||||
text-shadow: 1px 1px #ebebeb;
|
||||
}
|
||||
|
||||
a.phpdebugbar-tab span.phpdebugbar-badge {
|
||||
vertical-align: 0px;
|
||||
padding: 2px 8px 3px 8px;
|
||||
text-align: center;
|
||||
background: #2468f2;
|
||||
font-size: 11px;
|
||||
font-family: monospace;
|
||||
color: #fff;
|
||||
text-shadow: 1px 1px #3124f2;
|
||||
border-radius: 10px;
|
||||
top: -1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phpdebugbar-indicator {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.phpdebugbar-indicator span.phpdebugbar-tooltip,
|
||||
div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text {
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #f5f5f5;
|
||||
font-size: 12px;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
padding: 2px 18px;
|
||||
text-shadow: none;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
div.phpdebugbar-mini-design a.phpdebugbar-tab:hover span.phpdebugbar-text {
|
||||
left: 0px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-toolbar > .fa {
|
||||
width: 25px;
|
||||
font-size: 15px;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item {
|
||||
padding: 7px 10px;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:hover,
|
||||
ul.phpdebugbar-widgets-timeline li:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-sqlqueries ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-templates ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-templates ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item,
|
||||
.phpdebugbar-widgets-mails ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item {
|
||||
line-height: 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-mails ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-mails ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-subject {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-mails ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-headers {
|
||||
margin: 10px 0px;
|
||||
padding: 7px 10px;
|
||||
border-left: 2px solid #ddd;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-list .phpdebugbar-widgets-list-item .phpdebugbar-widgets-name {
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-sql.phpdebugbar-widgets-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-sql {
|
||||
flex: 1;
|
||||
margin-right: 5px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-duration {
|
||||
margin-left: auto;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-database {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item .phpdebugbar-widgets-stmt-id {
|
||||
margin-left: auto;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item table.phpdebugbar-widgets-params {
|
||||
background-color: #fdfdfd;
|
||||
margin: 10px 0px;
|
||||
font-size: 12px;
|
||||
border-left: 2px solid #cecece;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params th,
|
||||
div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params td {
|
||||
padding: 1px 10px!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-templates table.phpdebugbar-widgets-params th {
|
||||
padding: 2px 10px!important;
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-name {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-name .phpdebugbar-fa {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list li.phpdebugbar-widgets-list-item:nth-child(even) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value:before {
|
||||
font-family: PhpDebugbarFontAwesome;
|
||||
content: "\f005";
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
margin-right: 8px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-info {
|
||||
color: #1299DA;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-info:before {
|
||||
font-family: PhpDebugbarFontAwesome;
|
||||
content: "\f05a";
|
||||
color: #5896e2;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-error:before {
|
||||
color: #2468f2;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-value.phpdebugbar-widgets-warning:before {
|
||||
color: #FF9800;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages .phpdebugbar-widgets-value.phpdebugbar-widgets-warning {
|
||||
color: #FF9800;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-messages li.phpdebugbar-widgets-list-item pre.sf-dump {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-sqlqueries {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
|
||||
div.phpdebugbar-panel div.phpdebugbar-widgets-status {
|
||||
padding: 9px 20px!important;
|
||||
width: calc(100% - 20px);
|
||||
margin-left: -10px;
|
||||
margin-top: -10px;
|
||||
line-height: 11px!important;
|
||||
font-weight: bold!important;
|
||||
background: #f5f5f5!important;
|
||||
border-bottom: 1px solid #cecece!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-panel div.phpdebugbar-widgets-status > * {
|
||||
color: #383838!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-panel div.phpdebugbar-widgets-status > span:first-child:before {
|
||||
font-family: PhpDebugbarFontAwesome;
|
||||
content: "\f05a";
|
||||
color: #737373;
|
||||
text-shadow: 1px 1px #fff;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params th,
|
||||
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td {
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params th {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-name {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-sqlqueries table.phpdebugbar-widgets-params td.phpdebugbar-widgets-value {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-templates .phpdebugbar-widgets-list-item table.phpdebugbar-widgets-params {
|
||||
width: auto!important;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-list ul.phpdebugbar-widgets-table-list {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.phpdebugbar-text-muted {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
ul.phpdebugbar-widgets-cache a.phpdebugbar-widgets-forget {
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
padding: 0 4px;
|
||||
background: #2468f2;
|
||||
margin: 0 2px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
a.phpdebugbar-tab i {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-mini-design a.phpdebugbar-tab {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
div.phpdebugbar-header-right > a {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
div.phpdebugbar-header-right .phpdebugbar-indicator > i.phpdebugbar-fa {
|
||||
vertical-align: baseline;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-panel {
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div.phpdebugbar-panel table {
|
||||
margin: 10px 0px!important;
|
||||
width: 100%!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-panel table .phpdebugbar-widgets-name {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
dl.phpdebugbar-widgets-kvlist > :nth-child(4n-1),
|
||||
dl.phpdebugbar-widgets-kvlist > :nth-child(4n) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.phpdebugbar pre.sf-dump:after {
|
||||
clear: none!important;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item span.phpdebugbar-widgets-message {
|
||||
color: #dd1044;
|
||||
}
|
||||
|
||||
div.phpdebugbar-widgets-exceptions li.phpdebugbar-widgets-list-item > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
div.phpdebugbar-widgets-templates span.phpdebugbar-widgets-param-count:before {
|
||||
margin-right: 6px!important;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-list-item .phpdebugbar-widgets-bg-measure {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.phpdebugbar-widgets-bg-measure .phpdebugbar-widgets-value {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
opacity: 0.2;
|
||||
background: #242ef2;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Storage;
|
||||
|
||||
use DebugBar\Storage\FileStorage;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class AutoCleanFileStorage extends FileStorage
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'gc_percent_by_files_count' => 50,
|
||||
'files_count_max' => 1000,
|
||||
'files_count_keep' => 400,
|
||||
'gc_percent_by_hours' => 10,
|
||||
'hours_keep' => 24,
|
||||
];
|
||||
|
||||
public function __construct($dirname, array $config = [])
|
||||
{
|
||||
if (!class_exists(Finder::class)) {
|
||||
throw new \InvalidArgumentException('必须先安装 symfony/finder');
|
||||
}
|
||||
|
||||
parent::__construct($dirname);
|
||||
$this->config = array_merge($this->config, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function save($id, $data)
|
||||
{
|
||||
$this->autoClean();
|
||||
parent::save($id, $data);
|
||||
}
|
||||
|
||||
private function autoClean()
|
||||
{
|
||||
if (!is_dir($this->dirname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$gcPercent = random_int(0, 100);
|
||||
if ($gcPercent < $this->config['gc_percent_by_files_count']) {
|
||||
$this->cleanByDirMaxSize($this->config['files_count_max'], $this->config['files_count_keep']);
|
||||
}
|
||||
if ($gcPercent < $this->config['gc_percent_by_hours']) {
|
||||
$this->cleanByKeepHours($this->config['hours_keep']);
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanByDirMaxSize(int $filesCountMax, int $filesCountKeep)
|
||||
{
|
||||
$finder = Finder::create()->files()->name('*.json')->in($this->dirname);
|
||||
if (($totalCount = $finder->count()) > $filesCountMax) {
|
||||
foreach ($finder->sortByModifiedTime() as $file) {
|
||||
unlink($file->getRealPath());
|
||||
$totalCount--;
|
||||
if ($totalCount <= $filesCountKeep) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanByKeepHours(int $hoursKeep)
|
||||
{
|
||||
foreach (Finder::create()->files()->name('*.json')->date('< ' . $hoursKeep . ' hour ago')->in(
|
||||
$this->dirname
|
||||
) as $file) {
|
||||
unlink($file->getRealPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar\Traits;
|
||||
|
||||
trait DebugBarOverwrite
|
||||
{
|
||||
/**
|
||||
* 修改 $request_variables
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
$request = request();
|
||||
$request_variables = array(
|
||||
'method' => $request->method(),
|
||||
'uri' => $request->uri(),
|
||||
'ip' => $request->getRealIp(),
|
||||
);
|
||||
|
||||
// 以下未修改
|
||||
|
||||
$this->data = array(
|
||||
'__meta' => array_merge(
|
||||
array(
|
||||
'id' => $this->getCurrentRequestId(),
|
||||
'datetime' => date('Y-m-d H:i:s'),
|
||||
'utime' => microtime(true)
|
||||
),
|
||||
$request_variables
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($this->collectors as $name => $collector) {
|
||||
$this->data[$name] = $collector->collect();
|
||||
}
|
||||
|
||||
// Remove all invalid (non UTF-8) characters
|
||||
array_walk_recursive($this->data, function (&$item) {
|
||||
if (is_string($item) && !mb_check_encoding($item, 'UTF-8')) {
|
||||
$item = mb_convert_encoding($item, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
});
|
||||
|
||||
if ($this->storage !== null) {
|
||||
$this->storage->save($this->getCurrentRequestId(), $this->data);
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar;
|
||||
|
||||
use Closure;
|
||||
use DebugBar\DataCollector\DataCollectorInterface;
|
||||
use DebugBar\DataCollector\ExceptionsCollector;
|
||||
use DebugBar\DataCollector\MessagesCollector;
|
||||
use DebugBar\DebugBar;
|
||||
use DebugBar\OpenHandler;
|
||||
use DebugBar\Storage\FileStorage;
|
||||
use WebmanTech\Debugbar\DataCollector\LaravelQueryCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\LaravelRedisCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\MemoryCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\PhpInfoCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\RequestDataCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\RouteCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\SessionCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\TimeDataCollector;
|
||||
use WebmanTech\Debugbar\DataCollector\WebmanCollector;
|
||||
use WebmanTech\Debugbar\Ext\HttpExt;
|
||||
use WebmanTech\Debugbar\Helper\ArrayHelper;
|
||||
use WebmanTech\Debugbar\Helper\StringHelper;
|
||||
use WebmanTech\Debugbar\Storage\AutoCleanFileStorage;
|
||||
use WebmanTech\Debugbar\Traits\DebugBarOverwrite;
|
||||
use support\Container;
|
||||
use Throwable;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Route;
|
||||
|
||||
class WebmanDebugBar extends DebugBar
|
||||
{
|
||||
use DebugBarOverwrite;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = [
|
||||
'enabled' => true, // 弃用
|
||||
'storage' => true, // 定义 storage
|
||||
'http_driver' => true, // 定义 http_driver
|
||||
'open_handler_url' => '/_debugbar/open', // storage 启用时打开历史的路由
|
||||
'asset_base_url' => '/_debugbar/assets', // 静态资源的路由
|
||||
'sample_url' => '/_debugbar/sample', // 示例页面,可用于查看 debugbar 信息,设为 null 关闭
|
||||
'javascript_renderer_options' => [], // 其他 javascriptRenderer 参数
|
||||
/**
|
||||
* 需要忽略的请求路由
|
||||
*/
|
||||
'skip_request_path' => [
|
||||
'/_debugbar/open',
|
||||
'/_debugbar/assets/*',
|
||||
'*.css',
|
||||
'*.js',
|
||||
],
|
||||
/**
|
||||
* 需要忽略的请求 callback 处理, function(Request $request): bool {}
|
||||
*/
|
||||
'skip_request_callback' => null,
|
||||
/**
|
||||
* 支持的 collectors,可以配置成 class 或者 callback
|
||||
*/
|
||||
'collectors_boot' => [
|
||||
'phpinfo' => true,
|
||||
'webman' => true,
|
||||
'messages' => true,
|
||||
'exceptions' => true,
|
||||
'time' => true,
|
||||
'memory' => true,
|
||||
'route' => true,
|
||||
'laravelDB' => true,
|
||||
'laravelRedis' => true,
|
||||
],
|
||||
'collectors_response' => [
|
||||
'request' => true,
|
||||
'session' => true,
|
||||
],
|
||||
'options' => [
|
||||
/**
|
||||
* @see LaravelQueryCollector::$config
|
||||
*/
|
||||
'db' => [],
|
||||
/**
|
||||
* @see LaravelRedisCollector::$config
|
||||
*/
|
||||
'redis' => [],
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = ArrayHelper::merge($this->config, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $booted = false;
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
if ($this->booted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 存储
|
||||
if ($this->config['storage']) {
|
||||
if ($this->config['storage'] === true) {
|
||||
$this->config['storage'] = function () {
|
||||
$path = runtime_path() . '/debugbar';
|
||||
if (class_exists('Symfony\Component\Finder\Finder')) {
|
||||
return new AutoCleanFileStorage($path);
|
||||
}
|
||||
// 使用该存储形式会导致文件数量极多
|
||||
return new FileStorage($path);
|
||||
};
|
||||
}
|
||||
$storage = call_user_func($this->config['storage']);
|
||||
$this->setStorage($storage);
|
||||
}
|
||||
// Collector
|
||||
$collectorMaps = $this->collectorMaps();
|
||||
foreach ($this->config['collectors_boot'] as $name => $builder) {
|
||||
if ($builder === false) {
|
||||
continue;
|
||||
}
|
||||
if ($builder === true && isset($collectorMaps[$name])) {
|
||||
$builder = $collectorMaps[$name];
|
||||
}
|
||||
if ($collector = $this->buildCollector($builder)) {
|
||||
$this->addCollector($collector);
|
||||
}
|
||||
}
|
||||
// javascriptRenderer
|
||||
$this->bootJavascriptRenderer();
|
||||
|
||||
$this->booted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的 collector 配置
|
||||
* @return array
|
||||
*/
|
||||
protected function collectorMaps(): array
|
||||
{
|
||||
return [
|
||||
'webman' => WebmanCollector::class,
|
||||
'phpinfo' => PhpInfoCollector::class,
|
||||
'messages' => MessagesCollector::class,
|
||||
'time' => TimeDataCollector::class,
|
||||
'memory' => MemoryCollector::class,
|
||||
'exceptions' => ExceptionsCollector::class,
|
||||
'route' => RouteCollector::class,
|
||||
'laravelDB' => function () {
|
||||
if (class_exists('Illuminate\Database\Capsule\Manager')) {
|
||||
$timeDataCollector = null;
|
||||
if ($this->hasCollector('time')) {
|
||||
/** @var \DebugBar\DataCollector\TimeDataCollector $timeDataCollector */
|
||||
$timeDataCollector = $this->getCollector('time');
|
||||
}
|
||||
return new LaravelQueryCollector($this->config['options']['db'] ?? [], $timeDataCollector);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
'laravelRedis' => function () {
|
||||
if (class_exists('Illuminate\Redis\RedisManager')) {
|
||||
$timeDataCollector = null;
|
||||
if ($this->hasCollector('time')) {
|
||||
/** @var \DebugBar\DataCollector\TimeDataCollector $timeDataCollector */
|
||||
$timeDataCollector = $this->getCollector('time');
|
||||
}
|
||||
return new LaravelRedisCollector($this->config['options']['redis'] ?? [], $timeDataCollector);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
'request' => function (Request $request, Response $response) {
|
||||
return new RequestDataCollector($request, $response);
|
||||
},
|
||||
'session' => function (Request $request) {
|
||||
if (request() && request()->session()) {
|
||||
return new SessionCollector($request);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|callable $collector
|
||||
* @param mixed ...$params
|
||||
* @return DataCollectorInterface|null
|
||||
*/
|
||||
protected function buildCollector($collector, ...$params): ?DataCollectorInterface
|
||||
{
|
||||
if (is_string($collector) && strpos($collector, '\\') !== false && class_exists($collector)) {
|
||||
$collector = new $collector;
|
||||
}
|
||||
if (is_callable($collector)) {
|
||||
$collector = call_user_func($collector, ...$params);
|
||||
}
|
||||
if (!$collector instanceof DataCollectorInterface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $collector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getJavascriptRenderer($baseUrl = null, $basePath = null)
|
||||
{
|
||||
if ($this->jsRenderer === null) {
|
||||
$this->jsRenderer = new WebmanJavascriptRenderer($this, $baseUrl, $basePath);
|
||||
}
|
||||
return $this->jsRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* boot javascriptRenderer
|
||||
*/
|
||||
protected function bootJavascriptRenderer(): void
|
||||
{
|
||||
$renderer = $this->getJavascriptRenderer($this->config['asset_base_url']);
|
||||
// 历史访问
|
||||
if ($this->getStorage() && $this->config['open_handler_url']) {
|
||||
$renderer->setOpenHandlerUrl($this->config['open_handler_url']);
|
||||
}
|
||||
// 其他配置参数
|
||||
$renderer->setOptions($this->config['javascript_renderer_options']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册必要路由
|
||||
*/
|
||||
public function registerRoute(): void
|
||||
{
|
||||
$this->boot();
|
||||
|
||||
// 示例的路由
|
||||
if ($this->config['sample_url']) {
|
||||
Route::get($this->config['sample_url'], function () {
|
||||
return response("<html lang='en'><body><h1>DebugBar Sample</h1></body></html>");
|
||||
});
|
||||
}
|
||||
// 历史记录的路由
|
||||
if ($this->config['open_handler_url']) {
|
||||
Route::get($this->config['open_handler_url'], function () {
|
||||
$openHandler = new OpenHandler($this);
|
||||
$data = $openHandler->handle(request()->get(), false, true);
|
||||
return response($data);
|
||||
});
|
||||
}
|
||||
// 静态资源路由
|
||||
$renderer = $this->getJavascriptRenderer();
|
||||
if ($renderer instanceof WebmanJavascriptRenderer) {
|
||||
$renderer->registerAssetRoute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是需要忽略的请求
|
||||
* @param Request $request
|
||||
* @return bool
|
||||
*/
|
||||
public function isSkipRequest(Request $request): bool
|
||||
{
|
||||
if ($this->config['skip_request_path']) {
|
||||
$path = $request->path();
|
||||
foreach ($this->config['skip_request_path'] as $pathPattern) {
|
||||
if (StringHelper::matchWildcard($pathPattern, $path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->config['skip_request_callback']) {
|
||||
if (call_user_func($this->config['skip_request_callback'], $request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function modifyResponse(Request $request, Response $response): Response
|
||||
{
|
||||
// handle exception
|
||||
if ($exception = $response->exception()) {
|
||||
$this->addThrowable($exception);
|
||||
}
|
||||
// 启用 session 支持
|
||||
if ($this->config['http_driver']) {
|
||||
if ($this->config['http_driver'] === true) {
|
||||
$this->config['http_driver'] = new WebmanHttpDriver($request, $response);
|
||||
}
|
||||
if (is_callable($this->config['http_driver'])) {
|
||||
$this->config['http_driver'] = call_user_func($this->config['http_driver']);
|
||||
}
|
||||
$this->setHttpDriver($this->config['http_driver']);
|
||||
}
|
||||
// 添加 collectors
|
||||
$collectorMaps = $this->collectorMaps();
|
||||
foreach ($this->config['collectors_response'] as $name => $builder) {
|
||||
if ($builder === false) {
|
||||
continue;
|
||||
}
|
||||
if ($builder === true) {
|
||||
$builder = $collectorMaps[$name];
|
||||
}
|
||||
if ($collector = $this->buildCollector($builder, $request, $response)) {
|
||||
$this->addCollector($collector);
|
||||
}
|
||||
}
|
||||
// 处理 response
|
||||
/** @var HttpExt $httpExt */
|
||||
$httpExt = Container::make(HttpExt::class, ['request' => $request, 'response' => $response]);
|
||||
if ($httpExt->isRedirection()) {
|
||||
$this->stackData();
|
||||
} elseif (!$httpExt->isHtmlResponse()) {
|
||||
$this->sendDataInHeaders(true);
|
||||
} elseif ($httpExt->isHtmlAccepted()) {
|
||||
$response = $this->attachDebugBarToHtmlResponse($response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @return Response
|
||||
*/
|
||||
protected function attachDebugBarToHtmlResponse(Response $response): Response
|
||||
{
|
||||
$renderer = $this->getJavascriptRenderer();
|
||||
$head = $renderer->renderHead();
|
||||
$foot = $renderer->render();
|
||||
$content = $response->rawBody();
|
||||
|
||||
$pos = strripos($content, '</head>');
|
||||
if (false !== $pos) {
|
||||
$content = substr($content, 0, $pos) . $head . substr($content, $pos);
|
||||
} else {
|
||||
$foot = $head . $foot;
|
||||
}
|
||||
|
||||
$pos = strripos($content, '</body>');
|
||||
if (false !== $pos) {
|
||||
$content = substr($content, 0, $pos) . $foot . substr($content, $pos);
|
||||
} else {
|
||||
$content = $content . $foot;
|
||||
}
|
||||
|
||||
$response->withBody($content);
|
||||
$response->withoutHeader('Content-Length');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Throwable $e
|
||||
*/
|
||||
public function addThrowable(Throwable $e)
|
||||
{
|
||||
if ($this->hasCollector('exceptions')) {
|
||||
/** @var ExceptionsCollector $collector */
|
||||
$collector = $this->getCollector('exceptions');
|
||||
$collector->addThrowable($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $message
|
||||
* @param string $type
|
||||
*/
|
||||
public function addMessage($message, string $type = 'info')
|
||||
{
|
||||
if ($this->hasCollector('messages')) {
|
||||
/** @var MessagesCollector $collector */
|
||||
$collector = $this->getCollector('messages');
|
||||
$collector->addMessage($message, $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string|null $label
|
||||
*/
|
||||
public function startMeasure(string $name, string $label = null)
|
||||
{
|
||||
if ($this->hasCollector('time')) {
|
||||
/** @var \DebugBar\DataCollector\TimeDataCollector $collector */
|
||||
$collector = $this->getCollector('time');
|
||||
$collector->startMeasure($name, $label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function stopMeasure(string $name)
|
||||
{
|
||||
if ($this->hasCollector('time')) {
|
||||
/** @var \DebugBar\DataCollector\TimeDataCollector $collector */
|
||||
$collector = $this->getCollector('time');
|
||||
try {
|
||||
$collector->stopMeasure($name);
|
||||
} catch (\Exception $e) {
|
||||
$this->addThrowable($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $label
|
||||
* @param float $start
|
||||
* @param float $end
|
||||
*/
|
||||
public function addMeasure(string $label, float $start, float $end)
|
||||
{
|
||||
if ($this->hasCollector('time')) {
|
||||
/** @var \DebugBar\DataCollector\TimeDataCollector $collector */
|
||||
$collector = $this->getCollector('time');
|
||||
$collector->addMeasure($label, $start, $end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $label
|
||||
* @param Closure $closure
|
||||
* @return mixed
|
||||
*/
|
||||
public function measure(string $label, Closure $closure)
|
||||
{
|
||||
if ($this->hasCollector('time')) {
|
||||
/** @var \DebugBar\DataCollector\TimeDataCollector $collector */
|
||||
$collector = $this->getCollector('time');
|
||||
$result = $collector->measure($label, $closure);
|
||||
} else {
|
||||
$result = $closure();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar;
|
||||
|
||||
use DebugBar\HttpDriverInterface;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class WebmanHttpDriver implements HttpDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
public function __construct(Request $request, Response $response)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function setHeaders(array $headers)
|
||||
{
|
||||
foreach ($headers as $key => $value) {
|
||||
$this->response->header($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function isSessionStarted()
|
||||
{
|
||||
return !!$this->request->session();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function setSessionValue($name, $value)
|
||||
{
|
||||
$this->request->session()->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function hasSessionValue($name)
|
||||
{
|
||||
return $this->request->session()->has($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function getSessionValue($name)
|
||||
{
|
||||
return $this->request->session()->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
function deleteSessionValue($name)
|
||||
{
|
||||
$this->request->session()->delete($name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace WebmanTech\Debugbar;
|
||||
|
||||
use DebugBar\DebugBar;
|
||||
use DebugBar\JavascriptRenderer;
|
||||
use Webman\Route;
|
||||
|
||||
class WebmanJavascriptRenderer extends JavascriptRenderer
|
||||
{
|
||||
protected $ajaxHandlerBindToJquery = true;
|
||||
protected $ajaxHandlerBindToXHR = true;
|
||||
protected $ajaxHandlerBindToFetch = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $assetsInfo = [
|
||||
'webman' => [
|
||||
'path' => __DIR__ . '/Resources',
|
||||
'css' => [
|
||||
'webman-debugbar.css',
|
||||
],
|
||||
'js' => [],
|
||||
],
|
||||
'laravel' => [
|
||||
'path' => __DIR__ . '/Laravel/Resources',
|
||||
'css' => [],
|
||||
'js' => [
|
||||
'sqlqueries/widget.js',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
public function __construct(DebugBar $debugBar, $baseUrl = null, $basePath = null)
|
||||
{
|
||||
parent::__construct($debugBar, $baseUrl, $basePath);
|
||||
|
||||
foreach ($this->assetsInfo as $name => $item) {
|
||||
$this->addAssets($item['css'], $item['js'], $item['path'], $baseUrl . '/' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册静态资源路由
|
||||
*/
|
||||
public function registerAssetRoute(): void
|
||||
{
|
||||
Route::get($this->getBaseUrl() . '/[{path:.+}]', function ($request, $path = '') {
|
||||
// 安全检查,避免url里 /../../../password 这样的非法访问
|
||||
if (strpos($path, '..') !== false) {
|
||||
return response('<h1>400 Bad Request</h1>', 400);
|
||||
}
|
||||
// debugbar 的静态文件目录
|
||||
$staticBasePath = $this->getBasePath();
|
||||
// 其他文件
|
||||
foreach ($this->assetsInfo as $name => $item) {
|
||||
if (strpos($path, $name . '/') === 0) {
|
||||
$staticBasePath = $item['path'];
|
||||
$path = substr($path, strlen($name) + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 文件
|
||||
$file = "$staticBasePath/$path";
|
||||
if (!is_file($file)) {
|
||||
return response('<h1>404 Not Found</h1>', 404);
|
||||
}
|
||||
return response()->withFile($file);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
$enable = config('app.debug', false) && class_exists('WebmanTech\Debugbar\WebmanDebugBar');
|
||||
|
||||
return [
|
||||
'enable' => $enable,
|
||||
/**
|
||||
* @see \WebmanTech\Debugbar\WebmanDebugBar::$config
|
||||
*/
|
||||
'debugbar' => [
|
||||
'enable' => $enable,
|
||||
],
|
||||
];
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
use WebmanTech\Debugbar\Bootstrap\LaravelQuery;
|
||||
use WebmanTech\Debugbar\Bootstrap\LaravelRedisExec;
|
||||
|
||||
return [
|
||||
LaravelQuery::class,
|
||||
LaravelRedisExec::class,
|
||||
];
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
use WebmanTech\Debugbar\Middleware\DebugBarMiddleware;
|
||||
|
||||
return [
|
||||
'' => [
|
||||
DebugBarMiddleware::class,
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
use WebmanTech\Debugbar\DebugBar;
|
||||
|
||||
DebugBar::instance()->registerRoute();
|
||||
Reference in New Issue
Block a user