CDatabaseBasic   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 456
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 47.13%

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 1
dl 0
loc 456
ccs 74
cts 157
cp 0.4713
rs 8.96
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setOptions() 0 24 3
B connect() 0 35 6
A setVerbose() 0 4 1
A setFetchMode() 0 8 2
A setFetchModeClass() 0 4 1
A loadHistory() 0 10 2
A saveHistory() 0 15 2
A getNumQueries() 0 4 1
A getQueries() 0 4 1
A dump() 0 11 3
A expandParamArray() 0 26 4
A executeFetchAll() 0 8 1
A executeFetchOne() 0 8 1
A fetchAll() 0 4 1
A fetchOne() 0 4 1
A fetchObject() 0 4 1
A fetchInto() 0 5 1
B execute() 0 52 7
A lastInsertId() 0 4 1
A rowCount() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like CDatabaseBasic 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 CDatabaseBasic, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Mos\Database;
4
5
/**
6
 * Database wrapper, provides a database API for the framework but hides details of implementation.
7
 *
8
 */
9
class CDatabaseBasic
10
{
11
12
    use TSQLQueryBuilderBasic;
13
14
15
16
    /**
17
     * Properties
18
     */
19
    private $options;                   // Options used when creating the PDO object
20
    private $db   = null;               // The PDO object
21
    private $stmt = null;               // The latest statement used to execute a query
22
    private static $numQueries = 0;     // Count all queries made
23
    private static $queries    = [];    // Save all queries for debugging purpose
24
    private static $params     = [];    // Save all parameters for debugging purpose
25
26
27
28
    /**
29
     * Constructor creating a PDO object connecting to a choosen database.
30
     *
31
     * @param array $options containing details for connecting to the database.
32
     */
33 9
    public function __construct($options = [])
34
    {
35 9
        $this->setOptions($options);
36 9
    }
37
38
39
40
    /**
41
     * Set options and connection details.
42
     *
43
     * @param array $options containing details for connecting to the database.
44
     *
45
     * @return void
46
     */
47 9
    public function setOptions($options = [])
48
    {
49
        $default = [
50 9
            'dsn'             => null,
51 9
            'username'        => null,
52 9
            'password'        => null,
53 9
            'driver_options'  => null,
54 9
            'table_prefix'    => null,
55 9
            'fetch_mode'      => \PDO::FETCH_OBJ,
56 9
            'session_key'     => 'CDatabase',
57 9
            'verbose'         => null,
58 9
            'debug_connect'   => false,
59 9
        ];
60 9
        $this->options = array_merge($default, $options);
61
62 9
        if ($this->options['table_prefix']) {
63
            $this->setTablePrefix($this->options['table_prefix']);
64
        }
65
66 9
        if ($this->options['dsn']) {
67 9
            $dsn = explode(':', $this->options['dsn']);
68 9
            $this->setSQLDialect($dsn[0]);
69 9
        }
70 9
    }
71
72
73
74
    /**
75
     * Connect to the database.
76
     *
77
     * @param boolean $debug default false, set to true to throw exception with full connection details 
78
     * when connection fails.
79
     *
80
     * @return void
81
     */
82 9
    public function connect($debug = false)
83
    {
84 9
        if (isset($this->options['dsn'])) {
85 9
            if ($this->options['verbose']) {
86
                echo "<p>Connecting to dsn:<br><code>" . $this->options['dsn'] . "</code>";
87
            }
88
89
            try {
90 9
                $this->db = new \PDO(
91 9
                    $this->options['dsn'],
92 9
                    $this->options['username'],
93 9
                    $this->options['password'],
94 9
                    $this->options['driver_options']
95 9
                );
96
97 9
                $this->db->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $this->options['fetch_mode']);
98 9
                $this->db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
99
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
100 9
            } catch(\Exception $e) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after CATCH keyword; 0 found
Loading history...
Coding Style introduced by
Blank line found at start of control structure
Loading history...
101
102
                if ($debug || $this->options['debug_connect']) {
103
                    // For debug purpose, shows all connection details
104
                    throw $e;
105
                } else {
106
                    // Hide connection details.
107
                    throw new \PDOException("Could not connect to database, hiding connection details. Connect using 'debug' to see the full exception message.");
108
                }
109
            }
110
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
111 9
        } else {
112 1
            throw new \Exception("You can not connect, missing dsn.");
113
        }
114
115 9
        $this->loadHistory();
116 9
    }
117
118
119
120
    /**
121
     * Set and unset verbose mode to display queries made.
122
     *
123
     * @param boolean $on set true to display queries made through echo, false to disable.
124
     *
125
     * @return void
126
     */
127
    public function setVerbose($on = true)
