Completed
Push — master ( d402d0...8030e8 )
by Todd
02:00
created

Db   F

Complexity

Total Complexity 114

Size/Duplication

Total Lines 920
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 3

Test Coverage

Coverage 85.36%

Importance

Changes 0
Metric Value
wmc 114
lcom 4
cbo 3
dl 0
loc 920
ccs 239
cts 280
cp 0.8536
rs 1.263
c 0
b 0
f 0

41 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
A driverClass() 0 10 3
createTableDb() 0 1 ?
alterTableDb() 0 1 ?
A dropTable() 0 7 1
dropTableDb() 0 1 ?
fetchTableNamesDb() 0 1 ?
B fetchTableDef() 0 22 6
fetchTableDefDb() 0 1 ?
B fetchColumnDefs() 0 15 5
fetchColumnDefsDb() 0 1 ?
nativeDbType() 0 1 ?
F typeDef() 0 73 15
B dbType() 0 21 6
A fetchTableNames() 0 15 3
C defineTable() 0 78 12
A findPrimaryKeyIndex() 0 8 3
C fixIndexes() 0 45 14
A getPx() 0 3 1
B indexCompare() 0 12 5
get() 0 1 ?
A getOne() 0 6 2
insert() 0 1 ?
A load() 0 5 2
update() 0 1 ?
delete() 0 1 ?
A reset() 0 5 1
A buildIndexName() 0 13 3
A query() 0 22 3
A queryModify() 0 5 1
A queryID() 0 7 2
A queryDefine() 0 4 1
C val() 0 23 9
A escape() 0 6 2
A escapeLike() 0 3 1
A prefixTable() 0 8 4
A stripPrefix() 0 7 2
A quote() 0 8 2
A getPDO() 0 3 1
A setPDO() 0 4 1
A setPx() 0 3 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2014 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Db;
9
10
use PDO;
11
12
/**
13
 * Defines a standard set of methods that all database drivers must conform to.
14
 */
