Completed
Push — master ( 767c34...79d01e )
by Todd
02:06
created

MySqlDb::quote()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 9.6666
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 2
crap 3
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\Drivers;
9
10
use Garden\Db\Db;
11
use Garden\Db\Identifier;
12
use Garden\Db\Literal;
13
use PDO;
14
15
/**
16
 * A {@link Db} class for connecting to MySQL.
17
 */
18
class MySqlDb extends Db {
19
    const MYSQL_DATE_FORMAT = 'Y-m-d H:i:s';
20
21
    /**
22
     * @var string
23
     */
24
    protected $dbname;
25
26
    protected static $map = [
27
        Db::OP_GT => '>',
28
        Db::OP_GTE => '>=',
29
        Db::OP_LT => '<',
30
        Db::OP_LTE => '<=',
31
        Db::OP_LIKE => 'like',
32
        Db::OP_AND => 'and',
33
        Db::OP_OR => 'or',
34
    ];
35
36
    /**
37
     * {@inheritdoc}
38
     */
39 6
    protected function dropTableDb($table, array $options = []) {
40
        $sql = 'drop table '.
41 6
            ($options[Db::OPTION_IGNORE] ? 'if exists ' : '').
42 6
            $this->prefixTable($table);
43
44 6
        $this->queryDefine($sql);
45 6
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 8
    protected function fetchTableDefDb($table) {
51 8
        $columns = $this->fetchColumnDefsDb($table);
52
53 8
        if (empty($columns)) {
54
            // A table with no columns does not exist.
55 6
            return null;
56
        }
57
58 8
        $indexes = $this->fetchIndexesDb($table);
59
60
        $tableDef = [
61 8
            'name' => $table,
62 8
            'columns' => $columns,
63 8
            'indexes' => $indexes
64
        ];
65
66 8
        return $tableDef;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72 5
    protected function fetchColumnDefsDb($table) {
73 5
        $rows = $this->get(
74 5
            new Identifier('information_schema', 'COLUMNS'),
75
            [
76 5
                'TABLE_SCHEMA' => $this->getDbName(),
77 5
                'TABLE_NAME' => $this->prefixTable($table, false)
78
            ],
79
            [
80 5
                Db::OPTION_FETCH_MODE => PDO::FETCH_ASSOC,
81
                'order' => ['TABLE_NAME', 'ORDINAL_POSITION']
82
            ]
83
        );
84
85 5
        $columns = [];
86 5
        foreach ($rows as $row) {
87 5
            $columnType = $row['COLUMN_TYPE'];
88 5
            if ($columnType === 'tinyint(1)') {
89 1
                $columnType = 'bool';
90
            }
91 5
            $column = Db::typeDef($columnType);
92 5
            if ($column === null) {
93
                throw new \Exception("Unknown type '$columnType'.", 500);
94
            }
95
96 5
            $column['allowNull'] = strcasecmp($row['IS_NULLABLE'], 'YES') === 0;
97
98 5
            if (($default = $row['COLUMN_DEFAULT']) !== null) {
99 4
                $column['default'] = $this->forceType($default, $column['type']);
100
            }
101
102 5
            if ($row['EXTRA'] === 'auto_increment') {
103 1
                $column['autoIncrement'] = true;
104
            }
105
106 5
            if ($row['COLUMN_KEY'] === 'PRI') {
107 3
                $column['primary'] = true;
108
            }
109
110 5
            $columns[$row['COLUMN_NAME']] = $column;
111
        }
112
113 5
        return $columns;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 74
    public function get($table, array $where, array $options = []) {
120 74
        $sql = $this->buildSelect($table, $where, $options);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
121 74
        $result = $this->query($sql, [], $options);
122 74
        return $result;
123
    }
124
125
    /**
126
     * Build a sql select statement.
127
     *
128
     * @param string|Identifier $table The name of the main table.
129
     * @param array $where The where filter.
130
     * @param array $options An array of additional query options.
131
     * @return string Returns the select statement as a string.
132
     * @see Db::get()
133
     */
134 78
    protected function buildSelect($table, array $where, array $options = []) {
135 78
        $options += ['limit' => 0];
136
137 78
        $sql = '';
138
139
        // Build the select clause.
140 78
        if (!empty($options['columns'])) {
141 11
            $columns = array();
142 11
            foreach ($options['columns'] as $value) {
143 11
                $columns[] = $this->escape($value);
144
            }
145 11
            $sql .= 'select '.implode(', ', $columns);
146
        } else {
147 74
            $sql .= "select *";
148
        }
149
150
        // Build the from clause.
151 78
        if ($table instanceof Literal) {
152
            $table = $table->getValue($this);
153
        } else {
154 78
            $table = $this->prefixTable($table);
155
        }
156 78
        $sql .= "\nfrom $table";
157
158
        // Build the where clause.
159 78
        $whereString = $this->buildWhere($where, Db::OP_AND);
160 78
        if ($whereString) {
161 51
            $sql .= "\nwhere ".$whereString;
162
        }
163
164
        // Build the order.
165 78
        if (!empty($options['order'])) {
166 40
            $orders = [];
167 40
            foreach ($options['order'] as $column) {
168 40
                if ($column[0] === '-') {
169 2
                    $order = $this->escape(substr($column, 1)).' desc';
170
                } else {
171 40
                    $order = $this->escape($column);
172
                }
173 40
                $orders[] = $order;
174
            }
175 40
            $sql .= "\norder by ".implode(', ', $orders);
176
        }
177
178
        // Build the limit, offset.
179 78
        if (!empty($options['limit'])) {
180 30
            $limit = (int)$options['limit'];
181 30
            $sql .= "\nlimit $limit";
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
182
        }
183
184 78
        if (!empty($options['offset'])) {
185 1
            $sql .= ' offset '.((int)$options['offset']);
186 78
        } elseif (isset($options['page'])) {
187
            $offset = $options['limit'] * ($options['page'] - 1);
188
            $sql .= ' offset '.$offset;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
189
        }
190
191 78
        return $sql;
192
    }
193
194
    /**
195
     * Build a where clause from a where array.
196
     *
197
     * @param array $where There where string.
198
     * This is an array in the form `['column' => 'value']` with more advanced options for non-equality comparisons.
199
     * @param string $op The logical operator to join multiple field comparisons.
200
     * @return string The where string.
201
     */
202 80
    protected function buildWhere($where, $op = Db::OP_AND) {
203 80
        $map = static::$map;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
204 80
        $strop = $map[$op];
205
206 80
        $result = '';
207 80
        foreach ($where as $column => $value) {
208 53
            $btcolumn = $this->escape($column);
209
210 53
            if (is_array($value)) {
211 30
                if (is_numeric($column)) {
212
                    // This is a bracketed expression.
213 4
                    $result .= (empty($result) ? '' : "\n  $strop ").
214 4
                        "(\n  ".
215 4
                        $this->buildWhere($value, $op).
216 4
                        "\n  )";
217 30
                } elseif (in_array($column, [Db::OP_AND, Db::OP_OR])) {
218
                    // This is an AND/OR expression.
219 4
                    $result .= (empty($result) ? '' : "\n  $strop ").
220 4
                        "(\n  ".
221 4
                        $this->buildWhere($value, $column).
222 4
                        "\n  )";
223
                } else {
224 26
                    if (isset($value[0])) {
225
                        // This is a short in syntax.
226 2
                        $value = [Db::OP_IN => $value];
227
                    }
228
229 26
                    foreach ($value as $vop => $rval) {
230 26
                        if ($result) {
231 4
                            $result .= "\n  $strop ";
232
                        }
233
234
                        switch ($vop) {
235 26
                            case Db::OP_AND:
236 26
                            case Db::OP_OR:
237 4
                                if (is_numeric($column)) {
238
                                    $innerWhere = $rval;
239
                                } else {
240 4
                                    $innerWhere = [$column => $rval];
241
                                }
242
                                $result .= "(\n  ".
243 4
                                    $this->buildWhere($innerWhere, $vop).
0 ignored issues
show
Bug introduced by
It seems like $innerWhere defined by $rval on line 238 can also be of type null; however, Garden\Db\Drivers\MySqlDb::buildWhere() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
244 4
                                    "\n  )";
245 4
                                break;
246 26
                            case Db::OP_EQ:
247 6
                                if ($rval === null) {
248
                                    $result .= "$btcolumn is null";
249 6
                                } elseif (is_array($rval)) {
250 2
                                    $result .= "$btcolumn in ".$this->bracketList($rval);
251
                                } else {
252 4
                                    $result .= "$btcolumn = ".$this->quote($rval);
253
                                }
254 6
                                break;
255 22
                            case Db::OP_GT:
256 20
                            case Db::OP_GTE:
257 18
                            case Db::OP_LT:
258 12
                            case Db::OP_LTE:
259 12
                                $result .= "$btcolumn {$map[$vop]} ".$this->quote($rval);
260 12
                                break;
261 10
                            case Db::OP_LIKE:
262
                                $result .= $this->buildLike($btcolumn, $rval);
263
                                break;
264 10
                            case Db::OP_IN:
265
                                // Quote the in values.
266 4
                                $rval = array_map([$this, 'quote'], (array)$rval);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
267 4
                                $result .= "$btcolumn in (".implode(', ', $rval).')';
268 4
                                break;
269 6
                            case Db::OP_NEQ:
270 6
                                if ($rval === null) {
271 2
                                    $result .= "$btcolumn is not null";
272 4
                                } elseif (is_array($rval)) {
273 2
                                    $result .= "$btcolumn not in ".$this->bracketList($rval);
274
                                } else {
275 2
                                    $result .= "$btcolumn <> ".$this->quote($rval);
276
                                }
277 26
                                break;
278
                        }
279
                    }
280
                }
281
            } else {
282 27
                if ($result) {
283 9
                    $result .= "\n  $strop ";
284
                }
285
286
                // This is just an equality operator.
287 27
                if ($value === null) {
288 2
                    $result .= "$btcolumn is null";
289
                } else {
290 25
                    $result .= "$btcolumn = ".$this->quote($value);
291
                }
292
            }
293
        }
294 80
        return $result;
295
    }
296
297
    /**
298
     * Build a like expression.
299
     *
300
     * @param string $column The column name.
301
     * @param mixed $value The right-hand value.
302
     * @return string Returns the like expression.
303
     * @internal param bool $quotevals Whether or not to quote the values.
304
     */
305
    protected function buildLike($column, $value) {
306
        return "$column like ".$this->quote($value);
307
    }
308
309
    /**
310
     * Convert an array into a bracketed list suitable for MySQL clauses.
311
     *
312
     * @param array $row The row to expand.
313
     * @param string $quote The quotes to surroud the items with. There are two special cases.
314
     * ' (single quote)
315
     * : The row will be passed through {@link PDO::quote()}.
316
     * ` (backticks)
317
     * : The row will be passed through {@link MySqlDb::backtick()}.
318
     * @return string Returns the bracket list.
319
     */
320 74
    public function bracketList($row, $quote = "'") {
321
        switch ($quote) {
322 74
            case "'":
323 61
                $row = array_map([$this, 'quote'], $row);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
324 61
                $quote = '';
325 61
                break;
326 74
            case '`':
327 74
                $row = array_map([$this, 'escape'], $row);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
328 74
                $quote = '';
329 74
                break;
330
        }
331
332 74
        return "($quote".implode("$quote, $quote", $row)."$quote)";
333
    }
334
335
336
    /**
337
     * Get the current database name.
338
     *
339
     * @return mixed
340
     */
341 5
    private function getDbName() {
342 5
        if (!isset($this->dbname)) {
343
            $this->dbname = $this->getPDO()->query('select database()')->fetchColumn();
344
        }
345 5
        return $this->dbname;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351 17
    protected function nativeDbType(array $type) {
352 17
        static $translations = ['bool' => 'tinyint(1)', 'byte' => 'tinyint', 'short' => 'smallint', 'long' => 'bigint'];
353
354
        // Translate the dbtype to a MySQL native type.
355 17
        if (isset($translations[$type['dbtype']])) {
356 1
            $type['dbtype'] = $translations[$type['dbtype']];
357
        }
358
359
        // Unsigned is represented differently in MySQL.
360 17
        $unsigned = !empty($type['unsigned']);
361 17
        unset ($type['unsigned']);
362
363 17
        $dbType = static::dbType($type).($unsigned ? ' unsigned' : '');
364
365 17
        return $dbType;
366
    }
367
368
    /**
369
     * Parse a column type string and return it in a way that is suitable for a create/alter table statement.
370
     *
371
     * @param string $typeString The string to parse.
372
     * @return string Returns a canonical string.
0 ignored issues
show
Documentation introduced by
Should the return type not be string[]|null|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
373
     */
374
    protected function columnTypeString($typeString) {
375
        $type = null;
376
377
        if (substr($typeString, 0, 4) === 'enum') {
378
            // This is an enum which will come in as an array.
379
            if (preg_match_all("`'([^']+)'`", $typeString, $matches)) {
380
                $type = $matches[1];
381
            }
382
        } else {
383
            if (preg_match('`([a-z]+)\s*(?:\((\d+(?:\s*,\s*\d+)*)\))?\s*(unsigned)?`', $typeString, $matches)) {
384
                //         var_dump($matches);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
385
                $str = $matches[1];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
386
                $length = self::val(2, $matches);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
387
                $unsigned = self::val(3, $matches);
388
389
                if (substr($str, 0, 1) == 'u') {
390
                    $unsigned = true;
391
                    $str = substr($str, 1);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
392
                }
393
394
                // Remove the length from types without real lengths.
395
                if (in_array($str, array('tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'float', 'double'))) {
396
                    $length = null;
397
                }
398
399
                $type = $str;
400
                if ($length) {
401
                    $length = str_replace(' ', '', $length);
402
                    $type .= "($length)";
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
403
                }
404
                if ($unsigned) {
405
                    $type .= ' unsigned';
406
                }
407
            }
408
        }
409
410
        if (!$type) {
411
            debug_print_backtrace();
412
            trigger_error("Couldn't parse type $typeString", E_USER_ERROR);
413
        }
414
415
        return $type;
416
    }
417
418
    /**
419
     * Get the indexes from the database.
420
     *
421
     * @param string $table The name of the table to get the indexes for.
422
     * @return array|null
423
     */
424 4
    protected function fetchIndexesDb($table = '') {
425 4
        $stm = $this->get(
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
426 4
            new Identifier('information_schema', 'STATISTICS'),
427
            [
428 4
                'TABLE_SCHEMA' => $this->getDbName(),
429 4
                'TABLE_NAME' => $this->prefixTable($table, false)
430
            ],
431
            [
432
                'columns' => [
433
                    'INDEX_NAME',
434
                    'COLUMN_NAME',
435
                    'NON_UNIQUE'
436 4
                ],
437
                'order' => ['INDEX_NAME', 'SEQ_IN_INDEX']
438
            ]
439
        );
440 4
        $indexRows = $stm->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
441
442 4
        $indexes = [];
443 4
        foreach ($indexRows as $indexName => $columns) {
444
            $index = [
445 4
                'type' => null,
446 4
                'columns' => array_column($columns, 'COLUMN_NAME'),
447 4
                'name' => $indexName
448
            ];
449
450 4
            if ($indexName === 'PRIMARY') {
451 2
                $index['type'] = Db::INDEX_PK;
452
            } else {
453 2
                $index['type'] = $columns[0]['NON_UNIQUE'] ? Db::INDEX_IX : Db::INDEX_UNIQUE;
454
            }
455 4
            $indexes[] = $index;
456
        }
457
458 4
        return $indexes;
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464
    protected function fetchTableNamesDb() {
465
        // Get the table names.
466
        $tables = $this->get(
467
            new Identifier('information_schema', 'TABLES'),
468
            [
469
                'TABLE_SCHEMA' => $this->getDbName(),
470
                'TABLE_NAME' => [Db::OP_LIKE => $this->escapeLike($this->getPx()).'%']
471
            ],
472
            [
473
                'columns' => ['TABLE_NAME'],
474
                'fetchMode' => PDO::FETCH_ASSOC
475
            ]
476
        );
477
478
        return $tables->fetchAll(PDO::FETCH_COLUMN);
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484 17
    public function insert($table, array $row, array $options = []) {
485 17
        $sql = $this->buildInsert($table, $row, $options);
486 17
        $id = $this->queryID($sql, [], $options);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
487 17
        if (is_numeric($id)) {
488 17
            return (int)$id;
489
        } else {
490
            return $id;
491
        }
492
    }
493
494
    /**
495
     * Build an insert statement.
496
     *
497
     * @param string|Identifier $table The name of the table to insert to.
498
     * @param array $row The row to insert.
499
     * @param array $options An array of options for the insert. See {@link Db::insert} for the options.
500
     * @return string Returns the the sql string of the insert statement.
501
     */
502 33
    protected function buildInsert($table, array $row, $options = []) {
503 33
        if (self::val(Db::OPTION_UPSERT, $options)) {
504 2
            return $this->buildUpsert($table, $row, $options);
0 ignored issues
show
Bug introduced by
It seems like $table defined by parameter $table on line 502 can also be of type object<Garden\Db\Identifier>; however, Garden\Db\Drivers\MySqlDb::buildUpsert() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
505 33
        } elseif (self::val(Db::OPTION_IGNORE, $options)) {
506 2
            $sql = 'insert ignore ';
507 32
        } elseif (self::val(Db::OPTION_REPLACE, $options)) {
508 2
            $sql = 'replace ';
509
        } else {
510 31
            $sql = 'insert ';
511
        }
512 33
        $sql .= $this->prefixTable($table);
513
514
        // Add the list of values.
515
        $sql .=
516 33
            "\n".$this->bracketList(array_keys($row), '`').
517 33
            "\nvalues".$this->bracketList($row, "'");
518
519 33
        return $sql;
520
    }
521
522
    /**
523
     * Build an upsert statement.
524
     *
525
     * An upsert statement is an insert on duplicate key statement in MySQL.
526
     *
527
     * @param string $table The name of the table to update.
528
     * @param array $row The row to insert or update.
529
     * @param array $options An array of additional query options.
530
     * @return string Returns the upsert statement as a string.
531
     */
532 2
    protected function buildUpsert($table, array $row, $options = []) {
533
        // Build the initial insert statement first.
534 2
        unset($options[Db::OPTION_UPSERT]);
535 2
        $sql = $this->buildInsert($table, $row, $options);
536
537
        // Add the duplicate key stuff.
538 2
        $updates = [];
539 2
        foreach ($row as $key => $value) {
540 2
            $updates[] = $this->escape($key).' = values('.$this->escape($key).')';
541
        }
542 2
        $sql .= "\non duplicate key update ".implode(', ', $updates);
543
544 2
        return $sql;
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     */
550 54
    public function load($table, $rows, array $options = []) {
551 54
        $count = 0;
552 54
        $first = true;
553 54
        $spec = [];
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
554 54
        $stmt = null;
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
555
556
        // Loop over the rows and insert them with the statement.
557 54
        foreach ($rows as $row) {
558 44
            if ($first) {
559
                // Build the insert statement from the first row.
560 44
                foreach ($row as $key => $value) {
561 44
                    $spec[$key] = new Literal($this->paramName($key));
562
                }
563
564 44
                $sql = $this->buildInsert($table, $spec, $options);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
565 44
                $stmt = $this->getPDO()->prepare($sql);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
566 44
                $first = false;
567
            }
568
569 44
            $args = array_map([$this, 'argValue'], $row);
570 44
            $stmt->execute($args);
571 44
            $count += $stmt->rowCount();
572
        }
573
574 54
        return $count;
575
    }
576
577
    /**
578
     * Make a valid PDO parameter name from a string.
579
     *
580
     * This method replaces invalid placeholder characters with underscores.
581
     *
582
     * @param string $name The name to replace.
583
     * @return string
584
     */
585 44
    protected function paramName($name) {
586 44
        $result = ':'.preg_replace('`[^a-zA-Z0-9_]`', '_', $name);
587 44
        return $result;
588
    }
589
590
    /**
591
     * {@inheritdoc}
592
     */
593 8
    public function update($table, array $set, array $where, array $options = []) {
594 8
        $sql = $this->buildUpdate($table, $set, $where, $options);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
595 8
        $result = $this->queryModify($sql, [], $options);
596
597 8
        return $result;
598
    }
599
600
    /**
601
     * Build a sql update statement.
602
     *
603
     * @param string|Identifier $table The name of the table to update.
604
     * @param array $set An array of columns to set.
605
     * @param array $where The where filter.
606
     * @param array $options Additional options for the query.
607
     * @return string Returns the update statement as a string.
608
     */
609 3
    protected function buildUpdate($table, array $set, array $where, array $options = []) {
610
        $sql = 'update '.
611 3
            (self::val(Db::OPTION_IGNORE, $options) ? 'ignore ' : '').
612 3
            $this->prefixTable($table).
613 3
            "\nset\n  ";
614
615 3
        $parts = [];
616 3
        foreach ($set as $key => $value) {
617 3
            $escapedKey = $this->escape($key);
618
619 3
            $parts[] = "$escapedKey = ".$this->quote($value, $escapedKey);
620
        }
621 3
        $sql .= implode(",\n  ", $parts);
622
623 3
        if (!empty($where)) {
624 3
            $sql .= "\nwhere ".$this->buildWhere($where, Db::OP_AND);
625
        }
626
627 3
        return $sql;
628
    }
629
630
    /**
631
     * {@inheritdoc}
632
     */
633 30
    public function delete($table, array $where, array $options = []) {
634 30
        if (self::val(Db::OPTION_TRUNCATE, $options)) {
635
            if (!empty($where)) {
636
                throw new \InvalidArgumentException("You cannot truncate $table with a where filter.", 500);
637
            }
638
            $sql = 'truncate table '.$this->prefixTable($table);
639
        } else {
640 30
            $sql = 'delete from '.$this->prefixTable($table);
641
642 30
            if (!empty($where)) {
643
                $sql .= "\nwhere ".$this->buildWhere($where);
644
            }
645
        }
646 30
        return $this->queryModify($sql, [], $options);
647
    }
648
649
    /**
650
     * {@inheritdoc}
651
     */
652 17
    protected function createTableDb(array $tableDef, array $options = []) {
653 17
        $table = $tableDef['name'];
654
655
        // The table doesn't exist so this is a create table.
656 17
        $parts = array();
657 17
        foreach ($tableDef['columns'] as $name => $cdef) {
658 17
            $parts[] = $this->columnDefString($name, $cdef);
659
        }
660
661 17
        foreach (self::val('indexes', $tableDef, []) as $index) {
662 17
            $indexDef = $this->indexDefString($table, $index);
663 17
            if (!empty($indexDef)) {
664 16
                $parts[] = $indexDef;
665
            }
666
        }
667
668 17
        $tableName = $this->prefixTable($table);
669 17
        $sql = "create table $tableName (\n  ".
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
670 17
            implode(",\n  ", $parts).
671 17
            "\n)";
672
673 17
        if (self::val('collate', $options)) {
674
            $sql .= "\n collate {$options['collate']}";
675
        }
676
677 17
        $this->queryDefine($sql, $options);
678 17
    }
679
680
    /**
681
     * Construct a column definition string from an array defintion.
682
     *
683
     * @param string $name The name of the column.
684
     * @param array $cdef The column definition.
685
     * @return string Returns a string representing the column definition.
686
     */
687 17
    protected function columnDefString($name, array $cdef) {
688 17
        $result = $this->escape($name).' '.$this->nativeDbType($cdef);
689
690 17
        if (!self::val('allowNull', $cdef)) {
691 16
            $result .= ' not null';
692
        }
693
694 17
        if (isset($cdef['default'])) {
695 14
            $result .= ' default '.$this->quote($cdef['default']);
696
        }
697
698 17
        if (self::val('autoIncrement', $cdef)) {
699 10
            $result .= ' auto_increment';
700
        }
701
702 17
        return $result;
703
    }
704
705
    /**
706
     * Return the SDL string that defines an index.
707
     *
708
     * @param string $table The name of the table that the index is on.
709
     * @param array $def The index definition. This definition should have the following keys.
710
     *
711
     * columns
712
     * : An array of columns in the index.
713
     * type
714
     * : One of "index", "unique", or "primary".
715
     * @return null|string Returns the index string or null if the index is not correct.
716
     */
717 17
    protected function indexDefString($table, array $def) {
718 17
        $indexName = $this->escape($this->buildIndexName($table, $def));
719 17
        switch (self::val('type', $def, Db::INDEX_IX)) {
720 17
            case Db::INDEX_IX:
721 11
                return "index $indexName ".$this->bracketList($def['columns'], '`');
722 15
            case Db::INDEX_UNIQUE:
723 1
                return "unique $indexName ".$this->bracketList($def['columns'], '`');
724 15
            case Db::INDEX_PK:
725 14
                return "primary key ".$this->bracketList($def['columns'], '`');
726
        }
727 2
        return null;
728
    }
729
730
    /**
731
     * {@inheritdoc}
732
     */
733 4
    protected function alterTableDb(array $alterDef, array $options = []) {
734 4
        $table = $alterDef['name'];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
735 4
        $columnOrders = $this->getColumnOrders($alterDef['def']['columns']);
736 4
        $parts = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
737
738
        // Add the columns and indexes.
739 4
        foreach ($alterDef['add']['columns'] as $cname => $cdef) {
740
            // Figure out the order of the column.
741 2
            $pos = self::val($cname, $columnOrders, '');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
742 2
            $parts[] = 'add '.$this->columnDefString($cname, $cdef).$pos;
743
        }
744 4
        foreach ($alterDef['add']['indexes'] as $ixdef) {
745 3
            $parts[] = 'add '.$this->indexDefString($table, $ixdef);
746
        }
747
748
        // Alter the columns.
749 4
        foreach ($alterDef['alter']['columns'] as $cname => $cdef) {
750 2
            $parts[] = 'modify '.$this->columnDefString($cname, $cdef);
751
        }
752
753
        // Drop the columns and indexes.
754 4
        foreach ($alterDef['drop']['columns'] as $cname => $_) {
755
            $parts[] = 'drop '.$this->escape($cname);
756
        }
757 4
        foreach ($alterDef['drop']['indexes'] as $ixdef) {
758 2
            $parts[] = 'drop index '.$this->escape($ixdef['name']);
759
        }
760
761 4
        if (empty($parts)) {
762
            return false;
763
        }
764
765
        $sql = 'alter '.
766 4
            (self::val(Db::OPTION_IGNORE, $options) ? 'ignore ' : '').
767 4
            'table '.$this->prefixTable($table)."\n  ".
768 4
            implode(",\n  ", $parts);
769
770 4
        $this->queryDefine($sql, $options);
771 4
    }
772
773
    /**
774
     * Get an array of column orders so that added columns can be slotted into their correct spot.
775
     *
776
     * @param array $cdefs An array of column definitions.
777
     * @return array Returns an array of column orders suitable for an `alter table` statement.
778
     */
779 4
    private function getColumnOrders($cdefs) {
780 4
        $orders = array_flip(array_keys($cdefs));
781
782 4
        $prev = ' first';
783 4
        foreach ($orders as $cname => &$value) {
784 4
            $value = $prev;
785 4
            $prev = ' after '.$this->escape($cname);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 2 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
786
        }
787 4
        return $orders;
788
    }
789
790
    /**
791
     * Force a value into the appropriate php type based on its SQL type.
792
     *
793
     * @param mixed $value The value to force.
794
     * @param string $type The sqlite type name.
795
     * @return mixed Returns $value cast to the appropriate type.
796
     */
797 4
    protected function forceType($value, $type) {
798 4
        $type = strtolower($type);
799
800 4
        if ($type === 'null') {
801
            return null;
802 4
        } elseif ($type === 'boolean') {
803 1
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
804 3
        } elseif (in_array($type, ['int', 'integer', 'tinyint', 'smallint',
805
            'mediumint', 'bigint', 'unsigned big int', 'int2', 'int8', 'boolean'])) {
806 3
            return filter_var($value, FILTER_VALIDATE_INT);
807
        } elseif (in_array($type, ['real', 'double', 'double precision', 'float',
808
            'numeric', 'number', 'decimal(10,5)'])) {
809
            return filter_var($value, FILTER_VALIDATE_FLOAT);
810
        } else {
811
            return (string)$value;
812
        }
813
    }
814
815 41
    public function quote($value, $column = '') {
816 41
        if (is_bool($value)) {
817 1
            return (string)(int)$value;
818
        } elseif ($value instanceof \DateTimeInterface) {
819 1
            $value = $value->format(self::MYSQL_DATE_FORMAT);
820
        }
821
822 41
        return parent::quote($value, $column);
823
    }
824
825
    /**
826
     * Convert a value into something usable as a PDO parameter.
827
     *
828
     * @param mixed $value The value to convert.
829
     * @return mixed Returns the converted value or the value itself if it's fine.
830
     */
831 44
    private function argValue($value) {
832 44
        if (is_bool($value)) {
833
            return (int)$value;
834
        } elseif ($value instanceof \DateTimeInterface) {
835
            return $value->format(self::MYSQL_DATE_FORMAT);
836
        } else {
837 44
            return $value;
838
        }
839
    }
840
}
841