Database_Mysql_Driver   C
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 302
Duplicated Lines 50.66 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 153
loc 302
rs 5.9493
wmc 62
lcom 1
cbo 5

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A __destruct() 0 4 2
C connect() 0 31 8
A query() 20 20 4
A set_charset() 0 4 1
A escape_table() 18 18 3
C escape_column() 48 48 10
A regex() 0 6 2
A notregex() 0 6 2
A merge() 8 8 2
A limit() 0 4 1
F compile_select() 49 49 12
A escape_str() 10 10 3
A list_tables() 0 12 3
A show_error() 0 4 1
B list_fields() 0 25 6
A field_data() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Database_Mysql_Driver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Database_Mysql_Driver, and based on these observations, apply Extract Interface, too.

1
<?php defined('SYSPATH') or die('No direct access allowed.');
2
/**
3
 * MySQL Database Driver
4
 *
5
 * $Id: Mysql.php 4344 2009-05-11 16:41:39Z zombor $
6
 *
7
 * @package    Core
8
 * @author     Kohana Team
9
 * @copyright  (c) 2007-2008 Kohana Team
10
 * @license    http://kohanaphp.com/license.html
11
 */
12
class Database_Mysql_Driver extends Database_Driver
13
{
14
15
    /**
16
     * Database connection link
17
     */
18
    protected $link;
19
20
    /**
21
     * Database configuration
22
     */
23
    protected $db_config;
24
25
    /**
26
     * Sets the config for the class.
27
     *
28
     * @param  array  database configuration
29
     */
30
    public function __construct($config)
31
    {
32
        $this->db_config = $config;
33
34
        Kohana::log('debug', 'MySQL Database Driver Initialized');
35
    }
36
37
    /**
38
     * Closes the database connection.
39
     */
40
    public function __destruct()
41
    {
42
        is_resource($this->link) and mysql_close($this->link);
43
    }
44
45
    public function connect()
46
    {
47
        // Check if link already exists
48
        if (is_resource($this->link)) {
49
            return $this->link;
50
        }
51
52
        // Import the connect variables
53
        extract($this->db_config['connection']);
54
55
        // Persistent connections enabled?
56
        $connect = ($this->db_config['persistent'] == true) ? 'mysql_pconnect' : 'mysql_connect';
57
58
        // Build the connection info
59
        $host = isset($host) ? $host : $socket;
60
        $port = isset($port) ? ':'.$port : '';
61
62
        // Make the connection and select the database
63
        if (($this->link = $connect($host.$port, $user, $pass, true)) and mysql_select_db($database, $this->link)) {
64
            if ($charset = $this->db_config['character_set']) {
65
                $this->set_charset($charset);
66
            }
67
68
            // Clear password after successful connect
69
            $this->db_config['connection']['pass'] = null;
70
71
            return $this->link;
72
        }
73
74
        return false;
75
    }
76
77
    /**
78
     * @param string $sql
79
     */
80 View Code Duplication
    public function query($sql)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
    {
82
        // Only cache if it's turned on, and only cache if it's not a write statement
83
        if ($this->db_config['cache'] and ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) {
84
            $hash = $this->query_hash($sql);
85
86
            if (! isset($this->query_cache[$hash])) {
87
                // Set the cached object
88
                $this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
89
            } else {
90
                // Rewind cached result
91
                $this->query_cache[$hash]->rewind();
92
            }
93
94
            // Return the cached query
95
            return $this->query_cache[$hash];
96
        }
97
98
        return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
99
    }
100
101
    public function set_charset($charset)
102
    {
103
        $this->query('SET NAMES '.$this->escape_str($charset));
104
    }
105
106 View Code Duplication
    public function escape_table($table)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
107
    {
108
        if (!$this->db_config['escape']) {
109
            return $table;
110
        }
111
112
        if (stripos($table, ' AS ') !== false) {
113
            // Force 'AS' to uppercase
114
            $table = str_ireplace(' AS ', ' AS ', $table);
115
116
            // Runs escape_table on both sides of an AS statement
117
            $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
118
119
            // Re-create the AS statement
120
            return implode(' AS ', $table);
121
        }
122
        return '`'.str_replace('.', '`.`', $table).'`';
123
    }
124
125 View Code Duplication
    public function escape_column($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
    {
127
        if (!$this->db_config['escape']) {
128
            return $column;
129
        }
130
131
        if ($column == '*') {
132
            return $column;
133
        }
134
135
        // This matches any functions we support to SELECT.
136
        if (preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) {
137
            if (count($matches) == 3) {
138
                return $matches[1].'('.$this->escape_column($matches[2]).')';
139
            } elseif (count($matches) == 5) {
140
                return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
141
            }
142
        }
143
        
144
        // This matches any modifiers we support to SELECT.
145
        if (! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) {
146
            if (stripos($column, ' AS ') !== false) {
147
                // Force 'AS' to uppercase
148
                $column = str_ireplace(' AS ', ' AS ', $column);
149
150
                // Runs escape_column on both sides of an AS statement
151
                $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
152
153
                // Re-create the AS statement
154
                return implode(' AS ', $column);
155
            }
156
157
            return preg_replace('/[^.*]+/', '`$0`', $column);
158
        }
159
160
        $parts = explode(' ', $column);
161
        $column = '';
162
163
        for ($i = 0, $c = count($parts); $i < $c; $i++) {
164
            // The column is always last
165
            if ($i == ($c - 1)) {
166
                $column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
167
            } else { // otherwise, it's a modifier
168
                $column .= $parts[$i].' ';
169
            }
170
        }
171
        return $column;
172
    }
173
174
    public function regex($field, $match, $type, $num_regexs)
175
    {
176
        $prefix = ($num_regexs == 0) ? '' : $type;
177
178
        return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
179
    }
180
181
    public function notregex($field, $match, $type, $num_regexs)
182
    {
183
        $prefix = $num_regexs == 0 ? '' : $type;
184
185
        return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
186
    }
187
188 View Code Duplication
    public function merge($table, $keys, $values)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
    {
190
        // Escape the column names
191
        foreach ($keys as $key => $value) {
192
            $keys[$key] = $this->escape_column($value);
193
        }
194
        return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
195
    }
196
197
    public function limit($limit, $offset = 0)
198
    {
199
        return 'LIMIT '.$offset.', '.$limit;
200
    }
201
202 View Code Duplication
    public function compile_select($database)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        $sql = ($database['distinct'] == true) ? 'SELECT DISTINCT ' : 'SELECT ';
205
        $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
206
207
        if (count($database['from']) > 0) {
208
            // Escape the tables
209
            $froms = array();
210
            foreach ($database['from'] as $from) {
211
                $froms[] = $this->escape_column($from);
212
            }
213
            $sql .= "\nFROM (";
214
            $sql .= implode(', ', $froms).")";
215
        }
216
217
        if (count($database['join']) > 0) {
218
            foreach ($database['join'] as $join) {
219
                $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
220
            }
221
        }
222
223
        if (count($database['where']) > 0) {
224
            $sql .= "\nWHERE ";
225
        }
226
227
        $sql .= implode("\n", $database['where']);
228
229
        if (count($database['groupby']) > 0) {
230
            $sql .= "\nGROUP BY ";
231
            $sql .= implode(', ', $database['groupby']);
232
        }
233
234
        if (count($database['having']) > 0) {
235
            $sql .= "\nHAVING ";
236
            $sql .= implode("\n", $database['having']);
237
        }
238
239
        if (count($database['orderby']) > 0) {
240
            $sql .= "\nORDER BY ";
241
            $sql .= implode(', ', $database['orderby']);
242
        }
243
244
        if (is_numeric($database['limit'])) {
245
            $sql .= "\n";
246
            $sql .= $this->limit($database['limit'], $database['offset']);
247
        }
248
249
        return $sql;
250
    }
251
252 View Code Duplication
    public function escape_str($str)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
253
    {
254
        if (!$this->db_config['escape']) {
255
            return $str;
256
        }
257
258
        is_resource($this->link) or $this->connect();
259
260
        return mysql_real_escape_string($str, $this->link);
261
    }
262
263
    public function list_tables()
264
    {
265
        $tables = array();
266
267
        if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database']))) {
268
            foreach ($query->result(false) as $row) {
269
                $tables[] = current($row);
270
            }
271
        }
272
273
        return $tables;
274
    }