15
abstract class Db {
16
    use Utils\FetchModeTrait;
17
18
    const QUERY_DEFINE = 'define';
19
    const QUERY_READ = 'read';
20
    const QUERY_WRITE = 'write';
21
22
    const INDEX_PK = 'primary';
23
    const INDEX_IX = 'index';
24
    const INDEX_UNIQUE = 'unique';
25
26
    const OPTION_REPLACE = 'replace';
27
    const OPTION_IGNORE = 'ignore';
28
    const OPTION_UPSERT = 'upsert';
29
    const OPTION_TRUNCATE = 'truncate';
30
    const OPTION_DROP = 'drop';
31
    const OPTION_FETCH_MODE = 'fetchMode';
32
33
    const OP_EQ = '=';
34
    const OP_GT = '>';
35
    const OP_GTE = '>=';
36
    const OP_IN = '$in';
37
    const OP_LIKE = '$like';
38
    const OP_LT = '<';
39
    const OP_LTE = '<=';
40
    const OP_NEQ = '<>';
41
42
    const OP_AND = '$and';
43
    const OP_OR = '$or';
44
45
    /**
46
     * @var string[] Maps PDO drivers to db classes.
47
     */
48
    private static $drivers = [
49
        'mysql' => MySqlDb::class,
50
        'sqlite' => SqliteDb::class
51
    ];
52
53
    /**
54
     * @var array The canonical database types.
55
     */
56
    private static $types = [
57
        // String
58
        'char' => ['type' => 'string', 'length' => true],
59
        'varchar' => ['type' => 'string', 'length' => true],
60
        'tinytext' => ['type' => 'string', 'schema' => ['maxLength' => 255]],
61
        'text' => ['type' => 'string', 'schema' => ['maxLength' =>  65535]],
62
        'mediumtext' => ['type' => 'string', 'schema' => ['maxLength' => 16777215]],
63
        'longtext' => ['type' => 'string', 'schema' => ['maxLength' => 4294967295]],
64
        'binary' => ['type' => 'string', 'length' => true],
65
        'varbinary' => ['type' => 'string', 'length' => true],
66
67
        // Boolean
68
        'bool' => ['type' => 'boolean'],
69
70
        // Integer
71
        'byte' => ['type' => 'integer', 'schema' => ['maximum' => 127, 'minimum' => -128]],
72
        'short' => ['type' => 'integer', 'schema' => ['maximum' => 32767, 'minimum' => -32768]],
73
        'int' => ['type' => 'integer', 'schema' => ['maximum' => 2147483647, 'minimum' => -2147483648]],
74
        'long' => ['type' => 'integer'],
75
76
        // Number
77
        'float' => ['type' => 'number'],
78
        'double' => ['type' => 'number'],
79
        'decimal' => ['type' => 'number', 'precision' => true],
80
        'numeric' => ['type' => 'number', 'precision' => true],
81
82
        // Date/Time
83
        'datetime' => ['type' => 'datetime'],
84
        'timestamp' => ['type' => 'datetime'],
85
86
        // Enum
87
        'enum' => ['type' => 'string', 'enum' => true],
88
89
        // Schema types
90
        'string' => 'varchar',
91
        'boolean' => 'bool',
92
        'integer' => 'int',
93
        'number' => 'float',
94
95
        // Other aliases
96
        'character' => 'char',
97
        'tinyint' => 'byte',
98
        'int8' => 'byte',
99
        'smallint' => 'short',
100
        'int16' => 'short',
101
        'int32' => 'int',
102
        'bigint' => 'long',
103
        'int64' => 'long',
104
        'real' => 'double'
105
    ];
106
107
    /**
108
     * @var string The database prefix.
109
     */
110
    private $px = '';
111
112
    /**
113
     * @var array A cached copy of the table schemas indexed by lowercase name.
114
     */
115
    private $tables = [];
116
117
    /**
118
     * @var array|null A cached copy of the table names indexed by lowercase name.
119
     */
120
    private $tableNames = null;
121
122
    /**
123
     * @var \PDO
124
     */
125
    private $pdo;
126
127
    /**
128
     * Initialize an instance of the {@link MySqlDb} class.
129
     *
130
     * @param PDO $pdo The connection to the database.
131
     * @param string $px The database prefix.
132
     */
133
    public function __construct(PDO $pdo, $px = '') {
134
        $this->pdo = $pdo;
135
        $this->px = $px;
136
137
        $fetchMode = $this->pdo->getAttribute(PDO::ATTR_DEFAULT_FETCH_MODE);
138
        $this->setFetchMode(in_array($fetchMode, [0, PDO::FETCH_BOTH], true) ? PDO::FETCH_ASSOC: $fetchMode);
139
    }
140
141
    /**
142
     * Get the name of the class that handles a database driver.
143
     *
144
     * @param string|PDO $driver The name of the driver or a database connection.
145
     * @return null|string Returns the driver classname or **null** if one isn't found.
146
     */
147
    public static function driverClass($driver) {
148
        if ($driver instanceof PDO) {
149
            $name = $driver->getAttribute(PDO::ATTR_DRIVER_NAME);
150
        } else {
151
            $name = (string)$driver;
152
        }
153
154
        $name = strtolower($name);
155
        return isset(self::$drivers[$name]) ? self::$drivers[$name] : null;
156
    }
157
158
    /**
159
     * Add a table to the database.
160
     *
161
     * @param array $tableDef The table definition.
162
     * @param array $options An array of additional options when adding the table.
163
     */
164
    abstract protected function createTableDb(array $tableDef, array $options = []);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
165
166
    /**
167
     * Alter a table in the database.
168
     *
169
     * When altering a table you pass an array with three optional keys: add, drop, and alter.
170
     * Each value is consists of a table definition in a format that would be passed to {@link Db::setTableDef()}.
171
     *
172
     * @param array $alterDef The alter definition.
173
     * @param array $options An array of additional options when adding the table.
174
     */
175
    abstract protected function alterTableDb(array $alterDef, array $options = []);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
176
177
    /**
178
     * Drop a table.
179
     *
180
     * @param string $table The name of the table to drop.
181
     * @param array $options An array of additional options when adding the table.
182
     */
183 7
    final public function dropTable($table, array $options = []) {
184 7
        $options += [Db::OPTION_IGNORE => false];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
185 7
        $this->dropTableDb($table, $options);
186
187 7
        $tableKey = strtolower($table);
188 7
        unset($this->tables[$tableKey], $this->tableNames[$tableKey]);
189 7
    }
190
191
    /**
192
     * Perform the actual table drop.
193
     *
194
     * @param string $table The name of the table to drop.
195
     * @param array $options An array of additional options when adding the table.
196
     */
197
    abstract protected function dropTableDb($table, array $options = []);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
198
199
    /**
200
     * Get the names of all the tables in the database.
201
     *
202
     * @return string[] Returns an array of table names without prefixes.
203
     */
204 2
    final public function fetchTableNames() {
205 2
        if ($this->tableNames !== null) {
206 2
            return array_values($this->tableNames);
207
        }
208
209 2
        $names = $this->fetchTableNamesDb();
210
211 2
        $this->tableNames = [];
212 2
        foreach ($names as $name) {
213 2
            $name = $this->stripPrefix($name);
214 2
            $this->tableNames[strtolower($name)] = $name;
215
        }
216
217 2
        return array_values($this->tableNames);
218
    }
219
220
    /**
221
     * Fetch the table names from the underlying database layer.
222
     *
223
     * The driver should return all table names. It doesn't have to strip the prefix.
224
     *
225
     * @return string[]
226
     */
227
    abstract protected function fetchTableNamesDb();
228
229
    /**
230
     * Get a table definition.
231
     *
232
     * @param string $table The name of the table.
233
     * @return array|null Returns the table definition or null if the table does not exist.
234
     */
235 66
    final public function fetchTableDef($table) {
236 66
        $tableKey = strtolower($table);
237
238
        // First check the table cache.
239 66
        if (isset($this->tables[$tableKey])) {
240 51
            $tableDef = $this->tables[$tableKey];
241
242 51
            if (isset($tableDef['columns'], $tableDef['indexes'])) {
243 51
                return $tableDef;
244
            }
245 30
        } elseif ($this->tableNames !== null && !isset($this->tableNames[$tableKey])) {
246 24
            return null;
247
        }
248
249 10
        $tableDef = $this->fetchTableDefDb($table);
250 10
        if ($tableDef !== null) {
251 10
            $this->fixIndexes($tableDef['name'], $tableDef);
252 10
            $this->tables[$tableKey] = $tableDef;
253
        }
254
255 10
        return $tableDef;
256
    }
257
258
    /**
259
     * Fetch the table definition from the database.
260
     *
261
     * @param string $table The name of the table to get.
262
     * @return array|null Returns the table def or **null** if the table doesn't exist.
263
     */
264
    abstract protected function fetchTableDefDb($table);
265
266
267
    /**
268
     * Get the column definitions for a table.
269
     *
270
     * @param string $table The name of the table to get the columns for.
271
     * @return array|null Returns an array of column definitions.
272
     */
273 1
    final public function fetchColumnDefs($table) {
274 1
        $tableKey = strtolower($table);
275
276 1
        if (!empty($this->tables[$tableKey]['columns'])) {
277 1
            $this->tables[$tableKey]['columns'];
278
        } elseif ($this->tableNames !== null && !isset($this->tableNames[$tableKey])) {
279
            return null;
280
        }
281
282 1
        $columnDefs = $this->fetchColumnDefsDb($table);
283 1
        if ($columnDefs !== null) {
284 1
            $this->tables[$tableKey]['columns'] = $columnDefs;
285
        }
286 1
        return $columnDefs;
287
    }
288
289
    /**
290
     * Get the column definitions from the database.
291
     *
292
     * @param string $table The name of the table to fetch the columns for.
293
     * @return array|null
294
     */
295
    abstract protected function fetchColumnDefsDb($table);
296
297
    /**
298
     * Get the canonical type based on a type string.
299
     *
300
     * @param string $type A type string.
301
     * @return array|null Returns the type schema array or **null** if a type isn't found.
302
     */
303 51
    public static function typeDef($type) {
304
        // Check for the unsigned signifier.
305 51
        $unsigned = null;
306 51
        if ($type[0] === 'u') {
307 6
            $unsigned = true;
308 6
            $type = substr($type, 1);
309 49
        } elseif (preg_match('`(.+)\s+unsigned`i', $type, $m)) {
310 2
            $unsigned = true;
311 2
            $type = $m[1];
312
        }
313
314
        // Remove brackets from the type.
315 51
        $brackets = null;
316 51
        if (preg_match('`^(.*)\((.*)\)$`', $type, $m)) {
317 35
            $brackets = $m[2];
318 35
            $type = $m[1];
319
        }
320
321
        // Look for the type.
322 51
        $type = strtolower($type);
323 51
        if (isset(self::$types[$type])) {
324 51
            $row = self::$types[$type];
325 51
            $dbtype = $type;
326
327
            // Resolve an alias.
328 51
            if (is_string($row)) {
329 2
                $dbtype = $row;
330 51
                $row = self::$types[$row];
331
            }
332
        } else {
333
            return null;
334
        }
335
336
        // Now that we have a type row we can build a schema for it.
337
        $schema = [
338 51
            'type' => $row['type'],
339 51
            'dbtype' => $dbtype
340
        ];
341
342 51
        if (!empty($row['schema'])) {
343 40
            $schema += $row['schema'];
344
        }
345
346 51
        if ($row['type'] === 'integer' && $unsigned) {
347 6
            $schema['unsigned'] = true;
348
349 6
            if (!empty($schema['maximum'])) {
350 6
                $schema['maximum'] = $schema['maximum'] * 2 + 1;
351 6
                $schema['minimum'] = 0;
352
            }
353
        }
354
355 51
        if (!empty($row['length'])) {
356 28
            $schema['maxLength'] = (int)$brackets ?: 255;
357
        }
358
359 51
        if (!empty($row['precision'])) {
360 2
            $parts = array_map('trim', explode(',', $brackets));
361 2
            $schema['precision'] = (int)$parts[0];
362 2
            if (isset($parts[1])) {
363 2
                $schema['scale'] = (int)$parts[1];
364
            }
365
        }
366
367 51
        if (!empty($row['enum'])) {
368 1
            $enum = explode(',', $brackets);
369 1
            $schema['enum'] = array_map(function ($str) {
370 1
                return trim($str, "'\" \t\n\r\0\x0B");
371 1
            }, $enum);
372
        }
373
374 51
        return $schema;
375
    }
376
377
    /**
378
     * Get the database type string from a type definition.
379
     *
380
     * This is the opposite of {@link Db::typeDef()}.
381
     *
382
     * @param array $typeDef The type definition array.
383
     * @return string Returns a db type string.
384
     */
385 30
    protected static function dbType(array $typeDef) {
386 30
        $dbtype = $typeDef['dbtype'];
387
388 30
        if (!empty($typeDef['maxLength'])) {
389 16
            $dbtype .= "({$typeDef['maxLength']})";
390 28
        } elseif (!empty($typeDef['unsigned'])) {
391
            $dbtype = 'u'.$dbtype;
392 28
        } elseif (!empty($typeDef['precision'])) {
393
            $dbtype .= "({$typeDef['precision']}";
394
            if (!empty($typeDef['scale'])) {
395
                $dbtype .= ",{$typeDef['scale']}";
396
            }
397
            $dbtype .= ')';
398 28
        } elseif (!empty($typeDef['enum'])) {
399
            $parts = array_map(function ($str) {
400
                return "'{$str}'";
401
            }, $typeDef['enum']);
402
            $dbtype .= '('.implode(',', $parts).')';
403
        }
404 30
        return $dbtype;
405
    }
406
407
408
    /**
409
     * Get the native database type based on a type schema.
410
     *
411
     * The default implementation of this method returns the canonical db types. Individual database classes will have
412
     * to override to provide any differences.
413
     *
414
     * @param array $type The type schema.
415
     * @return string
416
     */
417
    abstract protected function nativeDbType(array $type);
418
419
    /**
420
     * Set a table definition to the database.
421
     *
422
     * @param array $tableDef The table definition.
423
     * @param array $options An array of additional options when adding the table.
424
     */
425 66
    final public function defineTable(array $tableDef, array $options = []) {
426 66
        $options += [Db::OPTION_DROP => false];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
427
428 66
        $tableName = $tableDef['name'];
429 66
        $tableKey = strtolower($tableName);
430 66
        $tableDef['name'] = $tableName;
431 66
        $curTable = $this->fetchTableDef($tableName);
432
433 66
        $this->fixIndexes($tableName, $tableDef, $curTable);
434
435 66
        if (!$curTable) {
436 30
            $this->createTableDb($tableDef, $options);
437 30
            $this->tables[$tableKey] = $tableDef;
438 30
            $this->tableNames[$tableKey] = $tableDef['name'];
439 30
            return;
440
        }
441
        // This is the alter statement.
442 47
        $alterDef = ['name' => $tableName];
443
444
        // Figure out the columns that have changed.
445 47
        $curColumns = (array)$curTable['columns'];
446 47
        $newColumns = (array)$tableDef['columns'];
447
448 47
        $alterDef['add']['columns'] = array_diff_key($newColumns, $curColumns);
449 47
        $alterDef['alter']['columns'] = array_uintersect_assoc($newColumns, $curColumns, function ($new, $curr) {
450 47
            $search = ['dbtype', 'allowNull', 'default'];
451 47
            foreach ($search as $key) {
452 47
                if (self::val($key, $curr) !== self::val($key, $new)) {
453
                    // Return 0 if the values are different, not the same.
454 47
                    return 0;
455
                }
456
            }
457 47
            return 1;
458 47
        });
459
460
        // Figure out the indexes that have changed.
461 47
        $curIndexes = (array)self::val('indexes', $curTable, []);
462 47
        $newIndexes = (array)self::val('indexes', $tableDef, []);
463
464 47
        $alterDef['add']['indexes'] = array_udiff($newIndexes, $curIndexes, [$this, 'indexCompare']);
465
466 47
        $dropIndexes = array_udiff($curIndexes, $newIndexes, [$this, 'indexCompare']);
467 47
        if ($options[Db::OPTION_DROP]) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
468 2
            $alterDef['drop']['columns'] = array_diff_key($curColumns, $newColumns);
469 2
            $alterDef['drop']['indexes'] = $dropIndexes;
470
        } else {
471 45
            $alterDef['drop']['columns'] = [];
472 45
            $alterDef['drop']['indexes'] = [];
473
474
            // If the primary key has changed then the old one needs to be dropped.
475 45
            if ($pk = $this->findPrimaryKeyIndex($dropIndexes)) {
476 4
                $alterDef['drop']['indexes'][] = $pk;
477
            }
478
        }
479
480
        // Check to see if any alterations at all need to be made.
481 47
        if (empty($alterDef['add']['columns']) && empty($alterDef['add']['indexes']) &&
482 47
            empty($alterDef['drop']['columns']) && empty($alterDef['drop']['indexes']) &&
483 47
            empty($alterDef['alter']['columns'])
484
        ) {
485 37
            return;
486
        }
487
488 10
        $alterDef['def'] = $tableDef;
489
490
        // Alter the table.
491 10
        $this->alterTableDb($alterDef, $options);
492
493
        // Update the cached schema.
494 10
        $tableDef['name'] = $tableName;
495 10
        $this->tables[$tableKey] = $tableDef;
496
497 10
        if ($this->tableNames === null) {
498 2
            $this->fetchTableNames();
499
        }
500
501 10
        $this->tableNames[$tableKey] = $tableName;
502 10
    }
