Completed
Push — master ( c4e1d5...d1730c )
by BENOIT
01:15
created

SelectQueryBuilder::end()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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