@@ -14,6 +14,7 @@ class QueryCollector extends PDOCollector
14
14
protected $ queries = [];
15
15
protected $ renderSqlWithParams = false ;
16
16
protected $ findSource = false ;
17
+ protected $ middleware = [];
17
18
protected $ explainQuery = false ;
18
19
protected $ explainTypes = ['SELECT ' ]; // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+
19
20
protected $ showHints = false ;
@@ -52,10 +53,12 @@ public function setShowHints($enabled = true)
52
53
* Enable/disable finding the source
53
54
*
54
55
* @param bool $value
56
+ * @param array $middleware
55
57
*/
56
- public function setFindSource ($ value = true )
58
+ public function setFindSource ($ value, array $ middleware )
57
59
{
58
60
$ this ->findSource = (bool ) $ value ;
61
+ $ this ->middleware = $ middleware ;
59
62
}
60
63
61
64
/**
@@ -97,7 +100,7 @@ public function addQuery($query, $bindings, $time, $connection)
97
100
$ explainResults = $ statement ->fetchAll (\PDO ::FETCH_CLASS );
98
101
}
99
102
100
- $ bindings = $ this ->checkBindings ($ bindings );
103
+ $ bindings = $ this ->getDataFormatter ()-> checkBindings ($ bindings );
101
104
if (!empty ($ bindings ) && $ this ->renderSqlWithParams ) {
102
105
foreach ($ bindings as $ key => $ binding ) {
103
106
// This regex matches placeholders only, not the question marks,
@@ -110,7 +113,8 @@ public function addQuery($query, $bindings, $time, $connection)
110
113
}
111
114
}
112
115
113
- $ source = null ;
116
+ $ source = [];
117
+
114
118
if ($ this ->findSource ) {
115
119
try {
116
120
$ source = $ this ->findSource ();
@@ -120,7 +124,8 @@ public function addQuery($query, $bindings, $time, $connection)
120
124
121
125
$ this ->queries [] = [
122
126
'query ' => $ query ,
123
- 'bindings ' => $ this ->escapeBindings ($ bindings ),
127
+ 'type ' => 'query ' ,
128
+ 'bindings ' => $ this ->getDataFormatter ()->escapeBindings ($ bindings ),
124
129
'time ' => $ time ,
125
130
'source ' => $ source ,
126
131
'explain ' => $ explainResults ,
@@ -133,36 +138,6 @@ public function addQuery($query, $bindings, $time, $connection)
133
138
}
134
139
}
135
140
136
- /**
137
- * Check bindings for illegal (non UTF-8) strings, like Binary data.
138
- *
139
- * @param $bindings
140
- * @return mixed
141
- */
142
- protected function checkBindings ($ bindings )
143
- {
144
- foreach ($ bindings as &$ binding ) {
145
- if (is_string ($ binding ) && !mb_check_encoding ($ binding , 'UTF-8 ' )) {
146
- $ binding = '[BINARY DATA] ' ;
147
- }
148
- }
149
- return $ bindings ;
150
- }
151
-
152
- /**
153
- * Make the bindings safe for outputting.
154
- *
155
- * @param array $bindings
156
- * @return array
157
- */
158
- protected function escapeBindings ($ bindings )
159
- {
160
- foreach ($ bindings as &$ binding ) {
161
- $ binding = htmlentities ($ binding , ENT_QUOTES , 'UTF-8 ' , false );
162
- }
163
- return $ bindings ;
164
- }
165
-
166
141
/**
167
142
* Explainer::performQueryAnalysis()
168
143
*
@@ -200,39 +175,102 @@ protected function performQueryAnalysis($query)
200
175
$ hints [] = 'An argument has a leading wildcard character: <code> ' . $ matches [1 ]. '</code>.
201
176
The predicate with this argument is not sargable and cannot use an index if one exists. ' ;
202
177
}
203
- return implode ( " <br /> " , $ hints) ;
178
+ return $ hints ;
204
179
}
205
180
206
181
/**
207
- * Use a backtrace to search for the origin of the query.
182
+ * Use a backtrace to search for the origins of the query.
183
+ *
184
+ * @return array
208
185
*/
209
186
protected function findSource ()
210
187
{
211
- $ traces = debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT );
212
- foreach ($ traces as $ trace ) {
213
- if (isset ($ trace ['class ' ]) && isset ($ trace ['file ' ]) && strpos (
214
- $ trace ['file ' ],
215
- DIRECTORY_SEPARATOR . 'vendor ' . DIRECTORY_SEPARATOR
216
- ) === false
217
- ) {
218
- if (isset ($ trace ['object ' ]) && is_a ($ trace ['object ' ], 'Twig_Template ' )) {
219
- list ($ file , $ line ) = $ this ->getTwigInfo ($ trace );
220
- } elseif (strpos ($ trace ['file ' ], storage_path ()) !== false ) {
221
- $ hash = pathinfo ($ trace ['file ' ], PATHINFO_FILENAME );
222
- $ line = isset ($ trace ['line ' ]) ? $ trace ['line ' ] : '? ' ;
223
-
224
- if ($ name = $ this ->findViewFromHash ($ hash )) {
225
- return 'view:: ' . $ name . ': ' . $ line ;
226
- }
227
- return 'view:: ' . $ hash . ': ' . $ line ;
188
+ $ stack = debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT );
189
+
190
+ $ sources = [];
191
+
192
+ foreach ($ stack as $ index => $ trace ) {
193
+ $ sources [] = $ this ->parseTrace ($ index , $ trace );
194
+ }
195
+
196
+ return array_filter ($ sources );
197
+ }
198
+
199
+ /**
200
+ * Parse a trace element from the backtrace stack.
201
+ *
202
+ * @param int $index
203
+ * @param array $trace
204
+ * @return array|bool
205
+ */
206
+ protected function parseTrace ($ index , array $ trace )
207
+ {
208
+ $ frame = (object ) [
209
+ 'index ' => $ index ,
210
+ 'namespace ' => null ,
211
+ 'name ' => null ,
212
+ 'line ' => isset ($ trace ['line ' ]) ? $ trace ['line ' ] : '? ' ,
213
+ ];
214
+
215
+ if (isset ($ trace ['function ' ]) && $ trace ['function ' ] == 'substituteBindings ' ) {
216
+ $ frame ->name = 'Route binding ' ;
217
+
218
+ return $ frame ;
219
+ }
220
+
221
+ if (isset ($ trace ['class ' ]) && isset ($ trace ['file ' ]) && strpos (
222
+ $ trace ['file ' ],
223
+ DIRECTORY_SEPARATOR . 'vendor ' . DIRECTORY_SEPARATOR
224
+ ) === false
225
+ ) {
226
+ $ file = $ trace ['file ' ];
227
+
228
+ if (isset ($ trace ['object ' ]) && is_a ($ trace ['object ' ], 'Twig_Template ' )) {
229
+ list ($ file , $ frame ->line ) = $ this ->getTwigInfo ($ trace );
230
+ } elseif (strpos ($ file , storage_path ()) !== false ) {
231
+ $ hash = pathinfo ($ file , PATHINFO_FILENAME );
232
+
233
+ if (! $ frame ->name = $ this ->findViewFromHash ($ hash )) {
234
+ $ frame ->name = $ hash ;
235
+ }
236
+
237
+ $ frame ->namespace = 'view ' ;
238
+
239
+ return $ frame ;
240
+ } elseif (strpos ($ file , 'Middleware ' ) !== false ) {
241
+ $ frame ->name = $ this ->findMiddlewareFromFile ($ file );
242
+
243
+ if ($ frame ->name ) {
244
+ $ frame ->namespace = 'middleware ' ;
228
245
} else {
229
- $ file = $ trace ['file ' ];
230
- $ line = isset ($ trace ['line ' ]) ? $ trace ['line ' ] : '? ' ;
246
+ $ frame ->name = $ this ->normalizeFilename ($ file );
231
247
}
232
248
233
- return $ this ->normalizeFilename ($ file ) . ': ' . $ line ;
234
- } elseif (isset ($ trace ['function ' ]) && $ trace ['function ' ] == 'Illuminate\Routing\{closure} ' ) {
235
- return 'Route binding ' ;
249
+ return $ frame ;
250
+ }
251
+
252
+ $ frame ->name = $ this ->normalizeFilename ($ file );
253
+
254
+ return $ frame ;
255
+ }
256
+
257
+
258
+ return false ;
259
+ }
260
+
261
+ /**
262
+ * Find the middleware alias from the file.
263
+ *
264
+ * @param string $file
265
+ * @return string|null
266
+ */
267
+ protected function findMiddlewareFromFile ($ file )
268
+ {
269
+ $ filename = pathinfo ($ file , PATHINFO_FILENAME );
270
+
271
+ foreach ($ this ->middleware as $ alias => $ class ) {
272
+ if (strpos ($ class , $ filename ) !== false ) {
273
+ return $ alias ;
236
274
}
237
275
}
238
276
}
@@ -298,6 +336,35 @@ protected function normalizeFilename($path)
298
336
return str_replace (base_path (), '' , $ path );
299
337
}
300
338
339
+ /**
340
+ * Collect a database transaction event.
341
+ * @param string $event
342
+ * @param \Illuminate\Database\Connection $connection
343
+ * @return array
344
+ */
345
+ public function collectTransactionEvent ($ event , $ connection )
346
+ {
347
+ $ source = [];
348
+
349
+ if ($ this ->findSource ) {
350
+ try {
351
+ $ source = $ this ->findSource ();
352
+ } catch (\Exception $ e ) {
353
+ }
354
+ }
355
+
356
+ $ this ->queries [] = [
357
+ 'query ' => $ event ,
358
+ 'type ' => 'transaction ' ,
359
+ 'bindings ' => [],
360
+ 'time ' => 0 ,
361
+ 'source ' => $ source ,
362
+ 'explain ' => [],
363
+ 'connection ' => $ connection ->getDatabaseName (),
364
+ 'hints ' => null ,
365
+ ];
366
+ }
367
+
301
368
/**
302
369
* Reset the queries.
303
370
*/
@@ -318,33 +385,37 @@ public function collect()
318
385
foreach ($ queries as $ query ) {
319
386
$ totalTime += $ query ['time ' ];
320
387
321
- $ bindings = $ query ['bindings ' ];
322
- if ($ query ['hints ' ]){
323
- $ bindings ['hints ' ] = $ query ['hints ' ];
324
- }
325
-
326
388
$ statements [] = [
327
- 'sql ' => $ this ->formatSql ($ query ['query ' ]),
328
- 'params ' => (object ) $ bindings ,
389
+ 'sql ' => $ this ->getDataFormatter ()->formatSql ($ query ['query ' ]),
390
+ 'type ' => $ query ['type ' ],
391
+ 'params ' => [],
392
+ 'bindings ' => $ query ['bindings ' ],
393
+ 'hints ' => $ query ['hints ' ],
394
+ 'backtrace ' => array_values ($ query ['source ' ]),
329
395
'duration ' => $ query ['time ' ],
330
- 'duration_str ' => $ this ->formatDuration ($ query ['time ' ]),
331
- 'stmt_id ' => $ query ['source ' ],
396
+ 'duration_str ' => ( $ query [ ' type ' ] == ' transaction ' ) ? '' : $ this ->formatDuration ($ query ['time ' ]),
397
+ 'stmt_id ' => $ this -> getDataFormatter ()-> formatSource ( reset ( $ query ['source ' ])) ,
332
398
'connection ' => $ query ['connection ' ],
333
399
];
334
400
335
401
//Add the results from the explain as new rows
336
402
foreach ($ query ['explain ' ] as $ explain ){
337
403
$ statements [] = [
338
404
'sql ' => ' - EXPLAIN # ' . $ explain ->id . ': ` ' . $ explain ->table . '` ( ' . $ explain ->select_type . ') ' ,
405
+ 'type ' => 'explain ' ,
339
406
'params ' => $ explain ,
340
407
'row_count ' => $ explain ->rows ,
341
408
'stmt_id ' => $ explain ->id ,
342
409
];
343
410
}
344
411
}
345
412
413
+ $ nb_statements = array_filter ($ queries , function ($ query ) {
414
+ return $ query ['type ' ] == 'query ' ;
415
+ });
416
+
346
417
$ data = [
347
- 'nb_statements ' => count ($ queries ),
418
+ 'nb_statements ' => count ($ nb_statements ),
348
419
'nb_failed_statements ' => 0 ,
349
420
'accumulated_duration ' => $ totalTime ,
350
421
'accumulated_duration_str ' => $ this ->formatDuration ($ totalTime ),
@@ -353,17 +424,6 @@ public function collect()
353
424
return $ data ;
354
425
}
355
426
356
- /**
357
- * Removes extra spaces at the beginning and end of the SQL query and its lines.
358
- *
359
- * @param string $sql
360
- * @return string
361
- */
362
- protected function formatSql ($ sql )
363
- {
364
- return trim (preg_replace ("/\s* \n\s*/ " , "\n" , $ sql ));
365
- }
366
-
367
427
/**
368
428
* {@inheritDoc}
369
429
*/
@@ -380,7 +440,7 @@ public function getWidgets()
380
440
return [
381
441
"queries " => [
382
442
"icon " => "database " ,
383
- "widget " => "PhpDebugBar.Widgets.SQLQueriesWidget " ,
443
+ "widget " => "PhpDebugBar.Widgets.LaravelSQLQueriesWidget " ,
384
444
"map " => "queries " ,
385
445
"default " => "[] "
386
446
],
0 commit comments