503
504
    /**
505
     * Find the primary key in an array of indexes.
506
     *
507
     * @param array $indexes The indexes to search.
508
     * @return array|null Returns the primary key or **null** if there isn't one.
509
     */
510 53
    protected function findPrimaryKeyIndex(array $indexes) {
511 53
        foreach ($indexes as $index) {
512 13
            if ($index['type'] === Db::INDEX_PK) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
513 13
                return $index;
514
            }
515
        }
516 43
        return null;
517
    }
518
519
    /**
520
     * Move the primary key index into the correct place for database drivers.
521
     *
522
     * @param string $tableName The name of the table.
523
     * @param array &$tableDef The table definition.
524
     * @param array|null $curTableDef The current database table def used to resolve conflicts in some names.
525
     * @throws \Exception Throws an exception when there is a mismatch between the primary index and the primary key
526
     * defined on the columns themselves.
527
     */
528 66
    private function fixIndexes($tableName, array &$tableDef, $curTableDef = null) {
529 66
        $tableDef += ['indexes' => []];
530
531
        // Loop through the columns and add the primary key index.
532 66
        $primaryColumns = [];
533 66
        foreach ($tableDef['columns'] as $cname => $cdef) {
534 66
            if (!empty($cdef['primary'])) {
535 66
                $primaryColumns[] = $cname;
536
            }
537
        }
538
539
        // Massage the primary key index.
540 66
        $primaryFound = false;
541 66
        foreach ($tableDef['indexes'] as &$indexDef) {
542 64
            $indexDef += ['name' => $this->buildIndexName($tableName, $indexDef), 'type' => null];
543
544 64
            if ($indexDef['type'] === Db::INDEX_PK) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
545 28
                $primaryFound = true;
546
547 28
                if (empty($primaryColumns)) {
548 8
                    foreach ($indexDef['columns'] as $cname) {
549 8
                        $tableDef['columns'][$cname]['primary'] = true;
550
                    }
551 22
                } elseif (array_diff($primaryColumns, $indexDef['columns'])) {
552 28
                    throw new \Exception("There is a mismatch in the primary key index and primary key columns.", 500);
553
                }
554 58
            } elseif (isset($curTableDef['indexes'])) {
555 41
                foreach ($curTableDef['indexes'] as $curIndexDef) {
556 41
                    if ($this->indexCompare($indexDef, $curIndexDef) === 0) {
557 41
                        if (!empty($curIndexDef['name'])) {
558 41
                            $indexDef['name'] = $curIndexDef['name'];
559
                        }
560 64
                        break;
561
                    }
562
                }
563
            }
564
        }
