Initial
This commit is contained in:
253
system/Debug/Toolbar/Collectors/Database.php
Normal file
253
system/Debug/Toolbar/Collectors/Database.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Debug\Toolbar\Collectors;
|
||||
|
||||
use CodeIgniter\Database\Query;
|
||||
|
||||
/**
|
||||
* Collector for the Database tab of the Debug Toolbar.
|
||||
*/
|
||||
class Database extends BaseCollector
|
||||
{
|
||||
/**
|
||||
* Whether this collector has timeline data.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTimeline = true;
|
||||
|
||||
/**
|
||||
* Whether this collector should display its own tab.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasTabContent = true;
|
||||
|
||||
/**
|
||||
* Whether this collector has data for the Vars tab.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasVarData = false;
|
||||
|
||||
/**
|
||||
* The name used to reference this collector in the toolbar.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $title = 'Database';
|
||||
|
||||
/**
|
||||
* Array of database connections.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $connections;
|
||||
|
||||
/**
|
||||
* The query instances that have been collected
|
||||
* through the DBQuery Event.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $queries = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->getConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* The static method used during Events to collect
|
||||
* data.
|
||||
*
|
||||
* @internal param $ array \CodeIgniter\Database\Query
|
||||
*/
|
||||
public static function collect(Query $query)
|
||||
{
|
||||
$config = config('Toolbar');
|
||||
|
||||
// Provide default in case it's not set
|
||||
$max = $config->maxQueries ?: 100;
|
||||
|
||||
if (count(static::$queries) < $max) {
|
||||
$queryString = $query->getQuery();
|
||||
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
|
||||
if (! is_cli()) {
|
||||
// when called in the browser, the first two trace arrays
|
||||
// are from the DB event trigger, which are unneeded
|
||||
$backtrace = array_slice($backtrace, 2);
|
||||
}
|
||||
|
||||
static::$queries[] = [
|
||||
'query' => $query,
|
||||
'string' => $queryString,
|
||||
'duplicate' => in_array($queryString, array_column(static::$queries, 'string', null), true),
|
||||
'trace' => $backtrace,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns timeline data formatted for the toolbar.
|
||||
*
|
||||
* @return array The formatted data or an empty array.
|
||||
*/
|
||||
protected function formatTimelineData(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->connections as $alias => $connection) {
|
||||
// Connection Time
|
||||
$data[] = [
|
||||
'name' => 'Connecting to Database: "' . $alias . '"',
|
||||
'component' => 'Database',
|
||||
'start' => $connection->getConnectStart(),
|
||||
'duration' => $connection->getConnectDuration(),
|
||||
];
|
||||
}
|
||||
|
||||
foreach (static::$queries as $query) {
|
||||
$data[] = [
|
||||
'name' => 'Query',
|
||||
'component' => 'Database',
|
||||
'start' => $query['query']->getStartTime(true),
|
||||
'duration' => $query['query']->getDuration(),
|
||||
'query' => $query['query']->debugToolbarDisplay(),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data of this collector to be formatted in the toolbar
|
||||
*/
|
||||
public function display(): array
|
||||
{
|
||||
$data['queries'] = array_map(static function (array $query) {
|
||||
$isDuplicate = $query['duplicate'] === true;
|
||||
|
||||
$firstNonSystemLine = '';
|
||||
|
||||
foreach ($query['trace'] as $index => &$line) {
|
||||
// simplify file and line
|
||||
if (isset($line['file'])) {
|
||||
$line['file'] = clean_path($line['file']) . ':' . $line['line'];
|
||||
unset($line['line']);
|
||||
} else {
|
||||
$line['file'] = '[internal function]';
|
||||
}
|
||||
|
||||
// find the first trace line that does not originate from `system/`
|
||||
if ($firstNonSystemLine === '' && strpos($line['file'], 'SYSTEMPATH') === false) {
|
||||
$firstNonSystemLine = $line['file'];
|
||||
}
|
||||
|
||||
// simplify function call
|
||||
if (isset($line['class'])) {
|
||||
$line['function'] = $line['class'] . $line['type'] . $line['function'];
|
||||
unset($line['class'], $line['type']);
|
||||
}
|
||||
|
||||
if (strrpos($line['function'], '{closure}') === false) {
|
||||
$line['function'] .= '()';
|
||||
}
|
||||
|
||||
$line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function'];
|
||||
|
||||
// add index numbering padded with nonbreaking space
|
||||
$indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT);
|
||||
$indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded);
|
||||
|
||||
$line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4);
|
||||
}
|
||||
|
||||
return [
|
||||
'hover' => $isDuplicate ? 'This query was called more than once.' : '',
|
||||
'class' => $isDuplicate ? 'duplicate' : '',
|
||||
'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms',
|
||||
'sql' => $query['query']->debugToolbarDisplay(),
|
||||
'trace' => $query['trace'],
|
||||
'trace-file' => $firstNonSystemLine,
|
||||
'qid' => md5($query['query'] . microtime()),
|
||||
];
|
||||
}, static::$queries);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the "badge" value for the button.
|
||||
*/
|
||||
public function getBadgeValue(): int
|
||||
{
|
||||
return count(static::$queries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Information to be displayed next to the title.
|
||||
*
|
||||
* @return string The number of queries (in parentheses) or an empty string.
|
||||
*/
|
||||
public function getTitleDetails(): string
|
||||
{
|
||||
$this->getConnections();
|
||||
|
||||
$queryCount = count(static::$queries);
|
||||
$uniqueCount = count(array_filter(static::$queries, static function ($query) {
|
||||
return $query['duplicate'] === false;
|
||||
}));
|
||||
$connectionCount = count($this->connections);
|
||||
|
||||
return sprintf(
|
||||
'(%d total Quer%s, %d %s unique across %d Connection%s)',
|
||||
$queryCount,
|
||||
$queryCount > 1 ? 'ies' : 'y',
|
||||
$uniqueCount,
|
||||
$uniqueCount > 1 ? 'of them' : '',
|
||||
$connectionCount,
|
||||
$connectionCount > 1 ? 's' : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this collector have any data collected?
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return empty(static::$queries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the icon.
|
||||
*
|
||||
* Icon from https://icons8.com - 1em package
|
||||
*/
|
||||
public function icon(): string
|
||||
{
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADMSURBVEhLY6A3YExLSwsA4nIycQDIDIhRWEBqamo/UNF/SjDQjF6ocZgAKPkRiFeEhoYyQ4WIBiA9QAuWAPEHqBAmgLqgHcolGQD1V4DMgHIxwbCxYD+QBqcKINseKo6eWrBioPrtQBq/BcgY5ht0cUIYbBg2AJKkRxCNWkDQgtFUNJwtABr+F6igE8olGQD114HMgHIxAVDyAhA/AlpSA8RYUwoeXAPVex5qHCbIyMgwBCkAuQJIY00huDBUz/mUlBQDqHGjgBjAwAAACexpph6oHSQAAAAASUVORK5CYII=';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connections from the database config
|
||||
*/
|
||||
private function getConnections()
|
||||
{
|
||||
$this->connections = \Config\Database::getConnections();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user