SQLBuilder::buildOwnCondition()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
rs 10
1
<?php declare(strict_types = 1);
2
/**
3
 * Created by Vitaly Iegorov <[email protected]>.
4
 * on 29.08.16 at 14:58
5
 */
6
namespace samsonframework\orm;
7
8
/**
9
 * SQL statement builder class.
10
 *
11
 * @author Vitaly Egorov <[email protected]>
12
 */
13
class SQLBuilder
14
{
15
    const NUMERIC_COLUMNS_TYPES = ['int', 'float', 'longint', 'smallint', 'tinyint'];
16
    const DATE_COLUMNS_TYPES = ['date', 'datetime', 'timestamp'];
17
18
    /**
19
     * Build update statement.
20
     *
21
     * @param TableMetadata  $tableMetadata Table metadata
22
     * @param array          $columnValues  Collection of columnName => columnValue
23
     * @param Condition|null $condition     Update filtering condition
24
     *
25
     * @return string Update SQL statement
26
     * @throws \InvalidArgumentException
27
     */
28
    public function buildUpdateStatement(TableMetadata $tableMetadata, array $columnValues, Condition $condition = null) : string
29
    {
30
        $sql = [];
31
        foreach ($columnValues as $columnName => $columnValue) {
32
            $columnName = $tableMetadata->getTableColumnName($columnName);
33
34
            // Check for null on not nullable columns
35
            if (!$tableMetadata->isColumnNullable($columnName) && $columnValue === null) {
36
                throw new \InvalidArgumentException(
37
                    sprintf(
38
                        'Cannot update column "%s::%s" with null - column is not nullable',
39
                        $tableMetadata->tableName,
40
                        $columnName
41
                    )
42
                );
43
            }
44
45
            // Do not update primary field
46
            if ($tableMetadata->getTablePrimaryField() === $columnName) {
47
                continue;
48
            }
49
            $sql[] = $this->buildFullColumnName($tableMetadata->tableName, $columnName) .
50
                $this->buildArgumentValue(
51
                    $columnValue,
52
                    $tableMetadata->getTableColumnType($columnName),
53
                    ArgumentInterface::EQUAL,
54
                    $tableMetadata->isColumnNullable($columnName),
55
                    $tableMetadata->getColumnDefaultValue($columnName)
56
                );
57
        }
58
59
        return 'UPDATE `' . $tableMetadata->tableName . '` SET ' . implode(', ', $sql)
60
        . ($condition !== null ? $this->buildWhereStatement($tableMetadata, $condition) : '');
61
    }
62
63
    /**
64
     * Build full table column name.
65
     *
66
     * @param string $tableName  Table name
67
     * @param string $columnName Field name
68
     *
69
     * @return string Full table column name
70
     */
71
    protected function buildFullColumnName(string $tableName, string $columnName) : string
72
    {
73
        return '`' . $tableName . '`.' . ($columnName === '*' ? '*' : '`' . $columnName . '`');
74
    }
75
76
    /**
77
     * Build argument value statement.
78
     *
79
     * @param string|array $value      Argument column value
80
     * @param string       $columnType Argument column type
81
     * @param string       $relation   Argument relation
82
     * @param bool         $nullable
83
     * @param string       $defaultValue
84
     *
85
     * @return string Argument relation with value statement
86
     */
87
    protected function buildArgumentValue(
88
        $value,
89
        string $columnType,
90
        string $relation,
91
        bool $nullable = false,
92
        $defaultValue = ''
93
    )
94
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
95
        return is_array($value)
96
            ? $this->buildArrayValue($columnType, $value, $relation)
97
            : $this->buildValue($columnType, $value, $relation, $nullable, $defaultValue);
98
    }
99
100
    /**
101
     * Build array argument value statement.
102
     *
103
     * @param string $columnType Table column type
104
     * @param array  $value      Table column array value
105
     * @param string $relation   Table column relation to value
106
     *
107
     * @return string Array argument relation with value statement
108
     */