565
566 66
        if (!$primaryFound && !empty($primaryColumns)) {
567 4
            $tableDef['indexes'][] = [
568 4
                'columns' => $primaryColumns,
569
                'type' => Db::INDEX_PK
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
570
            ];
571
        }
572 66
    }
573
574
    /**
575
     * Get the database prefix.
576
     *
577
     * @return string Returns the current db prefix.
578
     */
579 2
    public function getPx() {
580 2
        return $this->px;
581
    }
582
583
    /**
584
     * Set the database prefix.
585
     *
586
     * @param string $px The new database prefix.
587
     */
588
    public function setPx($px) {
589
        $this->px = $px;
590
    }
591
592
    /**
593
     * Compare two index definitions to see if they have the same columns and same type.
594
     *
595
     * @param array $a The first index.
596
     * @param array $b The second index.
597
     * @return int Returns an integer less than, equal to, or greater than zero if {@link $a} is
598
     * considered to be respectively less than, equal to, or greater than {@link $b}.
599
     */
600 45
    private function indexCompare(array $a, array $b) {
601 45
        if ($a['columns'] > $b['columns']) {
602 15
            return 1;
603 45
        } elseif ($a['columns'] < $b['columns']) {
604 15
            return -1;
605
        }
606
607 41
        return strcmp(
608 41
            isset($a['type']) ? $a['type'] : '',
609 41
            isset($b['type']) ? $b['type'] : ''
610
        );
611
    }
