Database_Mssql_Driver   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 278
Duplicated Lines 47.12 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 131
loc 278
rs 8.6206
wmc 50
lcom 1
cbo 4

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A __destruct() 0 4 2
D connect() 0 37 9
A query() 20 20 4
A escape_table() 14 14 2
C escape_column() 48 48 10
A limit() 0 4 1
F compile_select() 49 49 12
A escape_str() 0 13 3
A list_tables() 0 12 2
A show_error() 0 4 1
A list_fields() 0 11 2
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_Mssql_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_Mssql_Driver, and based on these observations, apply Extract Interface, too.

1
<?php defined('SYSPATH') or die('No direct access allowed.');
2
/**
3
 * MSSQL Database Driver
4
 *
5
 * @package    Core
6
 * @author     Kohana Team
7
 * @copyright  (c) 2007-2008 Kohana Team
8
 * @license    http://kohanaphp.com/license.html
9
 */
10
class Database_Mssql_Driver extends Database_Driver
11
{
12
    /**
13
     * Database connection link
14
     */
15
    protected $link;
16
17
    /**
18
     * Database configuration
19
     */
20
    protected $db_config;
21
22
    /**
23
     * Sets the config for the class.
24
     *
25
     * @param  array  database configuration
26
     */
27
    public function __construct($config)
28
    {
29
        $this->db_config = $config;
30
31
        Kohana::log('debug', 'MSSQL Database Driver Initialized');
32
    }
33
34
    /**
35
     * Closes the database connection.
36
     */
37
    public function __destruct()
38
    {
39
        is_resource($this->link) and mssql_close($this->link);
40
    }
41
42
    /**
43
     * Make the connection
44
     *
45
     * @return return connection
46
     */
47
    public function connect()
48
    {
49
        // Check if link already exists
50
        if (is_resource($this->link)) {
51
            return $this->link;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->link; (resource) is incompatible with the return type documented by Database_Mssql_Driver::connect of type return.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
52
        }
53
54
        // Import the connect variables
55
        extract($this->db_config['connection']);
56
57
        // Persistent connections enabled?
58
        $connect = ($this->db_config['persistent'] == true) ? 'mssql_pconnect' : 'mssql_connect';
59
60
        // Build the connection info
61
        $host = isset($host) ? $host : $socket;
62
63
        // Windows uses a comma instead of a colon
64
        $port = (isset($port) and is_string($port)) ? (KOHANA_IS_WIN ? ',' : ':').$port : '';
65
66
        // Make the connection and select the database
67
        if (($this->link = $connect($host.$port, $user, $pass, true)) and mssql_select_db($database, $this->link)) {
68
            /* This is being removed so I can use it, will need to come up with a more elegant workaround in the future...
69
             *
70
            if ($charset = $this->db_config['character_set'])
71
            {
72
                $this->set_charset($charset);
73
            }
74
            */
75
76
            // Clear password after successful connect
77
            $this->db_config['connection']['pass'] = null;
78
79
            return $this->link;
80
        }
81
82
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Database_Mssql_Driver::connect of type return.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
83
    }
84
85
    /**
86
     * @param string $sql
87
     */
88 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...
89
    {
90
        // Only cache if it's turned on, and only cache if it's not a write statement
91
        if ($this->db_config['cache'] and ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql)) {
92
            $hash = $this->query_hash($sql);
93
94
            if (! isset($this->query_cache[$hash])) {
95
                // Set the cached object
96
                $this->query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
97
            } else {
98
                // Rewind cached result
99
                $this->query_cache[$hash]->rewind();
100
            }
101
102
            // Return the cached query
103
            return $this->query_cache[$hash];
104
        }
105
106
        return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
107
    }
108
109 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...
110
    {
111
        if (stripos($table, ' AS ') !== false) {
112
            // Force 'AS' to uppercase
113
            $table = str_ireplace(' AS ', ' AS ', $table);
114
115
            // Runs escape_table on both sides of an AS statement
116
            $table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
117
118
            // Re-create the AS statement
119
            return implode(' AS ', $table);
120
        }
121
        return '['.str_replace('.', '[.]', $table).']';
122
    }
123
124 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...
125
    {
126
        if (!$this->db_config['escape']) {
127
            return $column;
128
        }
129
130
        if ($column == '*') {
131
            return $column;
132
        }
133
134
        // This matches any functions we support to SELECT.
135
        if (preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) {
136
            if (count($matches) == 3) {
137
                return $matches[1].'('.$this->escape_column($matches[2]).')';
138
            } elseif (count($matches) == 5) {
139
                return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]);
140
            }
