Completed
Push — master ( d0292c...69a6cf )
by recca
09:17 queued 02:09
created

DatabasePanel::hightlight()   C

Complexity

Conditions 16
Paths 1

Size

Total Lines 62
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 16

Importance

Changes 0
Metric Value
cc 16
eloc 38
nc 1
nop 3
dl 0
loc 62
ccs 33
cts 33
cp 1
crap 16
rs 6.1338
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Recca0120\LaravelTracy\Panels;
4
5
use PDO;
6
use DateTime;
7
use Exception;
8
9
class DatabasePanel extends AbstractSubscriablePanel implements IAjaxPanel
10
{
11
    /**
12
     * $queries.
13
     *
14
     * @var array
15
     */
16
    protected $queries = [];
17
18
    /**
19
     * $totalTime.
20
     *
21
     * @var float
22
     */
23
    protected $totalTime = 0.0;
24
25
    /**
26
     * $counter.
27
     *
28
     * @var int
29
     */
30
    protected $counter = 0;
31
32
    /**
33
     * KEYWORDS1.
34
     *
35
     * @var string
36
     */
37
    const 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';
38
39
    /**
40
     * KEYWORDS2.
41
     *
42
     * @var string
43
     */
44
    const KEYWORDS2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE';
45
46
    /**
47
     * explain sql.
48
     *
49
     * @param PDO $pdo
50
     * @param string $sql
51
     * @param array $bindings
52
     * @return array
53
     */
54 1
    public static function explain(PDO $pdo, $sql, $bindings = [])
55
    {
56 1
        $explains = [];
57 1
        if (preg_match('#\s*\(?\s*SELECT\s#iA', $sql)) {
58 1
            $statement = $pdo->prepare('EXPLAIN '.$sql);
59 1
            $statement->execute($bindings);
60 1
            $explains = $statement->fetchAll(PDO::FETCH_CLASS);
61
        }
62
63 1
        return $explains;
64
    }
65
66
    /**
67
     * Returns syntax highlighted SQL command.
68
     *
69
     *
70
     * @param string $sql
71
     * @param array $bindings
72
     * @param \PDO $pdo
73
     * @return string
74
     */
75 4
    public static function hightlight($sql, array $bindings = [], PDO $pdo = null)
76
    {
77
        // insert new lines
78 4
        $sql = " $sql ";
79 4
        $sql = preg_replace('#(?<=[\\s,(])('.static::KEYWORDS1.')(?=[\\s,)])#i', "\n\$1", $sql);
80
81
        // reduce spaces
82 4
        $sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
83
84
        // syntax highlight
85 4
        $sql = htmlspecialchars($sql, ENT_IGNORE, 'UTF-8');
86
        $sql = preg_replace_callback('#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])('.static::KEYWORDS1.')(?=[\\s,)])|(?<=[\\s,(=])('.static::KEYWORDS2.')(?=[\\s,)=])#is', function ($matches) {
87 4
            if (! empty($matches[1])) { // comment
88 1
                return '<em style="color:gray">'.$matches[1].'</em>';
89 4
            } elseif (! empty($matches[2])) { // error
90 1
                return '<strong style="color:red">'.$matches[2].'</strong>';
91 4
            } elseif (! empty($matches[3])) { // most important keywords
92 4
                return '<strong style="color:blue; text-transform: uppercase;">'.$matches[3].'</strong>';
93 1
            } elseif (! empty($matches[4])) { // other keywords
94 1
                return '<strong style="color:green">'.$matches[4].'</strong>';
95
            }
96 4
        }, $sql);
97
98
        $bindings = array_map(function ($binding) use ($pdo) {
99 3
            if (is_array($binding) === true) {
100
                $binding = implode(', ', array_map(function ($value) {
101 1
                    return is_string($value) === true ? htmlspecialchars('\''.$value.'\'', ENT_NOQUOTES, 'UTF-8') : $value;
102 1
                }, $binding));
103
104 1
                return htmlspecialchars('('.$binding.')', ENT_NOQUOTES, 'UTF-8');
105
            }
106
107 3
            if (is_string($binding) === true && (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $binding) || preg_last_error())) {
108
                return '<i title="Length '.strlen($binding).' bytes">&lt;binary&gt;</i>';
109
            }
110
111 3
            if (is_string($binding) === true) {
112 3
                $text = htmlspecialchars($pdo ? $pdo->quote($binding) : '\''.$binding.'\'', ENT_NOQUOTES, 'UTF-8');
113
114 3
                return '<span title="Length '.strlen($text).' characters">'.$text.'</span>';
115
            }
116
117 1
            if (is_resource($binding) === true) {
118 1
                $type = get_resource_type($binding);
119 1
                if ($type === 'stream') {
120 1
                    $info = stream_get_meta_data($binding);
121
                }
122
123 1
                return '<i'.(isset($info['uri']) ? ' title="'.htmlspecialchars($info['uri'], ENT_NOQUOTES, 'UTF-8').'"' : null)
124 1
                    .'>&lt;'.htmlspecialchars($type, ENT_NOQUOTES, 'UTF-8').' resource&gt;</i>';
125
            }
126
127 1
            if ($binding instanceof DateTime) {
128 1
                return htmlspecialchars('\''.$binding->format('Y-m-d H:i:s').'\'', ENT_NOQUOTES, 'UTF-8');
129
            }
130
131
            return htmlspecialchars($binding, ENT_NOQUOTES, 'UTF-8');
132 4
        }, $bindings);
133 4
        $sql = str_replace(['%', '?'], ['%%', '%s'], $sql);
134
135 4
        return '<div><code>'.nl2br(trim(vsprintf($sql, $bindings))).'</code></div>';
136
    }
