Completed
Push — master ( 906125...5fd544 )
by BENOIT
01:10
created

SelectQueryBuilder::orHaving()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace BenTools\Where\SelectQuery;
5
6
use BenTools\Where\Expression\Expression;
7
8
/**
9
 * Class SelectQuery
10
 *
11
 * @property $flags
12
 * @property $distinct
13
 * @property $columns
14
 * @property $from
15
 * @property $joins
16
 * @property $where
17
 * @property $groupBy
18
 * @property $having
19
 * @property $orderBy
20
 * @property $limit
21
 * @property $offset
22
 */
23
final class SelectQueryBuilder
24
{
25
    /**
26
     * @var array
27
     */
28
    private $flags = [];
29
30
    /**
31
     * @var bool
32
     */
33
    private $distinct = false;
34
35
    /**
36
     * @var array
37
     */
38
    private $columns = ['*'];
39
40
    /**
41
     * @var string
42
     */
43
    private $from;
44
45
    /**
46
     * @var array
47
     */
48
    private $joins = [];
49
50
    /**
51
     * @var Expression
52
     */
53
    private $where;
54
55
    /**
56
     * @var array
57
     */
58
    private $groupBy = [];
59
60
    /**
61
     * @var Expression
62
     */
63
    private $having;
64
65
    /**
66
     * @var array
67
     */
68
    private $orderBy = [];
69
70
    /**
71
     * @var int
72
     */
73
    private $limit;
74
75
    /**
76
     * @var int
77
     */
78
    private $offset;
79
80
    /**
81
     * @param array ...$columns
82
     * @return SelectQueryBuilder
83
     */
84
    public static function make(string ...$columns): self
85
    {
86
        $select = new self;
87
        if (0 !== func_num_args()) {
88
            $select->columns = $columns;
89
        }
90
        return $select;
91
    }
92
93
    /**
94
     * @param string[] ...$flags
95
     * @return SelectQueryBuilder
96
     */
97
    public function withFlags(string ...$flags): self
98
    {
99
        $clone = clone $this;
100
        $clone->flags = $flags;
101
        return $clone;
102
    }
103
104
    /**
105
     * @param string[] ...$flags
106
     * @return SelectQueryBuilder
107
     */
108
    public function withAddedFlags(string ...$flags): self
109
    {
110
        $clone = clone $this;
111
        $existingFlags = array_map('strtoupper', $clone->flags);
112
        foreach ($flags as $flag) {
113
            if (!in_array(strtoupper($flag), $existingFlags, true)) {
114
                $clone->flags[] = $flag;
115
            }
116
        }
117
        return $clone;
118
    }
119
120
    /**
121
     * @param bool $distinct
122
     * @return SelectQueryBuilder
123
     */
124
    public function distinct(bool $distinct = true): self
125
    {
126
        $clone = clone $this;
127
        $clone->distinct = $distinct;
128
        return $clone;
129
    }
130
131
    /**
132
     * @param string[] ...$columns
133
     * @return SelectQueryBuilder
134
     */
135
    public function withColumns(string ...$columns): self
136
    {
137
        $clone = clone $this;
138
        $clone->columns = $columns;
139
        return $clone;
140
    }
141
142
    /**
143
     * @param string[] ...$columns
144
     * @return SelectQueryBuilder
145
     */
146
    public function withAddedColumns(string ...$columns): self
147
    {
148
        $clone = clone $this;
149
        $clone->columns = array_merge($clone->columns, $columns);
150
        return $clone;
151
    }
152
153
    /**
154
     * @param string $table
155
     * @return SelectQueryBuilder
156
     */
157
    public function from(string $table = null): self
158
    {
159
        $clone = clone $this;
160
        $clone->from = $table;
161
        return $clone;
162
    }
163
164
    /**
165
     * @param string $table
166
     * @param string|Expression|null   $expression
167
     * @param array  ...$values
168
     * @return SelectQueryBuilder
169
     * @throws \InvalidArgumentException
170
     */
171
    public function join(string $table, $expression = null, ...$values): self
172
    {
173
        $clone = clone $this;
174
        $clone->joins[$table] = [
175
            't' => 'JOIN',
176
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
177
        ];
178
        return $clone;
179
    }
180
181
    /**
182
     * @param string $table
183
     * @param string|Expression|null   $expression
184
     * @param array  ...$values
185
     * @return SelectQueryBuilder
186
     * @throws \InvalidArgumentException
187
     */
188
    public function innerJoin(string $table, $expression = null, ...$values): self
189
    {
190
        $clone = clone $this;
191
        $clone->joins[$table] = [
192
            't' => 'INNER JOIN',
193
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
194
        ];
195
        return $clone;
196
    }
197
198
    /**
199
     * @param string $table
200
     * @param string|Expression|null   $expression
201
     * @param array  ...$values
202
     * @return SelectQueryBuilder
203
     * @throws \InvalidArgumentException
204
     */
205
    public function outerJoin(string $table, $expression = null, ...$values): self
206
    {
207
        $clone = clone $this;
208
        $clone->joins[$table] = [
209
            't' => 'OUTER JOIN',
210
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
211
        ];
212
        return $clone;
213
    }
214
215
    /**
216
     * @param string $table
217
     * @param string|Expression|null   $expression
218
     * @param array  ...$values
219
     * @return SelectQueryBuilder
220
     * @throws \InvalidArgumentException
221
     */
222
    public function leftJoin(string $table, $expression = null, ...$values): self
223
    {
224
        $clone = clone $this;
225
        $clone->joins[$table] = [
226
            't' => 'LEFT JOIN',
227
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
228
        ];
229
        return $clone;
230
    }
231
232
    /**
233
     * @param string $table
234
     * @param string|Expression|null   $expression
235
     * @param array  ...$values
236
     * @return SelectQueryBuilder
237
     * @throws \InvalidArgumentException
238
     */
239
    public function leftOuterJoin(string $table, $expression = null, ...$values): self
240
    {
241
        $clone = clone $this;
242
        $clone->joins[$table] = [
243
            't' => 'LEFT OUTER JOIN',
244
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
245
        ];
246
        return $clone;
247
    }
248
249
    /**
250
     * @param string $table
251
     * @param string|Expression|null   $expression
252
     * @param array  ...$values
253
     * @return SelectQueryBuilder
254
     * @throws \InvalidArgumentException
255
     */
256
    public function rightJoin(string $table, $expression = null, ...$values): self
257
    {
258
        $clone = clone $this;
259
        $clone->joins[$table] = [
260
            't' => 'RIGHT JOIN',
261
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
262
        ];
263
        return $clone;
264
    }
265
266
    /**
267
     * @param string $table
268
     * @param string|Expression|null   $expression
269
     * @param array  ...$values
270
     * @return SelectQueryBuilder
271
     * @throws \InvalidArgumentException
272
     */
273
    public function rightOuterJoin(string $table, $expression = null, ...$values): self
274
    {
275
        $clone = clone $this;
276
        $clone->joins[$table] = [
277
            't' => 'RIGHT OUTER JOIN',
278
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
279
        ];
280
        return $clone;
281
    }
282
283
    /**
284
     * @param string $table
285
     * @param string|Expression|null   $expression
286
     * @param array  ...$values
287
     * @return SelectQueryBuilder
288
     * @throws \InvalidArgumentException
289
     */
290
    public function fullJoin(string $table, $expression = null, ...$values): self
291
    {
292
        $clone = clone $this;
293
        $clone->joins[$table] = [
294
            't' => 'FULL JOIN',
295
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
296
        ];
297
        return $clone;
298
    }
299
300
    /**
301
     * @param string $table
302
     * @param string|Expression|null   $expression
303
     * @param array  ...$values
304
     * @return SelectQueryBuilder
305
     * @throws \InvalidArgumentException
306
     */
307
    public function fullOuterJoin(string $table, $expression = null, ...$values): self
308
    {
309
        $clone = clone $this;
310
        $clone->joins[$table] = [
311
            't' => 'FULL OUTER JOIN',
312
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
313
        ];
314
        return $clone;
315
    }
316
317
    /**
318
     * Reset all JOIN clauses.
319
     *
320
     * @return SelectQueryBuilder
321
     */
322
    public function resetJoins(): self
323
    {
324
        $clone = clone $this;
325
        $clone->joins = [];
326
        return $clone;
327
    }
328
329
    /**
330
     * Remove a specific JOIN clause.
331
     *
332
     * @param string $table
333
     * @return SelectQueryBuilder
334
     */
335
    public function withoutJoin(string $table)
336
    {
337
        $clone = clone $this;
338
        unset($clone->joins[$table]);
339
        return $clone;
340
    }
341
342
    /**
343
     * @param string|Expression|null  $expression
344
     * @param array ...$values
345
     * @return SelectQueryBuilder
346
     * @throws \InvalidArgumentException
347
     */
348
    public function where($expression = null, ...$values): self
349
    {
350
        $clone = clone $this;
351
        $clone->where = null !== $expression ? Expression::where($expression, ...$values) : null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null !== $expression ? \...ion, ...$values) : null can also be of type object<self>. However, the property $where is declared as type object<BenTools\Where\Expression\Expression>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
352
        return $clone;
353
    }