612
613
    /**
614
     * Get data from the database.
615
     *
616
     * @param string|Identifier $table The name of the table to get the data from.
617
     * @param array $where An array of where conditions.
618
     * @param array $options An array of additional options.
619
     * @return \PDOStatement Returns the result set.
620
     */
621
    abstract public function get($table, array $where, array $options = []);
622
623
    /**
624
     * Get a single row from the database.
625
     *
626
     * This is a convenience method that calls {@link Db::get()} and shifts off the first row.
627
     *
628
     * @param string|Identifier $table The name of the table to get the data from.
629
     * @param array $where An array of where conditions.
630
     * @param array $options An array of additional options.
631
     * @return array|object|null Returns the row or false if there is no row.
632
     */
633 16
    final public function getOne($table, array $where, array $options = []) {
634 16
        $rows = $this->get($table, $where, $options);
635 16
        $row = $rows->fetch();
636
637 16
        return $row === false ? null : $row;
638
    }
639
640
    /**
641
     * Insert a row into a table.
642
     *
643
     * @param string $table The name of the table to insert into.
644
     * @param array $row The row of data to insert.
645
     * @param array $options An array of options for the insert.
646
     *
647
     * Db::OPTION_IGNORE
648
     * : Whether or not to ignore inserts that lead to a duplicate key. *default false*
649
     * Db::OPTION_REPLACE
650
     * : Whether or not to replace duplicate keys. *default false*
651
     * Db::OPTION_UPSERT
652
     * : Whether or not to update the existing data when duplicate keys exist.
653
     *
654
     * @return mixed Returns the id of the inserted record, **true** if the table doesn't have an auto increment, or **false** otherwise.
655
     * @see Db::load()
656
     */
