2
2
3
3
namespace Barryvdh \Debugbar \DataCollector ;
4
4
5
+ use Barryvdh \Debugbar \Support \Explain ;
5
6
use DebugBar \DataCollector \PDO \PDOCollector ;
6
7
use DebugBar \DataCollector \TimeDataCollector ;
8
+ use Illuminate \Support \Facades \DB ;
7
9
use Illuminate \Support \Str ;
8
10
9
11
/**
@@ -152,7 +154,6 @@ public function addQuery($query)
152
154
$ limited = $ this ->softLimit && $ this ->queryCount > $ this ->softLimit ;
153
155
154
156
$ sql = (string ) $ query ->sql ;
155
- $ explainResults = [];
156
157
$ time = $ query ->time / 1000 ;
157
158
$ endTime = microtime (true );
158
159
$ startTime = $ endTime - $ time ;
@@ -168,41 +169,6 @@ public function addQuery($query)
168
169
} catch (\Throwable $ e ) {
169
170
// ignore error for non-pdo laravel drivers
170
171
}
171
- $ bindings = $ query ->connection ->prepareBindings ($ query ->bindings );
172
-
173
- // Run EXPLAIN on this query (if needed)
174
- if (!$ limited && $ this ->explainQuery && $ pdo && preg_match ('/^\s*( ' . implode ('| ' , $ this ->explainTypes ) . ') /i ' , $ sql )) {
175
- $ statement = $ pdo ->prepare ('EXPLAIN ' . $ sql );
176
- $ statement ->execute ($ bindings );
177
- $ explainResults = $ statement ->fetchAll (\PDO ::FETCH_CLASS );
178
- }
179
-
180
- $ bindings = $ this ->getDataFormatter ()->checkBindings ($ bindings );
181
- if (!empty ($ bindings ) && $ this ->renderSqlWithParams ) {
182
- foreach ($ bindings as $ key => $ binding ) {
183
- // This regex matches placeholders only, not the question marks,
184
- // nested in quotes, while we iterate through the bindings
185
- // and substitute placeholders by suitable values.
186
- $ regex = is_numeric ($ key )
187
- ? "/(?<!\?)\?(?=(?:[^' \\\']*'[^' \\']*')*[^' \\\']*$)(?!\?)/ "
188
- : "/: {$ key }(?=(?:[^' \\\']*'[^' \\\']*')*[^' \\\']*$)/ " ;
189
-
190
- // Mimic bindValue and only quote non-integer and non-float data types
191
- if (!is_int ($ binding ) && !is_float ($ binding )) {
192
- if ($ pdo ) {
193
- try {
194
- $ binding = $ pdo ->quote ((string ) $ binding );
195
- } catch (\Exception $ e ) {
196
- $ binding = $ this ->emulateQuote ($ binding );
197
- }
198
- } else {
199
- $ binding = $ this ->emulateQuote ($ binding );
200
- }
201
- }
202
-
203
- $ sql = preg_replace ($ regex , addcslashes ($ binding , '$ ' ), $ sql , 1 );
204
- }
205
- }
206
172
207
173
$ source = [];
208
174
@@ -213,16 +179,20 @@ public function addQuery($query)
213
179
}
214
180
}
215
181
182
+ $ bindings = match (true ) {
183
+ $ limited && filled ($ query ->bindings ) => null ,
184
+ default => $ query ->connection ->prepareBindings ($ query ->bindings ),
185
+ };
186
+
216
187
$ this ->queries [] = [
217
188
'query ' => $ sql ,
218
189
'type ' => 'query ' ,
219
- 'bindings ' => ! $ limited ? $ this -> getDataFormatter ()-> escapeBindings ( $ bindings) : null ,
190
+ 'bindings ' => $ bindings ,
220
191
'start ' => $ startTime ,
221
192
'time ' => $ time ,
222
193
'memory ' => $ this ->lastMemoryUsage ? memory_get_usage (false ) - $ this ->lastMemoryUsage : 0 ,
223
194
'source ' => $ source ,
224
- 'explain ' => $ explainResults ,
225
- 'connection ' => $ query ->connection ->getDatabaseName (),
195
+ 'connection ' => $ query ->connection ->getName (),
226
196
'driver ' => $ query ->connection ->getConfig ('driver ' ),
227
197
'hints ' => ($ this ->showHints && !$ limited ) ? $ hints : null ,
228
198
'show_copy ' => $ this ->showCopyButton ,
@@ -484,8 +454,7 @@ public function collectTransactionEvent($event, $connection)
484
454
'time ' => 0 ,
485
455
'memory ' => 0 ,
486
456
'source ' => $ source ,
487
- 'explain ' => [],
488
- 'connection ' => $ connection ->getDatabaseName (),
457
+ 'connection ' => $ connection ->getName (),
489
458
'driver ' => $ connection ->getConfig ('driver ' ),
490
459
'hints ' => null ,
491
460
'show_copy ' => false ,
@@ -516,15 +485,21 @@ public function collect()
516
485
$ totalTime += $ query ['time ' ];
517
486
$ totalMemory += $ query ['memory ' ];
518
487
519
- if (str_ends_with ($ query ['connection ' ], '.sqlite ' )) {
520
- $ query ['connection ' ] = $ this ->normalizeFilePath ($ query ['connection ' ]);
488
+ $ connectionName = DB ::connection ($ query ['connection ' ])->getDatabaseName ();
489
+ if (str_ends_with ($ connectionName , '.sqlite ' )) {
490
+ $ connectionName = $ this ->normalizeFilePath ($ connectionName );
521
491
}
522
492
493
+ $ canExplainQuery = match (true ) {
494
+ in_array ($ query ['driver ' ], ['mysql ' , 'pgsql ' ]) => $ query ['bindings ' ] !== null && preg_match ('/^\s*( ' . implode ('| ' , $ this ->explainTypes ) . ') /i ' , $ query ['query ' ]),
495
+ default => false ,
496
+ };
497
+
523
498
$ statements [] = [
524
- 'sql ' => $ this ->getDataFormatter ()-> formatSql ( $ query[ ' query ' ] ),
499
+ 'sql ' => $ this ->getSqlQueryToDisplay ( $ query ),
525
500
'type ' => $ query ['type ' ],
526
501
'params ' => [],
527
- 'bindings ' => $ query ['bindings ' ],
502
+ 'bindings ' => $ query ['bindings ' ] ?? [] ,
528
503
'hints ' => $ query ['hints ' ],
529
504
'show_copy ' => $ query ['show_copy ' ],
530
505
'backtrace ' => array_values ($ query ['source ' ]),
@@ -536,69 +511,16 @@ public function collect()
536
511
'filename ' => $ this ->getDataFormatter ()->formatSource ($ source , true ),
537
512
'source ' => $ this ->getDataFormatter ()->formatSource ($ source ),
538
513
'xdebug_link ' => is_object ($ source ) ? $ this ->getXdebugLink ($ source ->file ?: '' , $ source ->line ) : null ,
539
- 'connection ' => $ query ['connection ' ],
514
+ 'connection ' => $ connectionName ,
515
+ 'explain ' => $ this ->explainQuery && $ canExplainQuery ? [
516
+ 'url ' => route ('debugbar.queries.explain ' ),
517
+ 'visual-confirm ' => (new Explain ())->confirm ($ query ['connection ' ]),
518
+ 'driver ' => $ query ['driver ' ],
519
+ 'connection ' => $ query ['connection ' ],
520
+ 'query ' => $ query ['query ' ],
521
+ 'hash ' => (new Explain ())->hash ($ query ['connection ' ], $ query ['query ' ], $ query ['bindings ' ]),
522
+ ] : null ,
540
523
];
541
-
542
- if ($ query ['explain ' ]) {
543
- // Add the results from the EXPLAIN as new rows
544
- if ($ query ['driver ' ] === 'pgsql ' ) {
545
- $ explainer = trim (implode ("\n" , array_map (function ($ explain ) {
546
- return $ explain ->{'QUERY PLAN ' };
547
- }, $ query ['explain ' ])));
548
-
549
- if ($ explainer ) {
550
- $ statements [] = [
551
- 'sql ' => " - EXPLAIN: {$ explainer }" ,
552
- 'type ' => 'explain ' ,
553
- ];
554
- }
555
- } elseif ($ query ['driver ' ] === 'sqlite ' ) {
556
- $ vmi = '<table style="margin:-5px -11px !important;width: 100% !important"> ' ;
557
- $ vmi .= "<thead><tr>
558
- <td>Address</td>
559
- <td>Opcode</td>
560
- <td>P1</td>
561
- <td>P2</td>
562
- <td>P3</td>
563
- <td>P4</td>
564
- <td>P5</td>
565
- <td>Comment</td>
566
- </tr></thead> " ;
567
-
568
- foreach ($ query ['explain ' ] as $ explain ) {
569
- $ vmi .= "<tr>
570
- <td> {$ explain ->addr }</td>
571
- <td> {$ explain ->opcode }</td>
572
- <td> {$ explain ->p1 }</td>
573
- <td> {$ explain ->p2 }</td>
574
- <td> {$ explain ->p3 }</td>
575
- <td> {$ explain ->p4 }</td>
576
- <td> {$ explain ->p5 }</td>
577
- <td> {$ explain ->comment }</td>
578
- </tr> " ;
579
- }
580
-
581
- $ vmi .= '</table> ' ;
582
-
583
- $ statements [] = [
584
- 'sql ' => " - EXPLAIN: " ,
585
- 'type ' => 'explain ' ,
586
- 'params ' => [
587
- 'Virtual Machine Instructions ' => $ vmi ,
588
- ]
589
- ];
590
- } else {
591
- foreach ($ query ['explain ' ] as $ explain ) {
592
- $ statements [] = [
593
- 'sql ' => " - EXPLAIN # {$ explain ->id }: ` {$ explain ->table }` ( {$ explain ->select_type }) " ,
594
- 'type ' => 'explain ' ,
595
- 'params ' => $ explain ,
596
- 'row_count ' => $ explain ->rows ,
597
- 'stmt_id ' => $ explain ->id ,
598
- ];
599
- }
600
- }
601
- }
602
524
}
603
525
604
526
if ($ this ->durationBackground ) {
@@ -676,7 +598,7 @@ public function getWidgets()
676
598
return [
677
599
"queries " => [
678
600
"icon " => "database " ,
679
- "widget " => "PhpDebugBar.Widgets.SQLQueriesWidget " ,
601
+ "widget " => "PhpDebugBar.Widgets.LaravelQueriesWidget " ,
680
602
"map " => "queries " ,
681
603
"default " => "[] "
682
604
],
@@ -686,4 +608,48 @@ public function getWidgets()
686
608
]
687
609
];
688
610
}
611
+
612
+ private function getSqlQueryToDisplay (array $ query ): string
613
+ {
614
+ $ sql = $ query ['query ' ];
615
+ if ($ query ['type ' ] === 'query ' && $ this ->renderSqlWithParams && method_exists (DB ::connection ($ query ['connection ' ])->getQueryGrammar (), 'substituteBindingsIntoRawSql ' )) {
616
+ $ sql = DB ::connection ($ query ['connection ' ])->getQueryGrammar ()->substituteBindingsIntoRawSql ($ sql , $ query ['bindings ' ] ?? []);
617
+ } elseif ($ query ['type ' ] === 'query ' && $ this ->renderSqlWithParams ) {
618
+ $ bindings = $ this ->getDataFormatter ()->checkBindings ($ query ['bindings ' ]);
619
+ if (!empty ($ bindings )) {
620
+ $ pdo = null ;
621
+ try {
622
+ $ pdo = $ query ->connection ->getPdo ();
623
+ } catch (\Throwable ) {
624
+ // ignore error for non-pdo laravel drivers
625
+ }
626
+
627
+ foreach ($ bindings as $ key => $ binding ) {
628
+ // This regex matches placeholders only, not the question marks,
629
+ // nested in quotes, while we iterate through the bindings
630
+ // and substitute placeholders by suitable values.
631
+ $ regex = is_numeric ($ key )
632
+ ? "/(?<!\?)\?(?=(?:[^' \\\']*'[^' \\']*')*[^' \\\']*$)(?!\?)/ "
633
+ : "/: {$ key }(?=(?:[^' \\\']*'[^' \\\']*')*[^' \\\']*$)/ " ;
634
+
635
+ // Mimic bindValue and only quote non-integer and non-float data types
636
+ if (!is_int ($ binding ) && !is_float ($ binding )) {
637
+ if ($ pdo ) {
638
+ try {
639
+ $ binding = $ pdo ->quote ((string ) $ binding );
640
+ } catch (\Exception $ e ) {
641
+ $ binding = $ this ->emulateQuote ($ binding );
642
+ }
643
+ } else {
644
+ $ binding = $ this ->emulateQuote ($ binding );
645
+ }
646
+ }
647
+
648
+ $ sql = preg_replace ($ regex , addcslashes ($ binding , '$ ' ), $ sql , 1 );
649
+ }
650
+ }
651
+ }
652
+
653
+ return $ this ->getDataFormatter ()->formatSql ($ sql );
654
+ }
689
655
}
0 commit comments