Passed
Push — master ( 9d3685...1f10fe )
by y
01:27
created

SQL::group()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Helix\DB;
4
5
use Countable;
6
7
/**
8
 * Static helper for building SQL.
9
 *
10
 * The methods here are driver agnostic, and do not quote values.
11
 */
12
class SQL {
13
14
    /**
15
     * `(... AND ...)`
16
     *
17
     * @param string[] $conditions
18
     * @return string
19
     */
20
    public static function all (array $conditions): string {
21
        if (count($conditions) === 1) {
22
            return reset($conditions);
23
        }
24
        return '(' . implode(' AND ', $conditions) . ')';
25
    }
26
27
    /**
28
     * `(... OR ...)`
29
     *
30
     * @param string[] $conditions
31
     * @return string
32
     */
33
    public static function any (array $conditions): string {
34
        if (count($conditions) === 1) {
35
            return reset($conditions);
36
        }
37
        return '(' . implode(' OR ', $conditions) . ')';
38
    }
39
40
    /**
41
     * `AVG($column)`
42
     *
43
     * @param string $column
44
     * @return ExpressionInterface
45
     */
46
    public static function avg (string $column): ExpressionInterface {
47
        return new Expression("AVG({$column})");
48
    }
49
50
    /**
51
     * Used to generate a comparison.
52
     *
53
     * `"$a $operator $b"`
54
     *
55
     * If `$a` is an array, the keys and values are used for `$a` and `$b`.
56
     *
57
     * `["$k $operator $v", ...]`
58
     *
59
     * If `$b` is an array, all items are listed within parenthesis.
60
     *
61
     * `"$a $listOperator (...$b)"`
62
     *
63
     * As well as if `$a` is a two-dimensional array.
64
     *
65
     * `["$k $listOperator (...$v)", ...]`
66
     *
67
     * If `$b` is a `{@link Select}, it's used as a subquery.
68
     *
69
     * `"$a $operator $subOperator $b->toSql()"`
70
     *
71
     * As well as if `$a` is an array of {@link Select}.
72
     *
73
     * `["$k $operator $subOperator $v->toSql()", ...]`
74
     *
75
     * @param mixed $a
76
     * @param string $operator
77
     * @param mixed $b
78
     * @param string $subOperator
79
     * @param string $listOperator
80
     * @return string|array
81
     */
82
    public static function compare ($a, $operator, $b = null, $subOperator = null, $listOperator = null) {
83
        if (is_array($a)) {
84
            $cmp = [];
85
            foreach ($a as $k => $v) {
86
                $cmp[$k] = static::compare($k, $operator, $v, $subOperator, $listOperator);
87
            }
88
            return $cmp;
89
        }
90
        elseif (is_array($b)) {
91
            return "{$a} {$listOperator} (" . implode(',', $b) . ")";
92
        }
93
        elseif ($b instanceof Select) {
94
            return "{$a} {$operator} {$subOperator} ({$b->toSql()})";
95
        }
96
        return "{$a} {$operator} {$b}";
97
    }
98
99
    /**
100
     * `COUNT($column)`
101
     *
102
     * @param string $column
103
     * @return ExpressionInterface
104
     */
105
    public static function count (string $column): ExpressionInterface {
106
        return new Expression("COUNT({$column})");
107
    }
108
109
    /**
110
     * Returns a `GROUP BY` clause listing all given columns,
111
     * or an empty string if there are no columns.
112
     *
113
     * @param array $columns
114
     * @return string
115
     */
116
    public static function group (array $columns): string {
117
        if ($columns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $columns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
118
            return ' GROUP BY ' . implode(',', $columns);
119
        }
120
        return '';
121
    }
122
123
    /**
124
     * Returns a `HAVING` clause for all conditions,
125
     * or an empty string if there are no conditions.
126
     *
127
     * @param array $conditions
128
     * @return string
129
     */
130
    public static function having (array $conditions): string {
131
        if ($conditions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
132
            return ' HAVING ' . static::all($conditions);
133
        }
134
        return '';
135
    }
136
137
    /**
138
     * `$a = $b`, or `$a IN (...$b)`, or `$a = ANY ($b)`
139
     *
140
     * @param string|array $a
141
     * @param string|array|Select $b
142
     * @return string|array
143
     */
144
    public static function isEqual ($a, $b = null) {
145
        return static::compare($a, '=', $b, 'ANY', 'IN');
146
    }
147
148
    /**
149
     * `$a > $b`, or `$a > ALL ($b)`
150
     *
151
     * @param string|array $a
152
     * @param string|Select $b
153
     * @return string|array
154
     */
155
    public static function isGreater ($a, $b = null) {
156
        return static::compare($a, '>', $b, 'ALL');
157
    }
158
159
    /**
160
     * `$a >= $b`, or `$a >= ALL ($b)`
161
     *
162
     * @param string|array $a
163
     * @param string|Select $b
164
     * @return string|array
165
     */
166
    public static function isGreaterOrEqual ($a, $b = null) {
167
        return static::compare($a, '>=', $b, 'ALL');
168
    }
169
170
    /**
171
     * `$a < $b`, or `$a < ALL ($b)`
172
     *
173
     * @param string|array $a
174
     * @param string|Select $b
175
     * @return string|array
176
     */
177
    public static function isLess ($a, $b = null) {
178
        return static::compare($a, '<', $b, 'ALL');
179
    }
180
181
    /**
182
     * `$a <= $b`, or `$a <= ALL ($b)`
183
     *
184
     * @param string|array $a
185
     * @param string|Select $b
186
     * @return string|array
187
     */
188
    public static function isLessOrEqual ($a, $b = null) {
189
        return static::compare($a, '<=', $b, 'ALL');
190
    }
191
192
    /**
193
     * `$x LIKE $pattern`
194
     *
195
     * @param string|array $x
196
     * @param string $pattern
197
     * @return string|array
198
     */
199
    public static function isLike ($x, string $pattern = null) {
200
        return static::compare($x, 'LIKE', $pattern);
201
    }
202
203
    /**
204
     * `$a IS NOT UNKNOWN|TRUE|FALSE`
205
     *
206
     * @param string|array $x
207
     * @param null|bool $identity
208
     * @return string|array
209
     */
210
    public static function isNot ($x, $identity) {
211
        return static::compare($x, 'IS NOT', ['' => 'UNKNOWN', 1 => 'TRUE', 0 => 'FALSE'][$identity]);
212
    }
213
214
    /**
215
     * `$a <> $b`, or `$a NOT IN (...$b)`, or `$a <> ALL ($b)`
216
     *
217
     * @param string|array $a
218
     * @param string|array|Select $b
219
     * @return string|array
220
     */
221
    public static function isNotEqual ($a, $b = null) {
222
        return static::compare($a, '<>', $b, 'ALL', 'NOT IN');
223
    }
224
225
    /**
226
     * `$a NOT LIKE $pattern`
227
     *
228
     * @param string|array $x
229
     * @param string $pattern
230
     * @return string|array
231
     */
232
    public static function isNotLike ($x, string $pattern = null) {
233
        return static::compare($x, 'NOT LIKE', $pattern);
234
    }
235
236
    /**
237
     * `$x IS NOT NULL`
238
     *
239
     * @param string|array $x
240
     * @return string|array
241
     */
242
    public static function isNotNull ($x) {
243
        if (is_array($x)) {
244
            return array_map(__METHOD__, $x);
245
        }
246
        return "{$x} IS NOT NULL";
247
    }
248
249
    /**
250
     * `$x NOT REGEXP $pattern`
251
     *
252
     * @param string|array $x
253
     * @param string $pattern
254
     * @return string|array
255
     */
256
    public static function isNotRegExp ($x, string $pattern = null) {
257
        return static::compare($x, 'NOT REGEXP', $pattern);
258
    }
259
260
    /**
261
     * `$x REGEXP $pattern`
262
     *
263
     * @param string|array $x
264
     * @param string $pattern
265
     * @return string|array
266
     */
267
    public static function isRegExp ($x, string $pattern = null) {
268
        return static::compare($x, 'REGEXP', $pattern);
269
    }
270
271
    /**
272
     * Returns a `LIMIT ... [OFFSET ...]` clause,
273
     * or an empty string if the limit is zero.
274
     *
275
     * @param int $limit
276
     * @param int $offset
277
     * @return string
278
     */
279
    public static function limit (int $limit, int $offset = 0): string {
280
        if ($limit) {
281
            $limit = " LIMIT {$limit}";
282
            if ($offset) {
283
                $limit .= " OFFSET {$offset}";
284
            }
285
            return $limit;
286
        }
287
        return '';
288
    }
289
290
    /**
291
     * Returns an array of `?` placeholders.
292
     *
293
     * @param int|array|Countable $count
294
     * @return string[]
295
     */
296
    public static function marks ($count): array {
297
        if (is_array($count) or $count instanceof Countable) {
298
            $count = count($count);
299
        }
300
        return array_fill(0, $count, '?');
301
    }
302
303
    /**
304
     * `MAX($column)`
305
     *
306
     * @param string $column
307
     * @return ExpressionInterface
308
     */
309
    public static function max (string $column): ExpressionInterface {
310
        return new Expression("MAX({$column}");
311
    }
312
313
    /**
314
     * `MIN($column)`
315
     *
316
     * @param string $column
317
     * @return ExpressionInterface
318
     */
319
    public static function min (string $column): ExpressionInterface {
320
        return new Expression("MIN({$column})");
321
    }
322
323
    /**
324
     * `NOT($x)`
325
     *
326
     * @param string|array $x
327
     * @return string|array
328
     */
329
    public static function not ($x) {
330
        if (is_array($x)) {
331
            return array_map(__METHOD__, $x);
332
        }
333
        return "NOT({$x})";
334
    }
335
336
    /**
337
     * Returns an `ORDER BY` clause using the given order,
338
     * or an empty string if the order is empty.
339
     *
340
     * @param string $order
341
     * @return string
342
     */
343
    public static function order (string $order): string {
344
        if (strlen($order)) {
345
            return ' ORDER BY ' . $order;
346
        }
347
        return '';
348
    }
349
350
    /**
351
     * Returns a `SELECT` query string.
352
     *
353
     * @param string $table
354
     * @param array $columns String keys are used for aliasing.
355
     * @return string
356
     */
357
    public static function select (string $table, array $columns): string {
358
        $names = [];
359
        foreach ($columns as $alias => $name) {
360
            if (is_string($alias) and $alias !== $name) {
361
                $names[] = $name . ' AS ' . $alias;
362
            }
363
            else {
364
                $names[] = $name;
365
            }
366
        }
367
        return 'SELECT ' . implode(', ', $names) . ' FROM ' . $table;
368
    }
369
370
    /**
371
     * Converts an array of columns to `:named` placeholders for prepared queries.
372
     *
373
     * Qualified columns are slotted as `qualifier__column` (two underscores).
374
     *
375
     * @param string[] $columns
376
     * @return string[] `[column => :column]`
377
     */
378
    public static function slots (array $columns): array {
379
        $slots = [];
380
        foreach ($columns as $column) {
381
            $slots[(string)$column] = ':' . str_replace('.', '__', $column);
382
        }
383
        return $slots;
384
    }
385
386
    /**
387
     * Returns a `WHERE` clause for all conditions,
388
     * or an empty string if there are no conditions.
389
     *
390
     * @param array $conditions
391
     * @return string
392
     */
393
    public static function where (array $conditions): string {
394
        if ($conditions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
395
            return ' WHERE ' . static::all($conditions);
396
        }
397
        return '';
398
    }
399
400
    final private function __construct () { }
401
}