657
    abstract public function insert($table, array $row, array $options = []);
658
659
    /**
660
     * Load many rows into a table.
661
     *
662
     * @param string $table The name of the table to insert into.
663
     * @param \Traversable|array $rows A dataset to insert.
664
     * Note that all rows must contain the same columns.
665
     * The first row will be looked at for the structure of the insert and the rest of the rows will use this structure.
666
     * @param array $options An array of options for the inserts. See {@link Db::insert()} for details.
667
     * @see Db::insert()
668
     */
669
    public function load($table, $rows, array $options = []) {
670
        foreach ($rows as $row) {
671
            $this->insert($table, $row, $options);
672
        }
673
    }
674
675
676
    /**
677
     * Update a row or rows in a table.
678
     *
679
     * @param string $table The name of the table to update.
680
     * @param array $set The values to set.
681
     * @param array $where The where filter for the update.
682
     * @param array $options An array of options for the update.
683
     * @return int Returns the number of affected rows.
684
     */
685
    abstract public function update($table, array $set, array $where, array $options = []);
686
687
    /**
688
     * Delete rows from a table.
689
     *
690
     * @param string $table The name of the table to delete from.
691
     * @param array $where The where filter of the delete.
692
     * @param array $options An array of options.
693
     *
694
     * Db:OPTION_TRUNCATE
695
     * : Truncate the table instead of deleting rows. In this case {@link $where} must be blank.
696
     * @return int Returns the number of affected rows.
697
     */