141
        }
142
143
        // This matches any modifiers we support to SELECT.
144
        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)) {
145
            if (stripos($column, ' AS ') !== false) {
146
                // Force 'AS' to uppercase
147
                $column = str_ireplace(' AS ', ' AS ', $column);
148
149
                // Runs escape_column on both sides of an AS statement
150
                $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
151
152
                // Re-create the AS statement
153
                return implode(' AS ', $column);
154
            }
155
156
            return preg_replace('/[^.*]+/', '[$0]', $column);
157
        }
158
159
        $parts = explode(' ', $column);
160
        $column = '';
161
162
        for ($i = 0, $c = count($parts); $i < $c; $i++) {
163
            // The column is always last
164
            if ($i == ($c - 1)) {
165
                $column .= preg_replace('/[^.*]+/', '[$0]', $parts[$i]);
166
            } else { // otherwise, it's a modifier
167
                $column .= $parts[$i].' ';
168
            }
169
        }
170
        return $column;
171
    }
172
173
    /**
174
     * Limit in SQL Server 2000 only uses the keyword
175
     * 'TOP'; 2007 may have an offset keyword, but
176
     * I am unsure - for pagination style limit,offset
177
     * functionality, a fancy query needs to be built.
178
     *
179
     * @param unknown_type $limit
180
     * @return string
181
     */
182
    public function limit($limit, $offset=null)
183
    {
184
        return 'TOP '.$limit;
185
    }
186
187 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...
188
    {
189
        $sql = ($database['distinct'] == true) ? 'SELECT DISTINCT ' : 'SELECT ';
190
        $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
191
192
        if (count($database['from']) > 0) {
193
            // Escape the tables
194
            $froms = array();
195
            foreach ($database['from'] as $from) {
196
                $froms[] = $this->escape_column($from);
197
            }
198
            $sql .= "\nFROM ";
199
            $sql .= implode(', ', $froms);
200
        }
201
202
        if (count($database['join']) > 0) {
203
            foreach ($database['join'] as $join) {
204
                $sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
205
            }
206
        }
207
208
        if (count($database['where']) > 0) {
209
            $sql .= "\nWHERE ";
210
        }
211
212
        $sql .= implode("\n", $database['where']);
213
214
        if (count($database['groupby']) > 0) {
215
            $sql .= "\nGROUP BY ";
216
            $sql .= implode(', ', $database['groupby']);
217
        }
218
219
        if (count($database['having']) > 0) {
220
            $sql .= "\nHAVING ";
221
            $sql .= implode("\n", $database['having']);
222
        }
223
224
        if (count($database['orderby']) > 0) {
225
            $sql .= "\nORDER BY ";
226
            $sql .= implode(', ', $database['orderby']);
227
        }
228
229
        if (is_numeric($database['limit'])) {
230
            $sql .= "\n";
231
            $sql .= $this->limit($database['limit']);
232
        }
233
234
        return $sql;
235
    }
236
237
    public function escape_str($str)
238
    {
239
        if (!$this->db_config['escape']) {
240
            return $str;
241
        }
242
243
        is_resource($this->link) or $this->connect();
244
        //mssql_real_escape_string($str, $this->link); <-- this function doesn't exist
245
246
        $characters = array('/\x00/', '/\x1a/', '/\n/', '/\r/', '/\\\/', '/\'/');
247
        $replace    = array('\\\x00', '\\x1a', '\\n', '\\r', '\\\\', "''");
248
        return preg_replace($characters, $replace, $str);
249
    }
250
251
    public function list_tables()
252
    {
253
        $sql    = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']';
254
        $result = $this->query($sql)->result(false, MSSQL_ASSOC);
0 ignored issues
show
Documentation introduced by
MSSQL_ASSOC is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
255
256
        $retval = array();
257
        foreach ($result as $row) {
258
            $retval[] = current($row);
259
        }
260
261
        return $retval;
262
    }
263
264
    public function show_error()
265
    {
266
        return mssql_get_last_message($this->link);
267
    }
268
269
    public function list_fields($table)
270
    {
271
        $result = array();
272
273
        foreach ($this->field_data($table) as $row) {
274
            // Make an associative array
275
            $result[$row->Field] = $this->sql_type($row->Type);
276
        }
277
278
        return $result;
279
    }
280
281
    public function field_data($table)
