Completed
Push — master ( d1730c...d58317 )
by BENOIT
26:43
created

SelectQueryBuilder::andWhere()   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 $mainKeyword
12
 * @property $flags
13
 * @property $distinct
14
 * @property $columns
15
 * @property $from
16
 * @property $joins
17
 * @property $where
18
 * @property $groupBy
19
 * @property $having
20
 * @property $orderBy
21
 * @property $limit
22
 * @property $offset
23
 * @property $end
24
 */
25
final class SelectQueryBuilder
26
{
27
28
    /**
29
     * @var string
30
     */
31
    private $mainKeyword = 'SELECT';
32
33
    /**
34
     * @var array
35
     */
36
    private $flags = [];
37
38
    /**
39
     * @var bool
40
     */
41
    private $distinct = false;
42
43
    /**
44
     * @var array
45
     */
46
    private $columns = ['*'];
47
48
    /**
49
     * @var string
50
     */
51
    private $from;
52
53
    /**
54
     * @var array
55
     */
56
    private $joins = [];
57
58
    /**
59
     * @var Expression
60
     */
61
    private $where;
62
63
    /**
64
     * @var array
65
     */
66
    private $groupBy = [];
67
68
    /**
69
     * @var Expression
70
     */
71
    private $having;
72
73
    /**
74
     * @var array
75
     */
76
    private $orderBy = [];
77
78
    /**
79
     * @var int
80
     */
81
    private $limit;
82
83
    /**
84
     * @var int
85
     */
86
    private $offset;
87
88
    /**
89
     * @var string
90
     */
91
    private $end = ';';
92
93
    /**
94
     * @param Expression[]|string[] ...$columns
95
     * @return SelectQueryBuilder
96
     */
97
    public static function make(...$columns): self
98
    {
99
        $select = new self;
100
        if (0 !== func_num_args()) {
101
            $select->validateColumns(...$columns);
102
            $select->columns = $columns;
103
        }
104
        return $select;
105
    }
106
107
    /**
108
     * @param Expression[]|string[] ...$columns
109
     * @throws \InvalidArgumentException
110
     */
111
    private function validateColumns(...$columns)
112
    {
113
        foreach ($columns as $column) {
114
            if (!($column instanceof Expression || is_scalar($column))) {
115
                throw new \InvalidArgumentException(
116
                    sprintf(
117
                        "Expected string or Expression, got %s",
118
                        is_object($column) ? get_class($column) : gettype($column)
119
                    )
120
                );
121
            }
122
        }
123
    }
124
125
    /**
126
     * @param string $keyword
127
     * @return SelectQueryBuilder
128
     */
129
    public function withMainKeyword(string $keyword): self
130
    {
131
        $clone = clone $this;
132
        $clone->mainKeyword = $keyword;
133
        return $clone;
134
    }
135
136
    /**
137
     * @param Expression[]|string[] ...$columns
138
     * @return SelectQueryBuilder
139
     */
140
    public function withColumns(...$columns): self
141
    {
142
        $this->validateColumns(...$columns);
143
        $clone = clone $this;
144
        $clone->columns = $columns;
145
        return $clone;
146
    }
147
148
    /**
149
     * @param Expression[]|string[] ...$columns
150
     * @return SelectQueryBuilder
151
     */
152
    public function withAddedColumns(...$columns): self
153
    {
154
        $this->validateColumns(...$columns);
155
        $clone = clone $this;
156
        $clone->columns = array_merge($clone->columns, $columns);
157
        return $clone;
158
    }
159
160
    /**
161
     * @param string[] ...$flags
162
     * @return SelectQueryBuilder
163
     */
164
    public function withFlags(string ...$flags): self
165
    {
166
        $clone = clone $this;
167
        $clone->flags = $flags;
168
        return $clone;
169
    }
170
171
    /**
172
     * @param string[] ...$flags
173
     * @return SelectQueryBuilder
174
     */
175
    public function withAddedFlags(string ...$flags): self
176
    {
177
        $clone = clone $this;
178
        $existingFlags = array_map('strtoupper', $clone->flags);
179
        foreach ($flags as $flag) {
180
            if (!in_array(strtoupper($flag), $existingFlags, true)) {
181
                $clone->flags[] = $flag;
182
            }
183
        }
184
        return $clone;
185
    }
186
187
    /**
188
     * @param bool $distinct
189
     * @return SelectQueryBuilder
190
     */
191
    public function distinct(bool $distinct = true): self
192
    {
193
        $clone = clone $this;
194
        $clone->distinct = $distinct;
195
        return $clone;
196
    }
197
198
    /**
199
     * @param string $table
200
     * @return SelectQueryBuilder
201
     */
202
    public function from(string $table = null): self
203
    {
204
        $clone = clone $this;
205
        $clone->from = $table;
206
        return $clone;
207
    }
208
209
    /**
210
     * @param string                 $table
211
     * @param string|Expression|null $expression
212
     * @param array                  ...$values
213
     * @return SelectQueryBuilder
214
     * @throws \InvalidArgumentException
215
     */
216
    public function join(string $table, $expression = null, ...$values): self
217
    {
218
        $clone = clone $this;
219
        $clone->joins[$table] = [
220
            't' => 'JOIN',
221
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
222
        ];
223
        return $clone;
224
    }
225
226
    /**
227
     * @param string                 $table
228
     * @param string|Expression|null $expression
229
     * @param array                  ...$values
230
     * @return SelectQueryBuilder
231
     * @throws \InvalidArgumentException
232
     */
233
    public function innerJoin(string $table, $expression = null, ...$values): self
234
    {
235
        $clone = clone $this;
236
        $clone->joins[$table] = [
237
            't' => 'INNER JOIN',
238
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
239
        ];
240
        return $clone;
241
    }
242
243
    /**
244
     * @param string                 $table
245
     * @param string|Expression|null $expression
246
     * @param array                  ...$values
247
     * @return SelectQueryBuilder
248
     * @throws \InvalidArgumentException
249
     */
250
    public function outerJoin(string $table, $expression = null, ...$values): self
251
    {
252
        $clone = clone $this;
253
        $clone->joins[$table] = [
254
            't' => 'OUTER JOIN',
255
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
256
        ];
257
        return $clone;
258
    }
259
260
    /**
261
     * @param string                 $table
262
     * @param string|Expression|null $expression
263
     * @param array                  ...$values
264
     * @return SelectQueryBuilder
265
     * @throws \InvalidArgumentException
266
     */
267
    public function leftJoin(string $table, $expression = null, ...$values): self
268
    {
269
        $clone = clone $this;
270
        $clone->joins[$table] = [
271
            't' => 'LEFT JOIN',
272
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
273
        ];
274
        return $clone;
275
    }
276
277
    /**
278
     * @param string                 $table
279
     * @param string|Expression|null $expression
280
     * @param array                  ...$values
281
     * @return SelectQueryBuilder
282
     * @throws \InvalidArgumentException
283
     */
284
    public function leftOuterJoin(string $table, $expression = null, ...$values): self
285
    {
286
        $clone = clone $this;
287
        $clone->joins[$table] = [
288
            't' => 'LEFT OUTER JOIN',
289
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
290
        ];
291
        return $clone;
292
    }
293
294
    /**
295
     * @param string                 $table
296
     * @param string|Expression|null $expression
297
     * @param array                  ...$values
298
     * @return SelectQueryBuilder
299
     * @throws \InvalidArgumentException
300
     */
301
    public function rightJoin(string $table, $expression = null, ...$values): self
302
    {
303
        $clone = clone $this;
304
        $clone->joins[$table] = [
305
            't' => 'RIGHT JOIN',
306
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
307
        ];
308
        return $clone;
309
    }
310
311
    /**
312
     * @param string                 $table
313
     * @param string|Expression|null $expression
314
     * @param array                  ...$values
315
     * @return SelectQueryBuilder
316
     * @throws \InvalidArgumentException
317
     */
318
    public function rightOuterJoin(string $table, $expression = null, ...$values): self
319
    {
320
        $clone = clone $this;
321
        $clone->joins[$table] = [
322
            't' => 'RIGHT OUTER JOIN',
323
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
324
        ];
325
        return $clone;
326
    }
327
328
    /**
329
     * @param string                 $table
330
     * @param string|Expression|null $expression
331
     * @param array                  ...$values
332
     * @return SelectQueryBuilder
333
     * @throws \InvalidArgumentException
334
     */
335
    public function fullJoin(string $table, $expression = null, ...$values): self
336
    {
337
        $clone = clone $this;
338
        $clone->joins[$table] = [
339
            't' => 'FULL JOIN',
340
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
341
        ];
342
        return $clone;
343
    }
344
345
    /**
346
     * @param string                 $table
347
     * @param string|Expression|null $expression
348
     * @param array                  ...$values
349
     * @return SelectQueryBuilder
350
     * @throws \InvalidArgumentException
351
     */
352
    public function fullOuterJoin(string $table, $expression = null, ...$values): self
353
    {
354
        $clone = clone $this;
355
        $clone->joins[$table] = [
356
            't' => 'FULL OUTER JOIN',
357
            'c' => null !== $expression ? Expression::where($expression, ...$values) : null,
358
        ];
359
        return $clone;
360
    }
361
362
    /**
363
     * Reset all JOIN clauses.
364
     *
365
     * @return SelectQueryBuilder
366
     */
367
    public function resetJoins(): self
368
    {
369
        $clone = clone $this;
370
        $clone->joins = [];
371
        return $clone;
372
    }
373
374
    /**
375
     * Remove a specific JOIN clause.
376
     *
377
     * @param string $table
378
     * @return SelectQueryBuilder
379
     */
380
    public function withoutJoin(string $table)
381
    {
382
        $clone = clone $this;
383
        unset($clone->joins[$table]);
384
        return $clone;
385
    }
386
387
    /**
388
     * @param string|Expression|null $expression
389
     * @param array                  ...$values
390
     * @return SelectQueryBuilder
391
     * @throws \InvalidArgumentException
392
     */
393
    public function where($expression = null, ...$values): self
394
    {
395
        $clone = clone $this;
396
        $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...
397
        return $clone;
398
    }
399
400
    /**
401
     * @param string|Expression $expression
402
     * @param array             ...$values
403
     * @return SelectQueryBuilder
404
     * @throws \InvalidArgumentException
405
     */
406
    public function andWhere($expression, ...$values): self
407
    {
408
        if (null === $this->where) {
409
            return $this->where(Expression::where($expression, ...$values));
410
        }
411
        $clone = clone $this;
412
        $clone->where = $clone->where->and(Expression::where($expression, ...$values));
413
        return $clone;
414
    }
415
416
    /**
417
     * @param string|Expression $expression
418
     * @param array             ...$values
419
     * @return SelectQueryBuilder
420
     * @throws \InvalidArgumentException
421
     */
422
    public function orWhere($expression, ...$values): self
423
    {
424
        if (null === $this->where) {
425
            return $this->where(Expression::where($expression, ...$values));
426
        }
427
        $clone = clone $this;
428
        $clone->where = $clone->where->or(Expression::where($expression, ...$values));
429
        return $clone;
430
    }
431
432
    /**
433
     * @param string[] ...$groupBy
434
     * @return SelectQueryBuilder
435
     */
436
    public function groupBy(string ...$groupBy): self
437
    {
438
        $clone = clone $this;
439
        $clone->groupBy = $groupBy;
440
        return $clone;
441
    }
442
443
    /**
444
     * @param string[] ...$groupBy
445
     * @return SelectQueryBuilder
446
     */
447
    public function andGroupBy(string ...$groupBy): self
448
    {
449
        $clone = clone $this;
450
        $clone->groupBy = array_merge($clone->groupBy, $groupBy);
451
        return $clone;
452
    }
453
454
    /**
455
     * @param string|Expression|null $expression
456
     * @param array                  ...$values
457
     * @return SelectQueryBuilder
458
     * @throws \InvalidArgumentException
459
     */
460
    public function having($expression = null, ...$values): self
461
    {
462
        $clone = clone $this;
463
        $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...
464
        return $clone;
465
    }
466
467
    /**
468
     * @param string|Expression $expression
469
     * @param array             ...$values
470
     * @return SelectQueryBuilder
471
     * @throws \InvalidArgumentException
472
     */
473
    public function andHaving($expression, ...$values): self
474
    {
475
        if (null === $this->having) {
476
            return $this->having(Expression::where($expression, ...$values));
477
        }
478
        $clone = clone $this;
479
        $clone->having = $clone->having->and(Expression::where($expression, ...$values));
480
        return $clone;
481
    }
482
483
    /**
484
     * @param string|Expression $expression
485
     * @param array             ...$values
486
     * @return SelectQueryBuilder
487
     * @throws \InvalidArgumentException
488
     */
489
    public function orHaving($expression, ...$values): self
490
    {
491
        if (null === $this->having) {
492
            return $this->having(Expression::where($expression, ...$values));
493
        }
494
        $clone = clone $this;
495
        $clone->having = $clone->having->or(Expression::where($expression, ...$values));
496
        return $clone;
497
    }
498
499
    /**
500
     * @param string[] ...$groupBy
501
     * @return SelectQueryBuilder
502
     */
503
    public function orderBy(string ...$orderBy): self
504
    {
505
        $clone = clone $this;
506
        $clone->orderBy = $orderBy;
507
        return $clone;
508
    }
509
510
    /**
511
     * @param string[] ...$groupBy
512
     * @return SelectQueryBuilder
513
     */
514
    public function andOrderBy(string ...$orderBy): self
515
    {
516
        $clone = clone $this;
517
        $clone->orderBy = array_merge($clone->orderBy, $orderBy);
518
        return $clone;
519
    }
520
521
    /**
522
     * @param int|null $limit
523
     * @return SelectQueryBuilder
524
     */
525
    public function limit(int $limit = null): self
526
    {
527
        $clone = clone $this;
528
        $clone->limit = $limit;
529
        return $clone;
530
    }
531
532
    /**
533
     * @param int|null $offset
534
     * @return SelectQueryBuilder
535
     */
536
    public function offset(int $offset = null): self
537
    {
538
        $clone = clone $this;
539
        $clone->offset = $offset;
540
        return $clone;
541
    }
542
543
    /**
544
     * @param string|null $end
545
     * @return SelectQueryBuilder
546
     */
547
    public function end(string $end = null): self
548
    {
549
        $clone = clone $this;
550
        $clone->end = $end;
551
        return $clone;
552
    }
553
554
    /**
555
     * @return array
556
     */
557
    public function getValues(): array
558
    {
559
        $generator = function (Expression ...$expressions) {
560
            foreach ($expressions as $expression) {
561
                foreach ($expression->getValues() as $key => $value) {
562
                    if (is_numeric($key)) {
563
                        yield $value;
564
                    } else {
565
                        yield $key => $value;
566
                    }
567
                }
568
            }
569
        };
570
571
        $expressions = array_filter(array_merge($this->columns, array_column($this->joins, 'c'), [$this->where, $this->having]), function ($expression) {
572
            return $expression instanceof Expression;
573
        });
574
575
        return iterator_to_array($generator(...$expressions));
576
    }
577
578
    /**
579
     * @return string
580
     */
581
    public function __toString(): string
582
    {
583
        return SelectQueryStringifier::stringify($this);
584
    }
585
586
    /**
587
     * Read-only properties.
588
     *
589
     * @param $property
590
     * @return mixed
591
     * @throws \InvalidArgumentException
592
     */
593
    public function __get($property)
594
    {
595
        if (!property_exists($this, $property)) {
596
            throw new \InvalidArgumentException(sprintf('Property %s::$%s does not exist.', __CLASS__, $property));
597
        }
598
        return $this->{$property};
599
    }
600
}
601