698
    abstract public function delete($table, array $where, array $options = []);
699
700
    /**
701
     * Reset the internal table definition cache.
702
     *
703
     * @return $this
704
     */
705 10
    public function reset() {
706 10
        $this->tables = [];
707 10
        $this->tableNames = null;
708 10
        return $this;
709
    }
710
711
    /**
712
     * Build a standardized index name from an index definition.
713
     *
714
     * @param string $tableName The name of the table the index is in.
715
     * @param array $indexDef The index definition.
716
     * @return string Returns the index name.
717
     */
718 64
    protected function buildIndexName($tableName, array $indexDef) {
719 64
        $indexDef += ['type' => Db::INDEX_IX, 'suffix' => ''];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
720
721 64
        $type = $indexDef['type'];
722
723 64
        if ($type === Db::INDEX_PK) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
724 29
            return 'primary';
725
        }
726 58
        $px = self::val($type, [Db::INDEX_IX => 'ix_', Db::INDEX_UNIQUE => 'ux_'], 'ix_');
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
727 58
        $sx = $indexDef['suffix'];
728 58
        $result = $px.$tableName.'_'.($sx ?: implode('', $indexDef['columns']));
729 58
        return $result;
730
    }
731
732
    /**
733
     * Execute a query that fetches data.
734
     *
735
     * @param string $sql The query to execute.
736
     * @param array $params Input parameters for the query.
737
     * @param array $options Additional options.
738
     * @return \PDOStatement Returns the result of the query.
739
     * @throws \PDOException Throws an exception if something went wrong during the query.
740
     */
741 89
    protected function query($sql, array $params = [], array $options = []) {
742
        $options += [
743 89
            Db::OPTION_FETCH_MODE => $this->getFetchArgs()
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
744
        ];
745
746 89
        $stm = $this->getPDO()->prepare($sql);
747
748
749 89
        if ($options[Db::OPTION_FETCH_MODE]) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
750 82
            $stm->setFetchMode(...(array)$options[Db::OPTION_FETCH_MODE]);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
751
        }
752
753 89
        $r = $stm->execute($params);
754
755
        // This is a kludge for those that don't have errors turning into exceptions.
756 89
        if ($r === false) {
757
            list($state, $code, $msg) = $stm->errorInfo();
0 ignored issues
show
Unused Code introduced by
The assignment to $state is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
758
            throw new \PDOException($msg, $code);
759
        }
760
761 89
        return $stm;
762
    }
763
764
    /**
765
     * Query the database and return a row count.
766
     *
767
     * @param string $sql The query to execute.
768
     * @param array $params Input parameters for the query.
769
     * @param array $options Additional options.
770
     * @return int
771
     */
772 38
    protected function queryModify($sql, array $params = [], array $options = []) {
773 38
        $options += [Db::OPTION_FETCH_MODE => 0];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
774 38
        $stm = $this->query($sql, $params, $options);
775 38
        return $stm->rowCount();
776
    }
777
778
    /**
779
     * Query the database and return the ID of the record that was inserted.
780
     *
781
     * @param string $sql The query to execute.
782
     * @param array $params Input parameters for the query.
783
     * @param array $options Additional options.
784
     * @return mixed Returns the record ID.
785
     */
786 17
    protected function queryID($sql, array $params = [], array $options = []) {
787 17
        $options += [Db::OPTION_FETCH_MODE => 0];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
788 17
        $this->query($sql, $params, $options);
789 17
        $r = $this->getPDO()->lastInsertId();
790
791 17
        return is_numeric($r) ? (int)$r : $r;
792
    }
793
794
    /**
795
     * Query the database for a database define.
796
     *
797
     * @param string $sql The query to execute.
798
     * @param array $options Additional options.
799
     */