354
355
    /**
356
     * @param string|Expression $expression
357
     * @param array ...$values
358
     * @return SelectQueryBuilder
359
     * @throws \InvalidArgumentException
360
     */
361
    public function andWhere($expression, ...$values): self
362
    {
363
        if (null === $this->where) {
364
            return $this->where(Expression::where($expression, ...$values));
365
        }
366
        $clone = clone $this;
367
        $clone->where = $clone->where->and(Expression::where($expression, ...$values));
368
        return $clone;
369
    }
370
371
    /**
372
     * @param string|Expression $expression
373
     * @param array ...$values
374
     * @return SelectQueryBuilder
375
     * @throws \InvalidArgumentException
376
     */
377
    public function orWhere($expression, ...$values): self
378
    {
379
        if (null === $this->where) {
380
            return $this->where(Expression::where($expression, ...$values));
381
        }
382
        $clone = clone $this;
383
        $clone->where = $clone->where->or(Expression::where($expression, ...$values));
384
        return $clone;
385
    }
386
387
    /**
388
     * @param string[] ...$groupBy
389
     * @return SelectQueryBuilder
390
     */
391
    public function groupBy(string ...$groupBy): self
392
    {
393
        $clone = clone $this;
394
        $clone->groupBy = $groupBy;
395
        return $clone;
396
    }
