Skip to content

Commit 67d00b3

Browse files
authored
Merge pull request #5553 from paulbalandan/backtrace-collector
Refactor Database Collector display
2 parents a09f7d9 + 1f3d525 commit 67d00b3

File tree

3 files changed

+103
-18
lines changed

3 files changed

+103
-18
lines changed

system/Debug/Toolbar/Collectors/Database.php

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,19 @@ public static function collect(Query $query)
8585
if (count(static::$queries) < $max) {
8686
$queryString = $query->getQuery();
8787

88+
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
89+
90+
if (! is_cli()) {
91+
// when called in the browser, the first two trace arrays
92+
// are from the DB event trigger, which are unneeded
93+
$backtrace = array_slice($backtrace, 2);
94+
}
95+
8896
static::$queries[] = [
8997
'query' => $query,
9098
'string' => $queryString,
9199
'duplicate' => in_array($queryString, array_column(static::$queries, 'string', null), true),
92-
'trace' => debug_backtrace(),
100+
'trace' => $backtrace,
93101
];
94102
}
95103
}
@@ -134,23 +142,39 @@ public function display(): array
134142
$data['queries'] = array_map(static function (array $query) {
135143
$isDuplicate = $query['duplicate'] === true;
136144

137-
// Find the first line that doesn't include `system` in the backtrace
138-
$line = [];
145+
$firstNonSystemLine = '';
146+
147+
foreach ($query['trace'] as $index => &$line) {
148+
// simplify file and line
149+
if (isset($line['file'])) {
150+
$line['file'] = clean_path($line['file']) . ':' . $line['line'];
151+
unset($line['line']);
152+
} else {
153+
$line['file'] = '[internal function]';
154+
}
155+
156+
// find the first trace line that does not originate from `system/`
157+
if ($firstNonSystemLine === '' && strpos($line['file'], 'SYSTEMPATH') === false) {
158+
$firstNonSystemLine = $line['file'];
159+
}
139160

140-
foreach ($query['trace'] as &$traceLine) {
141-
// Clean up the file paths
142-
$traceLine['file'] = str_ireplace(APPPATH, 'APPPATH/', $traceLine['file']);
143-
$traceLine['file'] = str_ireplace(SYSTEMPATH, 'SYSTEMPATH/', $traceLine['file']);
144-
if (defined('VENDORPATH')) {
145-
// VENDORPATH is not defined unless `vendor/autoload.php` exists
146-
$traceLine['file'] = str_ireplace(VENDORPATH, 'VENDORPATH/', $traceLine['file']);
161+
// simplify function call
162+
if (isset($line['class'])) {
163+
$line['function'] = $line['class'] . $line['type'] . $line['function'];
164+
unset($line['class'], $line['type']);
147165
}
148-
$traceLine['file'] = str_ireplace(ROOTPATH, 'ROOTPATH/', $traceLine['file']);
149166

150-
if (strpos($traceLine['file'], 'SYSTEMPATH') !== false) {
151-
continue;
167+
if (strrpos($line['function'], '{closure}') === false) {
168+
$line['function'] .= '()';
152169
}
153-
$line = empty($line) ? $traceLine : $line;
170+
171+
$line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function'];
172+
173+
// add index numbering padded with nonbreaking space
174+
$indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT);
175+
$indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded);
176+
177+
$line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4);
154178
}
155179

156180
return [
@@ -159,8 +183,7 @@ public function display(): array
159183
'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms',
160184
'sql' => $query['query']->debugToolbarDisplay(),
161185
'trace' => $query['trace'],
162-
'trace-file' => str_replace(ROOTPATH, '/', $line['file'] ?? ''),
163-
'trace-line' => $line['line'] ?? '',
186+
'trace-file' => $firstNonSystemLine,
164187
'qid' => md5($query['query'] . microtime()),
165188
];
166189
}, static::$queries);

system/Debug/Toolbar/Views/_database.tpl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
<tr class="{class}" title="{hover}" data-toggle="{qid}-trace">
1111
<td class="narrow">{duration}</td>
1212
<td>{! sql !}</td>
13-
<td style="text-align: right">{trace-file}:<strong>{trace-line}</strong></td>
13+
<td style="text-align: right"><strong>{trace-file}</strong></td>
1414
</tr>
1515
<tr class="muted" id="{qid}-trace" style="display:none">
1616
<td></td>
1717
<td colspan="2">
1818
{trace}
19-
{file}:<strong>{line}</strong><br/>
19+
{index}<strong>{file}</strong><br/>
20+
{function}<br/><br/>
2021
{/trace}
2122
</td>
2223
</tr>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Debug\Toolbar\Collectors;
13+
14+
use CodeIgniter\Database\Query;
15+
use CodeIgniter\Test\CIUnitTestCase;
16+
use PHPUnit\Framework\MockObject\MockObject;
17+
18+
/**
19+
* @internal
20+
*/
21+
final class DatabaseTest extends CIUnitTestCase
22+
{
23+
public function testDisplay(): void
24+
{
25+
/** @var MockObject&Query $query */
26+
$query = $this->getMockBuilder(Query::class)
27+
->disableOriginalConstructor()
28+
->getMock();
29+
30+
// set mock returns
31+
$query->method('getQuery')->willReturn('SHOW TABLES;');
32+
$query->method('debugToolbarDisplay')->willReturn('<strong>SHOW</strong> TABLES;');
33+
$query->method('getDuration')->with(5)->willReturn('1.23456');
34+
35+
Database::collect($query); // <== $query will be called here
36+
$collector = new Database();
37+
38+
$queries = $collector->display()['queries'];
39+
40+
$this->assertSame('1234.56 ms', $queries[0]['duration']);
41+
$this->assertSame('<strong>SHOW</strong> TABLES;', $queries[0]['sql']);
42+
$this->assertSame(clean_path(__FILE__) . ':35', $queries[0]['trace-file']);
43+
44+
foreach ($queries[0]['trace'] as $i => $trace) {
45+
// since we added the index numbering
46+
$this->assertArrayHasKey('index', $trace);
47+
$this->assertSame(
48+
sprintf('%s', $i + 1),
49+
preg_replace(sprintf('/%s/', chr(0xC2) . chr(0xA0)), '', $trace['index'])
50+
);
51+
52+
// since we merged file & line together
53+
$this->assertArrayNotHasKey('line', $trace);
54+
$this->assertArrayHasKey('file', $trace);
55+
56+
// since we dropped object & args in the backtrace for performance
57+
// but args MAY STILL BE present in internal calls
58+
$this->assertArrayNotHasKey('object', $trace);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)