Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
139 / 139 |
|
100.00% |
38 / 38 |
CRAP | |
100.00% |
1 / 1 |
PredicateSet | |
100.00% |
139 / 139 |
|
100.00% |
38 / 38 |
82 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
add | |
100.00% |
50 / 50 |
|
100.00% |
1 / 1 |
19 | |||
addExpressions | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
and | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
or | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
equalTo | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
notEqualTo | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
greaterThan | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
greaterThanOrEqualTo | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
lessThan | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
lessThanOrEqualTo | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
like | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
notLike | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
between | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
notBetween | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
in | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
notIn | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isNull | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isNotNull | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
andPredicate | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
orPredicate | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addPredicate | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
7 | |||
addPredicates | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addPredicateSet | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addPredicateSets | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
nest | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
andNest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
orNest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setConjunction | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getConjunction | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setNextConjunction | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getNextConjunction | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasPredicates | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPredicates | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasPredicateSets | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPredicateSets | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
render | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
9 | |||
__toString | |
100.00% |
1 / 1 |
|
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 | */ |
14 | namespace 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 | */ |
26 | class 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 | } |