@@ -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,7 @@ public function addQuery($query, $bindings, $time, $connection)
120
124
121
125
$ this ->queries [] = [
122
126
'query ' => $ query ,
123
- 'bindings ' => $ this ->escapeBindings ($ bindings ),
127
+ 'bindings ' => $ this ->getDataFormatter ()-> escapeBindings ($ bindings ),
124
128
'time ' => $ time ,
125
129
'source ' => $ source ,
126
130
'explain ' => $ explainResults ,
@@ -133,36 +137,6 @@ public function addQuery($query, $bindings, $time, $connection)
133
137
}
134
138
}
135
139
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
140
/**
167
141
* Explainer::performQueryAnalysis()
168
142
*
@@ -200,39 +174,103 @@ protected function performQueryAnalysis($query)
200
174
$ hints [] = 'An argument has a leading wildcard character: <code> ' . $ matches [1 ]. '</code>.
201
175
The predicate with this argument is not sargable and cannot use an index if one exists. ' ;
202
176
}
203
- return implode ( " <br /> " , $ hints) ;
177
+ return $ hints ;
204
178
}
205
179
206
180
/**
207
- * Use a backtrace to search for the origin of the query.
181
+ * Use a backtrace to search for the origins of the query.
182
+ *
183
+ * @return array
208
184
*/
209
185
protected function findSource ()
210
186
{
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 ;
187
+ $ stack = debug_backtrace (DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT );
188
+
189
+ $ sources = [];
190
+
191
+ foreach ($ stack as $ index => $ trace ) {
192
+ $ sources [] = $ this ->parseTrace ($ index , $ trace );
193
+ }
194
+
195
+ return array_filter ($ sources );
196
+ }
197
+
198
+ /**
199
+ * Parse a trace element from the backtrace stack.
200
+ *
201
+ * @param int $index
202
+ * @param array $trace
203
+ * @return array|bool
204
+ */
205
+ protected function parseTrace ($ index , array $ trace )
206
+ {
207
+ $ frame = (object ) [
208
+ 'index ' => $ index ,
209
+ 'namespace ' => null ,
210
+ 'name ' => null ,
211
+ 'file ' => 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
+ $ frame ->file = $ trace ['file ' ];
227
+
228
+ if (isset ($ trace ['object ' ]) && is_a ($ trace ['object ' ], 'Twig_Template ' )) {
229
+ list ($ frame ->file , $ frame ->line ) = $ this ->getTwigInfo ($ trace );
230
+ } elseif (strpos ($ frame ->file , storage_path ()) !== false ) {
231
+ $ hash = pathinfo ($ frame ->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 ($ frame ->file , 'Middleware ' ) !== false ) {
241
+ $ frame ->name = $ this ->findMiddlewareFromFile ($ frame ->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 ($ frame ->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 ($ frame ->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,34 @@ 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
+ 'bindings ' => [],
359
+ 'time ' => 0 ,
360
+ 'source ' => $ source ,
361
+ 'explain ' => [],
362
+ 'connection ' => $ connection ->getDatabaseName (),
363
+ 'hints ' => null ,
364
+ ];
365
+ }
366
+
301
367
/**
302
368
* Reset the queries.
303
369
*/
@@ -318,17 +384,14 @@ public function collect()
318
384
foreach ($ queries as $ query ) {
319
385
$ totalTime += $ query ['time ' ];
320
386
321
- $ bindings = $ query ['bindings ' ];
322
- if ($ query ['hints ' ]){
323
- $ bindings ['hints ' ] = $ query ['hints ' ];
324
- }
387
+ $ metadata = $ this ->getDataFormatter ()->formatMetadata ($ query );
325
388
326
389
$ statements [] = [
327
- 'sql ' => $ this ->formatSql ($ query ['query ' ]),
328
- 'params ' => ( object ) $ bindings ,
390
+ 'sql ' => $ this ->getDataFormatter ()-> formatSql ($ query ['query ' ]),
391
+ 'params ' => $ metadata ,
329
392
'duration ' => $ query ['time ' ],
330
393
'duration_str ' => $ this ->formatDuration ($ query ['time ' ]),
331
- 'stmt_id ' => $ query ['source ' ],
394
+ 'stmt_id ' => $ this -> getDataFormatter ()-> formatSource ( reset ( $ query ['source ' ])) ,
332
395
'connection ' => $ query ['connection ' ],
333
396
];
334
397
@@ -353,17 +416,6 @@ public function collect()
353
416
return $ data ;
354
417
}
355
418
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
419
/**
368
420
* {@inheritDoc}
369
421
*/
0 commit comments