128
    {
129
        $this->options['verbose'] = $on;
130
    }
131
132
133
134
    /**
135
     * Set fetch mode. (OBSOLETE?)
136
     *
137
     * @param int $fetchmode as \PDO::FETCH_OBJ, \PDO::FETCH_CLASS, \PDO::FETCH_INTO, etc.
138
     *
139
     * @return void
140
     */
141
    public function setFetchMode($fetchmode = null)
142
    {
143
        $fetchmode = isset($fetchmode)
144
            ? $fetchmode
145
            : $this->options['fetch_mode'];
146
147
        $this->stmt->setFetchMode($fetchmode);
148
    }
149
150
151
152
    /**
153
     * Set fetchmode to insert Fetch one resultset from previous select statement as an object.
154
     * 
155
     * @param string $class to insert values into.
156
     *
157
     * @return boolean Returns TRUE on success or FALSE on failure.
158
     */
159
    public function setFetchModeClass($class)
160
    {
161
        return $this->stmt->setFetchMode(\PDO::FETCH_CLASS, $class);
162
    }
163
164
165
166
    /**
167
     * Load query-history from session if available.
168
     *
169
     * @return int number of database queries made.
170
     */
171 9
    public function loadHistory()
0 ignored issues
show
Coding Style introduced by
loadHistory uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
172
    {
173 9
        $key = $this->options['session_key'];
174 9
        if (isset($_SESSION['CDatabase'])) {
175
            self::$numQueries = $_SESSION[$key]['numQueries'];
176
            self::$queries    = $_SESSION[$key]['queries'];
177
            self::$params     = $_SESSION[$key]['params'];
178
            unset($_SESSION[$key]);
179
        }
180 9
    }
181
182
183
184
    /**
185
     * Save query-history in session, useful as a flashmemory when redirecting to another page.
186
     * 
187
     * @param string $extra enables to save some extra debug information.
188
     *
189
     * @return void
190
     */
191
    public function saveHistory($extra = null)
0 ignored issues
show
Coding Style introduced by
saveHistory uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
192
    {
193
        if (!is_null($extra)) {
194
            self::$queries[] = $extra;
195
            self::$params[] = null;
196
        }
197
198
        self::$queries[] = 'Saved query-history to session.';
199
        self::$params[] = null;
200
201
        $key = $this->options['session_key'];
202
        $_SESSION[$key]['numQueries'] = self::$numQueries;
203
        $_SESSION[$key]['queries']    = self::$queries;
204
        $_SESSION[$key]['params']     = self::$params;
205
    }
206
207
208
209
    /**
210
     * Get how many queries have been processed.
211
     *
212
     * @return int number of database queries made.
213
     */
214
    public function getNumQueries()
215
    {
216
        return self::$numQueries;
217
    }
218
219
220
221
    /**
222
     * Get all the queries that have been processed.
223
     *
224
     * @return array with queries.
225
     */
226
    public function getQueries()
227
    {
228
        return [self::$queries, self::$params];
229
    }
230
231
232
233
    /**
234
     * Get a html representation of all queries made, for debugging and analysing purpose.
235
     * 
236
     * @return string with html.
237
     */
238
    public function dump()
239
    {
240
        $html  = '<p><i>You have made ' . self::$numQueries . ' database queries.</i></p><pre>';
241
        
242
        foreach (self::$queries as $key => $val) {
243
            $params = empty(self::$params[$key]) ? null : htmlentities(print_r(self::$params[$key], 1), null, 'UTF-8') . '<br/><br/>';
244
            $html .= htmlentities($val, null, 'UTF-8') . '<br/><br/>' . $params;
245
        }
246
        
247
        return $html . '</pre>';
248
    }
249
250
251
252
    /**
253
     * Extend params array to support arrays in it, extract array items and add to $params and insert ? for each entry.
254
     *
255
     * @param string $query  as the query to prepare.
256
     * @param array  $params the parameters that may contain arrays.
257
     *
258
     * @return array with query and params.
259
     */
260 6
    protected function expandParamArray($query, $params)
261
    {
262 6
        $param = [];
263 6
        $offset = -1;
264
265 6
        foreach ($params as $val) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
266
267 2
            $offset = strpos($query, '?', $offset + 1);
268
269 2
            if (is_array($val)) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
270
            
271
                $nrOfItems = count($val);
272
            
273
                if ($nrOfItems) {
274
                    $query = substr($query, 0, $offset) . str_repeat('?,', $nrOfItems  - 1) . '?' . substr($query, $offset + 1);
275
                    $param = array_merge($param, $val);
276
                } else {
277
                    $param[] = null;
278
                }
279
            } else {
280 2
                $param[] = $val;
281
            }
282 6
        }