137
138
    /**
139
     * perform quer analysis hint.
140
     *
141
     * @param string $sql
142
     * @param string $version
143
     * @param float $driver
144
     * @return array
145
     */
146 2
    public static function performQueryAnalysis($sql, $version = null, $driver = null)
147
    {
148 2
        $hints = [];
149 2
        if (preg_match('/^\\s*SELECT\\s*`?[a-zA-Z0-9]*`?\\.?\\*/i', $sql)) {
150 2
            $hints[] = 'Use <code>SELECT *</code> only if you need all columns from table';
151
        }
152 2
        if (preg_match('/ORDER BY RAND()/i', $sql)) {
153 1
            $hints[] = '<code>ORDER BY RAND()</code> is slow, try to avoid if you can.
154
                You can <a href="http://stackoverflow.com/questions/2663710/how-does-mysqls-order-by-rand-work">read this</a>
155
                or <a href="http://stackoverflow.com/questions/1244555/how-can-i-optimize-mysqls-order-by-rand-function">this</a>';
156
        }
157 2
        if (strpos($sql, '!=') !== false) {
158 1
            $hints[] = 'The <code>!=</code> operator is not standard. Use the <code>&lt;&gt;</code> operator to test for inequality instead.';
159
        }
160 2
        if (stripos($sql, 'WHERE') === false) {
161 1
            $hints[] = 'The <code>SELECT</code> statement has no <code>WHERE</code> clause and could examine many more rows than intended';
162
        }
163 2
        if (preg_match('/LIMIT\\s/i', $sql) && stripos($sql, 'ORDER BY') === false) {
164 1
            $hints[] = '<code>LIMIT</code> without <code>ORDER BY</code> causes non-deterministic results, depending on the query execution plan';
165
        }
166 2
        if (preg_match('/LIKE\\s[\'"](%.*?)[\'"]/i', $sql, $matches)) {
167 1
            $hints[] = 'An argument has a leading wildcard character: <code>'.$matches[1].'</code>.
168 1
                The predicate with this argument is not sargable and cannot use an index if one exists.';
169
        }
170 2
        if ($version < 5.5 && $driver === 'mysql') {
171 2
            if (preg_match('/\\sIN\\s*\\(\\s*SELECT/i', $sql)) {
172 1
                $hints[] = '<code>IN()</code> and <code>NOT IN()</code> subqueries are poorly optimized in that MySQL version : '.$version.
173 1
                    '. MySQL executes the subquery as a dependent subquery for each row in the outer query';
174
            }
175
        }
176
177 2
        return $hints;
178
    }