109
    protected function buildArrayValue(string $columnType, array $value, string $relation = 'IN') : string
110
    {
111
        $relation = $relation === ArgumentInterface::NOT_EQUAL ? 'NOT IN' : 'IN';
112
113
        return $this->isColumnNumeric($columnType)
114
            ? $this->buildNumericArrayValue($value, $relation)
115
            : $this->buildStringArrayValue($value, $relation);
116
    }
117
118
    /**
119
     * Define if table column type is numeric.
120
     *
121
     * @param string $columnType Table column type
122
     *
123
     * @return bool True if column type is numeric
124
     */
125
    protected function isColumnNumeric(string $columnType) : bool
126
    {
127
        return in_array($columnType, self::NUMERIC_COLUMNS_TYPES, true);
128
    }
129
130
    /**
131
     * Build array with numeric values statement.
132
     *
133
     * @param array  $value    Array with numeric values
134
     * @param string $relation Table column relation to value
135
     *
136
     * @return string Array with numeric values statement
137
     */
138
    protected function buildNumericArrayValue(array $value, string $relation) : string
139
    {
140
        return $relation . ' (' . implode(',', $value) . ')';
141
    }
142
143
    /**
144
     * Build array string value statement.
145
     *
146
     * @param array  $value    Array with string values
147
     * @param string $relation Table column relation to value
148
     *
149
     * @return string Array with string values statement
150
     */
151
    protected function buildStringArrayValue(array $value, string $relation) : string
152
    {
153
        return $relation . ' ("' . implode('","', $value) . '")';
154
    }
155
156
    /**
157
     * Build not array argument value statement.
158
     *
159
     * @param string $columnType Table column type
160
     * @param mixed  $value      Table column value
161
     * @param string $relation   Table column relation to value
162
     *
163
     * @return string Not array argument relation with value statement
164
     */
165
    protected function buildValue(
166
        string $columnType,
167
        $value,
168
        string $relation,
169
        bool $nullable = false,
170
        $defaultValue = ''
171
    ) : string
172
    {
0 ignored issues
show
Coding Style introduced by
The closing parenthesis and the opening brace of a multi-line function declaration must be on the same line
Loading history...
173
        // Append space to relation if present
174
        $relation = strlen($relation) ? $relation . ' ' : '';
175
        
176
        if ($this->isColumnNumeric($columnType)) {
177
            return $relation . $this->buildNumericValue($value);
178
        } elseif ($this->isColumnDate($columnType)) {
179
            return $relation . $this->buildDateValue($value);
180
        } elseif (is_string($value)) {
181
            return $relation . $this->buildStringValue($value);
182
        } elseif ($value === null) {
183
            if ($nullable) {
184
                return $relation . $this->buildNullValue();
185
            } else {
186
                return $relation . $defaultValue;
187
            }
188
        }
189
190
        throw new \InvalidArgumentException('Cannot build column value ' . $value);
191
    }
192
193
    /**
194
     * Build not array numeric value statement.
195
     *
196
     * @param mixed $value Numeric value
197
     *
198
     * @return string Not array numeric value statement
199
     */
200
    protected function buildNumericValue($value) : string
201
    {
202
        return $value !== null ? (string)$value : '0';
203
    }
204
205
    /**
206
     * Define if table column type is date.
207
     *
208
     * @param string $columnType Table column type
209
     *
210
     * @return bool True if column type is date
211
     */
212
    protected function isColumnDate(string $columnType) : bool
213
    {
214
        return in_array($columnType, self::DATE_COLUMNS_TYPES, true);
215
    }
216
217
    /**
218
     * Build not array date value statement.
219
     *
220
     * @param mixed $value Date value
221
     *
222
     * @return string Not array date value statement
223
     */
224
    protected function buildDateValue($value) : string
225
    {
226
        return '"' . $value . '"';
227
    }
228
229
    /**
230
     * Build not array string value statement.
231
     *
232
     * @param string $value String value
233
     *
234
     * @return string Not array string value statement
235
     */
