1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* YiiDebugToolbarPanelSql class file. |
4
|
|
|
* |
5
|
|
|
* @author Sergey Malyshev <[email protected]> |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* YiiDebugToolbarPanelSql class. |
11
|
|
|
* |
12
|
|
|
* Description of YiiDebugToolbarPanelSql |
13
|
|
|
* |
14
|
|
|
* @author Sergey Malyshev <[email protected]> |
15
|
|
|
* @author Igor Golovanov <[email protected]> |
16
|
|
|
* @package YiiDebugToolbar |
17
|
|
|
* @since 1.1.7 |
18
|
|
|
*/ |
19
|
|
|
class YiiDebugToolbarPanelSql extends YiiDebugToolbarPanel |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* If true, the sql query in the list will use syntax highlighting. |
23
|
|
|
* |
24
|
|
|
* @var boolean |
25
|
|
|
*/ |
26
|
|
|
public $highlightSql = true; |
27
|
|
|
|
28
|
|
|
private $_countLimit = 1; |
29
|
|
|
|
30
|
|
|
private $_timeLimit = 0.01; |
31
|
|
|
|
32
|
|
|
private $_groupByToken = true; |
33
|
|
|
|
34
|
|
|
private $_dbConnections; |
35
|
|
|
|
36
|
|
|
private $_dbConnectionsCount; |
37
|
|
|
|
38
|
|
|
private $_textHighlighter; |
39
|
|
|
|
40
|
|
|
public function __construct($owner = null) |
41
|
|
|
{ |
42
|
|
|
parent::__construct($owner); |
43
|
|
|
|
44
|
|
|
try |
45
|
|
|
{ |
46
|
|
|
Yii::app()->db; |
47
|
|
|
} |
48
|
|
|
catch (Exception $e) |
49
|
|
|
{ |
50
|
|
|
$this->_dbConnections = false; |
51
|
|
|
} |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
public function getCountLimit() |
55
|
|
|
{ |
56
|
|
|
return $this->_countLimit; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
public function setCountLimit($value) |
60
|
|
|
{ |
61
|
|
|
$this->_countLimit = CPropertyValue::ensureInteger($value); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function getTimeLimit() |
65
|
|
|
{ |
66
|
|
|
return $this->_timeLimit; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
public function setTimeLimit($value) |
70
|
|
|
{ |
71
|
|
|
$this->_timeLimit = CPropertyValue::ensureFloat($value); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
public function getDbConnectionsCount() |
75
|
|
|
{ |
76
|
|
|
if (null === $this->_dbConnectionsCount) |
77
|
|
|
{ |
78
|
|
|
$this->_dbConnectionsCount = count($this->getDbConnections()); |
79
|
|
|
} |
80
|
|
|
return $this->_dbConnectionsCount; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
public function getDbConnections() |
84
|
|
|
{ |
85
|
|
|
if (null === $this->_dbConnections) |
86
|
|
|
{ |
87
|
|
|
$this->_dbConnections = array(); |
88
|
|
|
foreach (Yii::app()->components as $id=>$component) |
89
|
|
|
{ |
90
|
|
|
if (false !== is_object($component) |
91
|
|
|
&& false !== ($component instanceof CDbConnection)) |
|
|
|
|
92
|
|
|
{ |
93
|
|
|
$this->_dbConnections[$id] = $component; |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
return $this->_dbConnections; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* {@inheritdoc} |
102
|
|
|
*/ |
103
|
|
|
public function getMenuTitle() |
104
|
|
|
{ |
105
|
|
|
return YiiDebug::t('SQL'); |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* {@inheritdoc} |
110
|
|
|
*/ |
111
|
|
|
public function getMenuSubTitle($f=4) |
112
|
|
|
{ |
113
|
|
|
if (false !== $this->_dbConnections) |
114
|
|
|
{ |
115
|
|
|
$st = Yii::app()->db->getStats(); |
116
|
|
|
return YiiDebug::t('{n} query in {s} s.|{n} queries in {s} s.', array($st[0], '{s}'=>vsprintf('%0.'.$f.'F', $st[1]))); |
117
|
|
|
} |
118
|
|
|
return YiiDebug::t('No active connections'); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* {@inheritdoc} |
123
|
|
|
*/ |
124
|
|
|
public function getTitle() |
125
|
|
|
{ |
126
|
|
|
if (false !== $this->_dbConnections) |
127
|
|
|
{ |
128
|
|
|
$conn=$this->getDbConnectionsCount(); |
129
|
|
|
return YiiDebug::t('SQL Queries from {n} connection|SQL Queries from {n} connections', array($conn)); |
130
|
|
|
} |
131
|
|
|
return YiiDebug::t('No active connections'); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* {@inheritdoc} |
136
|
|
|
*/ |
137
|
|
|
public function getSubTitle() |
138
|
|
|
{ |
139
|
|
|
return false !== $this->_dbConnections |
140
|
|
|
? ('(' . self::getMenuSubTitle(6) . ')') |
141
|
|
|
: null; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Initialize panel |
146
|
|
|
*/ |
147
|
|
|
public function init() |
148
|
|
|
{ |
149
|
|
|
|
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* {@inheritdoc} |
154
|
|
|
*/ |
155
|
|
|
public function run() |
156
|
|
|
{ |
157
|
|
|
if (false === $this->_dbConnections) |
158
|
|
|
{ |
159
|
|
|
return; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
$logs = $this->filterLogs(); |
163
|
|
|
|
164
|
|
|
$this->render('sql', array( |
165
|
|
|
'connections' => $this->getDbConnections(), |
166
|
|
|
'connectionsCount' => $this->getDbConnectionsCount(), |
167
|
|
|
'summary' => $this->processSummary($logs), |
168
|
|
|
'callstack' => $this->processCallstack($logs) |
169
|
|
|
)); |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
private function duration($secs) |
173
|
|
|
{ |
174
|
|
|
$vals = array( |
175
|
|
|
'w' => (int) ($secs / 86400 / 7), |
176
|
|
|
'd' => $secs / 86400 % 7, |
177
|
|
|
'h' => $secs / 3600 % 24, |
178
|
|
|
'm' => $secs / 60 % 60, |
179
|
|
|
's' => $secs % 60 |
180
|
|
|
); |
181
|
|
|
$result = array(); |
182
|
|
|
$added = false; |
183
|
|
|
foreach ($vals as $k => $v) |
184
|
|
|
{ |
185
|
|
|
if ($v > 0 || false !== $added) |
186
|
|
|
{ |
187
|
|
|
$added = true; |
188
|
|
|
$result[] = $v . $k; |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
return implode(' ', $result); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Returns the DB server info by connection ID. |
196
|
|
|
* @param string $connectionId |
197
|
|
|
* @return mixed |
198
|
|
|
*/ |
199
|
|
|
public function getServerInfo($connectionId) |
200
|
|
|
{ |
201
|
|
|
if (null !== ($connection = Yii::app()->getComponent($connectionId)) |
202
|
|
|
&& false !== ($connection instanceof CDbConnection) |
|
|
|
|
203
|
|
|
&& !in_array($connection->driverName, array('sqlite', 'oci', 'dblib')) |
204
|
|
|
&& '' !== ($serverInfo = $connection->getServerInfo())) |
205
|
|
|
{ |
206
|
|
|
$info = array( |
207
|
|
|
YiiDebug::t('Driver') => $connection->getDriverName(), |
208
|
|
|
YiiDebug::t('Server Version') => $connection->getServerVersion() |
209
|
|
|
); |
210
|
|
|
|
211
|
|
|
$lines = explode(' ', $serverInfo); |
212
|
|
|
foreach($lines as $line) { |
213
|
|
|
list($key, $value) = explode(': ', $line, 2); |
214
|
|
|
$info[YiiDebug::t($key)] = $value; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
if(!empty($info[YiiDebug::t('Uptime')])) { |
218
|
|
|
$info[YiiDebug::t('Uptime')] = $this->duration($info[YiiDebug::t('Uptime')]); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
return $info; |
222
|
|
|
} |
223
|
|
|
return null; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Processing callstack. |
228
|
|
|
* |
229
|
|
|
* @param array $logs Logs. |
230
|
|
|
* @return array |
231
|
|
|
*/ |
232
|
|
|
protected function processCallstack(array $logs) |
233
|
|
|
{ |
234
|
|
|
if (empty($logs)) |
235
|
|
|
{ |
236
|
|
|
return $logs; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
$stack = array(); |
240
|
|
|
$results = array(); |
241
|
|
|
$n = 0; |
242
|
|
|
|
243
|
|
|
foreach ($logs as $log) |
244
|
|
|
{ |
245
|
|
|
if(CLogger::LEVEL_PROFILE !== $log[1]) |
246
|
|
|
continue; |
247
|
|
|
|
248
|
|
|
$message = $log[0]; |
249
|
|
|
|
250
|
|
|
if (0 === strncasecmp($message,'begin:',6)) |
251
|
|
|
{ |
252
|
|
|
$log[0] = substr($message,6); |
253
|
|
|
$log[4] = $n; |
254
|
|
|
$stack[] = $log; |
255
|
|
|
$n++; |
256
|
|
|
} |
257
|
|
|
else if (0 === strncasecmp($message, 'end:', 4)) |
258
|
|
|
{ |
259
|
|
|
$token = substr($message,4); |
260
|
|
|
if(null !== ($last = array_pop($stack)) && $last[0] === $token) |
261
|
|
|
{ |
262
|
|
|
$delta = $log[3] - $last[3]; |
263
|
|
|
$results[$last[4]] = array($token, $delta, count($stack)); |
264
|
|
|
} |
265
|
|
|
else |
266
|
|
|
throw new CException(Yii::t('yii-debug-toolbar', |
267
|
|
|
'Mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', |
268
|
|
|
array('{token}' => $token) |
269
|
|
|
)); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
// remaining entries should be closed here |
273
|
|
|
$now = microtime(true); |
274
|
|
|
while (null !== ($last = array_pop($stack))){ |
275
|
|
|
$results[$last[4]] = array($last[0], $now - $last[3], count($stack)); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
ksort($results); |
279
|
|
|
return array_map(array($this, 'formatLogEntry'), $results); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Processing summary. |
284
|
|
|
* |
285
|
|
|
* @param array $logs Logs. |
286
|
|
|
* @return array |
287
|
|
|
*/ |
288
|
|
|
protected function processSummary(array $logs) |
289
|
|
|
{ |
290
|
|
|
if (empty($logs)) |
291
|
|
|
{ |
292
|
|
|
return $logs; |
293
|
|
|
} |
294
|
|
|
$stack = array(); |
295
|
|
|
foreach($logs as $log) |
296
|
|
|
{ |
297
|
|
|
$message = $log[0]; |
298
|
|
|
if(0 === strncasecmp($message, 'begin:', 6)) |
299
|
|
|
{ |
300
|
|
|
$log[0] =substr($message, 6); |
301
|
|
|
$stack[] =$log; |
302
|
|
|
} |
303
|
|
|
else if(0 === strncasecmp($message,'end:',4)) |
304
|
|
|
{ |
305
|
|
|
$token = substr($message,4); |
306
|
|
|
if(null !== ($last = array_pop($stack)) && $last[0] === $token) |
307
|
|
|
{ |
308
|
|
|
$delta = $log[3] - $last[3]; |
309
|
|
|
|
310
|
|
View Code Duplication |
if(isset($results[$token])) |
|
|
|
|
311
|
|
|
$results[$token] = $this->aggregateResult($results[$token], $delta); |
312
|
|
|
else{ |
313
|
|
|
$results[$token] = array($token, 1, $delta, $delta, $delta); |
|
|
|
|
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
else |
317
|
|
|
throw new CException(Yii::t('yii-debug-toolbar', |
318
|
|
|
'Mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.', |
319
|
|
|
array('{token}' => $token))); |
320
|
|
|
} |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
$now = microtime(true); |
324
|
|
|
while(null !== ($last = array_pop($stack))) |
325
|
|
|
{ |
326
|
|
|
$delta = $now - $last[3]; |
327
|
|
|
$token = $last[0]; |
328
|
|
|
|
329
|
|
View Code Duplication |
if(isset($results[$token])) |
|
|
|
|
330
|
|
|
$results[$token] = $this->aggregateResult($results[$token], $delta); |
331
|
|
|
else{ |
332
|
|
|
$results[$token] = array($token, 1, $delta, $delta, $delta); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
$entries = array_values($results); |
337
|
|
|
$func = create_function('$a,$b','return $a[4]<$b[4]?1:0;'); |
338
|
|
|
|
339
|
|
|
usort($entries, $func); |
340
|
|
|
|
341
|
|
|
return array_map(array($this, 'formatLogEntry'), $entries); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* Format log entry |
346
|
|
|
* |
347
|
|
|
* @param array $entry |
348
|
|
|
* @return array |
349
|
|
|
*/ |
350
|
|
|
public function formatLogEntry(array $entry) |
351
|
|
|
{ |
352
|
|
|
// extract query from the entry |
353
|
|
|
$queryString = $entry[0]; |
354
|
|
|
$sqlStart = strpos($queryString, '(') + 1; |
355
|
|
|
$sqlEnd = strrpos($queryString , ')'); |
356
|
|
|
$sqlLength = $sqlEnd - $sqlStart; |
357
|
|
|
|
358
|
|
|
$queryString = substr($queryString, $sqlStart, $sqlLength); |
359
|
|
|
|
360
|
|
|
if (false !== strpos($queryString, '. Bound with ')) |
361
|
|
|
{ |
362
|
|
|
list($query, $params) = explode('. Bound with ', $queryString); |
363
|
|
|
|
364
|
|
|
$binds = array(); |
365
|
|
|
$matchResult = preg_match_all("/(?<key>[a-z0-9\.\_\-\:]+)=(?<value>[\d\.e\-\+]+|''|'.+?(?<!\\\)')/ims", $params, $paramsMatched, PREG_SET_ORDER); |
366
|
|
|
|
367
|
|
|
if ($matchResult) { |
368
|
|
|
foreach ($paramsMatched as $paramsMatch) |
369
|
|
|
if (isset($paramsMatch['key'], $paramsMatch['value'])) |
370
|
|
|
$binds[trim($paramsMatch['key'])] = trim($paramsMatch['value']); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
|
374
|
|
|
$entry[0] = strtr($query, $binds); |
375
|
|
|
} |
376
|
|
|
else |
377
|
|
|
{ |
378
|
|
|
$entry[0] = $queryString; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
if(false !== CPropertyValue::ensureBoolean($this->highlightSql)) |
382
|
|
|
{ |
383
|
|
|
$entry[0] = $this->getTextHighlighter()->highlight($entry[0]); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$entry[0] = strip_tags($entry[0], '<div>,<span>'); |
387
|
|
|
return $entry; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* @return CTextHighlighter the text highlighter |
393
|
|
|
*/ |
394
|
|
|
private function getTextHighlighter() |
395
|
|
|
{ |
396
|
|
|
if (null === $this->_textHighlighter) |
397
|
|
|
{ |
398
|
|
|
$this->_textHighlighter = Yii::createComponent(array( |
399
|
|
|
'class' => 'CTextHighlighter', |
400
|
|
|
'language' => 'sql', |
401
|
|
|
'showLineNumbers' => false, |
402
|
|
|
)); |
403
|
|
|
} |
404
|
|
|
return $this->_textHighlighter; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Aggregates the report result. |
410
|
|
|
* |
411
|
|
|
* @param array $result log result for this code block |
412
|
|
|
* @param float $delta time spent for this code block |
413
|
|
|
* @return array |
414
|
|
|
*/ |
415
|
|
|
protected function aggregateResult($result, $delta) |
416
|
|
|
{ |
417
|
|
|
list($token, $calls, $min, $max, $total) = $result; |
418
|
|
|
|
419
|
|
|
switch (true) |
420
|
|
|
{ |
421
|
|
|
case ($delta < $min): |
422
|
|
|
$min = $delta; |
423
|
|
|
break; |
424
|
|
|
case ($delta > $max): |
425
|
|
|
$max = $delta; |
426
|
|
|
break; |
427
|
|
|
default: |
428
|
|
|
// nothing |
429
|
|
|
break; |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
$calls++; |
433
|
|
|
$total += $delta; |
434
|
|
|
|
435
|
|
|
return array($token, $calls, $min, $max, $total); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Get filter logs. |
440
|
|
|
* |
441
|
|
|
* @return array |
442
|
|
|
*/ |
443
|
|
View Code Duplication |
protected function filterLogs() |
|
|
|
|
444
|
|
|
{ |
445
|
|
|
$logs = array(); |
446
|
|
|
foreach ($this->owner->getLogs() as $entry) |
447
|
|
|
{ |
448
|
|
|
if (CLogger::LEVEL_PROFILE === $entry[1] && 0 === strpos($entry[2], 'system.db.CDbCommand')) |
449
|
|
|
{ |
450
|
|
|
$logs[] = $entry; |
451
|
|
|
} |
452
|
|
|
} |
453
|
|
|
return $logs; |
454
|
|
|
} |
455
|
|
|
|
456
|
|
|
} |
457
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.