public static function dumpSql($sql, array $params = NULL, Connection $connection = NULL)
{
static $keywords1 = 'SELECT|(?:ON\\s+DUPLICATE\\s+KEY)?UPDATE|INSERT(?:\\s+INTO)?|REPLACE(?:\\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\\s+BY|ORDER\\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\\s+JOIN|INNER\\s+JOIN|TRUNCATE';
static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE';
// insert new lines
$sql = " {$sql} ";
$sql = preg_replace("#(?<=[\\s,(])({$keywords1})(?=[\\s,)])#i", "\n\$1", $sql);
// reduce spaces
$sql = preg_replace('#[ \\t]{2,}#', ' ', $sql);
$sql = wordwrap($sql, 100);
$sql = preg_replace('#([ \\t]*\\r?\\n){2,}#', "\n", $sql);
// syntax highlight
$sql = htmlSpecialChars($sql, ENT_IGNORE, 'UTF-8');
$sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])({$keywords1})(?=[\\s,)])|(?<=[\\s,(=])({$keywords2})(?=[\\s,)=])#is", function ($matches) {
if (!empty($matches[1])) {
// comment
return '<em style="color:gray">' . $matches[1] . '</em>';
} elseif (!empty($matches[2])) {
// error
return '<strong style="color:red">' . $matches[2] . '</strong>';
} elseif (!empty($matches[3])) {
// most important keywords
return '<strong style="color:blue">' . $matches[3] . '</strong>';
} elseif (!empty($matches[4])) {
// other keywords
return '<strong style="color:green">' . $matches[4] . '</strong>';
}
}, $sql);
// parameters
$sql = preg_replace_callback('#\\?#', function () use($params, $connection) {
static $i = 0;
if (!isset($params[$i])) {
return '?';
}
$param = $params[$i++];
if (is_string($param) && (preg_match('#[^\\x09\\x0A\\x0D\\x20-\\x7E\\xA0-\\x{10FFFF}]#u', $param) || preg_last_error())) {
return '<i title="Length ' . strlen($param) . ' bytes"><binary></i>';
} elseif (is_string($param)) {
$length = Nette\Utils\Strings::length($param);
$truncated = Nette\Utils\Strings::truncate($param, self::$maxLength);
$text = htmlspecialchars($connection ? $connection->quote($truncated) : '\'' . $truncated . '\'', ENT_NOQUOTES, 'UTF-8');
return '<span title="Length ' . $length . ' characters">' . $text . '</span>';
} elseif (is_resource($param)) {
$type = get_resource_type($param);
if ($type === 'stream') {
$info = stream_get_meta_data($param);
}
return '<i' . (isset($info['uri']) ? ' title="' . htmlspecialchars($info['uri'], ENT_NOQUOTES, 'UTF-8') . '"' : NULL) . '><' . htmlSpecialChars($type, ENT_NOQUOTES, 'UTF-8') . ' resource></i> ';
} else {
return htmlspecialchars($param, ENT_NOQUOTES, 'UTF-8');
}
}, $sql);
return '<pre class="dump">' . trim($sql) . "</pre>\n";
}
public function getPanel() { $this->disabled = TRUE; $s = ''; $h = 'htmlSpecialChars'; foreach ($this->queries as $i => $query) { list($sql, $params, $time, $rows, $connection, $source) = $query; $explain = NULL; // EXPLAIN is called here to work SELECT FOUND_ROWS() if ($this->explain && preg_match('#\\s*\\(?\\s*SELECT\\s#iA', $sql)) { try { $cmd = is_string($this->explain) ? $this->explain : 'EXPLAIN'; $explain = $connection->queryArgs("{$cmd} {$sql}", $params)->fetchAll(); } catch (\PDOException $e) { } } $s .= '<tr><td>' . sprintf('%0.3f', $time * 1000); if ($explain) { static $counter; $counter++; $s .= "<br /><a href='#' class='nette-toggler' rel='#nette-DbConnectionPanel-row-{$counter}'>explain ►</a>"; } $s .= '</td><td class="nette-DbConnectionPanel-sql">' . Helpers::dumpSql(self::$maxLength ? Nette\Utils\Strings::truncate($sql, self::$maxLength) : $sql); if ($explain) { $s .= "<table id='nette-DbConnectionPanel-row-{$counter}' class='nette-collapsed'><tr>"; foreach ($explain[0] as $col => $foo) { $s .= "<th>{$h($col)}</th>"; } $s .= "</tr>"; foreach ($explain as $row) { $s .= "<tr>"; foreach ($row as $col) { $s .= "<td>{$h($col)}</td>"; } $s .= "</tr>"; } $s .= "</table>"; } if ($source) { $s .= Nette\Diagnostics\Helpers::editorLink($source[0], $source[1])->class('nette-DbConnectionPanel-source'); } $s .= '</td><td>'; foreach ($params as $param) { $s .= Debugger::dump($param, TRUE); } $s .= '</td><td>' . $rows . '</td></tr>'; } return empty($this->queries) ? '' : '<style> #nette-debug td.nette-DbConnectionPanel-sql { background: white !important } #nette-debug .nette-DbConnectionPanel-source { color: #BBB !important } #nette-debug nette-DbConnectionPanel tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style> <h1>Queries: ' . count($this->queries) . ($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') . '</h1> <div class="nette-inner nette-DbConnectionPanel"> <table> <tr><th>Time ms</th><th>SQL Statement</th><th>Params</th><th>Rows</th></tr>' . $s . ' </table> </div>'; }