397
398
    /**
399
     * @param string[] ...$groupBy
400
     * @return SelectQueryBuilder
401
     */
402
    public function andGroupBy(string ...$groupBy): self
403
    {
404
        $clone = clone $this;
405
        $clone->groupBy = array_merge($clone->groupBy, $groupBy);
406
        return $clone;
407
    }
408
409
    /**
410
     * @param string|Expression|null $expression
411
     * @param array ...$values
412
     * @return SelectQueryBuilder
413
     * @throws \InvalidArgumentException
414
     */
415
    public function having($expression = null, ...$values): self
416
    {
417
        $clone = clone $this;
418
        $clone->having = null !== $expression ? Expression::where($expression, ...$values): null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null !== $expression ? \...ion, ...$values) : null can also be of type object<self>. However, the property $having is declared as type object<BenTools\Where\Expression\Expression>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
419
        return $clone;
420
    }
421
422
    /**
423
     * @param string|Expression $expression
424
     * @param array ...$values
425
     * @return SelectQueryBuilder
426
     * @throws \InvalidArgumentException
427
     */
428
    public function andHaving($expression, ...$values): self
429
    {
430
        if (null === $this->having) {
431
            return $this->having(Expression::where($expression, ...$values));
432
        }
433
        $clone = clone $this;
434
        $clone->having = $clone->having->and(Expression::where($expression, ...$values));
435
        return $clone;
436
    }
437
438
    /**
439
     * @param string|Expression $expression
440
     * @param array ...$values
441
     * @return SelectQueryBuilder
442
     * @throws \InvalidArgumentException
443
     */
444
    public function orHaving($expression, ...$values): self
445
    {
446
        if (null === $this->having) {
447
            return $this->having(Expression::where($expression, ...$values));
448
        }
449
        $clone = clone $this;
450
        $clone->having = $clone->having->or(Expression::where($expression, ...$values));
451
        return $clone;
452
    }
453
454
    /**
455
     * @param string[] ...$groupBy
456
     * @return SelectQueryBuilder
457
     */
458
    public function orderBy(string ...$orderBy): self
459
    {
460
        $clone = clone $this;
461
        $clone->orderBy = $orderBy;
462
        return $clone;
463
    }
464
465
    /**
466
     * @param string[] ...$groupBy
467
     * @return SelectQueryBuilder
468
     */
469
    public function andOrderBy(string ...$orderBy): self
470
    {
471
        $clone = clone $this;
472
        $clone->orderBy = array_merge($clone->orderBy, $orderBy);
473
        return $clone;
474
    }
475
476
    /**
477
     * @param int|null $limit
478
     * @return SelectQueryBuilder
479
     */
480
    public function limit(int $limit = null): self
481
    {
482
        $clone = clone $this;
483
        $clone->limit = $limit;
484
        return $clone;
485
    }
486
487
    /**
488
     * @param int|null $offset
489
     * @return SelectQueryBuilder
490
     */
491
    public function offset(int $offset = null): self
492
    {
493
        $clone = clone $this;
494
        $clone->offset = $offset;
495
        return $clone;
496
    }
497
498
    /**
499
     * @return array
500
     */
501
    public function getValues(): array
502
    {
503
        $generator = function (Expression ...$expressions) {
504
            foreach ($expressions as $expression) {
505
                foreach ($expression->getValues() as $key => $value) {
506
                    if (is_numeric($key)) {
507
                        yield $value;
508
                    } else {
509
                        yield $key => $value;
510
                    }
511
                }
512
            }
513
        };
514
515
        $expressions = array_filter(array_merge(array_column($this->joins, 'c'), [$this->where, $this->having]));
516
        return iterator_to_array($generator(...$expressions));
517
    }
518
519
    /**
520
     * @return string
521
     */
522
    public function __toString(): string
523
    {
524
        return SelectQueryStringifier::stringify($this);
525
    }
526
527
    /**
528
     * Read-only properties.
529
     *
530
     * @param $property
531
     * @return mixed
532
     * @throws \InvalidArgumentException
533
     */
534
    public function __get($property)
535
    {
536
        if (!property_exists($this, $property)) {
537
            throw new \InvalidArgumentException(sprintf('Property %s::$%s does not exist.', __CLASS__, $property));
538
        }
539
        return $this->{$property};
540
    }
541
}
542