275
276
    public function show_error()
277
    {
278
        return mysql_error($this->link);
279
    }
280
281
    public function list_fields($table)
282
    {
283
        $result = null;
284
285
        foreach ($this->field_data($table) as $row) {
286
            // Make an associative array
287
            $result[$row->Field] = $this->sql_type($row->Type);
288
289
            if ($row->Key === 'PRI' and $row->Extra === 'auto_increment') {
290
                // For sequenced (AUTO_INCREMENT) tables
291
                $result[$row->Field]['sequenced'] = true;
292
            }
293
294
            if ($row->null === 'YES') {
295
                // Set NULL status
296
                $result[$row->Field]['null'] = true;
297
            }
298
        }
299
300
        if (!isset($result)) {
301
            throw new Kohana_Database_Exception('database.table_not_found', $table);
302
        }
303
304
        return $result;
305
    }
306
307
    public function field_data($table)
308
    {
309
        $result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table));
310
311
        return $result->result_array(true);
312
    }
313
} // End Database_Mysql_Driver Class
314
315
/**
316
 * MySQL Result
317
 */
318
class Mysql_Result extends Database_Result
319
{
320
321
    // Fetch function and return type
322
    protected $fetch_type  = 'mysql_fetch_object';
323
    protected $return_type = MYSQL_ASSOC;
324
325
    /**
326
     * Sets up the result variables.
327
     *
328
     * @param  resource  query result
329
     * @param  resource  database link
330
     * @param  boolean   return objects or arrays
331
     * @param  string    SQL query that was run
332
     * @param resource $result
333
     */
334
    public function __construct($result, $link, $object = true, $sql)
335
    {
336
        $this->result = $result;
337
338
        // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
339
        if (is_resource($result)) {
340
            $this->current_row = 0;
341
            $this->total_rows  = mysql_num_rows($this->result);
342
            $this->fetch_type = ($object === true) ? 'mysql_fetch_object' : 'mysql_fetch_array';
343
        } elseif (is_bool($result)) {
344
            if ($result == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
345
                // SQL error
346
                throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
347
            } else {
348
                // Its an DELETE, INSERT, REPLACE, or UPDATE query
349
                $this->insert_id  = mysql_insert_id($link);
350
                $this->total_rows = mysql_affected_rows($link);
351
            }
352
        }
353
354
        // Set result type
355
        $this->result($object);
356
357
        // Store the SQL
358
        $this->sql = $sql;
359
    }
360
361
    /**
362
     * Destruct, the cleanup crew!
363
     */
364
    public function __destruct()
365
    {
366
        if (is_resource($this->result)) {
367
            mysql_free_result($this->result);
368
        }
369
    }
370
371 View Code Duplication
    public function result($object = true, $type = MYSQL_ASSOC)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
    {
373
        $this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
374
375
        // This check has to be outside the previous statement, because we do not
376
        // know the state of fetch_type when $object = NULL
377
        // NOTE - The class set by $type must be defined before fetching the result,
378
        // autoloading is disabled to save a lot of stupid overhead.
379
        if ($this->fetch_type == 'mysql_fetch_object' and $object === true) {
380
            $this->return_type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
381
        } else {
382
            $this->return_type = $type;
383
        }
384
385
        return $this;
386
    }
387
388
    public function as_array($object = null, $type = MYSQL_ASSOC)
389
    {
390
        return $this->result_array($object, $type);
391
    }
392
393
    public function result_array($object = null, $type = MYSQL_ASSOC)
394
    {
395
        $rows = array();
396
397
        if (is_string($object)) {
398
            $fetch = $object;
399
        } elseif (is_bool($object)) {
400
            if ($object === true) {
401
                $fetch = 'mysql_fetch_object';
402
403
                $type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
404
            } else {
405
                $fetch = 'mysql_fetch_array';
406
            }
407
        } else {
408
            // Use the default config values
409
            $fetch = $this->fetch_type;
410
411
            if ($fetch == 'mysql_fetch_object') {
412
                $type = (is_string($this->return_type) and Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass';
413
            }
414
        }
415
416
        if (mysql_num_rows($this->result)) {
417
            // Reset the pointer location to make sure things work properly
418
            mysql_data_seek($this->result, 0);
419
420
            while ($row = $fetch($this->result, $type)) {
421
                $rows[] = $row;
422
            }
423
        }
424
425
        return isset($rows) ? $rows : array();
426
    }
427
428
    public function list_fields()
429
    {
430
        $field_names = array();
431
        while ($field = mysql_fetch_field($this->result)) {
432
            $field_names[] = $field->name;
433
        }
434
435
        return $field_names;
436
    }
437
438 View Code Duplication
    public function seek($offset)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
439
    {
440
        if ($this->offsetExists($offset) and mysql_data_seek($this->result, $offset)) {
441
            // Set the current row to the offset
442
            $this->current_row = $offset;
443
444
            return true;
445
        } else {
446
            return false;
447
        }
448
    }
449
} // End Mysql_Result Class
450