Completed
Push — master ( 6e0700...30b471 )
by Todd
02:33
created

MySqlDb::buildLike()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
crap 2
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
 * A {@link Db} class for connecting to MySQL.
14
 */
15
class MySqlDb extends Db {
16
    const MYSQL_DATE_FORMAT = 'Y-m-d H:i:s';
17
18
    /**
19
     * @var string
20
     */
21
    protected $dbname;
22
23
    protected static $map = [
24
        Db::OP_GT => '>',
25
        Db::OP_GTE => '>=',
26
        Db::OP_LT => '<',
27
        Db::OP_LTE => '<=',
28
        Db::OP_LIKE => 'like',
29
        Db::OP_AND => 'and',
30
        Db::OP_OR => 'or',
31
    ];
32
33
    /**
34
     * {@inheritdoc}
35
     */
36 6
    protected function dropTableDb($table, array $options = []) {
37
        $sql = 'drop table '.
38 6
            ($options[Db::OPTION_IGNORE] ? 'if exists ' : '').
39 6
            $this->prefixTable($table);
40
41 6
        $this->queryDefine($sql);
42 6
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47 8
    protected function fetchTableDefDb($table) {
48 8
        $columns = $this->fetchColumnDefsDb($table);
49
50 8
        if (empty($columns)) {
51
            // A table with no columns does not exist.
52 6
            return null;
53
        }
54
55 8
        $indexes = $this->fetchIndexesDb($table);
56
57
        $tableDef = [
58 8
            'name' => $table,
59 8
            'columns' => $columns,
60
            'indexes' => $indexes
61 8
        ];
62
63 8
        return $tableDef;
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69 5
    protected function fetchColumnDefsDb($table) {
70 5
        $rows = $this->get(
71 5
            new Identifier('information_schema', 'COLUMNS'),
72
            [
73 5
                'TABLE_SCHEMA' => $this->getDbName(),
74 5
                'TABLE_NAME' => $this->prefixTable($table, false)
75 5
            ],
76
            [
77 5
                Db::OPTION_FETCH_MODE => PDO::FETCH_ASSOC,
78 5
                'order' => ['TABLE_NAME', 'ORDINAL_POSITION']
79 5
            ]
80 5
        );
81
82 5
        $columns = [];
83 5
        foreach ($rows as $row) {
84 5
            $columnType = $row['COLUMN_TYPE'];
85 5
            if ($columnType === 'tinyint(1)') {
86 1
                $columnType = 'bool';
87 1
            }
88 5
            $column = Db::typeDef($columnType);
89 5
            if ($column === null) {
90
                throw new \Exception("Unknown type '$columnType'.", 500);
91
            }
92
93 5
            $column['allowNull'] = strcasecmp($row['IS_NULLABLE'], 'YES') === 0;
94
95 5
            if (($default = $row['COLUMN_DEFAULT']) !== null) {
96 4
                $column['default'] = $this->forceType($default, $column['type']);
97 4
            }
98
99 5
            if ($row['EXTRA'] === 'auto_increment') {
100 1
                $column['autoIncrement'] = true;
101 1
            }
102
103 5
            if ($row['COLUMN_KEY'] === 'PRI') {
104 3
                $column['primary'] = true;
105 3
            }
106
107 5
            $columns[$row['COLUMN_NAME']] = $column;
108 5
        }
109
110 5
        return $columns;
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116 72
    public function get($table, array $where, array $options = []) {
117 72
        $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...
118 72
        $result = $this->query($sql, [], $options);
119 72
        return $result;
120
    }
121
122
    /**
123
     * Build a sql select statement.
124
     *
125
     * @param string|Identifier $tableName The name of the main table.
126
     * @param array $where The where filter.
127
     * @param array $options An array of additional query options.
128
     * @return string Returns the select statement as a string.
129
     * @see Db::get()
130
     */
131 76
    protected function buildSelect($tableName, array $where, array $options = []) {
132 76
        $options += ['limit' => 0];
133
134 76
        $sql = '';
135
136
        // Build the select clause.
137 76
        if (!empty($options['columns'])) {
138 11
            $columns = array();
139 11
            foreach ($options['columns'] as $value) {
140 11
                $columns[] = $this->escape($value);
141 11
            }
142 11
            $sql .= 'select '.implode(', ', $columns);
143 11
        } else {
144 72
            $sql .= "select *";
145
        }
146
147
        // Build the from clause.
148 76
        if ($tableName instanceof Literal) {
149
            $tableName = $tableName->getValue($this);
150
        } else {
151 76
            $tableName = $this->prefixTable($tableName);
152
        }
153 76
        $sql .= "\nfrom $tableName";
154
155
        // Build the where clause.
156 76
        $whereString = $this->buildWhere($where, Db::OP_AND);
157 76
        if ($whereString) {
158 49
            $sql .= "\nwhere ".$whereString;
159 49
        }
160
161
        // Build the order.
162 76
        if (!empty($options['order'])) {
163 40
            $orders = [];
164 40
            foreach ($options['order'] as $column) {
165 40
                if ($column[0] === '-') {
166 2
                    $order = $this->escape(substr($column, 1)).' desc';
167 2
                } else {
168 40
                    $order = $this->escape($column);
169
                }
170 40
                $orders[] = $order;
171 40
            }
172 40
            $sql .= "\norder by ".implode(', ', $orders);
173 40
        }
174
175
        // Build the limit, offset.
176 76
        if (!empty($options['limit'])) {
177 28
            $limit = (int)$options['limit'];
178 28
            $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...
179 28
        }
180
181 76
        if (!empty($options['offset'])) {
182 1
            $sql .= ' offset '.((int)$options['offset']);
183 76
        } elseif (isset($options['page'])) {
184
            $offset = $options['limit'] * ($options['page'] - 1);
185
            $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...
186
        }
187
188 76
        return $sql;
189
    }
190
191
    /**
192
     * Build a where clause from a where array.
193
     *
194
     * @param array $where There where string.
195
     * This is an array in the form `['column' => 'value']` with more advanced options for non-equality comparisons.
196
     * @param string $op The logical operator to join multiple field comparisons.
197
     * @return string The where string.
198
     */
199 78
    protected function buildWhere($where, $op = Db::OP_AND) {
200 78
        $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...
201 78
        $strop = $map[$op];
202
203 78
        $result = '';
204 78
        foreach ($where as $column => $value) {
205 51
            $btcolumn = $this->escape($column);
206
207 51
            if (is_array($value)) {
208 30
                if (is_numeric($column)) {
209
                    // This is a bracketed expression.
210 4
                    $result .= (empty($result) ? '' : "\n  $strop ").
211 4
                        "(\n  ".
212 4
                        $this->buildWhere($value, $op).
213 4
                        "\n  )";
214 30
                } elseif (in_array($column, [Db::OP_AND, Db::OP_OR])) {
215
                    // This is an AND/OR expression.
216 4
                    $result .= (empty($result) ? '' : "\n  $strop ").
217 4
                        "(\n  ".
218 4
                        $this->buildWhere($value, $column).
219 4
                        "\n  )";
220 4
                } else {
221 26
                    if (isset($value[0])) {
222
                        // This is a short in syntax.
223 2
                        $value = [Db::OP_IN => $value];
224 2
                    }
225
226 26
                    foreach ($value as $vop => $rval) {
227 26
                        if ($result) {
228 4
                            $result .= "\n  $strop ";
229 4
                        }
230
231
                        switch ($vop) {
232 26
                            case Db::OP_AND:
233 26
                            case Db::OP_OR:
234 4
                                if (is_numeric($column)) {
235
                                    $innerWhere = $rval;
236
                                } else {
237 4
                                    $innerWhere = [$column => $rval];
238
                                }
239
                                $result .= "(\n  ".
240 4
                                    $this->buildWhere($innerWhere, $vop).
0 ignored issues
show
Bug introduced by
It seems like $innerWhere defined by $rval on line 235 can also be of type null; however, Garden\Db\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...
241 4
                                    "\n  )";
242 4
                                break;
243 26
                            case Db::OP_EQ:
244 6
                                if ($rval === null) {
245
                                    $result .= "$btcolumn is null";
246 6
                                } elseif (is_array($rval)) {
247 2
                                    $result .= "$btcolumn in ".$this->bracketList($rval);
248 2
                                } else {
249 4
                                    $result .= "$btcolumn = ".$this->quote($rval);
250
                                }
251 6
                                break;
252 22
                            case Db::OP_GT:
253 22
                            case Db::OP_GTE:
254 22
                            case Db::OP_LT:
255 22
                            case Db::OP_LTE:
256 12
                                $result .= "$btcolumn {$map[$vop]} ".$this->quote($rval);
257 12
                                break;
258 10
                            case Db::OP_LIKE:
259
                                $result .= $this->buildLike($btcolumn, $rval);
260
                                break;
261 10
                            case Db::OP_IN:
262
                                // Quote the in values.
263 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...
264 4
                                $result .= "$btcolumn in (".implode(', ', $rval).')';
265 4
                                break;
266 6
                            case Db::OP_NEQ:
267 6
                                if ($rval === null) {
268 2
                                    $result .= "$btcolumn is not null";
269 6
                                } elseif (is_array($rval)) {
270 2
                                    $result .= "$btcolumn not in ".$this->bracketList($rval);
271 2
                                } else {
272 2
                                    $result .= "$btcolumn <> ".$this->quote($rval);
273
                                }
274 6
                                break;
275
                        }
276 26
                    }
277
                }
278 30
            } else {
279 25
                if ($result) {
280 9
                    $result .= "\n  $strop ";
281 9
                }
282
283
                // This is just an equality operator.
284 25
                if ($value === null) {
285 2
                    $result .= "$btcolumn is null";
286 2
                } else {
287 23
                    $result .= "$btcolumn = ".$this->quote($value);
288
                }
289
            }
290 78
        }
291 78
        return $result;
292
    }
293
294
    /**
295
     * Build a like expression.
296
     *
297
     * @param string $column The column name.
298
     * @param mixed $value The right-hand value.
299
     * @return string Returns the like expression.
300
     * @internal param bool $quotevals Whether or not to quote the values.
301
     */
302
    protected function buildLike($column, $value) {
303
        return "$column like ".$this->quote($value);
304
    }
305
306
    /**
307
     * Convert an array into a bracketed list suitable for MySQL clauses.
308
     *
309
     * @param array $row The row to expand.
310
     * @param string $quote The quotes to surroud the items with. There are two special cases.
311
     * ' (single quote)
312
     * : The row will be passed through {@link PDO::quote()}.
313
     * ` (backticks)
314
     * : The row will be passed through {@link MySqlDb::backtick()}.
315
     * @return string Returns the bracket list.
316
     */
317 72
    public function bracketList($row, $quote = "'") {
318
        switch ($quote) {
319 72
            case "'":
320 59
                $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...
321 59
                $quote = '';
322 59
                break;
323 72
            case '`':
324 72
                $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...
325 72
                $quote = '';
326 72
                break;
327
        }
328
329 72
        return "($quote".implode("$quote, $quote", $row)."$quote)";
330
    }
331
332
333
    /**
334
     * Get the current database name.
335
     *
336
     * @return mixed
337
     */
338 5
    private function getDbName() {
339 5
        if (!isset($this->dbname)) {
340
            $this->dbname = $this->getPDO()->query('select database()')->fetchColumn();
341
        }
342 5
        return $this->dbname;
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     */
348 17
    protected function nativeDbType(array $type) {
349 17
        static $translations = ['bool' => 'tinyint(1)', 'byte' => 'tinyint', 'short' => 'smallint', 'long' => 'bigint'];
350
351
        // Translate the dbtype to a MySQL native type.
352 17
        if (isset($translations[$type['dbtype']])) {
353 1
            $type['dbtype'] = $translations[$type['dbtype']];
354 1
        }
355
356
        // Unsigned is represented differently in MySQL.
357 17
        $unsigned = !empty($type['unsigned']);
358 17
        unset ($type['unsigned']);
359
360 17
        $dbType = static::dbType($type).($unsigned ? ' unsigned' : '');
361
362 17
        return $dbType;
363
    }
364
365
    /**
366
     * Parse a column type string and return it in a way that is suitable for a create/alter table statement.
367
     *
368
     * @param string $typeString The string to parse.
369
     * @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...
370
     */
371
    protected function columnTypeString($typeString) {
372
        $type = null;
373
374
        if (substr($typeString, 0, 4) === 'enum') {
375
            // This is an enum which will come in as an array.
376
            if (preg_match_all("`'([^']+)'`", $typeString, $matches)) {
377
                $type = $matches[1];
378
            }
379
        } else {
380
            if (preg_match('`([a-z]+)\s*(?:\((\d+(?:\s*,\s*\d+)*)\))?\s*(unsigned)?`', $typeString, $matches)) {
381
                //         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...
382
                $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...
383
                $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...
384
                $unsigned = self::val(3, $matches);
385
386
                if (substr($str, 0, 1) == 'u') {
387
                    $unsigned = true;
388
                    $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...
389
                }
390
391
                // Remove the length from types without real lengths.
392
                if (in_array($str, array('tinyint', 'smallint', 'mediumint', 'int', 'bigint', 'float', 'double'))) {
393
                    $length = null;
394
                }
395
396
                $type = $str;
397
                if ($length) {
398
                    $length = str_replace(' ', '', $length);
399
                    $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...
400
                }
401
                if ($unsigned) {
402
                    $type .= ' unsigned';
403
                }
404
            }
405
        }
406
407
        if (!$type) {
408
            debug_print_backtrace();
409
            trigger_error("Couldn't parse type $typeString", E_USER_ERROR);
410
        }
411
412
        return $type;
413
    }
414
415
    /**
416
     * Get the indexes from the database.
417
     *
418
     * @param string $table The name of the table to get the indexes for.
419
     * @return array|null
420
     */
421 4
    protected function fetchIndexesDb($table = '') {
422 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...
423 4
            new Identifier('information_schema', 'STATISTICS'),
424
            [
425 4
                'TABLE_SCHEMA' => $this->getDbName(),
426 4
                'TABLE_NAME' => $this->prefixTable($table, false)
427 4
            ],
428
            [
429
                'columns' => [
430 4
                    'INDEX_NAME',
431 4
                    'COLUMN_NAME',
432
                    'NON_UNIQUE'
433 4
                ],
434 4
                'order' => ['INDEX_NAME', 'SEQ_IN_INDEX']
435 4
            ]
436 4
        );
437 4
        $indexRows = $stm->fetchAll(PDO::FETCH_ASSOC | PDO::FETCH_GROUP);
438
439 4
        $indexes = [];
440 4
        foreach ($indexRows as $indexName => $columns) {
441
            $index = [
442 4
                'type' => null,
443 4
                'columns' => array_column($columns, 'COLUMN_NAME'),
444
                'name' => $indexName
445 4
            ];
446
447 4
            if ($indexName === 'PRIMARY') {
448 2
                $index['type'] = Db::INDEX_PK;
449 2
            } else {
450 2
                $index['type'] = $columns[0]['NON_UNIQUE'] ? Db::INDEX_IX : Db::INDEX_UNIQUE;
451
            }
452 4
            $indexes[] = $index;
453 4
        }
454
455 4
        return $indexes;
456
    }
457
458
    /**
459
     * {@inheritdoc}
460
     */
461
    protected function fetchTableNamesDb() {
462
        // Get the table names.
463
        $tables = $this->get(
464
            new Identifier('information_schema', 'TABLES'),
465
            [
466
                'TABLE_SCHEMA' => $this->getDbName(),
467
                'TABLE_NAME' => [Db::OP_LIKE => $this->escapeLike($this->getPx()).'%']
468
            ],
469
            [
470
                'columns' => ['TABLE_NAME'],
471
                'fetchMode' => PDO::FETCH_ASSOC
472
            ]
473
        );
474
475
        return $tables->fetchAll(PDO::FETCH_COLUMN);
476
    }
477
478
    /**
479
     * {@inheritdoc}
480
     */
481 15
    public function insert($table, array $row, array $options = []) {
482 15
        $sql = $this->buildInsert($table, $row, $options);
483 15
        $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...
484 15
        if (is_numeric($id)) {
485 15
            return (int)$id;
486
        } else {
487
            return $id;
488
        }
489
    }
490
491
    /**
492
     * Build an insert statement.
493
     *
494
     * @param string|Identifier $tableName The name of the table to insert to.
495
     * @param array $row The row to insert.
496
     * @param array $options An array of options for the insert. See {@link Db::insert} for the options.
497
     * @return string Returns the the sql string of the insert statement.
498
     */
499 32
    protected function buildInsert($tableName, array $row, $options = []) {
500 32
        if (self::val(Db::OPTION_UPSERT, $options)) {
501 2
            return $this->buildUpsert($tableName, $row, $options);
0 ignored issues
show
Bug introduced by
It seems like $tableName defined by parameter $tableName on line 499 can also be of type object<Garden\Db\Identifier>; however, Garden\Db\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...
502 32
        } elseif (self::val(Db::OPTION_IGNORE, $options)) {
503 2
            $sql = 'insert ignore ';
504 32
        } elseif (self::val(Db::OPTION_REPLACE, $options)) {
505 2
            $sql = 'replace ';
506 2
        } else {
507 30
            $sql = 'insert ';
508
        }
509 32
        $sql .= $this->prefixTable($tableName);
510
511
        // Add the list of values.
512
        $sql .=
513 32
            "\n".$this->bracketList(array_keys($row), '`').
514 32
            "\nvalues".$this->bracketList($row, "'");
515
516 32
        return $sql;
517
    }
518
519
    /**
520
     * Build an upsert statement.
521
     *
522
     * An upsert statement is an insert on duplicate key statement in MySQL.
523
     *
524
     * @param string $tableName The name of the table to update.
525
     * @param array $row The row to insert or update.
526
     * @param array $options An array of additional query options.
527
     * @return string Returns the upsert statement as a string.
528
     */
529 2
    protected function buildUpsert($tableName, array $row, $options = []) {
530
        // Build the initial insert statement first.
531 2
        unset($options[Db::OPTION_UPSERT]);
532 2
        $sql = $this->buildInsert($tableName, $row, $options);
533
534
        // Add the duplicate key stuff.
535 2
        $updates = [];
536 2
        foreach ($row as $key => $value) {
537 2
            $updates[] = $this->escape($key).' = values('.$this->escape($key).')';
538 2
        }
539 2
        $sql .= "\non duplicate key update ".implode(', ', $updates);
540
541 2
        return $sql;
542
    }
543
544
    /**
545
     * {@inheritdoc}
546
     */
547 54
    public function load($table, $rows, array $options = []) {
548 54
        $count = 0;
549 54
        $first = true;
550 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...
551 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...
552
553
        // Loop over the rows and insert them with the statement.
554 54
        foreach ($rows as $row) {
555 44
            if ($first) {
556
                // Build the insert statement from the first row.
557 44
                foreach ($row as $key => $value) {
558 44
                    $spec[$key] = new Literal($this->paramName($key));
559 44
                }
560
561 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...
562 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...
563 44
                $first = false;
564 44
            }
565
566 44
            $stmt->execute($row);
567 44
            $count += $stmt->rowCount();
568 54
        }
569
570 54
        return $count;
571
    }
572
573
    /**
574
     * Make a valid PDO parameter name from a string.
575
     *
576
     * This method replaces invalid placeholder characters with underscores.
577
     *
578
     * @param string $name The name to replace.
579
     * @return string
580
     */
581 44
    protected function paramName($name) {
582 44
        $result = ':'.preg_replace('`[^a-zA-Z0-9_]`', '_', $name);
583 44
        return $result;
584
    }
585
586
    /**
587
     * {@inheritdoc}
588
     */
589 6
    public function update($table, array $set, array $where, array $options = []) {
590 6
        $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...
591 6
        $result = $this->queryModify($sql, [], $options);
592
593 6
        return $result;
594
    }
595
596
    /**
597
     * Build a sql update statement.
598
     *
599
     * @param string|Identifier $tableName The name of the table to update.
600
     * @param array $set An array of columns to set.
601
     * @param array $where The where filter.
602
     * @param array $options Additional options for the query.
603
     * @return string Returns the update statement as a string.
604
     */
605 2
    protected function buildUpdate($tableName, array $set, array $where, array $options = []) {
606
        $sql = 'update '.
607 2
            (self::val(Db::OPTION_IGNORE, $options) ? 'ignore ' : '').
608 2
            $this->prefixTable($tableName).
609 2
            "\nset\n  ";
610
611 2
        $parts = [];
612 2
        foreach ($set as $key => $value) {
613 2
            $quotedKey = $this->escape($key);
614
615 2
            $parts[] = $quotedKey.' = '.$this->quote($value);
616 2
        }
617 2
        $sql .= implode(",\n  ", $parts);
618
619 2
        if (!empty($where)) {
620 2
            $sql .= "\nwhere ".$this->buildWhere($where, Db::OP_AND);
621 2
        }
622
623 2
        return $sql;
624
    }
625
626
    /**
627
     * {@inheritdoc}
628
     */
629 30
    public function delete($table, array $where, array $options = []) {
630 30
        if (self::val(Db::OPTION_TRUNCATE, $options)) {
631
            if (!empty($where)) {
632
                throw new \InvalidArgumentException("You cannot truncate $table with a where filter.", 500);
633
            }
634
            $sql = 'truncate table '.$this->prefixTable($table);
635
        } else {
636 30
            $sql = 'delete from '.$this->prefixTable($table);
637
638 30
            if (!empty($where)) {
639
                $sql .= "\nwhere ".$this->buildWhere($where);
640
            }
641
        }
642 30
        return $this->queryModify($sql, [], $options);
643
    }
644
645
    /**
646
     * {@inheritdoc}
647
     */
648 17
    protected function createTableDb(array $tableDef, array $options = []) {
649 17
        $table = $tableDef['name'];
650
651
        // The table doesn't exist so this is a create table.
652 17
        $parts = array();
653 17
        foreach ($tableDef['columns'] as $name => $cdef) {
654 17
            $parts[] = $this->columnDefString($name, $cdef);
655 17
        }
656
657 17
        foreach (self::val('indexes', $tableDef, []) as $index) {
658 17
            $indexDef = $this->indexDefString($table, $index);
659 17
            if (!empty($indexDef)) {
660 16
                $parts[] = $indexDef;
661 16
            }
662 17
        }
663
664 17
        $tableName = $this->prefixTable($table);
665 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...
666 17
            implode(",\n  ", $parts).
667 17
            "\n)";
668
669 17
        if (self::val('collate', $options)) {
670
            $sql .= "\n collate {$options['collate']}";
671
        }
672
673 17
        $this->queryDefine($sql, $options);
674 17
    }
675
676
    /**
677
     * Construct a column definition string from an array defintion.
678
     *
679
     * @param string $name The name of the column.
680
     * @param array $cdef The column definition.
681
     * @return string Returns a string representing the column definition.
682
     */
683 17
    protected function columnDefString($name, array $cdef) {
684 17
        $result = $this->escape($name).' '.$this->nativeDbType($cdef);
685
686 17
        if (!self::val('allowNull', $cdef)) {
687 16
            $result .= ' not null';
688 16
        }
689
690 17
        if (isset($cdef['default'])) {
691 14
            $result .= ' default '.$this->quote($cdef['default']);
692 14
        }
693
694 17
        if (self::val('autoIncrement', $cdef)) {
695 10
            $result .= ' auto_increment';
696 10
        }
697
698 17
        return $result;
699
    }
700
701
    /**
702
     * Return the SDL string that defines an index.
703
     *
704
     * @param string $tableName The name of the table that the index is on.
705
     * @param array $def The index defintion. This definition should have the following keys.
706
     *
707
     * columns
708
     * : An array of columns in the index.
709
     * type
710
     * : One of "index", "unique", or "primary".
711
     * @return null|string Returns the index string or null if the index is not correct.
712
     */
713 17
    protected function indexDefString($tableName, array $def) {
714 17
        $indexName = $this->escape($this->buildIndexName($tableName, $def));
715 17
        switch (self::val('type', $def, Db::INDEX_IX)) {
716 17
            case Db::INDEX_IX:
717 11
                return "index $indexName ".$this->bracketList($def['columns'], '`');
718 15
            case Db::INDEX_UNIQUE:
719 1
                return "unique $indexName ".$this->bracketList($def['columns'], '`');
720 15
            case Db::INDEX_PK:
721 14
                return "primary key ".$this->bracketList($def['columns'], '`');
722 2
        }
723 2
        return null;
724
    }
725
726
    /**
727
     * {@inheritdoc}
728
     */
729 4
    protected function alterTableDb(array $alterDef, array $options = []) {
730 4
        $tablename = $alterDef['name'];
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...
731 4
        $columnOrders = $this->getColumnOrders($alterDef['def']['columns']);
732 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...
733
734
        // Add the columns and indexes.
735 4
        foreach ($alterDef['add']['columns'] as $cname => $cdef) {
736
            // Figure out the order of the column.
737 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...
738 2
            $parts[] = 'add '.$this->columnDefString($cname, $cdef).$pos;
739 4
        }
740 4
        foreach ($alterDef['add']['indexes'] as $ixdef) {
741 3
            $parts[] = 'add '.$this->indexDefString($tablename, $ixdef);
742 4
        }
743
744
        // Alter the columns.
745 4
        foreach ($alterDef['alter']['columns'] as $cname => $cdef) {
746 2
            $parts[] = 'modify '.$this->columnDefString($cname, $cdef);
747 4
        }
748
749
        // Drop the columns and indexes.
750 4
        foreach ($alterDef['drop']['columns'] as $cname => $_) {
751
            $parts[] = 'drop '.$this->escape($cname);
752 4
        }
753 4
        foreach ($alterDef['drop']['indexes'] as $ixdef) {
754 2
            $parts[] = 'drop index '.$this->escape($ixdef['name']);
755 4
        }
756
757 4
        if (empty($parts)) {
758
            return false;
759
        }
760
761
        $sql = 'alter '.
762 4
            (self::val(Db::OPTION_IGNORE, $options) ? 'ignore ' : '').
763 4
            'table '.$this->prefixTable($tablename)."\n  ".
764 4
            implode(",\n  ", $parts);
765
766 4
        $this->queryDefine($sql, $options);
767 4
    }
768
769
    /**
770
     * Get an array of column orders so that added columns can be slotted into their correct spot.
771
     *
772
     * @param array $cdefs An array of column definitions.
773
     * @return array Returns an array of column orders suitable for an `alter table` statement.
774
     */
775 4
    private function getColumnOrders($cdefs) {
776 4
        $orders = array_flip(array_keys($cdefs));
777
778 4
        $prev = ' first';
779 4
        foreach ($orders as $cname => &$value) {
780 4
            $value = $prev;
781 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...
782 4
        }
783 4
        return $orders;
784
    }
785
786
    /**
787
     * Force a value into the appropriate php type based on its SQL type.
788
     *
789
     * @param mixed $value The value to force.
790
     * @param string $type The sqlite type name.
791
     * @return mixed Returns $value cast to the appropriate type.
792
     */
793 4
    protected function forceType($value, $type) {
794 4
        $type = strtolower($type);
795
796 4
        if ($type === 'null') {
797
            return null;
798 4
        } elseif ($type === 'boolean') {
799 1
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
800 3
        } elseif (in_array($type, ['int', 'integer', 'tinyint', 'smallint',
801 3
            'mediumint', 'bigint', 'unsigned big int', 'int2', 'int8', 'boolean'])) {
802 3
            return filter_var($value, FILTER_VALIDATE_INT);
803
        } elseif (in_array($type, ['real', 'double', 'double precision', 'float',
804
            'numeric', 'number', 'decimal(10,5)'])) {
805
            return filter_var($value, FILTER_VALIDATE_FLOAT);
806
        } else {
807
            return (string)$value;
808
        }
809
    }
810
811 40
    public function quote($value, $column = '') {
812 40
        if (is_bool($value)) {
813 1
            return (string)(int)$value;
814
        } else {
815 40
            return parent::quote($value, $column);
816
        }
817
    }
818
}
819