Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
139 / 139
100.00% covered (success)
100.00%
38 / 38
CRAP
100.00% covered (success)
100.00%
1 / 1
PredicateSet
100.00% covered (success)
100.00%
139 / 139
100.00% covered (success)
100.00%
38 / 38
82
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 add
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
1 / 1
19
 addExpressions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 and
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 or
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 equalTo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notEqualTo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 greaterThan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 greaterThanOrEqualTo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lessThan
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lessThanOrEqualTo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 like
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notLike
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 between
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notBetween
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 in
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notIn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNull
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNotNull
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 andPredicate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 orPredicate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addPredicate
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 addPredicates
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addPredicateSet
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addPredicateSets
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 nest
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 andNest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 orNest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setConjunction
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getConjunction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setNextConjunction
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getNextConjunction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPredicates
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPredicates
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPredicateSets
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPredicateSets
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
9
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Db\Sql;
15
16/**
17 * Predicate set class
18 *
19 * @category   Pop
20 * @package    Pop\Db
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    6.5.0
25 */
26class PredicateSet
27{
28
29    /**
30     * SQL object
31     * @var ?AbstractSql
32     */
33    protected ?AbstractSql $sql = null;
34
35    /**
36     * Predicates
37     * @var array
38     */
39    protected array$predicates = [];
40
41    /**
42     * Nested predicate sets
43     * @var array
44     */
45    protected array $predicateSets = [];
46
47    /**
48     * Conjunction
49     * @var ?string
50     */
51    protected ?string $conjunction = null;
52
53    /**
54     * Next conjunction
55     * @var string
56     */
57    protected string $nextConjunction = 'AND';
58
59    /**
60     * Constructor
61     *
62     * Instantiate the predicate set object
63     *
64     * @param  AbstractSql $sql
65     * @param  mixed       $predicates
66     * @param  ?string     $conjunction
67     * @throws Exception
68     */
69    public function __construct(AbstractSql $sql, mixed $predicates = null, ?string $conjunction = null)
70    {
71        $this->sql = $sql;
72
73        if ($predicates !== null) {
74            if (is_array($predicates)) {
75                $this->addPredicates($predicates);
76            } else {
77                $this->addPredicate($predicates);
78            }
79        }
80
81        if ($conjunction !== null) {
82            $this->setConjunction($conjunction);
83        }
84    }
85
86    /**
87     * Add a predicate from a string expression
88     *
89     * @param  string $expression
90     * @return PredicateSet
91     */
92    public function add(string $expression): PredicateSet
93    {
94        ['column' => $column, 'operator' => $operator, 'value' => $value] = Parser\Expression::parse($expression);
95
96        if (is_array($value)) {
97            foreach ($value as $k => $v) {
98                if ($this->sql->isParameter($v, $column)) {
99                    $value[$k] = $this->sql->getParameter($v, $column);
100                }
101            }
102        } else {
103            if ($this->sql->isParameter($value, $column)) {
104                $value = $this->sql->getParameter($value, $column);
105            }
106        }
107
108        switch ($operator) {
109            case '=':
110                $this->equalTo($column, $value);
111                break;
112            case '!=':
113                $this->notEqualTo($column, $value);
114                break;
115            case '>':
116                $this->greaterThan($column, $value);
117                break;
118            case '>=':
119                $this->greaterThanOrEqualTo($column, $value);
120                break;
121            case '<=':
122                $this->lessThanOrEqualTo($column, $value);
123                break;
124            case '<':
125                $this->lessThan($column, $value);
126                break;
127            case 'LIKE':
128                $this->like($column, $value);
129                break;
130            case 'NOT LIKE':
131                $this->notLike($column, $value);
132                break;
133            case 'BETWEEN':
134                $this->between($column, $value[0], $value[1]);
135                break;
136            case 'NOT BETWEEN':
137                $this->notBetween($column, $value[0], $value[1]);
138                break;
139            case 'IN':
140                $this->in($column, $value);
141                break;
142            case 'NOT IN':
143                $this->notIn($column, $value);
144                break;
145            case 'IS NULL':
146                $this->isNull($column);
147                break;
148            case 'IS NOT NULL':
149                $this->isNotNull($column);
150                break;
151
152        }
153
154        return $this;
155    }
156
157    /**
158     * Add a predicates from string expressions
159     *
160     * @param  array $expressions
161     * @return PredicateSet
162     */
163    public function addExpressions(array $expressions): PredicateSet
164    {
165        foreach ($expressions as $expression) {
166            $this->add($expression);
167        }
168
169        return $this;
170    }
171
172    /**
173     * Add an AND predicate from a string expression
174     *
175     * @param  ?string $expression
176     * @throws Exception
177     * @return PredicateSet
178     */
179    public function and(?string $expression = null): PredicateSet
180    {
181        $this->setNextConjunction('AND');
182        if ($expression !== null) {
183            $this->add($expression);
184        }
185        return $this;
186    }
187
188    /**
189     * Add an OR predicate from a string expression
190     *
191     * @param  ?string $expression
192     * @throws Exception
193     * @return PredicateSet
194     */
195    public function or(?string $expression = null): PredicateSet
196    {
197        $this->setNextConjunction('OR');
198        if ($expression !== null) {
199            $this->add($expression);
200        }
201        return $this;
202    }
203
204    /**
205     * Predicate for =
206     *
207     * @param  string $column
208     * @param  string $value
209     * @return PredicateSet
210     */
211    public function equalTo(string $column, string $value): PredicateSet
212    {
213        return $this->addPredicate(new Predicate\EqualTo([$column, $value], $this->nextConjunction));
214    }
215
216    /**
217     * Predicate for !=
218     *
219     * @param  string $column
220     * @param  string $value
221     * @return PredicateSet
222     */
223    public function notEqualTo(string $column, string $value): PredicateSet
224    {
225        return $this->addPredicate(new Predicate\NotEqualTo([$column, $value], $this->nextConjunction));
226    }
227
228    /**
229     * Predicate for >
230     *
231     * @param  string $column
232     * @param  string $value
233     * @return PredicateSet
234     */
235    public function greaterThan(string $column, string $value): PredicateSet
236    {
237        return $this->addPredicate(new Predicate\GreaterThan([$column, $value], $this->nextConjunction));
238    }
239
240    /**
241     * Predicate for >=
242     *
243     * @param  string $column
244     * @param  string $value
245     * @return PredicateSet
246     */
247    public function greaterThanOrEqualTo(string $column, string $value): PredicateSet
248    {
249        return $this->addPredicate(new Predicate\GreaterThanOrEqualTo([$column, $value], $this->nextConjunction));
250    }
251
252    /**
253     * Predicate for <
254     *
255     * @param  string $column
256     * @param  string $value
257     * @return PredicateSet
258     */
259    public function lessThan(string $column, string $value): PredicateSet
260    {
261        return $this->addPredicate(new Predicate\LessThan([$column, $value], $this->nextConjunction));
262    }
263
264    /**
265     * Predicate for <=
266     *
267     * @param  string $column
268     * @param  string $value
269     * @return PredicateSet
270     */
271    public function lessThanOrEqualTo(string $column, string $value): PredicateSet
272    {
273        return $this->addPredicate(new Predicate\LessThanOrEqualTo([$column, $value], $this->nextConjunction));
274    }
275
276    /**
277     * Predicate for LIKE
278     *
279     * @param  string $column
280     * @param  string $value
281     * @return PredicateSet
282     */
283    public function like(string $column, string $value): PredicateSet
284    {
285        return $this->addPredicate(new Predicate\Like([$column, $value], $this->nextConjunction));
286    }
287
288    /**
289     * Predicate for NOT LIKE
290     *
291     * @param  string $column
292     * @param  string $value
293     * @return PredicateSet
294     */
295    public function notLike(string $column, string $value): PredicateSet
296    {
297        return $this->addPredicate(new Predicate\NotLike([$column, $value], $this->nextConjunction));
298    }
299
300    /**
301     * Predicate for BETWEEN
302     *
303     * @param  string $column
304     * @param  string $value1
305     * @param  string $value2
306     * @return PredicateSet
307     */
308    public function between(string $column, string $value1, string $value2): PredicateSet
309    {
310        return $this->addPredicate(new Predicate\Between([$column, $value1, $value2], $this->nextConjunction));
311    }
312
313    /**
314     * Predicate for NOT BETWEEN
315     *
316     * @param  string $column
317     * @param  string $value1
318     * @param  string $value2
319     * @return PredicateSet
320     */
321    public function notBetween(string $column, string $value1, string $value2): PredicateSet
322    {
323        return $this->addPredicate(new Predicate\NotBetween([$column, $value1, $value2], $this->nextConjunction));
324    }
325
326    /**
327     * Predicate for IN
328     *
329     * @param  string $column
330     * @param  mixed  $values
331     * @return PredicateSet
332     */
333    public function in(string $column, mixed $values): PredicateSet
334    {
335        return $this->addPredicate(new Predicate\In([$column, $values], $this->nextConjunction));
336    }
337
338    /**
339     * Predicate for NOT IN
340     *
341     * @param  string $column
342     * @param  mixed  $values
343     * @return PredicateSet
344     */
345    public function notIn(string $column, mixed $values): PredicateSet
346    {
347        return $this->addPredicate(new Predicate\NotIn([$column, $values], $this->nextConjunction));
348    }
349
350    /**
351     * Predicate for IS NULL
352     *
353     * @param  string $column
354     * @return PredicateSet
355     */
356    public function isNull(string $column): PredicateSet
357    {
358        return $this->addPredicate(new Predicate\IsNull($column, $this->nextConjunction));
359    }
360
361    /**
362     * Predicate for IS NOT NULL
363     *
364     * @param  string $column
365     * @return PredicateSet
366     */
367    public function isNotNull(string $column): PredicateSet
368    {
369        return $this->addPredicate(new Predicate\IsNotNull($column, $this->nextConjunction));
370    }
371
372    /**
373     * Add AND predicate
374     *
375     * @param  Predicate\AbstractPredicate $predicate
376     * @throws Predicate\Exception
377     * @return PredicateSet
378     */
379    public function andPredicate(Predicate\AbstractPredicate $predicate): PredicateSet
380    {
381        $predicate->setConjunction('AND');
382        return $this->addPredicate($predicate);
383    }
384
385    /**
386     * Add OR predicate
387     *
388     * @param  Predicate\AbstractPredicate $predicate
389     * @throws Predicate\Exception
390     * @return PredicateSet
391     */
392    public function orPredicate(Predicate\AbstractPredicate $predicate): PredicateSet
393    {
394        $predicate->setConjunction('OR');
395        return $this->addPredicate($predicate);
396    }
397
398    /**
399     * Add predicate
400     *
401     * @param  Predicate\AbstractPredicate $predicate
402     * @return PredicateSet
403     */
404    public function addPredicate(Predicate\AbstractPredicate $predicate): PredicateSet
405    {
406        $values = $predicate->getValues();
407
408        if (is_array($values)) {
409            $column = array_shift($values);
410
411            foreach ($values as $key => $value) {
412                if (is_array($value)) {
413                    foreach ($value as $k => $v) {
414                        if ($this->sql->isParameter($v, $column)) {
415                            $values[$key][$k] = $this->sql->getParameter($v, $column);
416                        }
417                    }
418                } else {
419                    if ($this->sql->isParameter($value, $column)) {
420                        $values[$key] = $this->sql->getParameter($value, $column);
421                    }
422                }
423            }
424
425            $predicate->setValues(array_merge([$column], $values));
426        }
427
428        $this->predicates[] = $predicate;
429        return $this;
430    }
431
432    /**
433     * Add predicates
434     *
435     * @param  array $predicates
436     * @return PredicateSet
437     */
438    public function addPredicates(array $predicates): PredicateSet
439    {
440        foreach ($predicates as $predicate) {
441            $this->addPredicate($predicate);
442        }
443
444        return $this;
445    }
446
447    /**
448     * Add predicate set
449     *
450     * @param  PredicateSet $predicateSet
451     * @return PredicateSet
452     */
453    public function addPredicateSet(PredicateSet $predicateSet): PredicateSet
454    {
455        $this->predicateSets[] = $predicateSet;
456        return $this;
457    }
458
459    /**
460     * Add predicate sets
461     *
462     * @param  array $predicateSets
463     * @return PredicateSet
464     */
465    public function addPredicateSets(array $predicateSets): PredicateSet
466    {
467        foreach ($predicateSets as $predicateSet) {
468            $this->addPredicateSet($predicateSet);
469        }
470
471        return $this;
472    }
473
474    /**
475     * Add a nested predicate set
476     *
477     * @param  string $conjunction
478     * @return PredicateSet
479     */
480    public function nest(string $conjunction = 'AND'): PredicateSet
481    {
482        $predicateSet = new self($this->sql, null, $conjunction);
483        $this->addPredicateSet($predicateSet);
484        return $predicateSet;
485    }
486
487    /**
488     * Add a nested predicate set with the AND conjunction
489     *
490     * @return PredicateSet
491     */
492    public function andNest(): PredicateSet
493    {
494        return $this->nest('AND');
495    }
496
497    /**
498     * Add a nested predicate set with the OR conjunction
499     *
500     * @return PredicateSet
501     */
502    public function orNest(): PredicateSet
503    {
504        return $this->nest('OR');
505    }
506
507    /**
508     * Get the conjunction
509     *
510     * @param  string $conjunction
511     * @throws Exception
512     * @return PredicateSet
513     */
514    public function setConjunction(string $conjunction): PredicateSet
515    {
516        if ((strtoupper($conjunction) != 'OR') && (strtoupper($conjunction) != 'AND')) {
517            throw new Exception("Error: The conjunction must be 'AND' or 'OR'. '" . $conjunction . "' is not allowed.");
518        }
519
520        $this->conjunction = $conjunction;
521
522        return $this;
523    }
524
525    /**
526     * Get the conjunction
527     *
528     * @return ?string
529     */
530    public function getConjunction(): ?string
531    {
532        return $this->conjunction;
533    }
534
535    /**
536     * Get the next conjunction
537     *
538     * @param  string $conjunction
539     * @throws Exception
540     * @return PredicateSet
541     */
542    public function setNextConjunction(string $conjunction): PredicateSet
543    {
544        if ((strtoupper($conjunction) != 'OR') && (strtoupper($conjunction) != 'AND')) {
545            throw new Exception("Error: The conjunction must be 'AND' or 'OR'. '" . $conjunction . "' is not allowed.");
546        }
547
548        $this->nextConjunction = $conjunction;
549
550        return $this;
551    }
552
553    /**
554     * Get the next conjunction
555     *
556     * @return string
557     */
558    public function getNextConjunction(): string
559    {
560        return $this->nextConjunction;
561    }
562
563    /**
564     * Has predicates
565     *
566     * @return bool
567     */
568    public function hasPredicates(): bool
569    {
570        return (count($this->predicates) > 0);
571    }
572
573    /**
574     * Get predicates
575     *
576     * @return array
577     */
578    public function getPredicates(): array
579    {
580        return $this->predicates;
581    }
582
583    /**
584     * Has predicates
585     *
586     * @return bool
587     */
588    public function hasPredicateSets(): bool
589    {
590        return (count($this->predicateSets) > 0);
591    }
592
593    /**
594     * Get predicates
595     *
596     * @return array
597     */
598    public function getPredicateSets(): array
599    {
600        return $this->predicateSets;
601    }
602
603    /**
604     * Predicate set render method
605     *
606     * @throws Exception
607     * @return string
608     */
609    public function render(): string
610    {
611        $predicateString = '';
612
613        foreach ($this->predicates as $i => $predicate) {
614            $predicateString .= ($i == 0) ?
615                $predicate->render($this->sql) : ' ' . $predicate->getConjunction() . ' ' . $predicate->render($this->sql);
616        }
617
618        foreach ($this->predicateSets as $i => $predicateSet) {
619            if (empty($predicateSet->getConjunction())) {
620                throw new Exception('Error: The combination conjunction was not set for this predicate set.');
621            }
622            $predicateString .= ' ' . $predicateSet->getConjunction() . ' ' . $predicateSet->render();
623        }
624
625        if (((count($this->predicateSets) > 0) && (count($this->predicates) > 0)) ||
626            (count($this->predicateSets) > 1) || (count($this->predicates) > 1)) {
627            return '(' . $predicateString . ')';
628        } else {
629            return $predicateString;
630        }
631    }
632
633    /**
634     * Return predicate set string
635     *
636     * @return string
637     */
638    public function __toString(): string
639    {
640        return $this->render();
641    }
642
643}