179
180
    /**
181
     * subscribe.
182
     */
183 2
    protected function subscribe()
184
    {
185 2
        $events = $this->laravel['events'];
186 2
        if (version_compare($this->laravel->version(), 5.2, '>=') === true) {
187
            $events->listen('Illuminate\Database\Events\QueryExecuted', function ($event) {
188 1
                $this->logQuery(
189 1
                    $event->sql,
190 1
                    $event->bindings,
191 1
                    $event->time,
192 1
                    $event->connectionName,
193 1
                    $event->connection->getPdo()
194
                );
195 1
            });
196
        } else {
197 1
            $events->listen('illuminate.query', function ($sql, $bindings, $time, $connectionName) {
198 1
                $this->logQuery(
199
                    $sql,
200
                    $bindings,
201
                    $time,
202
                    $connectionName,
203 1
                    $this->laravel['db']->connection($connectionName)->getPdo()
204
                );
205 1
            });
206
        }
207 2
    }
208
209
    /**
210
     * logQuery.
211
     *
212
     * @param string $sql
213
     * @param array $bindings
214
     * @param int $time
215
     * @param string $name
216
     * @param PDO $pdo
217
     * @param string $driver
218
     * @return $this
219
     */
220 3
    public function logQuery($sql, $bindings = [], $time = 0, $name = null, PDO $pdo = null, $driver = 'mysql')
221
    {
222 3
        ++$this->counter;
223 3
        $this->totalTime += $time;
224 3
        $source = self::findSource();
225 3
        $editorLink = self::editorLink($source);
226 3
        $this->queries[] = [
227 3
            'sql' => $sql,
228 3
            'bindings' => $bindings,
229 3
            'time' => $time,
230 3
            'name' => $name,
231 3
            'pdo' => $pdo,
232 3
            'driver' => $driver,
233 3
            'source' => $source,
234 3
            'editorLink' => $editorLink,
235
            'formattedSql' => null,
236
            'fullSql' => null,
237
        ];
238
239 3
        return $this;
240
    }
241
242
    /**
243
     * getAttributes.
244
     *
245
     * @return array
246
     */
247 3
    protected function getAttributes()
248
    {
249 3
        $queries = [];
250 3
        foreach ($this->queries as $query) {
251 3
            $sql = $query['sql'];
252 3
            $bindings = $query['bindings'];
253 3
            $pdo = $query['pdo'];
254 3
            $driver = $query['driver'];
255 3
            $version = 0;
256
257 3
            $hightlight = static::hightlight($sql, $bindings, $pdo);
258 3
            $explains = [];
259 3
            $hints = [];
260 3
            if ($pdo instanceof PDO) {
261 2
                $driver = $this->getDatabaseDriver($pdo);
262 2
                if ($driver === 'mysql') {
263 1
                    $version = $this->getDatabaseVersion($pdo);
264 1
                    $explains = static::explain($pdo, $sql, $bindings);
265 1
                    $hints = static::performQueryAnalysis($sql, $version, $driver);
266
                }
267
            }
268
269 3
            $queries[] = array_merge($query, compact('hightlight', 'explains', 'hints', 'driver', 'version'));
270
        }
271
272
        return [
273 3
            'counter' => $this->counter,
274 3
            'totalTime' => $this->totalTime,
275 3
            'queries' => $queries,
276
        ];
277
    }
278
279
    /**
280
     * getDatabaseDriver.
281
     *
282
     * @param \PDO $pdo
283
     * @return string
284
     */
285 2
    protected function getDatabaseDriver(PDO $pdo)
286
    {
287
        try {
288 2
            $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
289 1
        } catch (Exception $e) {
290 1
            $driver = null;
291
        }
292
293 2
        return $driver;
294
    }
295
296
    /**
297
     * getDatabaseVersion.
298
     *
299
     * @param \PDO $pdo
300
     * @return string
301
     */
302 1
    protected function getDatabaseVersion(PDO $pdo)
303
    {
304
        try {
305 1
            $version = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
306 1
        } catch (Exception $e) {
307 1
            $version = 0;
308
        }
309
310 1
        return $version;
311
    }
312
}
313