800 30
    protected function queryDefine($sql, array $options = []) {
801 30
        $options += [Db::OPTION_FETCH_MODE => 0];
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
802 30
        $this->query($sql, [], $options);
803 30
    }
804
805
    /**
806
     * Safely get a value out of an array.
807
     *
808
     * This function will always return a value even if the array key doesn't exist.
809
     * The self::val() function is one of the biggest workhorses of Vanilla and shows up a lot throughout other code.
810
     * It's much preferable to use this function if your not sure whether or not an array key exists rather than
811
     * using @ error suppression.
812
     *
813
     * This function uses optimizations found in the [facebook libphputil library](https://github.com/facebook/libphutil).
814
     *
815
     * @param string|int $key The array key.
816
     * @param array|object $array The array to get the value from.
817
     * @param mixed $default The default value to return if the key doesn't exist.
818
     * @return mixed The item from the array or `$default` if the array key doesn't exist.
819
     * @category Array Functions
820
     */
821 84
    protected static function val($key, $array, $default = null) {
822 84
        if (is_array($array)) {
823
            // isset() is a micro-optimization - it is fast but fails for null values.
824 84
            if (isset($array[$key])) {
825 77
                return $array[$key];
826
            }
827
828
            // Comparing $default is also a micro-optimization.
829 84
            if ($default === null || array_key_exists($key, $array)) {
830 84
                return null;
831
            }
832
        } elseif (is_object($array)) {
833
            if (isset($array->$key)) {
834
                return $array->$key;
835
            }
836
837
            if ($default === null || property_exists($array, $key)) {
838
                return null;
839
            }
840
        }
841
842 4
        return $default;
843
    }
844
845
    /**
846
     * Escape an identifier.
847
     *
848
     * @param string|Literal $identifier The identifier to escape.
849
     * @return string Returns the field properly escaped.
850
     */
851 93
    public function escape($identifier) {
852 93
        if ($identifier instanceof Literal) {
853 2
            return $identifier->getValue($this);
854
        }
855 93
        return '`'.str_replace('`', '``', $identifier).'`';
856
    }
857
858
    /**
859
     * Escape a a like string so that none of its characters work as wildcards.
860
     *
861
     * @param string $str The string to escape.
862
     * @return string Returns an escaped string.
863
     */
864 2
    protected function escapeLike($str) {
865 2
        return addcslashes($str, '_%');
866
    }
867
868
    /**
869
     * Prefix a table name.
870
     *
871
     * @param string|Identifier $table The name of the table to prefix.
872
     * @param bool $escape Whether or not to escape the output.
873
     * @return string Returns a full table name.
874
     */
875 93
    protected function prefixTable($table, $escape = true) {
876 93
        if ($table instanceof Identifier) {
877 7
            return $escape ? $table->escape($this) : (string)$table;
878
        } else {
879 93
            $table = $this->px.$table;
880 93
            return $escape ? $this->escape($table) : $table;
881
        }
882
    }
883
884
    /**
885
     * Strip the database prefix off a table name.
886
     *
887
     * @param string $table The name of the table to strip.
888
     * @return string Returns the table name stripped of the prefix.
889
     */
890 2
    protected function stripPrefix($table) {
891 2
        $len = strlen($this->px);
892 2
        if (strcasecmp(substr($table, 0, $len), $this->px) === 0) {
893 2
            $table = substr($table, $len);
894
        }
895 2
        return $table;
896
    }
897
898
    /**
899
     * Optionally quote a where value.
900
     *
901
     * @param mixed $value The value to quote.
902
     * @param string $column The column being operated on. It must already be quoted.
903
     * @return string Returns the value, optionally quoted.
904
     * @internal param bool $quote Whether or not to quote the value.
905
     */
906 42
    public function quote($value, $column = '') {
907 42
        if ($value instanceof Literal) {
908
            /* @var Literal $value */
909 25
            return $value->getValue($this, $column);
910
        } else {
911 37
            return $this->getPDO()->quote($value);
912
        }
913
    }
914
915
    /**
916
     * Gets the {@link PDO} object for this connection.
917
     *
918
     * @return \PDO
919
     */
920 93
    public function getPDO() {
921 93
        return $this->pdo;
922
    }
923
924
    /**
925
     * Set the connection to the database.
926
     *
927
     * @param PDO $pdo The new connection to the database.
928
     * @return $this
929
     */
930
    public function setPDO(PDO $pdo) {
931
        $this->pdo = $pdo;
932
        return $this;
933
    }
934
}
935