236
    protected function buildStringValue(string $value) : string
237
    {
238
        return '"' . $value . '"';
239
    }
240
241
    /**
242
     * Build not array NULL value statement.
243
     *
244
     * @return string Not array string value statement
245
     */
246
    protected function buildNullValue() : string
247
    {
248
        return 'NULL';
249
    }
250
251
    /**
252
     * Build where statement.
253
     *
254
     * @param TableMetadata $metadata
255
     * @param Condition     $condition
256
     *
257
     * @return string Limitation statement
258
     * @throws \InvalidArgumentException
259
     *
260
     */
261
    public function buildWhereStatement(TableMetadata $metadata, Condition $condition) : string
262
    {
263
        $conditions = [];
264
265
        foreach ($condition as $argument) {
266
            if ($argument instanceof ConditionInterface) {
267
                $result = $this->buildWhereStatement($metadata, $argument);
268
            } else {
269
                $result = $this->buildArgumentCondition($argument, $metadata);
0 ignored issues
show
Security Bug introduced by
It seems like $argument defined by $argument on line 265 can also be of type false; however, samsonframework\orm\SQLB...uildArgumentCondition() does only seem to accept object<samsonframework\orm\Argument>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
270
            }
271
            if (isset($result{0})) {
272
                $conditions[] = $result;
273
            }
274
        }
275
276
        if (count($conditions)) {
277
            return '(' . implode(') ' . $condition->relation . ' (', $conditions) . ')';
278
        } else { // If arguments is empty return empty where statement
279
            return '';
280
        }
281
    }
282
283
    /**
284
     * Build argument condition.
285
     *
286
     * @param Argument      $argument Condition argument
287
     * @param TableMetadata $metadata Table metadata
288
     *
289
     * @return string Argument condition statement
290
     * @throws \InvalidArgumentException If argument column does not exist
291
     */
292
    protected function buildArgumentCondition(Argument $argument, TableMetadata $metadata)
293
    {
294
        switch ($argument->relation) {
295
            case ArgumentInterface::OWN:
296
                return $this->buildOwnCondition($argument->field);
297
            case ArgumentInterface::ISNULL:
298
            case ArgumentInterface::NOTNULL:
299
                $columnName = $metadata->getTableColumnName($argument->field);
300
                return $this->buildNullCondition($columnName, $argument->relation);
301
            default:
302
                $columnName = $metadata->getTableColumnName($argument->field);
303
                return $this->buildCondition(
304
                    $columnName,
305
                    $this->buildArgumentValue(
306
                        $argument->value,
307
                        $metadata->getTableColumnType($columnName),
308
                        $argument->relation
309
                    )
310
                );
311
        }
312
    }
313
314
    /**
315
     * Build own  condition statement.
316
     *
317
     * @param string $ownCondition Condition statement
318
     *
319
     * @return string Own condition statement
320
     */
321
    protected function buildOwnCondition(string $ownCondition) : string
322
    {
323
        return $this->buildCondition($ownCondition);
324
    }
325
326
    /**
327
     * Build generic condition statement.
328
     *
329
     * @param string $columnName Table column name
330
     * @param string $relation   Table column value relation
331
     * @param string $value      Table column value
332
     *
333
     * @return string Generic condition statement
334
     */
335
    protected function buildCondition(string $columnName, string $relation = '', string $value = '') : string
336
    {
337
        return trim($columnName . ' ' . $relation . ' ' . $value);
338
    }
339
340
    /**
341
     * Build is null/not null condition statement.
342
     *
343
     * @param string $columnName Table column name
344
     * @param string $nullRelation Table column null relation
345
     *
346
     * @return string Is null/not null condition statement
347
     */
348
    protected function buildNullCondition(string $columnName, string $nullRelation) : string
349
    {
350
        return $this->buildCondition($columnName, $nullRelation);
351
    }