283
284 6
        return array($query, $param);
285
    }
286
287
288
289
    /**
290
     * Execute a select-query with arguments and return all resultset.
291
     * 
292
     * @param string  $query      the SQL query with ?.
293
     * @param array   $params     array which contains the argument to replace ?.
294
     *
295
     * @return array with resultset.
296
     */
297 1
    public function executeFetchAll(
298
        $query = null,
299
        $params = []
300
    ) {
301
302 1
        $this->execute($query, $params);
303 1
        return $this->fetchAll();
304
    }
305
306
307
308
    /**
309
     * Execute a select-query with arguments and return one resultset.
310
     *
311
     * @param string  $query      the SQL query with ?.
312
     * @param array   $params     array which contains the argument to replace ?.
313
     *
314
     * @return array with resultset.
315
     */
316
    public function executeFetchOne(
317
        $query = null,
318
        $params = []
319
    ) {
320
321
        $this->execute($query, $params);
322
        return $this->fetchOne();
323
    }
324
325
326
327
    /**
328
     * Fetch all resultset from previous select statement.
329
     * 
330
     * @return array with resultset.
331
     */
332 1
    public function fetchAll()
333
    {
334 1
        return $this->stmt->fetchAll();
335
    }
336
337
338
339
    /**
340
     * Fetch one resultset from previous select statement.
341
     * 
342
     * @return array with resultset.
343
     */
344
    public function fetchOne()
345
    {
346
        return $this->stmt->fetch();
347
    }
348
349
350
351
    /**
352
     * Fetch one resultset from previous select statement as an object.
353
     * 
354
     * @param object $class which type of object to instantiate.
355
     *
356
     * @return array with resultset.
357
     */
358
    public function fetchObject($class)
359
    {
360
        return $this->stmt->fetchObject($class);
361
    }
362
363
364
365
    /**
366
     * Fetch one resultset from previous select statement as an object.
367
     * 
368
     * @param object $object to insert values into.
369
     *
370
     * @return array with resultset.
371
     */
372
    public function fetchInto($object)
373
    {
374
        $this->stmt->setFetchMode(\PDO::FETCH_INTO, $object);
375
        return $this->stmt->fetch();
376
    }
377
378
379
380
    /**
381
     * Execute a SQL-query and ignore the resultset.
382
     *
383
     * @param string  $query  the SQL query with ?.
384
     * @param array   $params array which contains the argument to replace ?.
385
     *
386
     * @throws Exception when failing to prepare question.
387
     *
388
     * @return boolean returns TRUE on success or FALSE on failure.
389
     */
390 6
    public function execute(
391
        $query = null,
392
        $params = []
393
    ) {
394
395 6
        if (is_null($query)) {
396 4
            $query = $this->getSQL();
397 6
        } else if (is_array($query)) {
398 1
            $params = $query;
399 1
            $query = $this->getSQL();
400 1
        }
401
402 6
        list($query, $params) = $this->expandParamArray($query, $params);
403
404 6
        self::$queries[] = $query;
405 6
        self::$params[]  = $params;
406 6
        self::$numQueries++;
407
408 6
        if ($this->options['verbose']) {
409
            echo "<p>Num query = "
410
                . self::$numQueries
411
                . "</p><p>Query = </p><pre>"
412
                . htmlentities($query)
413
                . "</pre>"
414
                . (empty($params)
415
                    ? null
416
                    : "<p>Params:</p><pre>" . htmlentities(print_r($params, 1)) . "</pre>"
417
                );
418
        }
419
420 6
        $this->stmt = $this->db->prepare($query);
421
422 6
        if (!$this->stmt) {
423
            $msg = "Error in preparing query: "
424
                . $this->db->errorCode()
425
                . " "
426
                . htmlentities(print_r($this->db->errorInfo(), 1));
427
            throw new \Exception($msg);
428
        }
429
430 6
        $res = $this->stmt->execute($params);
431
432 6
        if (!$res) {
433
            $msg = "Error in executing query: "
434
                . $this->stmt->errorCode()
435
                . " "
436
                . htmlentities(print_r($this->stmt->errorInfo(), 1));
437
            throw new \Exception($msg);
438
        }
439
440 6
        return $res;
441
    }
442
443
444
445
    /**
446
     * Return last insert id.
447
     */
448 1
    public function lastInsertId()
449
    {
450 1
        return $this->db->lastInsertId();
451
    }
452
453
454
455
    /**
456
    * Return rows affected of last INSERT, UPDATE, DELETE
457
    */
458
    public function rowCount()
459
    {
460
        return is_null($this->stmt)
461
            ? $this->stmt
462
            : $this->stmt->rowCount();
463
    }
464
}
465