282
    {
283
        $query = $this->query("SELECT COLUMN_NAME AS Field, DATA_TYPE as Type  FROM INFORMATION_SCHEMA.Columns WHERE TABLE_NAME = '".$this->escape_table($table)."'", $this->link);
0 ignored issues
show
Unused Code introduced by
The call to Database_Mssql_Driver::query() has too many arguments starting with $this->link.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
284
285
        return $query->result_array(true);
286
    }
287
}
288
289
/**
290
 * MSSQL Result
291
 */
292
class Mssql_Result extends Database_Result
293
{
294
295
    // Fetch function and return type
296
    protected $fetch_type  = 'mssql_fetch_object';
297
    protected $return_type = MSSQL_ASSOC;
298
299
    /**
300
     * Sets up the result variables.
301
     *
302
     * @param  resource  query result
303
     * @param  resource  database link
304
     * @param  boolean   return objects or arrays
305
     * @param  string    SQL query that was run
306
     */
307
    public function __construct($result, $link, $object = true, $sql)
308
    {
309
        $this->result = $result;
310
311
        // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
312
        if (is_resource($result)) {
313
            $this->current_row = 0;
314
            $this->total_rows  = mssql_num_rows($this->result);
315
            $this->fetch_type = ($object === true) ? 'mssql_fetch_object' : 'mssql_fetch_array';
316
        } elseif (is_bool($result)) {
317
            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...
318
                // SQL error
319
                throw new Kohana_Database_Exception('database.error', mssql_get_last_message($link).' - '.$sql);
320
            } else {
321
                // Its an DELETE, INSERT, REPLACE, or UPDATE querys
322
                $last_id          = mssql_query('SELECT @@IDENTITY AS last_id', $link);
323
                $result           = mssql_fetch_assoc($last_id);
324
                $this->insert_id  = $result['last_id'];
325
                $this->total_rows = mssql_rows_affected($link);
326
            }
327
        }
328
329
        // Set result type
330
        $this->result($object);
331
332
        // Store the SQL
333
        $this->sql = $sql;
334
    }
335
336
    /**
337
     * Destruct, the cleanup crew!
338
     */
339
    public function __destruct()
340
    {
341
        if (is_resource($this->result)) {
342
            mssql_free_result($this->result);
343
        }
344
    }
345
346 View Code Duplication
    public function result($object = true, $type = MSSQL_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...
347
    {
348
        $this->fetch_type = ((bool) $object) ? 'mssql_fetch_object' : 'mssql_fetch_array';
349
350
        // This check has to be outside the previous statement, because we do not
351
        // know the state of fetch_type when $object = NULL
352
        // NOTE - The class set by $type must be defined before fetching the result,
353
        // autoloading is disabled to save a lot of stupid overhead.
354
        if ($this->fetch_type == 'mssql_fetch_object') {
355
            $this->return_type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
356
        } else {
357
            $this->return_type = $type;
358
        }
359
360
        return $this;
361
    }
362
363
    public function as_array($object = null, $type = MSSQL_ASSOC)
364
    {
365
        return $this->result_array($object, $type);
366
    }
367
368 View Code Duplication
    public function result_array($object = null, $type = MSSQL_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...
369
    {
370
        $rows = array();
371
372
        if (is_string($object)) {
373
            $fetch = $object;
374
        } elseif (is_bool($object)) {
375
            if ($object === true) {
376
                $fetch = 'mssql_fetch_object';
377
378
                // NOTE - The class set by $type must be defined before fetching the result,
379
                // autoloading is disabled to save a lot of stupid overhead.
380
                $type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
381
            } else {
382
                $fetch = 'mssql_fetch_array';
383
            }
384
        } else {
385
            // Use the default config values
386
            $fetch = $this->fetch_type;
387
388
            if ($fetch == 'mssql_fetch_object') {
389
                $type = (is_string($type) and Kohana::auto_load($type)) ? $type : 'stdClass';
390
            }
391
        }
392
393
        if (mssql_num_rows($this->result)) {
394
            // Reset the pointer location to make sure things work properly
395
            mssql_data_seek($this->result, 0);
396
397
            while ($row = $fetch($this->result, $type)) {
398
                $rows[] = $row;
399
            }
400
        }
401
402
        return isset($rows) ? $rows : array();
403
    }
404
405
    public function list_fields()
406
    {
407
        $field_names = array();
408
        while ($field = mssql_fetch_field($this->result)) {
409
            $field_names[] = $field->name;
410
        }
411
412
        return $field_names;
413
    }
414
415
    public function seek($offset)
416
    {
417
        if (! $this->offsetExists($offset)) {
418
            return false;
419
        }
420
421
        return mssql_data_seek($this->result, $offset);
422
    }
423
} // End mssql_Result Class
424