352
353
    /**
354
     * Build insert statement.
355
     *
356
     * @param TableMetadata $tableMetadata Table metadata
357
     * @param array         $columnValues  Collection of columnName => columnValue
358
     *
359
     * @return string Insert SQL statement
360
     * @throws \InvalidArgumentException
361
     */
362
    public function buildInsertStatement(TableMetadata $tableMetadata, array $columnValues) : string
363
    {
364
        $valuesSQL = [];
365
        $columnNames = [];
366
        foreach ($columnValues as $columnName => $columnValue) {
367
            $columnNames[] = $columnName = $tableMetadata->getTableColumnName($columnName);
368
            $valuesSQL[] = $this->buildArgumentValue(
369
                $columnValue,
370
                $tableMetadata->columnTypes[$columnName],
371
                '',
372
                $tableMetadata->columnNullable[$columnName],
373
                $tableMetadata->columnDefaults[$columnName]
374
            );
375
        }
376
377
        $sql = 'INSERT INTO `' . $tableMetadata->tableName . '` (`' . implode('`, `', $columnNames) . '`) ';
378
        $sql .= 'VALUES (' . implode(', ', $valuesSQL) . ')';
379
380
        return $sql;
381
    }
382
383
    /**
384
     * Build selected fields SELECT statement part.
385
     *
386
     * @param array $tableColumns Tables and column names collection
387
     *
388
     * @return string SELECT statement
389
     */
390
    public function buildSelectStatement(array $tableColumns) : string
391
    {
392
        return 'SELECT ' . implode(', ', $this->buildFullColumnNames($tableColumns));
393
    }
394
395
    /**
396
     * Build full table column names collection.
397
     *
398
     * @param array $tableColumns Tables and column names collection
399
     *
400
     * @return array Collection of full column names for query
401
     */
402
    protected function buildFullColumnNames(array $tableColumns) : array
403
    {
404
        $grouping = [];
405
        foreach ($tableColumns as $tableName => $columnNames) {
406
            /** @var array $columnNames */
407
            foreach ($columnNames = is_array($columnNames) ? $columnNames : [$columnNames] as $columnName) {
408
                $grouping[] = $this->buildFullColumnName($tableName, $columnName);
409
            }
410
        }
411
412
        return $grouping;
413
    }
414
415
    /**
416
     * Build FROM statement part.
417
     *
418
     * @param array $tableNames Tables and column names collection
419
     *
420
     * @return string FROM statement
421
     */
422
    public function buildFromStatement(array $tableNames = []) : string
423
    {
424
        return 'FROM `' . implode('`, `', $tableNames) . '`';
425
    }
426
427
    /**
428
     * Build grouping statement.
429
     *
430
     * @param array $tableColumns Tables and column names collection
431
     *
432
     * @return string Grouping statement
433
     */
434
    public function buildGroupStatement(array $tableColumns) : string
435
    {
436
        return 'GROUP BY ' . implode(', ', $this->buildFullColumnNames($tableColumns));
437
    }
438
439
    /**
440
     * Build ordering statement.
441
     *
442
     * @param array $tableColumns Tables and column names collection
443
     * @param array $orders       Collection of columns sorting order
444
     *
445
     * @return string Ordering statement
446
     * @throws \InvalidArgumentException
447
     */
448
    public function buildOrderStatement(array $tableColumns, array $orders) : string
449
    {
450
        $ordering = [];
451
        $i = 0;
452
        foreach ($this->buildFullColumnNames($tableColumns) as $columnName) {
453
            $ordering[] = $columnName . ' ' . ($orders[$i++] ?? 'ASC');
454
        }
455
456
        return 'ORDER BY ' . implode(', ', $ordering);
457
    }
458
459
    /**
460
     * Build limitation statement.
461
     *
462
     * @param int $rows   Rows amount for limitation
463
     * @param int $offset Rows offset
464
     *
465
     * @return string Limitation statement
466
     */
467
    public function buildLimitStatement(int $rows, int $offset = 0) : string
468
    {
469
        return 'LIMIT ' . $offset . ', ' . $rows;
470
    }
471
}
472