PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.1.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.1.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Database / Query / Builder.php

Builder.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 1.1.0, at vendor/wpfluent/framework/src/WPFluent/Database/Query/Builder.php

4,422 lines 121.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Framework\Database\Query;
4
5 use Closure;
6 use DateTimePeriod;
7 use LogicException;
8 use RuntimeException;
9 use DateTimeInterface;
10 use InvalidArgumentException;
11 use FluentCommunity\Framework\Support\Arr;
12 use FluentCommunity\Framework\Support\Str;
13 use FluentCommunity\Framework\Support\Helper;
14 use FluentCommunity\Framework\Support\MacroableTrait;
15 use FluentCommunity\Framework\Support\Collection;
16 use FluentCommunity\Framework\Pagination\Paginator;
17 use FluentCommunity\Framework\Support\ForwardsCalls;
18 use FluentCommunity\Framework\Support\LazyCollection;
19 use FluentCommunity\Framework\Database\Query\Expression;
20 use FluentCommunity\Framework\Database\Query\Grammars\Grammar;
21 use FluentCommunity\Framework\Database\Query\Processors\Processor;
22 use FluentCommunity\Framework\Database\Query\ConditionExpression;
23 use FluentCommunity\Framework\Support\ArrayableInterface;
24 use FluentCommunity\Framework\Database\ConnectionInterface;
25 use FluentCommunity\Framework\Database\Concerns\BuildsQueries;
26 use FluentCommunity\Framework\Database\Concerns\ExplainsQueries;
27 use FluentCommunity\Framework\Database\Orm\Relations\Relation;
28 use FluentCommunity\Framework\Database\Orm\Builder as OrmBuilder;
29
30 class Builder
31 {
32 use BuildsQueries, ExplainsQueries, ForwardsCalls, MacroableTrait {
33 __call as macroCall;
34 }
35
36 /**
37 * The database connection instance.
38 *
39 * @var \FluentCommunity\Framework\Database\ConnectionInterface
40 */
41 public $connection;
42
43 /**
44 * The database query grammar instance.
45 *
46 * @var \FluentCommunity\Framework\Database\Query\Grammars\Grammar
47 */
48 public $grammar;
49
50 /**
51 * The database query post processor instance.
52 *
53 * @var \FluentCommunity\Framework\Database\Query\Processors\Processor
54 */
55 public $processor;
56
57 /**
58 * The current query value bindings.
59 *
60 * @var array
61 */
62 public $bindings = [
63 'select' => [],
64 'from' => [],
65 'join' => [],
66 'where' => [],
67 'groupBy' => [],
68 'having' => [],
69 'order' => [],
70 'union' => [],
71 'unionOrder' => [],
72 ];
73
74 /**
75 * An aggregate function and column to be run.
76 *
77 * @var array
78 */
79 public $aggregate;
80
81 /**
82 * The columns that should be returned.
83 *
84 * @var array
85 */
86 public $columns;
87
88 /**
89 * Indicates if the query returns distinct results.
90 *
91 * Occasionally contains the columns that should be distinct.
92 *
93 * @var bool|array
94 */
95 public $distinct = false;
96
97 /**
98 * The table which the query is targeting.
99 *
100 * @var string
101 */
102 public $from;
103
104 /**
105 * The index hint for the query.
106 *
107 * @var \FluentCommunity\Framework\Database\Query\IndexHint
108 */
109 public $indexHint;
110
111 /**
112 * The table joins for the query.
113 *
114 * @var array
115 */
116 public $joins;
117
118 /**
119 * The where constraints for the query.
120 *
121 * @var array
122 */
123 public $wheres = [];
124
125 /**
126 * The groupings for the query.
127 *
128 * @var array
129 */
130 public $groups;
131
132 /**
133 * The having constraints for the query.
134 *
135 * @var array
136 */
137 public $havings;
138
139 /**
140 * The orderings for the query.
141 *
142 * @var array
143 */
144 public $orders;
145
146 /**
147 * The maximum number of records to return.
148 *
149 * @var int
150 */
151 public $limit;
152
153 /**
154 * The maximum number of records to return per group.
155 *
156 * @var array
157 */
158 public $groupLimit;
159
160 /**
161 * The number of records to skip.
162 *
163 * @var int
164 */
165 public $offset;
166
167 /**
168 * The query union statements.
169 *
170 * @var array
171 */
172 public $unions;
173
174 /**
175 * The maximum number of union records to return.
176 *
177 * @var int
178 */
179 public $unionLimit;
180
181 /**
182 * The number of union records to skip.
183 *
184 * @var int
185 */
186 public $unionOffset;
187
188 /**
189 * The orderings for the union query.
190 *
191 * @var array
192 */
193 public $unionOrders;
194
195 /**
196 * Indicates whether row locking is being used.
197 *
198 * @var string|bool
199 */
200 public $lock;
201
202 /**
203 * The callbacks that should be invoked before the query is executed.
204 *
205 * @var array
206 */
207 public $beforeQueryCallbacks = [];
208
209 /**
210 * The callbacks that should be invoked after retrieving data from the database.
211 *
212 * @var array
213 */
214 protected $afterQueryCallbacks = [];
215
216 /**
217 * All of the available clause operators.
218 *
219 * @var string[]
220 */
221 public $operators = [
222 '=', '<', '>', '<=', '>=', '<>', '!=', '<=>',
223 'like', 'like binary', 'not like', 'ilike',
224 '&', '|', '^', '<<', '>>', '&~', 'is', 'is not',
225 'rlike', 'not rlike', 'regexp', 'not regexp',
226 '~', '~*', '!~', '!~*', 'similar to',
227 'not similar to', 'not ilike', '~~*', '!~~*',
228 ];
229
230 /**
231 * All of the available bitwise operators.
232 *
233 * @var string[]
234 */
235 public $bitwiseOperators = [
236 '&', '|', '^', '<<', '>>', '&~',
237 ];
238
239 /**
240 * Whether to use write pdo for the select.
241 *
242 * @var bool
243 */
244 public $useWritePdo = false;
245
246 /**
247 * Allow dynamic property injection.
248 *
249 * @var array
250 */
251 protected $dynamicProperties = [];
252
253 /**
254 * Create a new query builder instance.
255 *
256 * @param \FluentCommunity\Framework\Database\ConnectionInterface $connection
257 * @param \FluentCommunity\Framework\Database\Query\Grammars\Grammar|null $grammar
258 * @param \FluentCommunity\Framework\Database\Query\Processors\Processor|null $processor
259 * @return void
260 */
261 public function __construct(
262 ConnectionInterface $connection,
263 Grammar $grammar = null,
264 Processor $processor = null
265 ) {
266 $this->connection = $connection;
267 $this->grammar = $grammar ?: $connection->getQueryGrammar();
268 $this->processor = $processor ?: $connection->getPostProcessor();
269 }
270
271 /**
272 * Set the columns to be selected.
273 *
274 * @param array|mixed $columns
275 * @return $this
276 */
277 public function select($columns = ['*'])
278 {
279 $this->columns = [];
280 $this->bindings['select'] = [];
281
282 $columns = is_array($columns) ? $columns : func_get_args();
283
284 foreach ($columns as $as => $column) {
285 if (is_string($as) && $this->isQueryable($column)) {
286 $this->selectSub($column, $as);
287 } else {
288 $this->columns[] = $column;
289 }
290 }
291
292 return $this;
293 }
294
295 /**
296 * Add a subselect expression to the query.
297 *
298 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
299 * @param string $as
300 * @return $this
301 *
302 * @throws \InvalidArgumentException
303 */
304 public function selectSub($query, $as)
305 {
306 [$query, $bindings] = $this->createSub($query);
307
308 return $this->selectRaw(
309 '('.$query.') as '.$this->grammar->wrap($as), $bindings
310 );
311 }
312
313 /**
314 * Add a new "raw" select expression to the query.
315 *
316 * @param string $expression
317 * @param array $bindings
318 * @return $this
319 */
320 public function selectRaw($expression, array $bindings = [])
321 {
322 $this->addSelect(new Expression($expression));
323
324 if ($bindings) {
325 $this->addBinding($bindings, 'select');
326 }
327
328 return $this;
329 }
330
331 /**
332 * Makes "from" fetch from a subquery.
333 *
334 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
335 * @param string $as
336 * @return $this
337 *
338 * @throws \InvalidArgumentException
339 */
340 public function fromSub($query, $as)
341 {
342 [$query, $bindings] = $this->createSub($query);
343
344 return $this->fromRaw('('.$query.') as '.$this->grammar->wrapTable($as), $bindings);
345 }
346
347 /**
348 * Add a raw from clause to the query.
349 *
350 * @param string $expression
351 * @param mixed $bindings
352 * @return $this
353 */
354 public function fromRaw($expression, $bindings = [])
355 {
356 $this->from = new Expression($expression);
357
358 $this->addBinding($bindings, 'from');
359
360 return $this;
361 }
362
363 /**
364 * Creates a subquery and parse it.
365 *
366 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
367 * @return array
368 */
369 protected function createSub($query)
370 {
371 // If the given query is a Closure, we will execute it while passing in a new
372 // query instance to the Closure. This will give the developer a chance to
373 // format and work with the query before we cast it to a raw SQL string.
374 if ($query instanceof Closure) {
375 $callback = $query;
376
377 $callback($query = $this->forSubQuery());
378 }
379
380 return $this->parseSub($query);
381 }
382
383 /**
384 * Parse the subquery into SQL and bindings.
385 *
386 * @param mixed $query
387 * @return array
388 *
389 * @throws \InvalidArgumentException
390 */
391 protected function parseSub($query)
392 {
393 if ($query instanceof self || $query instanceof OrmBuilder || $query instanceof Relation) {
394 $query = $this->prependDatabaseNameIfCrossDatabaseQuery($query);
395
396 return [$query->toSql(), $query->getBindings()];
397 } elseif (is_string($query)) {
398 return [$query, []];
399 } else {
400 throw new InvalidArgumentException(
401 'A subquery must be a query builder instance, a Closure, or a string.'
402 );
403 }
404 }
405
406 /**
407 * Prepend the database name if the given query is on another database.
408 *
409 * @param mixed $query
410 * @return mixed
411 */
412 protected function prependDatabaseNameIfCrossDatabaseQuery($query)
413 {
414 if ($query->getConnection()->getDatabaseName() !==
415 $this->getConnection()->getDatabaseName()) {
416 $databaseName = $query->getConnection()->getDatabaseName();
417
418 if (! str_starts_with($query->from, $databaseName) && ! str_contains($query->from, '.')) {
419 $query->from($databaseName.'.'.$query->from);
420 }
421 }
422
423 return $query;
424 }
425
426 /**
427 * Add a new select column to the query.
428 *
429 * @param array|mixed $column
430 * @return $this
431 */
432 public function addSelect($column)
433 {
434 $columns = is_array($column) ? $column : func_get_args();
435
436 foreach ($columns as $as => $column) {
437 if (is_string($as) && $this->isQueryable($column)) {
438 if (is_null($this->columns)) {
439 $this->select($this->from.'.*');
440 }
441
442 $this->selectSub($column, $as);
443 } else {
444 if (is_array($this->columns) && in_array($column, $this->columns, true)) {
445 continue;
446 }
447
448 $this->columns[] = $column;
449 }
450 }
451
452 return $this;
453 }
454
455 /**
456 * Force the query to only return distinct results.
457 *
458 * @param mixed ...$distinct
459 * @return $this
460 */
461 public function distinct()
462 {
463 $columns = func_get_args();
464
465 if (count($columns) > 0) {
466 $this->distinct = is_array($columns[0]) || is_bool($columns[0]) ? $columns[0] : $columns;
467 } else {
468 $this->distinct = true;
469 }
470
471 return $this;
472 }
473
474 /**
475 * Set the table which the query is targeting.
476 *
477 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $table
478 * @param string|null $as
479 * @return $this
480 */
481 public function from($table, $as = null)
482 {
483 if ($this->isQueryable($table)) {
484 return $this->fromSub($table, $as);
485 }
486
487 $this->from = $as ? "{$table} as {$as}" : $table;
488
489 return $this;
490 }
491
492 /**
493 * Add an index hint to suggest a query index.
494 *
495 * @param string $index
496 * @return $this
497 */
498 public function useIndex($index)
499 {
500 $this->indexHint = new IndexHint('hint', $index);
501
502 return $this;
503 }
504
505 /**
506 * Add an index hint to force a query index.
507 *
508 * @param string $index
509 * @return $this
510 */
511 public function forceIndex($index)
512 {
513 $this->indexHint = new IndexHint('force', $index);
514
515 return $this;
516 }
517
518 /**
519 * Add an index hint to ignore a query index.
520 *
521 * @param string $index
522 * @return $this
523 */
524 public function ignoreIndex($index)
525 {
526 $this->indexHint = new IndexHint('ignore', $index);
527
528 return $this;
529 }
530
531 /**
532 * Add a join clause to the query.
533 *
534 * @param string $table
535 * @param \Closure|string $first
536 * @param string|null $operator
537 * @param string|null $second
538 * @param string $type
539 * @param bool $where
540 * @return $this
541 */
542 public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
543 {
544 $join = $this->newJoinClause($this, $type, $table);
545
546 // If the first "column" of the join is really a Closure instance the developer
547 // is trying to build a join with a complex "on" clause containing more than
548 // one condition, so we'll add the join and call a Closure with the query.
549 if ($first instanceof Closure) {
550 $first($join);
551
552 $this->joins[] = $join;
553
554 $this->addBinding($join->getBindings(), 'join');
555 }
556
557 // If the column is simply a string, we can assume the join simply has a basic
558 // "on" clause with a single condition. So we will just build the join with
559 // this simple join clauses attached to it. There is not a join callback.
560 else {
561 $method = $where ? 'where' : 'on';
562
563 $this->joins[] = $join->$method($first, $operator, $second);
564
565 $this->addBinding($join->getBindings(), 'join');
566 }
567
568 return $this;
569 }
570
571 /**
572 * Add a "join where" clause to the query.
573 *
574 * @param string $table
575 * @param \Closure|string $first
576 * @param string $operator
577 * @param string $second
578 * @param string $type
579 * @return $this
580 */
581 public function joinWhere($table, $first, $operator, $second, $type = 'inner')
582 {
583 return $this->join($table, $first, $operator, $second, $type, true);
584 }
585
586 /**
587 * Add a subquery join clause to the query.
588 *
589 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
590 * @param string $as
591 * @param \Closure|string $first
592 * @param string|null $operator
593 * @param string|null $second
594 * @param string $type
595 * @param bool $where
596 * @return $this
597 *
598 * @throws \InvalidArgumentException
599 */
600 public function joinSub($query, $as, $first, $operator = null, $second = null, $type = 'inner', $where = false)
601 {
602 [$query, $bindings] = $this->createSub($query);
603
604 $expression = '('.$query.') as '.$this->grammar->wrapTable($as);
605
606 $this->addBinding($bindings, 'join');
607
608 return $this->join(new Expression($expression), $first, $operator, $second, $type, $where);
609 }
610
611 /**
612 * Add a lateral join clause to the query.
613 *
614 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
615 * @param string $as
616 * @param string $type
617 * @return $this
618 */
619 public function joinLateral($query, string $as, string $type = 'inner')
620 {
621 [$query, $bindings] = $this->createSub($query);
622
623 $expression = '('.$query.') as '.$this->grammar->wrapTable($as);
624
625 $this->addBinding($bindings, 'join');
626
627 $this->joins[] = $this->newJoinLateralClause($this, $type, new Expression($expression));
628
629 return $this;
630 }
631
632 /**
633 * Add a lateral left join to the query.
634 *
635 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
636 * @param string $as
637 * @return $this
638 */
639 public function leftJoinLateral($query, string $as)
640 {
641 return $this->joinLateral($query, $as, 'left');
642 }
643
644 /**
645 * Add a left join to the query.
646 *
647 * @param string $table
648 * @param \Closure|string $first
649 * @param string|null $operator
650 * @param string|null $second
651 * @return $this
652 */
653 public function leftJoin($table, $first, $operator = null, $second = null)
654 {
655 return $this->join($table, $first, $operator, $second, 'left');
656 }
657
658 /**
659 * Add a "join where" clause to the query.
660 *
661 * @param string $table
662 * @param \Closure|string $first
663 * @param string $operator
664 * @param string $second
665 * @return $this
666 */
667 public function leftJoinWhere($table, $first, $operator, $second)
668 {
669 return $this->joinWhere($table, $first, $operator, $second, 'left');
670 }
671
672 /**
673 * Add a subquery left join to the query.
674 *
675 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
676 * @param string $as
677 * @param \Closure|string $first
678 * @param string|null $operator
679 * @param string|null $second
680 * @return $this
681 */
682 public function leftJoinSub($query, $as, $first, $operator = null, $second = null)
683 {
684 return $this->joinSub($query, $as, $first, $operator, $second, 'left');
685 }
686
687 /**
688 * Add a right join to the query.
689 *
690 * @param string $table
691 * @param \Closure|string $first
692 * @param string|null $operator
693 * @param string|null $second
694 * @return $this
695 */
696 public function rightJoin($table, $first, $operator = null, $second = null)
697 {
698 return $this->join($table, $first, $operator, $second, 'right');
699 }
700
701 /**
702 * Add a "right join where" clause to the query.
703 *
704 * @param string $table
705 * @param \Closure|string $first
706 * @param string $operator
707 * @param string $second
708 * @return $this
709 */
710 public function rightJoinWhere($table, $first, $operator, $second)
711 {
712 return $this->joinWhere($table, $first, $operator, $second, 'right');
713 }
714
715 /**
716 * Add a subquery right join to the query.
717 *
718 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Orm\Builder|string $query
719 * @param string $as
720 * @param \Closure|string $first
721 * @param string|null $operator
722 * @param string|null $second
723 * @return $this
724 */
725 public function rightJoinSub($query, $as, $first, $operator = null, $second = null)
726 {
727 return $this->joinSub($query, $as, $first, $operator, $second, 'right');
728 }
729
730 /**
731 * Add a "cross join" clause to the query.
732 *
733 * @param string $table
734 * @param \Closure|string|null $first
735 * @param string|null $operator
736 * @param string|null $second
737 * @return $this
738 */
739 public function crossJoin($table, $first = null, $operator = null, $second = null)
740 {
741 if ($first) {
742 return $this->join($table, $first, $operator, $second, 'cross');
743 }
744
745 $this->joins[] = $this->newJoinClause($this, 'cross', $table);
746
747 return $this;
748 }
749
750 /**
751 * Add a subquery cross join to the query.
752 *
753 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
754 * @param string $as
755 * @return $this
756 */
757 public function crossJoinSub($query, $as)
758 {
759 [$query, $bindings] = $this->createSub($query);
760
761 $expression = '('.$query.') as '.$this->grammar->wrapTable($as);
762
763 $this->addBinding($bindings, 'join');
764
765 $this->joins[] = $this->newJoinClause($this, 'cross', new Expression($expression));
766
767 return $this;
768 }
769
770 /**
771 * Get a new join clause.
772 *
773 * @param \FluentCommunity\Framework\Database\Query\Builder $parentQuery
774 * @param string $type
775 * @param string $table
776 * @return \FluentCommunity\Framework\Database\Query\JoinClause
777 */
778 protected function newJoinClause(self $parentQuery, $type, $table)
779 {
780 return new JoinClause($parentQuery, $type, $table);
781 }
782
783 /**
784 * Get a new join lateral clause.
785 *
786 * @param \FluentCommunity\Framework\Database\Query\Builder $parentQuery
787 * @param string $type
788 * @param string $table
789 * @return \FluentCommunity\Framework\Database\Query\JoinLateralClause
790 */
791 protected function newJoinLateralClause(self $parentQuery, $type, $table)
792 {
793 return new JoinLateralClause($parentQuery, $type, $table);
794 }
795
796 /**
797 * Merge an array of where clauses and bindings.
798 *
799 * @param array $wheres
800 * @param array $bindings
801 * @return Builder
802 */
803 public function mergeWheres($wheres, $bindings)
804 {
805 $this->wheres = array_merge($this->wheres, (array) $wheres);
806
807 $this->bindings['where'] = array_values(
808 array_merge($this->bindings['where'], (array) $bindings)
809 );
810
811 return $this;
812 }
813
814 /**
815 * Add a basic where clause to the query.
816 *
817 * @param \Closure|string|array $column
818 * @param mixed $operator
819 * @param mixed $value
820 * @param string $boolean
821 * @return $this
822 */
823 public function where($column, $operator = null, $value = null, $boolean = 'and')
824 {
825 if ($column instanceof ConditionExpression) {
826 $type = 'Expression';
827
828 $this->wheres[] = compact('type', 'column', 'boolean');
829
830 return $this;
831 }
832
833 // If the column is an array, we will assume it is an array of key-value pairs
834 // and can add them each as a where clause. We will maintain the boolean we
835 // received when the method was called and pass it into the nested where.
836 if (is_array($column)) {
837 return $this->addArrayOfWheres($column, $boolean);
838 }
839
840 // Here we will make some assumptions about the operator. If only 2 values are
841 // passed to the method, we will assume that the operator is an equals sign
842 // and keep going. Otherwise, we'll require the operator to be passed in.
843 [$value, $operator] = $this->prepareValueAndOperator(
844 $value, $operator, func_num_args() === 2
845 );
846
847 // If the column is actually a Closure instance, we will assume the developer
848 // wants to begin a nested where statement which is wrapped in parentheses.
849 // We will add that Closure to the query and return back out immediately.
850 if ($column instanceof Closure && is_null($operator)) {
851 return $this->whereNested($column, $boolean);
852 }
853
854 // If the column is a Closure instance and there is an operator value, we will
855 // assume the developer wants to run a subquery and then compare the result
856 // of that subquery with the given value that was provided to the method.
857 if ($this->isQueryable($column) && ! is_null($operator)) {
858 [$sub, $bindings] = $this->createSub($column);
859
860 return $this->addBinding($bindings, 'where')
861 ->where(new Expression('('.$sub.')'), $operator, $value, $boolean);
862 }
863
864 // If the given operator is not found in the list of valid operators we will
865 // assume that the developer is just short-cutting the '=' operators and
866 // we will set the operators to '=' and set the values appropriately.
867 if ($this->invalidOperator($operator)) {
868 [$value, $operator] = [$operator, '='];
869 }
870
871 // If the value is a Closure, it means the developer is performing an entire
872 // sub-select within the query and we will need to compile the sub-select
873 // within the where clause to get the appropriate query record results.
874 if ($this->isQueryable($value)) {
875 return $this->whereSub($column, $operator, $value, $boolean);
876 }
877
878 // If the value is "null", we will just assume the developer wants to add a
879 // where null clause to the query. So, we will allow a short-cut here to
880 // that method for convenience so the developer doesn't have to check.
881 if (is_null($value)) {
882 return $this->whereNull($column, $boolean, $operator !== '=');
883 }
884
885 $type = 'Basic';
886
887 $columnString = ($column instanceof Expression)
888 ? $this->grammar->getValue($column)
889 : $column;
890
891 // If the column is making a JSON reference we'll check to see if the value
892 // is a boolean. If it is, we'll add the raw boolean string as an actual
893 // value to the query to ensure this is properly handled by the query.
894 if (str_contains($columnString, '->') && is_bool($value)) {
895 $value = new Expression($value ? 'true' : 'false');
896
897 if (is_string($column)) {
898 $type = 'JsonBoolean';
899 }
900 }
901
902 if ($this->isBitwiseOperator($operator)) {
903 $type = 'Bitwise';
904 }
905
906 // Now that we are working with just a simple query we can put the elements
907 // in our array and add the query binding to our array of bindings that
908 // will be bound to each SQL statements when it is finally executed.
909 $this->wheres[] = compact(
910 'type', 'column', 'operator', 'value', 'boolean'
911 );
912
913 if (! $value instanceof Expression) {
914 $this->addBinding($this->flattenValue($value), 'where');
915 }
916
917 return $this;
918 }
919
920 /**
921 * Add an array of where clauses to the query.
922 *
923 * @param array $column
924 * @param string $boolean
925 * @param string $method
926 * @return $this
927 */
928 protected function addArrayOfWheres($column, $boolean, $method = 'where')
929 {
930 return $this->whereNested(function ($query) use ($column, $method, $boolean) {
931 foreach ($column as $key => $value) {
932 if (is_numeric($key) && is_array($value)) {
933 $query->{$method}(...array_values($value));
934 } else {
935 $query->{$method}($key, '=', $value, $boolean);
936 }
937 }
938 }, $boolean);
939 }
940
941 /**
942 * Prepare the value and operator for a where clause.
943 *
944 * @param string $value
945 * @param string $operator
946 * @param bool $useDefault
947 * @return array
948 *
949 * @throws \InvalidArgumentException
950 */
951 public function prepareValueAndOperator($value, $operator, $useDefault = false)
952 {
953 if ($useDefault) {
954 return [$operator, '='];
955 }
956
957 if ($this->invalidOperatorAndValue($operator, $value)) {
958 throw new InvalidArgumentException(
959 'Illegal operator and value combination.'
960 );
961 }
962
963 return [$value, $operator];
964 }
965
966 /**
967 * Determine if the given operator and value combination is legal.
968 *
969 * Prevents using Null values with invalid operators.
970 *
971 * @param string $operator
972 * @param mixed $value
973 * @return bool
974 */
975 protected function invalidOperatorAndValue($operator, $value)
976 {
977 return is_null($value) && in_array($operator, $this->operators) &&
978 ! in_array($operator, ['=', '<>', '!=']);
979 }
980
981 /**
982 * Determine if the given operator is supported.
983 *
984 * @param string $operator
985 * @return bool
986 */
987 protected function invalidOperator($operator)
988 {
989 return ! is_string($operator) || (! in_array(strtolower($operator), $this->operators, true) &&
990 ! in_array(strtolower($operator), $this->grammar->getOperators(), true));
991 }
992
993 /**
994 * Determine if the operator is a bitwise operator.
995 *
996 * @param string $operator
997 * @return bool
998 */
999 protected function isBitwiseOperator($operator)
1000 {
1001 return in_array(strtolower($operator), $this->bitwiseOperators, true) ||
1002 in_array(strtolower($operator), $this->grammar->getBitwiseOperators(), true);
1003 }
1004
1005 /**
1006 * Add an "or where" clause to the query.
1007 *
1008 * @param \Closure|string|array $column
1009 * @param mixed $operator
1010 * @param mixed $value
1011 * @return $this
1012 */
1013 public function orWhere($column, $operator = null, $value = null)
1014 {
1015 [$value, $operator] = $this->prepareValueAndOperator(
1016 $value, $operator, func_num_args() === 2
1017 );
1018
1019 return $this->where($column, $operator, $value, 'or');
1020 }
1021
1022 /**
1023 * Add a basic "where not" clause to the query.
1024 *
1025 * @param \Closure|string|array|\FluentCommunity\Framework\Database\Query\Expression $column
1026 * @param mixed $operator
1027 * @param mixed $value
1028 * @param string $boolean
1029 * @return $this
1030 */
1031 public function whereNot(
1032 $column,
1033 $operator = null,
1034 $value = null,
1035 $boolean = 'and'
1036 ) {
1037 if (is_array($column)) {
1038 return $this->whereNested(function ($query) use ($column, $operator, $value, $boolean) {
1039 $query->where($column, $operator, $value, $boolean);
1040 }, $boolean.' not');
1041 }
1042
1043 return $this->where($column, $operator, $value, $boolean.' not');
1044 }
1045
1046 /**
1047 * Add an "or where not" clause to the query.
1048 *
1049 * @param \Closure|string|array|\FluentCommunity\Framework\Database\Query\Expression $column
1050 * @param mixed $operator
1051 * @param mixed $value
1052 * @return $this
1053 */
1054 public function orWhereNot($column, $operator = null, $value = null)
1055 {
1056 return $this->whereNot($column, $operator, $value, 'or');
1057 }
1058
1059 /**
1060 * Add a "where" clause comparing two columns to the query.
1061 *
1062 * @param string|array $first
1063 * @param string|null $operator
1064 * @param string|null $second
1065 * @param string|null $boolean
1066 * @return $this
1067 */
1068 public function whereColumn($first, $operator = null, $second = null, $boolean = 'and')
1069 {
1070 // If the column is an array, we will assume it is an array of key-value pairs
1071 // and can add them each as a where clause. We will maintain the boolean we
1072 // received when the method was called and pass it into the nested where.
1073 if (is_array($first)) {
1074 return $this->addArrayOfWheres($first, $boolean, 'whereColumn');
1075 }
1076
1077 // If the given operator is not found in the list of valid operators we will
1078 // assume that the developer is just short-cutting the '=' operators and
1079 // we will set the operators to '=' and set the values appropriately.
1080 if ($this->invalidOperator($operator)) {
1081 [$second, $operator] = [$operator, '='];
1082 }
1083
1084 // Finally, we will add this where clause into this array of clauses that we
1085 // are building for the query. All of them will be compiled via a grammar
1086 // once the query is about to be executed and run against the database.
1087 $type = 'Column';
1088
1089 $this->wheres[] = compact(
1090 'type', 'first', 'operator', 'second', 'boolean'
1091 );
1092
1093 return $this;
1094 }
1095
1096 /**
1097 * Add an "or where" clause comparing two columns to the query.
1098 *
1099 * @param string|array $first
1100 * @param string|null $operator
1101 * @param string|null $second
1102 * @return $this
1103 */
1104 public function orWhereColumn($first, $operator = null, $second = null)
1105 {
1106 return $this->whereColumn($first, $operator, $second, 'or');
1107 }
1108
1109 /**
1110 * Add a raw where clause to the query.
1111 *
1112 * @param string $sql
1113 * @param mixed $bindings
1114 * @param string $boolean
1115 * @return $this
1116 */
1117 public function whereRaw($sql, $bindings = [], $boolean = 'and')
1118 {
1119 $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean];
1120
1121 $this->addBinding((array) $bindings, 'where');
1122
1123 return $this;
1124 }
1125
1126 /**
1127 * Add a raw or where clause to the query.
1128 *
1129 * @param string $sql
1130 * @param mixed $bindings
1131 * @return $this
1132 */
1133 public function orWhereRaw($sql, $bindings = [])
1134 {
1135 return $this->whereRaw($sql, $bindings, 'or');
1136 }
1137
1138 /**
1139 * Add a "where like" clause to the query.
1140 *
1141 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1142 * @param string $value
1143 * @param bool $caseSensitive
1144 * @param string $boolean
1145 * @param bool $not
1146 * @return $this
1147 */
1148 public function whereLike(
1149 $column,
1150 $value,
1151 $caseSensitive = false,
1152 $boolean = 'and',
1153 $not = false
1154 ) {
1155 $type = 'Like';
1156
1157 $this->wheres[] = compact('type', 'column', 'value', 'caseSensitive', 'boolean', 'not');
1158
1159 if (method_exists($this->grammar, 'prepareWhereLikeBinding')) {
1160 $value = $this->grammar->prepareWhereLikeBinding($value, $caseSensitive);
1161 }
1162
1163 $this->addBinding($value);
1164
1165 return $this;
1166 }
1167
1168 /**
1169 * Add an "or where like" clause to the query.
1170 *
1171 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1172 * @param string $value
1173 * @param bool $caseSensitive
1174 * @return $this
1175 */
1176 public function orWhereLike($column, $value, $caseSensitive = false)
1177 {
1178 return $this->whereLike($column, $value, $caseSensitive, 'or', false);
1179 }
1180
1181 /**
1182 * Add a "where not like" clause to the query.
1183 *
1184 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1185 * @param string $value
1186 * @param bool $caseSensitive
1187 * @param string $boolean
1188 * @return $this
1189 */
1190 public function whereNotLike(
1191 $column,
1192 $value,
1193 $caseSensitive = false,
1194 $boolean = 'and'
1195 ) {
1196 return $this->whereLike($column, $value, $caseSensitive, $boolean, true);
1197 }
1198
1199 /**
1200 * Add an "or where not like" clause to the query.
1201 *
1202 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1203 * @param string $value
1204 * @param bool $caseSensitive
1205 * @return $this
1206 */
1207 public function orWhereNotLike($column, $value, $caseSensitive = false)
1208 {
1209 return $this->whereNotLike($column, $value, $caseSensitive, 'or');
1210 }
1211
1212 /**
1213 * Add a "where in" clause to the query.
1214 *
1215 * @param string $column
1216 * @param mixed $values
1217 * @param string $boolean
1218 * @param bool $not
1219 * @return $this
1220 */
1221 public function whereIn($column, $values, $boolean = 'and', $not = false)
1222 {
1223 $type = $not ? 'NotIn' : 'In';
1224
1225 // If the value is a query builder instance we will assume the developer wants to
1226 // look for any values that exists within this given query. So we will add the
1227 // query accordingly so that this query is properly executed when it is run.
1228 if ($this->isQueryable($values)) {
1229 [$query, $bindings] = $this->createSub($values);
1230
1231 $values = [new Expression($query)];
1232
1233 $this->addBinding($bindings, 'where');
1234 }
1235
1236 // Next, if the value is ArrayableInterface we need to cast it to its raw array form so we
1237 // have the underlying array value instead of an Arrayable object which is not
1238 // able to be added as a binding, etc. We will then add to the wheres array.
1239 if ($values instanceof ArrayableInterface) {
1240 $values = $values->toArray();
1241 }
1242
1243 $this->wheres[] = compact('type', 'column', 'values', 'boolean');
1244
1245 if (count($values) !== count(Arr::flatten($values, 1))) {
1246 throw new InvalidArgumentException('Nested arrays may not be passed to whereIn method.');
1247 }
1248
1249 // Finally, we'll add a binding for each value unless that value is an
1250 // expression in which case we will just skip over it since it will
1251 // be the query as a raw string and not as a parameterized
1252 // place-holder to be replaced by the PDO.
1253 $this->addBinding($this->cleanBindings($values), 'where');
1254
1255 return $this;
1256 }
1257
1258 /**
1259 * Add an "or where in" clause to the query.
1260 *
1261 * @param string $column
1262 * @param mixed $values
1263 * @return $this
1264 */
1265 public function orWhereIn($column, $values)
1266 {
1267 return $this->whereIn($column, $values, 'or');
1268 }
1269
1270 /**
1271 * Add a "where not in" clause to the query.
1272 *
1273 * @param string $column
1274 * @param mixed $values
1275 * @param string $boolean
1276 * @return $this
1277 */
1278 public function whereNotIn($column, $values, $boolean = 'and')
1279 {
1280 return $this->whereIn($column, $values, $boolean, true);
1281 }
1282
1283 /**
1284 * Add an "or where not in" clause to the query.
1285 *
1286 * @param string $column
1287 * @param mixed $values
1288 * @return $this
1289 */
1290 public function orWhereNotIn($column, $values)
1291 {
1292 return $this->whereNotIn($column, $values, 'or');
1293 }
1294
1295 /**
1296 * Add a "where in raw" clause for integer values to the query.
1297 *
1298 * @param string $column
1299 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1300 * @param string $boolean
1301 * @param bool $not
1302 * @return $this
1303 */
1304 public function whereIntegerInRaw($column, $values, $boolean = 'and', $not = false)
1305 {
1306 $type = $not ? 'NotInRaw' : 'InRaw';
1307
1308 if ($values instanceof ArrayableInterface) {
1309 $values = $values->toArray();
1310 }
1311
1312 $values = Arr::flatten($values);
1313
1314 foreach ($values as &$value) {
1315 $value = (int) ($value instanceof BackedEnum ? $value->value : $value);
1316 }
1317
1318 $this->wheres[] = compact('type', 'column', 'values', 'boolean');
1319
1320 return $this;
1321 }
1322
1323 /**
1324 * Add an "or where in raw" clause for integer values to the query.
1325 *
1326 * @param string $column
1327 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1328 * @return $this
1329 */
1330 public function orWhereIntegerInRaw($column, $values)
1331 {
1332 return $this->whereIntegerInRaw($column, $values, 'or');
1333 }
1334
1335 /**
1336 * Add a "where not in raw" clause for integer values to the query.
1337 *
1338 * @param string $column
1339 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1340 * @param string $boolean
1341 * @return $this
1342 */
1343 public function whereIntegerNotInRaw($column, $values, $boolean = 'and')
1344 {
1345 return $this->whereIntegerInRaw($column, $values, $boolean, true);
1346 }
1347
1348 /**
1349 * Add an "or where not in raw" clause for integer values to the query.
1350 *
1351 * @param string $column
1352 * @param \FluentCommunity\Framework\Support\ArrayableInterface|array $values
1353 * @return $this
1354 */
1355 public function orWhereIntegerNotInRaw($column, $values)
1356 {
1357 return $this->whereIntegerNotInRaw($column, $values, 'or');
1358 }
1359
1360 /**
1361 * Add a "where null" clause to the query.
1362 *
1363 * @param string|array $columns
1364 * @param string $boolean
1365 * @param bool $not
1366 * @return $this
1367 */
1368 public function whereNull($columns, $boolean = 'and', $not = false)
1369 {
1370 $type = $not ? 'NotNull' : 'Null';
1371
1372 foreach (Arr::wrap($columns) as $column) {
1373 $this->wheres[] = compact('type', 'column', 'boolean');
1374 }
1375
1376 return $this;
1377 }
1378
1379 /**
1380 * Add an "or where null" clause to the query.
1381 *
1382 * @param string|array $column
1383 * @return $this
1384 */
1385 public function orWhereNull($column)
1386 {
1387 return $this->whereNull($column, 'or');
1388 }
1389
1390 /**
1391 * Add a "where not null" clause to the query.
1392 *
1393 * @param string|array $columns
1394 * @param string $boolean
1395 * @return $this
1396 */
1397 public function whereNotNull($columns, $boolean = 'and')
1398 {
1399 return $this->whereNull($columns, $boolean, true);
1400 }
1401
1402 /**
1403 * Add a where between statement to the query.
1404 *
1405 * @param string|\FluentCommunity\Framework\Database\Query\Expression $column
1406 * @param array $values
1407 * @param string $boolean
1408 * @param bool $not
1409 * @return $this
1410 */
1411 public function whereBetween($column, array $values, $boolean = 'and', $not = false)
1412 {
1413 $type = 'between';
1414
1415 $type = 'between';
1416
1417 if ($values instanceof DateTimePeriod) {
1418 $values = [$values->getStartDate(), $values->getEndDate()];
1419 }
1420
1421 $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not');
1422
1423 $this->addBinding(
1424 array_slice(
1425 $this->cleanBindings(Arr::flatten($values)), 0, 2
1426 ), 'where'
1427 );
1428
1429 return $this;
1430 }
1431
1432 /**
1433 * Add a where between statement using columns to the query.
1434 *
1435 * @param string $column
1436 * @param array $values
1437 * @param string $boolean
1438 * @param bool $not
1439 * @return $this
1440 */
1441 public function whereBetweenColumns(
1442 $column,
1443 array $values,
1444 $boolean = 'and',
1445 $not = false
1446 ) {
1447 $type = 'betweenColumns';
1448
1449 $this->wheres[] = compact('type', 'column', 'values', 'boolean', 'not');
1450
1451 return $this;
1452 }
1453
1454 /**
1455 * Add an or where between statement to the query.
1456 *
1457 * @param string $column
1458 * @param array $values
1459 * @return $this
1460 */
1461 public function orWhereBetween($column, array $values)
1462 {
1463 return $this->whereBetween($column, $values, 'or');
1464 }
1465
1466 /**
1467 * Add an or where between statement using columns to the query.
1468 *
1469 * @param string $column
1470 * @param array $values
1471 * @return $this
1472 */
1473 public function orWhereBetweenColumns($column, array $values)
1474 {
1475 return $this->whereBetweenColumns($column, $values, 'or');
1476 }
1477
1478 /**
1479 * Add a where not between statement to the query.
1480 *
1481 * @param string $column
1482 * @param array $values
1483 * @param string $boolean
1484 * @return $this
1485 */
1486 public function whereNotBetween($column, iterable $values, $boolean = 'and')
1487 {
1488 return $this->whereBetween($column, $values, $boolean, true);
1489 }
1490
1491 /**
1492 * Add a where not between statement using columns to the query.
1493 *
1494 * @param string $column
1495 * @param array $values
1496 * @param string $boolean
1497 * @return $this
1498 */
1499 public function whereNotBetweenColumns($column, array $values, $boolean = 'and')
1500 {
1501 return $this->whereBetweenColumns($column, $values, $boolean, true);
1502 }
1503
1504 /**
1505 * Add an or where not between statement to the query.
1506 *
1507 * @param string $column
1508 * @param array $values
1509 * @return $this
1510 */
1511 public function orWhereNotBetween($column, iterable $values)
1512 {
1513 return $this->whereNotBetween($column, $values, 'or');
1514 }
1515
1516 /**
1517 * Add an or where not between statement using columns to the query.
1518 *
1519 * @param string $column
1520 * @param array $values
1521 * @return $this
1522 */
1523 public function orWhereNotBetweenColumns($column, array $values)
1524 {
1525 return $this->whereNotBetweenColumns($column, $values, 'or');
1526 }
1527
1528 /**
1529 * Add an "or where not null" clause to the query.
1530 *
1531 * @param string $column
1532 * @return $this
1533 */
1534 public function orWhereNotNull($column)
1535 {
1536 return $this->whereNotNull($column, 'or');
1537 }
1538
1539 /**
1540 * Add a "where date" statement to the query.
1541 *
1542 * @param \FluentCommunity\Framework\Database\Query\Expression|string $column
1543 * @param string $operator
1544 * @param \DateTimeInterface|string|null $value
1545 * @param string $boolean
1546 * @return $this
1547 */
1548 public function whereDate($column, $operator, $value = null, $boolean = 'and')
1549 {
1550 [$value, $operator] = $this->prepareValueAndOperator(
1551 $value, $operator, func_num_args() === 2
1552 );
1553
1554 $value = $this->flattenValue($value);
1555
1556 if ($value instanceof DateTimeInterface) {
1557 $value = $value->format('Y-m-d');
1558 }
1559
1560 return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean);
1561 }
1562
1563 /**
1564 * Add an "or where date" statement to the query.
1565 *
1566 * @param string $column
1567 * @param string $operator
1568 * @param \DateTimeInterface|string|null $value
1569 * @return $this
1570 */
1571 public function orWhereDate($column, $operator, $value = null)
1572 {
1573 [$value, $operator] = $this->prepareValueAndOperator(
1574 $value, $operator, func_num_args() === 2
1575 );
1576
1577 return $this->whereDate($column, $operator, $value, 'or');
1578 }
1579
1580 /**
1581 * Add a "where time" statement to the query.
1582 *
1583 * @param string $column
1584 * @param string $operator
1585 * @param \DateTimeInterface|string|null $value
1586 * @param string $boolean
1587 * @return $this
1588 */
1589 public function whereTime($column, $operator, $value = null, $boolean = 'and')
1590 {
1591 [$value, $operator] = $this->prepareValueAndOperator(
1592 $value, $operator, func_num_args() === 2
1593 );
1594
1595 $value = $this->flattenValue($value);
1596
1597 if ($value instanceof DateTimeInterface) {
1598 $value = $value->format('H:i:s');
1599 }
1600
1601 return $this->addDateBasedWhere('Time', $column, $operator, $value, $boolean);
1602 }
1603
1604 /**
1605 * Add an "or where time" statement to the query.
1606 *
1607 * @param string $column
1608 * @param string $operator
1609 * @param \DateTimeInterface|string|null $value
1610 * @return $this
1611 */
1612 public function orWhereTime($column, $operator, $value = null)
1613 {
1614 [$value, $operator] = $this->prepareValueAndOperator(
1615 $value, $operator, func_num_args() === 2
1616 );
1617
1618 return $this->whereTime($column, $operator, $value, 'or');
1619 }
1620
1621 /**
1622 * Add a "where day" statement to the query.
1623 *
1624 * @param string $column
1625 * @param string $operator
1626 * @param \DateTimeInterface|string|null $value
1627 * @param string $boolean
1628 * @return $this
1629 */
1630 public function whereDay($column, $operator, $value = null, $boolean = 'and')
1631 {
1632 [$value, $operator] = $this->prepareValueAndOperator(
1633 $value, $operator, func_num_args() === 2
1634 );
1635
1636 $value = $this->flattenValue($value);
1637
1638 if ($value instanceof DateTimeInterface) {
1639 $value = $value->format('d');
1640 }
1641
1642 if (! $value instanceof Expression) {
1643 $value = sprintf('%02d', $value);
1644 }
1645
1646 return $this->addDateBasedWhere('Day', $column, $operator, $value, $boolean);
1647 }
1648
1649 /**
1650 * Add an "or where day" statement to the query.
1651 *
1652 * @param string $column
1653 * @param string $operator
1654 * @param \DateTimeInterface|string|null $value
1655 * @return $this
1656 */
1657 public function orWhereDay($column, $operator, $value = null)
1658 {
1659 [$value, $operator] = $this->prepareValueAndOperator(
1660 $value, $operator, func_num_args() === 2
1661 );
1662
1663 return $this->whereDay($column, $operator, $value, 'or');
1664 }
1665
1666 /**
1667 * Add a "where month" statement to the query.
1668 *
1669 * @param string $column
1670 * @param string $operator
1671 * @param \DateTimeInterface|string|null $value
1672 * @param string $boolean
1673 * @return $this
1674 */
1675 public function whereMonth($column, $operator, $value = null, $boolean = 'and')
1676 {
1677 [$value, $operator] = $this->prepareValueAndOperator(
1678 $value, $operator, func_num_args() === 2
1679 );
1680
1681 $value = $this->flattenValue($value);
1682
1683 if ($value instanceof DateTimeInterface) {
1684 $value = $value->format('m');
1685 }
1686
1687 if (! $value instanceof Expression) {
1688 $value = sprintf('%02d', $value);
1689 }
1690
1691 return $this->addDateBasedWhere('Month', $column, $operator, $value, $boolean);
1692 }
1693
1694 /**
1695 * Add an "or where month" statement to the query.
1696 *
1697 * @param string $column
1698 * @param string $operator
1699 * @param \DateTimeInterface|string|null $value
1700 * @return $this
1701 */
1702 public function orWhereMonth($column, $operator, $value = null)
1703 {
1704 [$value, $operator] = $this->prepareValueAndOperator(
1705 $value, $operator, func_num_args() === 2
1706 );
1707
1708 return $this->whereMonth($column, $operator, $value, 'or');
1709 }
1710
1711 /**
1712 * Add a "where year" statement to the query.
1713 *
1714 * @param string $column
1715 * @param string $operator
1716 * @param \DateTimeInterface|string|int|null $value
1717 * @param string $boolean
1718 * @return $this
1719 */
1720 public function whereYear($column, $operator, $value = null, $boolean = 'and')
1721 {
1722 [$value, $operator] = $this->prepareValueAndOperator(
1723 $value, $operator, func_num_args() === 2
1724 );
1725
1726 $value = $this->flattenValue($value);
1727
1728 if ($value instanceof DateTimeInterface) {
1729 $value = $value->format('Y');
1730 }
1731
1732 return $this->addDateBasedWhere('Year', $column, $operator, $value, $boolean);
1733 }
1734
1735 /**
1736 * Add an "or where year" statement to the query.
1737 *
1738 * @param string $column
1739 * @param string $operator
1740 * @param \DateTimeInterface|string|int|null $value
1741 * @return $this
1742 */
1743 public function orWhereYear($column, $operator, $value = null)
1744 {
1745 [$value, $operator] = $this->prepareValueAndOperator(
1746 $value, $operator, func_num_args() === 2
1747 );
1748
1749 return $this->whereYear($column, $operator, $value, 'or');
1750 }
1751
1752 /**
1753 * Add a date based (year, month, day, time) statement to the query.
1754 *
1755 * @param string $type
1756 * @param string $column
1757 * @param string $operator
1758 * @param mixed $value
1759 * @param string $boolean
1760 * @return $this
1761 */
1762 protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
1763 {
1764 $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
1765
1766 if (! $value instanceof Expression) {
1767 $this->addBinding($value, 'where');
1768 }
1769
1770 return $this;
1771 }
1772
1773 /**
1774 * Add a nested where statement to the query.
1775 *
1776 * @param \Closure $callback
1777 * @param string $boolean
1778 * @return $this
1779 */
1780 public function whereNested(Closure $callback, $boolean = 'and')
1781 {
1782 $callback($query = $this->forNestedWhere());
1783
1784 return $this->addNestedWhereQuery($query, $boolean);
1785 }
1786
1787 /**
1788 * Create a new query instance for nested where condition.
1789 *
1790 * @return \FluentCommunity\Framework\Database\Query\Builder
1791 */
1792 public function forNestedWhere()
1793 {
1794 return $this->newQuery()->from($this->from);
1795 }
1796
1797 /**
1798 * Add another query builder as a nested where to the query builder.
1799 *
1800 * @param \FluentCommunity\Framework\Database\Query\Builder $query
1801 * @param string $boolean
1802 * @return $this
1803 */
1804 public function addNestedWhereQuery($query, $boolean = 'and')
1805 {
1806 if (count($query->wheres)) {
1807 $type = 'Nested';
1808
1809 $this->wheres[] = compact('type', 'query', 'boolean');
1810
1811 $this->addBinding($query->getRawBindings()['where'], 'where');
1812 }
1813
1814 return $this;
1815 }
1816
1817 /**
1818 * Add a full sub-select to the query.
1819 *
1820 * @param string $column
1821 * @param string $operator
1822 * @param \Closure $callback
1823 * @param string $boolean
1824 * @return $this
1825 */
1826 protected function whereSub($column, $operator, $callback, $boolean)
1827 {
1828 $type = 'Sub';
1829
1830 if ($callback instanceof Closure) {
1831 // Once we have the query instance we can simply execute it so it can add all
1832 // of the sub-select's conditions to itself, and then we can cache it off
1833 // in the array of where clauses for the "main" parent query instance.
1834 $callback($query = $this->forSubQuery());
1835 } else {
1836 $query = $callback instanceof OrmBuilder ? $callback->toBase() : $callback;
1837 }
1838
1839 $this->wheres[] = compact(
1840 'type', 'column', 'operator', 'query', 'boolean'
1841 );
1842
1843 $this->addBinding($query->getBindings(), 'where');
1844
1845 return $this;
1846 }
1847
1848 /**
1849 * Add an exists clause to the query.
1850 *
1851 * @param \Closure $callback
1852 * @param string $boolean
1853 * @param bool $not
1854 * @return $this
1855 */
1856 public function whereExists(Closure $callback, $boolean = 'and', $not = false)
1857 {
1858 if ($callback instanceof Closure) {
1859 $query = $this->forSubQuery();
1860
1861 // Similar to the sub-select clause, we will create a new query instance so
1862 // the developer may cleanly specify the entire exists query and we will
1863 // compile the whole thing in the grammar and insert it into the SQL.
1864 $callback($query);
1865 } else {
1866 $query = $callback instanceof OrmBuilder ? $callback->toBase() : $callback;
1867 }
1868
1869 return $this->addWhereExistsQuery($query, $boolean, $not);
1870 }
1871
1872 /**
1873 * Add an or exists clause to the query.
1874 *
1875 * @param \Closure $callback
1876 * @param bool $not
1877 * @return $this
1878 */
1879 public function orWhereExists($callback, $not = false)
1880 {
1881 return $this->whereExists($callback, 'or', $not);
1882 }
1883
1884 /**
1885 * Add a where not exists clause to the query.
1886 *
1887 * @param \Closure $callback
1888 * @param string $boolean
1889 * @return $this
1890 */
1891 public function whereNotExists($callback, $boolean = 'and')
1892 {
1893 return $this->whereExists($callback, $boolean, true);
1894 }
1895
1896 /**
1897 * Add a where not exists clause to the query.
1898 *
1899 * @param \Closure $callback
1900 * @return $this
1901 */
1902 public function orWhereNotExists($callback)
1903 {
1904 return $this->orWhereExists($callback, true);
1905 }
1906
1907 /**
1908 * Add an exists clause to the query.
1909 *
1910 * @param \FluentCommunity\Framework\Database\Query\Builder $query
1911 * @param string $boolean
1912 * @param bool $not
1913 * @return $this
1914 */
1915 public function addWhereExistsQuery(self $query, $boolean = 'and', $not = false)
1916 {
1917 $type = $not ? 'NotExists' : 'Exists';
1918
1919 $this->wheres[] = compact('type', 'query', 'boolean');
1920
1921 $this->addBinding($query->getBindings(), 'where');
1922
1923 return $this;
1924 }
1925
1926 /**
1927 * Adds a where condition using row values.
1928 *
1929 * @param array $columns
1930 * @param string $operator
1931 * @param array $values
1932 * @param string $boolean
1933 * @return $this
1934 *
1935 * @throws \InvalidArgumentException
1936 */
1937 public function whereRowValues($columns, $operator, $values, $boolean = 'and')
1938 {
1939 if (count($columns) !== count($values)) {
1940 throw new InvalidArgumentException('The number of columns must match the number of values');
1941 }
1942
1943 $type = 'RowValues';
1944
1945 $this->wheres[] = compact('type', 'columns', 'operator', 'values', 'boolean');
1946
1947 $this->addBinding($this->cleanBindings($values));
1948
1949 return $this;
1950 }
1951
1952 /**
1953 * Adds an or where condition using row values.
1954 *
1955 * @param array $columns
1956 * @param string $operator
1957 * @param array $values
1958 * @return $this
1959 */
1960 public function orWhereRowValues($columns, $operator, $values)
1961 {
1962 return $this->whereRowValues($columns, $operator, $values, 'or');
1963 }
1964
1965 /**
1966 * Add a "where JSON contains" clause to the query.
1967 *
1968 * @param string $column
1969 * @param mixed $value
1970 * @param string $boolean
1971 * @param bool $not
1972 * @return $this
1973 */
1974 public function whereJsonContains($column, $value, $boolean = 'and', $not = false)
1975 {
1976 $type = 'JsonContains';
1977
1978 $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not');
1979
1980 if (! $value instanceof Expression) {
1981 $this->addBinding($this->grammar->prepareBindingForJsonContains($value));
1982 }
1983
1984 return $this;
1985 }
1986
1987 /**
1988 * Add an "or where JSON contains" clause to the query.
1989 *
1990 * @param string $column
1991 * @param mixed $value
1992 * @return $this
1993 */
1994 public function orWhereJsonContains($column, $value)
1995 {
1996 return $this->whereJsonContains($column, $value, 'or');
1997 }
1998
1999 /**
2000 * Add a "where JSON not contains" clause to the query.
2001 *
2002 * @param string $column
2003 * @param mixed $value
2004 * @param string $boolean
2005 * @return $this
2006 */
2007 public function whereJsonDoesntContain($column, $value, $boolean = 'and')
2008 {
2009 return $this->whereJsonContains($column, $value, $boolean, true);
2010 }
2011
2012 /**
2013 * Add an "or where JSON not contains" clause to the query.
2014 *
2015 * @param string $column
2016 * @param mixed $value
2017 * @return $this
2018 */
2019 public function orWhereJsonDoesntContain($column, $value)
2020 {
2021 return $this->whereJsonDoesntContain($column, $value, 'or');
2022 }
2023
2024 /**
2025 * Add a "where JSON overlaps" clause to the query.
2026 *
2027 * @param string $column
2028 * @param mixed $value
2029 * @param string $boolean
2030 * @param bool $not
2031 * @return $this
2032 */
2033 public function whereJsonOverlaps($column, $value, $boolean = 'and', $not = false)
2034 {
2035 $type = 'JsonOverlaps';
2036
2037 $this->wheres[] = compact('type', 'column', 'value', 'boolean', 'not');
2038
2039 if (! $value instanceof Expression) {
2040 $this->addBinding($this->grammar->prepareBindingForJsonContains($value));
2041 }
2042
2043 return $this;
2044 }
2045
2046 /**
2047 * Add an "or where JSON overlaps" clause to the query.
2048 *
2049 * @param string $column
2050 * @param mixed $value
2051 * @return $this
2052 */
2053 public function orWhereJsonOverlaps($column, $value)
2054 {
2055 return $this->whereJsonOverlaps($column, $value, 'or');
2056 }
2057
2058 /**
2059 * Add a "where JSON not overlap" clause to the query.
2060 *
2061 * @param string $column
2062 * @param mixed $value
2063 * @param string $boolean
2064 * @return $this
2065 */
2066 public function whereJsonDoesntOverlap($column, $value, $boolean = 'and')
2067 {
2068 return $this->whereJsonOverlaps($column, $value, $boolean, true);
2069 }
2070
2071 /**
2072 * Add an "or where JSON not overlap" clause to the query.
2073 *
2074 * @param string $column
2075 * @param mixed $value
2076 * @return $this
2077 */
2078 public function orWhereJsonDoesntOverlap($column, $value)
2079 {
2080 return $this->whereJsonDoesntOverlap($column, $value, 'or');
2081 }
2082
2083 /**
2084 * Add a clause that determines if a JSON path exists to the query.
2085 *
2086 * @param string $column
2087 * @param string $boolean
2088 * @param bool $not
2089 * @return $this
2090 */
2091 public function whereJsonContainsKey($column, $boolean = 'and', $not = false)
2092 {
2093 $type = 'JsonContainsKey';
2094
2095 $this->wheres[] = compact('type', 'column', 'boolean', 'not');
2096
2097 return $this;
2098 }
2099
2100 /**
2101 * Add an "or" clause that determines if a JSON path exists to the query.
2102 *
2103 * @param string $column
2104 * @return $this
2105 */
2106 public function orWhereJsonContainsKey($column)
2107 {
2108 return $this->whereJsonContainsKey($column, 'or');
2109 }
2110
2111 /**
2112 * Add a clause that determines if a JSON path does not exist to the query.
2113 *
2114 * @param string $column
2115 * @param string $boolean
2116 * @return $this
2117 */
2118 public function whereJsonDoesntContainKey($column, $boolean = 'and')
2119 {
2120 return $this->whereJsonContainsKey($column, $boolean, true);
2121 }
2122
2123 /**
2124 * Add an "or" clause that determines if a JSON path does not exist to the query.
2125 *
2126 * @param string $column
2127 * @return $this
2128 */
2129 public function orWhereJsonDoesntContainKey($column)
2130 {
2131 return $this->whereJsonDoesntContainKey($column, 'or');
2132 }
2133
2134 /**
2135 * Add a "where JSON length" clause to the query.
2136 *
2137 * @param string $column
2138 * @param mixed $operator
2139 * @param mixed $value
2140 * @param string $boolean
2141 * @return $this
2142 */
2143 public function whereJsonLength(
2144 $column,
2145 $operator,
2146 $value = null,
2147 $boolean = 'and'
2148 ) {
2149 $type = 'JsonLength';
2150
2151 [$value, $operator] = $this->prepareValueAndOperator(
2152 $value, $operator, func_num_args() === 2
2153 );
2154
2155 $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean');
2156
2157 if (! $value instanceof Expression) {
2158 $this->addBinding((int) $this->flattenValue($value));
2159 }
2160
2161 return $this;
2162 }
2163
2164 /**
2165 * Add an "or where JSON length" clause to the query.
2166 *
2167 * @param string $column
2168 * @param mixed $operator
2169 * @param mixed $value
2170 * @return $this
2171 */
2172 public function orWhereJsonLength($column, $operator, $value = null)
2173 {
2174 [$value, $operator] = $this->prepareValueAndOperator(
2175 $value, $operator, func_num_args() === 2
2176 );
2177
2178 return $this->whereJsonLength($column, $operator, $value, 'or');
2179 }
2180
2181 /**
2182 * Handles dynamic "where" clauses to the query.
2183 *
2184 * @param string $method
2185 * @param array $parameters
2186 * @return $this
2187 */
2188 public function dynamicWhere($method, $parameters)
2189 {
2190 $finder = substr($method, 5);
2191
2192 $segments = preg_split(
2193 '/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE
2194 );
2195
2196 // The connector variable will determine which connector will be used for the
2197 // query condition. We will change it as we come across new boolean values
2198 // in the dynamic method strings, which could contain a number of these.
2199 $connector = 'and';
2200
2201 $index = 0;
2202
2203 foreach ($segments as $segment) {
2204 // If the segment is not a boolean connector, we can assume it is a column's name
2205 // and we will add it to the query as a new constraint as a where clause, then
2206 // we can keep iterating through the dynamic method string's segments again.
2207 if ($segment !== 'And' && $segment !== 'Or') {
2208 $this->addDynamic($segment, $connector, $parameters, $index);
2209
2210 $index++;
2211 }
2212
2213 // Otherwise, we will store the connector so we know how the next where clause we
2214 // find in the query should be connected to the previous ones, meaning we will
2215 // have the proper boolean connector to connect the next where clause found.
2216 else {
2217 $connector = $segment;
2218 }
2219 }
2220
2221 return $this;
2222 }
2223
2224 /**
2225 * Add a single dynamic where clause statement to the query.
2226 *
2227 * @param string $segment
2228 * @param string $connector
2229 * @param array $parameters
2230 * @param int $index
2231 * @return void
2232 */
2233 protected function addDynamic($segment, $connector, $parameters, $index)
2234 {
2235 // Once we have parsed out the columns and formatted the boolean operators we
2236 // are ready to add it to this query as a where clause just like any other
2237 // clause on the query. Then we'll increment the parameter index values.
2238 $bool = strtolower($connector);
2239
2240 $this->where(Str::snake($segment), '=', $parameters[$index], $bool);
2241 }
2242
2243 /**
2244 * Add a "where fulltext" clause to the query.
2245 *
2246 * @param string|string[] $columns
2247 * @param string $value
2248 * @param string $boolean
2249 * @return $this
2250 */
2251 public function whereFullText(
2252 $columns,
2253 $value,
2254 array $options = [],
2255 $boolean = 'and'
2256 ) {
2257 $type = 'Fulltext';
2258
2259 $columns = (array) $columns;
2260
2261 $this->wheres[] = compact('type', 'columns', 'value', 'options', 'boolean');
2262
2263 $this->addBinding($value);
2264
2265 return $this;
2266 }
2267
2268 /**
2269 * Add a "or where fulltext" clause to the query.
2270 *
2271 * @param string|string[] $columns
2272 * @param string $value
2273 * @return $this
2274 */
2275 public function orWhereFullText($columns, $value, array $options = [])
2276 {
2277 return $this->whereFulltext($columns, $value, $options, 'or');
2278 }
2279
2280 /**
2281 * Add a "where" clause to the query for multiple columns with "and" conditions between them.
2282 *
2283 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2284 * @param mixed $operator
2285 * @param mixed $value
2286 * @param string $boolean
2287 * @return $this
2288 */
2289 public function whereAll(
2290 $columns,
2291 $operator = null,
2292 $value = null,
2293 $boolean = 'and'
2294 ) {
2295 [$value, $operator] = $this->prepareValueAndOperator(
2296 $value, $operator, func_num_args() === 2
2297 );
2298
2299 $this->whereNested(function ($query) use ($columns, $operator, $value) {
2300 foreach ($columns as $column) {
2301 $query->where($column, $operator, $value, 'and');
2302 }
2303 }, $boolean);
2304
2305 return $this;
2306 }
2307
2308 /**
2309 * Add an "or where" clause to the query for multiple columns with "and" conditions between them.
2310 *
2311 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2312 * @param mixed $operator
2313 * @param mixed $value
2314 * @return $this
2315 */
2316 public function orWhereAll($columns, $operator = null, $value = null)
2317 {
2318 return $this->whereAll($columns, $operator, $value, 'or');
2319 }
2320
2321 /**
2322 * Add a "where" clause to the query for multiple columns with "or" conditions between them.
2323 *
2324 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2325 * @param mixed $operator
2326 * @param mixed $value
2327 * @param string $boolean
2328 * @return $this
2329 */
2330 public function whereAny(
2331 $columns,
2332 $operator = null,
2333 $value = null,
2334 $boolean = 'and'
2335 ) {
2336 [$value, $operator] = $this->prepareValueAndOperator(
2337 $value, $operator, func_num_args() === 2
2338 );
2339
2340 $this->whereNested(function ($query) use ($columns, $operator, $value) {
2341 foreach ($columns as $column) {
2342 $query->where($column, $operator, $value, 'or');
2343 }
2344 }, $boolean);
2345
2346 return $this;
2347 }
2348
2349 /**
2350 * Add an "or where" clause to the query for multiple columns with "or" conditions between them.
2351 *
2352 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2353 * @param mixed $operator
2354 * @param mixed $value
2355 * @return $this
2356 */
2357 public function orWhereAny($columns, $operator = null, $value = null)
2358 {
2359 return $this->whereAny($columns, $operator, $value, 'or');
2360 }
2361
2362 /**
2363 * Add a "where not" clause to the query for multiple columns where none of the conditions should be true.
2364 *
2365 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2366 * @param mixed $operator
2367 * @param mixed $value
2368 * @param string $boolean
2369 * @return $this
2370 */
2371 public function whereNone($columns, $operator = null, $value = null, $boolean = 'and')
2372 {
2373 return $this->whereAny($columns, $operator, $value, $boolean.' not');
2374 }
2375
2376 /**
2377 * Add an "or where not" clause to the query for multiple columns where none of the conditions should be true.
2378 *
2379 * @param \FluentCommunity\Framework\Database\Query\Expression[]|string[] $columns
2380 * @param mixed $operator
2381 * @param mixed $value
2382 * @return $this
2383 */
2384 public function orWhereNone($columns, $operator = null, $value = null)
2385 {
2386 return $this->whereNone($columns, $operator, $value, 'or');
2387 }
2388
2389 /**
2390 * Add a "group by" clause to the query.
2391 *
2392 * @param array|string ...$groups
2393 * @return $this
2394 */
2395 public function groupBy(...$groups)
2396 {
2397 foreach ($groups as $group) {
2398 $this->groups = array_merge(
2399 (array) $this->groups,
2400 Arr::wrap($group)
2401 );
2402 }
2403
2404 return $this;
2405 }
2406
2407 /**
2408 * Add a raw groupBy clause to the query.
2409 *
2410 * @param string $sql
2411 * @param array $bindings
2412 * @return $this
2413 */
2414 public function groupByRaw($sql, array $bindings = [])
2415 {
2416 $this->groups[] = new Expression($sql);
2417
2418 $this->addBinding($bindings, 'groupBy');
2419
2420 return $this;
2421 }
2422
2423 /**
2424 * Add a "having" clause to the query.
2425 *
2426 * @param string $column
2427 * @param string|null $operator
2428 * @param string|null $value
2429 * @param string $boolean
2430 * @return $this
2431 */
2432 public function having($column, $operator = null, $value = null, $boolean = 'and')
2433 {
2434 $type = 'Basic';
2435
2436 if ($column instanceof ConditionExpression) {
2437 $type = 'Expression';
2438
2439 $this->havings[] = compact('type', 'column', 'boolean');
2440
2441 return $this;
2442 }
2443
2444 // Here we will make some assumptions about the operator. If only 2 values are
2445 // passed to the method, we will assume that the operator is an equals sign
2446 // and keep going. Otherwise, we'll require the operator to be passed in.
2447 [$value, $operator] = $this->prepareValueAndOperator(
2448 $value, $operator, func_num_args() === 2
2449 );
2450
2451 if ($column instanceof Closure && is_null($operator)) {
2452 return $this->havingNested($column, $boolean);
2453 }
2454
2455 // If the given operator is not found in the list of valid operators we will
2456 // assume that the developer is just short-cutting the '=' operators and
2457 // we will set the operators to '=' and set the values appropriately.
2458 if ($this->invalidOperator($operator)) {
2459 [$value, $operator] = [$operator, '='];
2460 }
2461
2462 if ($this->isBitwiseOperator($operator)) {
2463 $type = 'Bitwise';
2464 }
2465
2466 $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');
2467
2468 if (! $value instanceof Expression) {
2469 $this->addBinding($this->flattenValue($value), 'having');
2470 }
2471
2472 return $this;
2473 }
2474
2475 /**
2476 * Add an "or having" clause to the query.
2477 *
2478 * @param string $column
2479 * @param string|null $operator
2480 * @param string|null $value
2481 * @return $this
2482 */
2483 public function orHaving($column, $operator = null, $value = null)
2484 {
2485 [$value, $operator] = $this->prepareValueAndOperator(
2486 $value, $operator, func_num_args() === 2
2487 );
2488
2489 return $this->having($column, $operator, $value, 'or');
2490 }
2491
2492 /**
2493 * Add a nested having statement to the query.
2494 *
2495 * @param \Closure $callback
2496 * @param string $boolean
2497 * @return $this
2498 */
2499 public function havingNested(Closure $callback, $boolean = 'and')
2500 {
2501 $callback($query = $this->forNestedWhere());
2502
2503 return $this->addNestedHavingQuery($query, $boolean);
2504 }
2505
2506 /**
2507 * Add another query builder as a nested having to the query builder.
2508 *
2509 * @param \FluentCommunity\Framework\Database\Query\Builder $query
2510 * @param string $boolean
2511 * @return $this
2512 */
2513 public function addNestedHavingQuery($query, $boolean = 'and')
2514 {
2515 if (count($query->havings)) {
2516 $type = 'Nested';
2517
2518 $this->havings[] = compact('type', 'query', 'boolean');
2519
2520 $this->addBinding($query->getRawBindings()['having'], 'having');
2521 }
2522
2523 return $this;
2524 }
2525
2526 /**
2527 * Add a "having null" clause to the query.
2528 *
2529 * @param string|array $columns
2530 * @param string $boolean
2531 * @param bool $not
2532 * @return $this
2533 */
2534 public function havingNull($columns, $boolean = 'and', $not = false)
2535 {
2536 $type = $not ? 'NotNull' : 'Null';
2537
2538 foreach (Arr::wrap($columns) as $column) {
2539 $this->havings[] = compact('type', 'column', 'boolean');
2540 }
2541
2542 return $this;
2543 }
2544
2545 /**
2546 * Add an "or having null" clause to the query.
2547 *
2548 * @param string $column
2549 * @return $this
2550 */
2551 public function orHavingNull($column)
2552 {
2553 return $this->havingNull($column, 'or');
2554 }
2555
2556 /**
2557 * Add a "having not null" clause to the query.
2558 *
2559 * @param string|array $columns
2560 * @param string $boolean
2561 * @return $this
2562 */
2563 public function havingNotNull($columns, $boolean = 'and')
2564 {
2565 return $this->havingNull($columns, $boolean, true);
2566 }
2567
2568 /**
2569 * Add an "or having not null" clause to the query.
2570 *
2571 * @param string $column
2572 * @return $this
2573 */
2574 public function orHavingNotNull($column)
2575 {
2576 return $this->havingNotNull($column, 'or');
2577 }
2578
2579 /**
2580 * Add a "having between " clause to the query.
2581 *
2582 * @param string $column
2583 * @param array $values
2584 * @param string $boolean
2585 * @param bool $not
2586 * @return $this
2587 */
2588 public function havingBetween($column, array $values, $boolean = 'and', $not = false)
2589 {
2590 $type = 'between';
2591
2592 if ($values instanceof DateTimePeriod) {
2593 $values = [$values->getStartDate(), $values->getEndDate()];
2594 }
2595
2596 $this->havings[] = compact('type', 'column', 'values', 'boolean', 'not');
2597
2598 $this->addBinding(array_slice($this->cleanBindings(Arr::flatten($values)), 0, 2), 'having');
2599
2600 return $this;
2601 }
2602
2603 /**
2604 * Add a raw having clause to the query.
2605 *
2606 * @param string $sql
2607 * @param array $bindings
2608 * @param string $boolean
2609 * @return $this
2610 */
2611 public function havingRaw($sql, array $bindings = [], $boolean = 'and')
2612 {
2613 $type = 'Raw';
2614
2615 $this->havings[] = compact('type', 'sql', 'boolean');
2616
2617 $this->addBinding($bindings, 'having');
2618
2619 return $this;
2620 }
2621
2622 /**
2623 * Add a raw or having clause to the query.
2624 *
2625 * @param string $sql
2626 * @param array $bindings
2627 * @return $this
2628 */
2629 public function orHavingRaw($sql, array $bindings = [])
2630 {
2631 return $this->havingRaw($sql, $bindings, 'or');
2632 }
2633
2634 /**
2635 * Add an "order by" clause to the query.
2636 *
2637 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2638 * @param string $direction
2639 * @return $this
2640 *
2641 * @throws \InvalidArgumentException
2642 */
2643 public function orderBy($column, $direction = 'asc')
2644 {
2645 if ($this->isQueryable($column)) {
2646 [$query, $bindings] = $this->createSub($column);
2647
2648 $column = new Expression('('.$query.')');
2649
2650 $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order');
2651 }
2652
2653 $direction = strtolower($direction);
2654
2655 if (! in_array($direction, ['asc', 'desc'], true)) {
2656 throw new InvalidArgumentException('Order direction must be "asc" or "desc".');
2657 }
2658
2659 $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
2660 'column' => $column,
2661 'direction' => $direction,
2662 ];
2663
2664 return $this;
2665 }
2666
2667 /**
2668 * Add a descending "order by" clause to the query.
2669 *
2670 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2671 * @return $this
2672 */
2673 public function orderByDesc($column)
2674 {
2675 return $this->orderBy($column, 'desc');
2676 }
2677
2678 /**
2679 * Add an "order by" clause for a timestamp to the query.
2680 *
2681 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2682 * @return $this
2683 */
2684 public function latest($column = 'created_at')
2685 {
2686 return $this->orderBy($column, 'desc');
2687 }
2688
2689 /**
2690 * Add an "order by" clause for a timestamp to the query.
2691 *
2692 * @param \Closure|\FluentCommunity\Framework\Database\Orm\Builder|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string $column
2693 * @return $this
2694 */
2695 public function oldest($column = 'created_at')
2696 {
2697 return $this->orderBy($column, 'asc');
2698 }
2699
2700 /**
2701 * Put the query's results in random order.
2702 *
2703 * @param string $seed
2704 * @return $this
2705 */
2706 public function inRandomOrder($seed = '')
2707 {
2708 return $this->orderByRaw($this->grammar->compileRandom($seed));
2709 }
2710
2711 /**
2712 * Add a raw "order by" clause to the query.
2713 *
2714 * @param string $sql
2715 * @param array $bindings
2716 * @return $this
2717 */
2718 public function orderByRaw($sql, $bindings = [])
2719 {
2720 $type = 'Raw';
2721
2722 $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'sql');
2723
2724 $this->addBinding($bindings, $this->unions ? 'unionOrder' : 'order');
2725
2726 return $this;
2727 }
2728
2729 /**
2730 * Alias to set the "offset" value of the query.
2731 *
2732 * @param int $value
2733 * @return $this
2734 */
2735 public function skip($value)
2736 {
2737 return $this->offset($value);
2738 }
2739
2740 /**
2741 * Set the "offset" value of the query.
2742 *
2743 * @param int $value
2744 * @return $this
2745 */
2746 public function offset($value)
2747 {
2748 $property = $this->unions ? 'unionOffset' : 'offset';
2749
2750 $this->$property = max(0, (int) $value);
2751
2752 return $this;
2753 }
2754
2755 /**
2756 * Alias to set the "limit" value of the query.
2757 *
2758 * @param int $value
2759 * @return $this
2760 */
2761 public function take($value)
2762 {
2763 return $this->limit($value);
2764 }
2765
2766 /**
2767 * Set the "limit" value of the query.
2768 *
2769 * @param int $value
2770 * @return $this
2771 */
2772 public function limit($value)
2773 {
2774 $property = $this->unions ? 'unionLimit' : 'limit';
2775
2776 if ($value >= 0) {
2777 $this->$property = ! is_null($value) ? (int) $value : null;
2778 }
2779
2780 return $this;
2781 }
2782
2783 /**
2784 * Add a "group limit" clause to the query.
2785 *
2786 * @param int $value
2787 * @param string $column
2788 * @return $this
2789 */
2790 public function groupLimit($value, $column)
2791 {
2792 if ($value >= 0) {
2793 $this->groupLimit = compact('value', 'column');
2794 }
2795
2796 return $this;
2797 }
2798
2799 /**
2800 * Set the limit and offset for a given page.
2801 *
2802 * @param int $page
2803 * @param int $perPage
2804 * @return $this
2805 */
2806 public function forPage($page, $perPage = 15)
2807 {
2808 return $this->offset(($page - 1) * $perPage)->limit($perPage);
2809 }
2810
2811 /**
2812 * Constrain the query to the previous "page" of results before a given ID.
2813 *
2814 * @param int $perPage
2815 * @param int|null $lastId
2816 * @param string $column
2817 * @return $this
2818 */
2819 public function forPageBeforeId($perPage = 15, $lastId = 0, $column = 'id')
2820 {
2821 $this->orders = $this->removeExistingOrdersFor($column);
2822
2823 if (! is_null($lastId)) {
2824 $this->where($column, '<', $lastId);
2825 }
2826
2827 return $this->orderBy($column, 'desc')
2828 ->limit($perPage);
2829 }
2830
2831 /**
2832 * Constrain the query to the next "page" of results after a given ID.
2833 *
2834 * @param int $perPage
2835 * @param int|null $lastId
2836 * @param string $column
2837 * @return $this
2838 */
2839 public function forPageAfterId($perPage = 15, $lastId = 0, $column = 'id')
2840 {
2841 $this->orders = $this->removeExistingOrdersFor($column);
2842
2843 if (! is_null($lastId)) {
2844 $this->where($column, '>', $lastId);
2845 }
2846
2847 return $this->orderBy($column, 'asc')
2848 ->limit($perPage);
2849 }
2850
2851 /**
2852 * Remove all existing orders and optionally add a new order.
2853 *
2854 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Query\Expression|string|null $column
2855 * @param string $direction
2856 * @return $this
2857 */
2858 public function reorder($column = null, $direction = 'asc')
2859 {
2860 $this->orders = null;
2861 $this->unionOrders = null;
2862 $this->bindings['order'] = [];
2863 $this->bindings['unionOrder'] = [];
2864
2865 if ($column) {
2866 return $this->orderBy($column, $direction);
2867 }
2868
2869 return $this;
2870 }
2871
2872 /**
2873 * Get an array with all orders with a given column removed.
2874 *
2875 * @param string $column
2876 * @return array
2877 */
2878 protected function removeExistingOrdersFor($column)
2879 {
2880 return Collection::make($this->orders)
2881 ->reject(function ($order) use ($column) {
2882 return isset($order['column'])
2883 ? $order['column'] === $column : false;
2884 })->values()->all();
2885 }
2886
2887 /**
2888 * Add a union statement to the query.
2889 *
2890 * @param \FluentCommunity\Framework\Database\Query\Builder|\Closure $query
2891 * @param bool $all
2892 * @return $this
2893 */
2894 public function union($query, $all = false)
2895 {
2896 if ($query instanceof Closure) {
2897 $query($query = $this->newQuery());
2898 }
2899
2900 $this->unions[] = compact('query', 'all');
2901
2902 $this->addBinding($query->getBindings(), 'union');
2903
2904 return $this;
2905 }
2906
2907 /**
2908 * Add a union all statement to the query.
2909 *
2910 * @param \FluentCommunity\Framework\Database\Query\Builder|\Closure $query
2911 * @return $this
2912 */
2913 public function unionAll($query)
2914 {
2915 return $this->union($query, true);
2916 }
2917
2918 /**
2919 * Lock the selected rows in the table.
2920 *
2921 * @param string|bool $value
2922 * @return $this
2923 */
2924 public function lock($value = true)
2925 {
2926 $this->lock = $value;
2927
2928 if (! is_null($this->lock)) {
2929 $this->useWritePdo();
2930 }
2931
2932 return $this;
2933 }
2934
2935 /**
2936 * Lock the selected rows in the table for updating.
2937 *
2938 * @return \FluentCommunity\Framework\Database\Query\Builder
2939 */
2940 public function lockForUpdate()
2941 {
2942 return $this->lock(true);
2943 }
2944
2945 /**
2946 * Share lock the selected rows in the table.
2947 *
2948 * @return \FluentCommunity\Framework\Database\Query\Builder
2949 */
2950 public function sharedLock()
2951 {
2952 return $this->lock(false);
2953 }
2954
2955 /**
2956 * Register a closure to be invoked before the query is executed.
2957 *
2958 * @param callable $callback
2959 * @return $this
2960 */
2961 public function beforeQuery(callable $callback)
2962 {
2963 $this->beforeQueryCallbacks[] = $callback;
2964
2965 return $this;
2966 }
2967
2968 /**
2969 * Invoke the "before query" modification callbacks.
2970 *
2971 * @return void
2972 */
2973 public function applyBeforeQueryCallbacks()
2974 {
2975 foreach ($this->beforeQueryCallbacks as $callback) {
2976 $callback($this);
2977 }
2978
2979 $this->beforeQueryCallbacks = [];
2980 }
2981
2982 /**
2983 * Register a closure to be invoked after the query is executed.
2984 *
2985 * @param \Closure $callback
2986 * @return $this
2987 */
2988 public function afterQuery(Closure $callback)
2989 {
2990 $this->afterQueryCallbacks[] = $callback;
2991
2992 return $this;
2993 }
2994
2995 /**
2996 * Invoke the "after query" modification callbacks.
2997 *
2998 * @param mixed $result
2999 * @return mixed
3000 */
3001 public function applyAfterQueryCallbacks($result)
3002 {
3003 foreach ($this->afterQueryCallbacks as $afterQueryCallback) {
3004 $result = $afterQueryCallback($result) ?: $result;
3005 }
3006
3007 return $result;
3008 }
3009
3010 /**
3011 * Get the SQL representation of the query.
3012 *
3013 * @return string
3014 */
3015 public function toSql()
3016 {
3017 $this->applyBeforeQueryCallbacks();
3018
3019 return $this->grammar->compileSelect($this);
3020 }
3021
3022 /**
3023 * Get the raw SQL representation of the query with embedded bindings.
3024 *
3025 * @return string
3026 */
3027 public function toRawSql()
3028 {
3029 return $this->grammar->substituteBindingsIntoRawSql(
3030 $this->toSql(), $this->connection->prepareBindings($this->getBindings())
3031 );
3032 }
3033
3034 /**
3035 * Execute a query for a single record by ID.
3036 *
3037 * @param int|string $id
3038 * @param array $columns
3039 * @return mixed|static
3040 */
3041 public function find($id, $columns = ['*'])
3042 {
3043 return $this->where('id', '=', $id)->first($columns);
3044 }
3045
3046 /**
3047 * Execute a query for a single record by ID or call a callback.
3048 *
3049 * @template TValue
3050 *
3051 * @param mixed $id
3052 * @param (\Closure(): TValue)|list<string>|string $columns
3053 * @param (\Closure(): TValue)|null $callback
3054 * @return object|TValue
3055 */
3056 public function findOr($id, $columns = ['*'], Closure $callback = null)
3057 {
3058 if ($columns instanceof Closure) {
3059 $callback = $columns;
3060
3061 $columns = ['*'];
3062 }
3063
3064 if (! is_null($data = $this->find($id, $columns))) {
3065 return $data;
3066 }
3067
3068 return $callback();
3069 }
3070
3071 /**
3072 * Get a single column's value from the first result of a query.
3073 *
3074 * @param string $column
3075 * @return mixed
3076 */
3077 public function value($column)
3078 {
3079 $result = (array) $this->first([$column]);
3080
3081 return count($result) > 0 ? reset($result) : null;
3082 }
3083
3084 /**
3085 * Get a single expression value from the first result of a query.
3086 *
3087 * @param string $expression
3088 * @param array $bindings
3089 * @return mixed
3090 */
3091 public function rawValue(string $expression, array $bindings = [])
3092 {
3093 $result = (array) $this->selectRaw($expression, $bindings)->first();
3094
3095 return count($result) > 0 ? reset($result) : null;
3096 }
3097
3098 /**
3099 * Get a single column's value from the first result of a query if it's the sole matching record.
3100 *
3101 * @param string $column
3102 * @return mixed
3103 *
3104 * @throws \FluentCommunity\Framework\Database\RecordsNotFoundException
3105 * @throws \FluentCommunity\Framework\Database\MultipleRecordsFoundException
3106 */
3107 public function soleValue($column)
3108 {
3109 $result = (array) $this->sole([$column]);
3110
3111 return reset($result);
3112 }
3113
3114 /**
3115 * Execute the query as a "select" statement.
3116 *
3117 * @param array|string $columns
3118 * @return \FluentCommunity\Framework\Support\Collection
3119 */
3120 public function get($columns = ['*'])
3121 {
3122 $items = Helper::collect($this->onceWithColumns(Arr::wrap($columns), function () {
3123 return $this->processor->processSelect($this, $this->runSelect());
3124 }));
3125
3126 return $this->applyAfterQueryCallbacks(
3127 isset($this->groupLimit) ? $this->withoutGroupLimitKeys($items) : $items
3128 );
3129 }
3130
3131 /**
3132 * Run the query as a "select" statement against the connection.
3133 *
3134 * @return array
3135 */
3136 protected function runSelect()
3137 {
3138 return $this->connection->select(
3139 $this->toSql(), $this->getBindings(), ! $this->useWritePdo
3140 );
3141 }
3142
3143 /**
3144 * Remove the group limit keys from the results in the collection.
3145 *
3146 * @param \FluentCommunity\Framework\Support\Collection $items
3147 * @return \FluentCommunity\Framework\Support\Collection
3148 */
3149 protected function withoutGroupLimitKeys($items)
3150 {
3151 $keysToRemove = ['laravel_row'];
3152
3153 if (is_string($this->groupLimit['column'])) {
3154 $column = Helper::last(explode('.', $this->groupLimit['column']));
3155
3156 $keysToRemove[] = '@laravel_group := '.$this->grammar->wrap($column);
3157 $keysToRemove[] = '@laravel_group := '.$this->grammar->wrap('pivot_'.$column);
3158 }
3159
3160 $items->each(function ($item) use ($keysToRemove) {
3161 foreach ($keysToRemove as $key) {
3162 unset($item->$key);
3163 }
3164 });
3165
3166 return $items;
3167 }
3168
3169 /**
3170 * Paginate the given query into a simple paginator.
3171 *
3172 * @param int $perPage
3173 * @param array $columns
3174 * @param string $pageName
3175 * @param int|null $page
3176 * @return \FluentCommunity\Framework\Pagination\LengthAwarePaginatorInterface
3177 */
3178 public function paginate(
3179 $perPage = 15,
3180 $columns = ['*'],
3181 $pageName = 'page',
3182 $page = null,
3183 $total = null
3184 ) {
3185 $page = $page ?: Paginator::resolveCurrentPage($pageName);
3186
3187 $total = Helper::value($total) ?? $this->getCountForPagination();
3188
3189 $perPage = $perPage instanceof Closure ? $perPage($total) : $perPage;
3190
3191 $results = $total ? $this->forPage($page, $perPage)->get($columns) : Helper::collect();
3192
3193 return $this->paginator($results, $total, $perPage, $page, [
3194 'path' => Paginator::resolveCurrentPath(),
3195 'pageName' => $pageName,
3196 ]);
3197 }
3198
3199 /**
3200 * Get a paginator only supporting simple next and previous links.
3201 *
3202 * This is more efficient on larger data-sets, etc.
3203 *
3204 * @param int $perPage
3205 * @param array $columns
3206 * @param string $pageName
3207 * @param int|null $page
3208 * @return \FluentCommunity\Framework\Pagination\PaginatorInterface
3209 */
3210 public function simplePaginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null)
3211 {
3212 $page = $page ?: Paginator::resolveCurrentPage($pageName);
3213
3214 $this->offset(($page - 1) * $perPage)->limit($perPage + 1);
3215
3216 return $this->simplePaginator($this->get($columns), $perPage, $page, [
3217 'path' => Paginator::resolveCurrentPath(),
3218 'pageName' => $pageName,
3219 ]);
3220 }
3221
3222 /**
3223 * Get a paginator only supporting simple next and previous links.
3224 *
3225 * This is more efficient on larger data-sets, etc.
3226 *
3227 * @param int|null $perPage
3228 * @param array $columns
3229 * @param string $cursorName
3230 * @param \FluentCommunity\Framework\Pagination\Cursor|string|null $cursor
3231 * @return \FluentCommunity\Framework\Pagination\CursorPaginatorInterface
3232 */
3233 public function cursorPaginate($perPage = 15, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
3234 {
3235 return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor);
3236 }
3237
3238 /**
3239 * Ensure the proper order by required for cursor pagination.
3240 *
3241 * @param bool $shouldReverse
3242 * @return \FluentCommunity\Framework\Support\Collection
3243 */
3244 protected function ensureOrderForCursorPagination($shouldReverse = false)
3245 {
3246 if (empty($this->orders) && empty($this->unionOrders)) {
3247 $this->enforceOrderBy();
3248 }
3249
3250 $reverseDirection = function ($order) {
3251 if (! isset($order['direction'])) {
3252 return $order;
3253 }
3254
3255 $order['direction'] = $order['direction'] === 'asc' ? 'desc' : 'asc';
3256
3257 return $order;
3258 };
3259
3260 if ($shouldReverse) {
3261 $this->orders = Helper::collect($this->orders)->map($reverseDirection)->toArray();
3262 $this->unionOrders = Helper::collect($this->unionOrders)->map($reverseDirection)->toArray();
3263 }
3264
3265 $orders = ! empty($this->unionOrders) ? $this->unionOrders : $this->orders;
3266
3267 return Helper::collect($orders)
3268 ->filter(fn ($order) => Arr::has($order, 'direction'))
3269 ->values();
3270 }
3271
3272 /**
3273 * Get the count of the total records for the paginator.
3274 *
3275 * @param array $columns
3276 * @return int
3277 */
3278 public function getCountForPagination($columns = ['*'])
3279 {
3280 $results = $this->runPaginationCountQuery($columns);
3281
3282 // Once we have run the pagination count query, we will get the resulting count and
3283 // take into account what type of query it was. When there is a group by we will
3284 // just return the count of the entire results set since that will be correct.
3285 if (! isset($results[0])) {
3286 return 0;
3287 } elseif (is_object($results[0])) {
3288 return (int) $results[0]->aggregate;
3289 }
3290
3291 return (int) array_change_key_case((array) $results[0])['aggregate'];
3292 }
3293
3294 /**
3295 * Run a pagination count query.
3296 *
3297 * @param array $columns
3298 * @return array
3299 */
3300 protected function runPaginationCountQuery($columns = ['*'])
3301 {
3302 if ($this->groups || $this->havings) {
3303 $clone = $this->cloneForPaginationCount();
3304
3305 if (is_null($clone->columns) && ! empty($this->joins)) {
3306 $clone->select($this->from.'.*');
3307 }
3308
3309 return $this->newQuery()
3310 ->from(new Expression('('.$clone->toSql().') as '.$this->grammar->wrap('aggregate_table')))
3311 ->mergeBindings($clone)
3312 ->setAggregate('count', $this->withoutSelectAliases($columns))
3313 ->get()->all();
3314 }
3315
3316 $without = $this->unions ? ['unionOrders', 'unionLimit', 'unionOffset'] : ['columns', 'orders', 'limit', 'offset'];
3317
3318 return $this->cloneWithout($without)
3319 ->cloneWithoutBindings($this->unions ? ['unionOrder'] : ['select', 'order'])
3320 ->setAggregate('count', $this->withoutSelectAliases($columns))
3321 ->get()->all();
3322 }
3323
3324 /**
3325 * Clone the existing query instance for usage in a pagination subquery.
3326 *
3327 * @return self
3328 */
3329 protected function cloneForPaginationCount()
3330 {
3331 return $this->cloneWithout(['orders', 'limit', 'offset'])
3332 ->cloneWithoutBindings(['order']);
3333 }
3334
3335 /**
3336 * Remove the column aliases since they will break count queries.
3337 *
3338 * @param array $columns
3339 * @return array
3340 */
3341 protected function withoutSelectAliases(array $columns)
3342 {
3343 return array_map(function ($column) {
3344 return is_string($column) && ($aliasPosition = stripos($column, ' as ')) !== false
3345 ? substr($column, 0, $aliasPosition) : $column;
3346 }, $columns);
3347 }
3348
3349 /**
3350 * Get a lazy collection for the given query.
3351 *
3352 * @return \FluentCommunity\Framework\Support\LazyCollection
3353 */
3354 public function cursor()
3355 {
3356 if (is_null($this->columns)) {
3357 $this->columns = ['*'];
3358 }
3359
3360 return (new LazyCollection(function () {
3361 yield from $this->connection->cursor(
3362 $this->toSql(), $this->getBindings(), ! $this->useWritePdo
3363 );
3364 }))->map(function ($item) {
3365 return $this->applyAfterQueryCallbacks(Helper::collect([$item]))->first();
3366 })->reject(fn ($item) => is_null($item));
3367 }
3368
3369 /**
3370 * Throw an exception if the query doesn't have an orderBy clause.
3371 *
3372 * @return void
3373 *
3374 * @throws \RuntimeException
3375 */
3376 protected function enforceOrderBy()
3377 {
3378 if (empty($this->orders) && empty($this->unionOrders)) {
3379 throw new RuntimeException('You must specify an orderBy clause when using this function.');
3380 }
3381 }
3382
3383 /**
3384 * Get a collection instance containing the values of a given column.
3385 *
3386 * @param string $column
3387 * @param string|null $key
3388 * @return \FluentCommunity\Framework\Support\Collection
3389 */
3390 public function pluck($column, $key = null)
3391 {
3392 // First, we will need to select the results of the query accounting for the
3393 // given columns / key. Once we have the results, we will be able to take
3394 // the results and get the exact data that was requested for the query.
3395 $queryResult = $this->onceWithColumns(
3396 is_null($key) ? [$column] : [$column, $key],
3397 function () {
3398 return $this->processor->processSelect(
3399 $this, $this->runSelect()
3400 );
3401 }
3402 );
3403
3404 if (empty($queryResult)) {
3405 return Helper::collect();
3406 }
3407
3408 // If the columns are qualified with a table or have an alias, we cannot use
3409 // those directly in the "pluck" operations since the results from the DB
3410 // are only keyed by the column itself. We'll strip the table out here.
3411 $column = $this->stripTableForPluck($column);
3412
3413 $key = $this->stripTableForPluck($key);
3414
3415 return $this->applyAfterQueryCallbacks(
3416 is_array($queryResult[0])
3417 ? $this->pluckFromArrayColumn($queryResult, $column, $key)
3418 : $this->pluckFromObjectColumn($queryResult, $column, $key)
3419 );
3420 }
3421
3422 /**
3423 * Strip off the table name or alias from a column identifier.
3424 *
3425 * @param string $column
3426 * @return string|null
3427 */
3428 protected function stripTableForPluck($column)
3429 {
3430 if (is_null($column)) {
3431 return $column;
3432 }
3433
3434 $columnString = $column instanceof ExpressionContract
3435 ? $this->grammar->getValue($column)
3436 : $column;
3437
3438 $separator = str_contains(strtolower($columnString), ' as ') ? ' as ' : '\.';
3439
3440 return Helper::last(preg_split('~'.$separator.'~i', $columnString));
3441 }
3442
3443 /**
3444 * Retrieve column values from rows represented as objects.
3445 *
3446 * @param array $queryResult
3447 * @param string $column
3448 * @param string $key
3449 * @return \FluentCommunity\Framework\Support\Collection
3450 */
3451 protected function pluckFromObjectColumn($queryResult, $column, $key)
3452 {
3453 $results = [];
3454
3455 if (is_null($key)) {
3456 foreach ($queryResult as $row) {
3457 $results[] = $row->$column;
3458 }
3459 } else {
3460 foreach ($queryResult as $row) {
3461 $results[$row->$key] = $row->$column;
3462 }
3463 }
3464
3465 return Helper::collect($results);
3466 }
3467
3468 /**
3469 * Retrieve column values from rows represented as arrays.
3470 *
3471 * @param array $queryResult
3472 * @param string $column
3473 * @param string $key
3474 * @return \FluentCommunity\Framework\Support\Collection
3475 */
3476 protected function pluckFromArrayColumn($queryResult, $column, $key)
3477 {
3478 $results = [];
3479
3480 if (is_null($key)) {
3481 foreach ($queryResult as $row) {
3482 $results[] = $row[$column];
3483 }
3484 } else {
3485 foreach ($queryResult as $row) {
3486 $results[$row[$key]] = $row[$column];
3487 }
3488 }
3489
3490 return Helper::collect($results);
3491 }
3492
3493 /**
3494 * Concatenate values of a given column as a string.
3495 *
3496 * @param string $column
3497 * @param string $glue
3498 * @return string
3499 */
3500 public function implode($column, $glue = '')
3501 {
3502 return $this->pluck($column)->implode($glue);
3503 }
3504
3505 /**
3506 * Determine if any rows exist for the current query.
3507 *
3508 * @return bool
3509 */
3510 public function exists()
3511 {
3512 $this->applyBeforeQueryCallbacks();
3513
3514 $results = $this->connection->select(
3515 $this->grammar->compileExists($this), $this->getBindings(), ! $this->useWritePdo
3516 );
3517
3518 // If the results has rows, we will get the row and see if the exists column is a
3519 // boolean true. If there is no results for this query we will return false as
3520 // there are no rows for this query at all and we can return that info here.
3521 if (isset($results[0])) {
3522 $results = (array) $results[0];
3523
3524 return (bool) $results['exists'];
3525 }
3526
3527 return false;
3528 }
3529
3530 /**
3531 * Determine if no rows exist for the current query.
3532 *
3533 * @return bool
3534 */
3535 public function doesntExist()
3536 {
3537 return ! $this->exists();
3538 }
3539
3540 /**
3541 * Execute the given callback if no rows exist for the current query.
3542 *
3543 * @param \Closure $callback
3544 * @return mixed
3545 */
3546 public function existsOr(Closure $callback)
3547 {
3548 return $this->exists() ? true : $callback();
3549 }
3550
3551 /**
3552 * Execute the given callback if rows exist for the current query.
3553 *
3554 * @param \Closure $callback
3555 * @return mixed
3556 */
3557 public function doesntExistOr(Closure $callback)
3558 {
3559 return $this->doesntExist() ? true : $callback();
3560 }
3561
3562 /**
3563 * Retrieve the "count" result of the query.
3564 *
3565 * @param string $columns
3566 * @return int
3567 */
3568 public function count($columns = '*')
3569 {
3570 return (int) $this->aggregate(__FUNCTION__, Arr::wrap($columns));
3571 }
3572
3573 /**
3574 * Retrieve the minimum value of a given column.
3575 *
3576 * @param string $column
3577 * @return mixed
3578 */
3579 public function min($column)
3580 {
3581 return $this->aggregate(__FUNCTION__, [$column]);
3582 }
3583
3584 /**
3585 * Retrieve the maximum value of a given column.
3586 *
3587 * @param string $column
3588 * @return mixed
3589 */
3590 public function max($column)
3591 {
3592 return $this->aggregate(__FUNCTION__, [$column]);
3593 }
3594
3595 /**
3596 * Retrieve the sum of the values of a given column.
3597 *
3598 * @param string $column
3599 * @return mixed
3600 */
3601 public function sum($column)
3602 {
3603 $result = $this->aggregate(__FUNCTION__, [$column]);
3604
3605 return $result ?: 0;
3606 }
3607
3608 /**
3609 * Retrieve the average of the values of a given column.
3610 *
3611 * @param string $column
3612 * @return mixed
3613 */
3614 public function avg($column)
3615 {
3616 return $this->aggregate(__FUNCTION__, [$column]);
3617 }
3618
3619 /**
3620 * Alias for the "avg" method.
3621 *
3622 * @param string $column
3623 * @return mixed
3624 */
3625 public function average($column)
3626 {
3627 return $this->avg($column);
3628 }
3629
3630 /**
3631 * Execute an aggregate function on the database.
3632 *
3633 * @param string $function
3634 * @param array $columns
3635 * @return mixed
3636 */
3637 public function aggregate($function, $columns = ['*'])
3638 {
3639 $results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns'])
3640 ->cloneWithoutBindings($this->unions || $this->havings ? [] : ['select'])
3641 ->setAggregate($function, $columns)
3642 ->get($columns);
3643
3644 if (! $results->isEmpty()) {
3645 return array_change_key_case((array) $results[0])['aggregate'];
3646 }
3647 }
3648
3649 /**
3650 * Execute a numeric aggregate function on the database.
3651 *
3652 * @param string $function
3653 * @param array $columns
3654 * @return float|int
3655 */
3656 public function numericAggregate($function, $columns = ['*'])
3657 {
3658 $result = $this->aggregate($function, $columns);
3659
3660 // If there is no result, we can obviously just return 0 here. Next, we will check
3661 // if the result is an integer or float. If it is already one of these two data
3662 // types we can just return the result as-is, otherwise we will convert this.
3663 if (! $result) {
3664 return 0;
3665 }
3666
3667 if (is_int($result) || is_float($result)) {
3668 return $result;
3669 }
3670
3671 // If the result doesn't contain a decimal place, we will assume it is an int then
3672 // cast it to one. When it does we will cast it to a float since it needs to be
3673 // cast to the expected data type for the developers out of pure convenience.
3674 return ! str_contains((string) $result, '.')
3675 ? (int) $result : (float) $result;
3676 }
3677
3678 /**
3679 * Set the aggregate property without running the query.
3680 *
3681 * @param string $function
3682 * @param array $columns
3683 * @return $this
3684 */
3685 protected function setAggregate($function, $columns)
3686 {
3687 $this->aggregate = compact('function', 'columns');
3688
3689 if (empty($this->groups)) {
3690 $this->orders = null;
3691
3692 $this->bindings['order'] = [];
3693 }
3694
3695 return $this;
3696 }
3697
3698 /**
3699 * Execute the given callback while selecting the given columns.
3700 *
3701 * After running the callback, the columns are reset to the original value.
3702 *
3703 * @param array $columns
3704 * @param callable $callback
3705 * @return mixed
3706 */
3707 protected function onceWithColumns($columns, $callback)
3708 {
3709 $original = $this->columns;
3710
3711 if (is_null($original)) {
3712 $this->columns = $columns;
3713 }
3714
3715 $result = $callback();
3716
3717 $this->columns = $original;
3718
3719 return $result;
3720 }
3721
3722 /**
3723 * Insert new records into the database.
3724 *
3725 * @param array $values
3726 * @return bool
3727 */
3728 public function insert(array $values)
3729 {
3730 // Since every insert gets treated like a batch insert, we will make sure the
3731 // bindings are structured in a way that is convenient when building these
3732 // inserts statements by verifying these elements are actually an array.
3733 if (empty($values)) {
3734 return true;
3735 }
3736
3737 if (! is_array(reset($values))) {
3738 $values = [$values];
3739 }
3740
3741 // Here, we will sort the insert keys for every record so that each insert is
3742 // in the same order for the record. We need to make sure this is the case
3743 // so there are not any errors or problems when inserting these records.
3744 else {
3745 foreach ($values as $key => $value) {
3746 ksort($value);
3747
3748 $values[$key] = $value;
3749 }
3750 }
3751
3752 $this->applyBeforeQueryCallbacks();
3753
3754 // Finally, we will run this query against the database connection and return
3755 // the results. We will need to also flatten these bindings before running
3756 // the query so they are all in one huge, flattened array for execution.
3757 return $this->connection->insert(
3758 $this->grammar->compileInsert($this, $values),
3759 $this->cleanBindings(Arr::flatten($values, 1))
3760 );
3761 }
3762
3763 /**
3764 * Insert new records into the database while ignoring errors.
3765 *
3766 * @param array $values
3767 * @return int
3768 */
3769 public function insertOrIgnore(array $values)
3770 {
3771 if (empty($values)) {
3772 return 0;
3773 }
3774
3775 if (! is_array(reset($values))) {
3776 $values = [$values];
3777 } else {
3778 foreach ($values as $key => $value) {
3779 ksort($value);
3780
3781 $values[$key] = $value;
3782 }
3783 }
3784
3785 $this->applyBeforeQueryCallbacks();
3786
3787 return $this->connection->affectingStatement(
3788 $this->grammar->compileInsertOrIgnore($this, $values),
3789 $this->cleanBindings(Arr::flatten($values, 1))
3790 );
3791 }
3792
3793 /**
3794 * Insert a new record and get the value of the primary key.
3795 *
3796 * @param array $values
3797 * @param string|null $sequence
3798 * @return int
3799 */
3800 public function insertGetId(array $values, $sequence = null)
3801 {
3802 $this->applyBeforeQueryCallbacks();
3803
3804 $sql = $this->grammar->compileInsertGetId($this, $values, $sequence);
3805
3806 $values = $this->cleanBindings($values);
3807
3808 return $this->processor->processInsertGetId($this, $sql, $values, $sequence);
3809 }
3810
3811 /**
3812 * Insert new records into the table using a subquery.
3813 *
3814 * @param array $columns
3815 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|string $query
3816 * @return int
3817 */
3818 public function insertUsing(array $columns, $query)
3819 {
3820 $this->applyBeforeQueryCallbacks();
3821
3822 [$sql, $bindings] = $this->createSub($query);
3823
3824 return $this->connection->affectingStatement(
3825 $this->grammar->compileInsertUsing($this, $columns, $sql),
3826 $this->cleanBindings($bindings)
3827 );
3828 }
3829
3830 /**
3831 * Insert new records into the table using a subquery while ignoring errors.
3832 *
3833 * @param array $columns
3834 * @param \Closure|\FluentCommunity\Framework\Database\Query\Builder|\FluentCommunity\Framework\Database\Eloquent\Builder<*>|string $query
3835 * @return int
3836 */
3837 public function insertOrIgnoreUsing(array $columns, $query)
3838 {
3839 $this->applyBeforeQueryCallbacks();
3840
3841 [$sql, $bindings] = $this->createSub($query);
3842
3843 return $this->connection->affectingStatement(
3844 $this->grammar->compileInsertOrIgnoreUsing($this, $columns, $sql),
3845 $this->cleanBindings($bindings)
3846 );
3847 }
3848
3849 /**
3850 * Update records in the database.
3851 *
3852 * @param array $values
3853 * @return int
3854 */
3855 public function update(array $values)
3856 {
3857 $this->applyBeforeQueryCallbacks();
3858
3859 $values = Helper::collect($values)->map(function ($value) {
3860 if (! $value instanceof Builder) {
3861 return ['value' => $value, 'bindings' => $value];
3862 }
3863
3864 [$query, $bindings] = $this->parseSub($value);
3865
3866 return ['value' => new Expression("({$query})"), 'bindings' => fn () => $bindings];
3867 });
3868
3869 $sql = $this->grammar->compileUpdate($this, $values->map(fn ($value) => $value['value'])->all());
3870
3871 return $this->connection->update($sql, $this->cleanBindings(
3872 $this->grammar->prepareBindingsForUpdate($this->bindings, $values->map(fn ($value) => $value['bindings'])->all())
3873 ));
3874 }
3875
3876 /**
3877 * Update records in a PostgreSQL database using the update from syntax.
3878 *
3879 * @param array $values
3880 * @return int
3881 */
3882 public function updateFrom(array $values)
3883 {
3884 if (! method_exists($this->grammar, 'compileUpdateFrom')) {
3885 throw new LogicException('This database engine does not support the updateFrom method.');
3886 }
3887
3888 $this->applyBeforeQueryCallbacks();
3889
3890 $sql = $this->grammar->compileUpdateFrom($this, $values);
3891
3892 return $this->connection->update($sql, $this->cleanBindings(
3893 $this->grammar->prepareBindingsForUpdateFrom($this->bindings, $values)
3894 ));
3895 }
3896
3897 /**
3898 * Insert or update a record matching the attributes, and fill it with values.
3899 *
3900 * @param array $attributes
3901 * @param array $values
3902 * @return bool
3903 */
3904 public function updateOrInsert(array $attributes, $values = [])
3905 {
3906 $exists = $this->where($attributes)->exists();
3907
3908 if ($values instanceof Closure) {
3909 $values = $values($exists);
3910 }
3911
3912 if (! $exists) {
3913 return $this->insert(array_merge($attributes, $values));
3914 }
3915
3916 if (empty($values)) {
3917 return true;
3918 }
3919
3920 return (bool) $this->limit(1)->update($values);
3921 }
3922
3923 /**
3924 * Insert new records or update the existing ones.
3925 *
3926 * @param array $values
3927 * @param array|string $uniqueBy
3928 * @param array|null $update
3929 * @return int
3930 */
3931 public function upsert(array $values, $uniqueBy, $update = null)
3932 {
3933 if (empty($values)) {
3934 return 0;
3935 } elseif ($update === []) {
3936 return (int) $this->insert($values);
3937 }
3938
3939 if (! is_array(reset($values))) {
3940 $values = [$values];
3941 } else {
3942 foreach ($values as $key => $value) {
3943 ksort($value);
3944
3945 $values[$key] = $value;
3946 }
3947 }
3948
3949 if (is_null($update)) {
3950 $update = array_keys(reset($values));
3951 }
3952
3953 $this->applyBeforeQueryCallbacks();
3954
3955 $bindings = $this->cleanBindings(array_merge(
3956 Arr::flatten($values, 1),
3957 Helper::collect($update)->reject(function ($value, $key) {
3958 return is_int($key);
3959 })->all()
3960 ));
3961
3962 return $this->connection->affectingStatement(
3963 $this->grammar->compileUpsert($this, $values, (array) $uniqueBy, $update),
3964 $bindings
3965 );
3966 }
3967
3968 /**
3969 * Increment a column's value by a given amount.
3970 *
3971 * @param string $column
3972 * @param float|int $amount
3973 * @param array $extra
3974 * @return int
3975 *
3976 * @throws \InvalidArgumentException
3977 */
3978 public function increment($column, $amount = 1, array $extra = [])
3979 {
3980 if (! is_numeric($amount)) {
3981 throw new InvalidArgumentException('Non-numeric value passed to increment method.');
3982 }
3983
3984 return $this->incrementEach([$column => $amount], $extra);
3985 }
3986
3987 /**
3988 * Increment the given column's values by the given amounts.
3989 *
3990 * @param array<string, float|int|numeric-string> $columns
3991 * @param array<string, mixed> $extra
3992 * @return int
3993 *
3994 * @throws \InvalidArgumentException
3995 */
3996 public function incrementEach(array $columns, array $extra = [])
3997 {
3998 foreach ($columns as $column => $amount) {
3999 if (! is_numeric($amount)) {
4000 throw new InvalidArgumentException("Non-numeric value passed as increment amount for column: '$column'.");
4001 } elseif (! is_string($column)) {
4002 throw new InvalidArgumentException('Non-associative array passed to incrementEach method.');
4003 }
4004
4005 $columns[$column] = $this->raw("{$this->grammar->wrap($column)} + $amount");
4006 }
4007
4008 return $this->update(array_merge($columns, $extra));
4009 }
4010
4011 /**
4012 * Decrement a column's value by a given amount.
4013 *
4014 * @param string $column
4015 * @param float|int $amount
4016 * @param array $extra
4017 * @return int
4018 *
4019 * @throws \InvalidArgumentException
4020 */
4021 public function decrement($column, $amount = 1, array $extra = [])
4022 {
4023 if (! is_numeric($amount)) {
4024 throw new InvalidArgumentException('Non-numeric value passed to decrement method.');
4025 }
4026
4027 return $this->decrementEach([$column => $amount], $extra);
4028 }
4029
4030 /**
4031 * Decrement the given column's values by the given amounts.
4032 *
4033 * @param array<string, float|int|numeric-string> $columns
4034 * @param array<string, mixed> $extra
4035 * @return int
4036 *
4037 * @throws \InvalidArgumentException
4038 */
4039 public function decrementEach(array $columns, array $extra = [])
4040 {
4041 foreach ($columns as $column => $amount) {
4042 if (! is_numeric($amount)) {
4043 throw new InvalidArgumentException("Non-numeric value passed as decrement amount for column: '$column'.");
4044 } elseif (! is_string($column)) {
4045 throw new InvalidArgumentException('Non-associative array passed to decrementEach method.');
4046 }
4047
4048 $columns[$column] = $this->raw("{$this->grammar->wrap($column)} - $amount");
4049 }
4050
4051 return $this->update(array_merge($columns, $extra));
4052 }
4053
4054 /**
4055 * Delete records from the database.
4056 *
4057 * @param mixed $id
4058 * @return int
4059 */
4060 public function delete($id = null)
4061 {
4062 // If an ID is passed to the method, we will set the where clause to check the
4063 // ID to let developers to simply and quickly remove a single row from this
4064 // database without manually specifying the "where" clauses on the query.
4065 if (! is_null($id)) {
4066 $this->where($this->from.'.id', '=', $id);
4067 }
4068
4069 $this->applyBeforeQueryCallbacks();
4070
4071 return $this->connection->delete(
4072 $this->grammar->compileDelete($this), $this->cleanBindings(
4073 $this->grammar->prepareBindingsForDelete($this->bindings)
4074 )
4075 );
4076 }
4077
4078 /**
4079 * Run a truncate statement on the table.
4080 *
4081 * @return void
4082 */
4083 public function truncate()
4084 {
4085 $this->applyBeforeQueryCallbacks();
4086
4087 foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) {
4088 $this->connection->statement($sql, $bindings);
4089 }
4090 }
4091
4092 /**
4093 * Get a new instance of the query builder.
4094 *
4095 * @return \FluentCommunity\Framework\Database\Query\Builder
4096 */
4097 public function newQuery()
4098 {
4099 return new static($this->connection, $this->grammar, $this->processor);
4100 }
4101
4102 /**
4103 * Create a new query instance for a sub-query.
4104 *
4105 * @return \FluentCommunity\Framework\Database\Query\Builder
4106 */
4107 protected function forSubQuery()
4108 {
4109 return $this->newQuery();
4110 }
4111
4112 /**
4113 * Get all of the query builder's columns in a text-only array with all expressions evaluated.
4114 *
4115 * @return array
4116 */
4117 public function getColumns()
4118 {
4119 return ! is_null($this->columns)
4120 ? array_map(fn ($column) => $this->grammar->getValue($column), $this->columns)
4121 : [];
4122 }
4123
4124 /**
4125 * Create a raw database expression.
4126 *
4127 * @param mixed $value
4128 * @return \FluentCommunity\Framework\Database\Query\Expression
4129 */
4130 public function raw($value)
4131 {
4132 return $this->connection->raw($value);
4133 }
4134
4135 /**
4136 * Get the query builder instances that are used in the union of the query.
4137 *
4138 * @return \Illuminate\Support\Collection
4139 */
4140 protected function getUnionBuilders()
4141 {
4142 return isset($this->unions)
4143 ? Helper::collect($this->unions)->pluck('query')
4144 : Helper::collect();
4145 }
4146
4147 /**
4148 * Get the current query value bindings in a flattened array.
4149 *
4150 * @return array
4151 */
4152 public function getBindings()
4153 {
4154 return Arr::flatten($this->bindings);
4155 }
4156
4157 /**
4158 * Get the raw array of bindings.
4159 *
4160 * @return array
4161 */
4162 public function getRawBindings()
4163 {
4164 return $this->bindings;
4165 }
4166
4167 /**
4168 * Set the bindings on the query builder.
4169 *
4170 * @param array $bindings
4171 * @param string $type
4172 * @return $this
4173 *
4174 * @throws \InvalidArgumentException
4175 */
4176 public function setBindings(array $bindings, $type = 'where')
4177 {
4178 if (! array_key_exists($type, $this->bindings)) {
4179 throw new InvalidArgumentException("Invalid binding type: {$type}.");
4180 }
4181
4182 $this->bindings[$type] = $bindings;
4183
4184 return $this;
4185 }
4186
4187 /**
4188 * Add a binding to the query.
4189 *
4190 * @param mixed $value
4191 * @param string $type
4192 * @return $this
4193 *
4194 * @throws \InvalidArgumentException
4195 */
4196 public function addBinding($value, $type = 'where')
4197 {
4198 if (! array_key_exists($type, $this->bindings)) {
4199 throw new InvalidArgumentException("Invalid binding type: {$type}.");
4200 }
4201
4202 if (is_array($value)) {
4203 $this->bindings[$type] = array_values(array_map(
4204 [$this, 'castBinding'],
4205 array_merge($this->bindings[$type], $value),
4206 ));
4207 } else {
4208 $this->bindings[$type][] = $this->castBinding($value);
4209 }
4210
4211 return $this;
4212 }
4213
4214 /**
4215 * Cast the given binding value.
4216 *
4217 * @param mixed $value
4218 * @return mixed
4219 */
4220 public function castBinding($value)
4221 {
4222 if (function_exists('enum_exists')) {
4223 if ($value instanceof \BackedEnum) {
4224 return $value->value;
4225 }
4226 }
4227
4228 return $value;
4229 }
4230
4231 /**
4232 * Merge an array of bindings into our bindings.
4233 *
4234 * @param \FluentCommunity\Framework\Database\Query\Builder $query
4235 * @return $this
4236 */
4237 public function mergeBindings(self $query)
4238 {
4239 $this->bindings = array_merge_recursive($this->bindings, $query->bindings);
4240
4241 return $this;
4242 }
4243
4244 /**
4245 * Remove all of the expressions from a list of bindings.
4246 *
4247 * @param array $bindings
4248 * @return array
4249 */
4250 public function cleanBindings(array $bindings)
4251 {
4252 return Helper::collect($bindings)
4253 ->reject(function ($binding) {
4254 return $binding instanceof Expression;
4255 })
4256 ->map([$this, 'castBinding'])
4257 ->values()
4258 ->all();
4259 }
4260
4261 /**
4262 * Get a scalar type value from an unknown type of input.
4263 *
4264 * @param mixed $value
4265 * @return mixed
4266 */
4267 protected function flattenValue($value)
4268 {
4269 return is_array($value) ? Helper::head(Arr::flatten($value)) : $value;
4270 }
4271
4272 /**
4273 * Get the default key name of the table.
4274 *
4275 * @return string
4276 */
4277 protected function defaultKeyName()
4278 {
4279 return 'id';
4280 }
4281
4282 /**
4283 * Get the database connection instance.
4284 *
4285 * @return \FluentCommunity\Framework\Database\ConnectionInterface
4286 */
4287 public function getConnection()
4288 {
4289 return $this->connection;
4290 }
4291
4292 /**
4293 * Get the database query processor instance.
4294 *
4295 * @return \FluentCommunity\Framework\Database\Query\Processors\Processor
4296 */
4297 public function getProcessor()
4298 {
4299 return $this->processor;
4300 }
4301
4302 /**
4303 * Get the query grammar instance.
4304 *
4305 * @return \FluentCommunity\Framework\Database\Query\Grammars\MySqlGrammar|\FluentCommunity\Framework\Database\Query\Grammars\SQLiteGrammar
4306 */
4307 public function getGrammar()
4308 {
4309 return $this->grammar;
4310 }
4311
4312 /**
4313 * Use the write pdo for query.
4314 *
4315 * @return $this
4316 */
4317 public function useWritePdo()
4318 {
4319 $this->useWritePdo = true;
4320
4321 return $this;
4322 }
4323
4324 /**
4325 * Determine if the value is a query builder instance or a Closure.
4326 *
4327 * @param mixed $value
4328 * @return bool
4329 */
4330 protected function isQueryable($value)
4331 {
4332 return $value instanceof self ||
4333 $value instanceof OrmBuilder ||
4334 $value instanceof Relation ||
4335 $value instanceof Closure;
4336 }
4337
4338 /**
4339 * Clone the query.
4340 *
4341 * @return static
4342 */
4343 public function clone()
4344 {
4345 return clone $this;
4346 }
4347
4348 /**
4349 * Clone the query without the given properties.
4350 *
4351 * @param array $properties
4352 * @return static
4353 */
4354 public function cloneWithout(array $properties)
4355 {
4356 return Helper::tap($this->clone(), function ($clone) use ($properties) {
4357 foreach ($properties as $property) {
4358 $clone->{$property} = null;
4359 }
4360 });
4361 }
4362
4363 /**
4364 * Clone the query without the given bindings.
4365 *
4366 * @param array $except
4367 * @return static
4368 */
4369 public function cloneWithoutBindings(array $except)
4370 {
4371 return Helper::tap($this->clone(), function ($clone) use ($except) {
4372 foreach ($except as $type) {
4373 $clone->bindings[$type] = [];
4374 }
4375 });
4376 }
4377
4378 /**
4379 * Handle dynamic method calls into the method.
4380 *
4381 * @param string $method
4382 * @param array $parameters
4383 * @return mixed
4384 *
4385 * @throws \BadMethodCallException
4386 */
4387 public function __call($method, $parameters)
4388 {
4389 if (static::hasMacro($method)) {
4390 return $this->macroCall($method, $parameters);
4391 }
4392
4393 if (Str::startsWith($method, 'where')) {
4394 return $this->dynamicWhere($method, $parameters);
4395 }
4396
4397 static::throwBadMethodCallException($method);
4398 }
4399
4400 /**
4401 * Set a dynamic property.
4402 *
4403 * @param string $key
4404 * @param mixed $value
4405 */
4406 public function __set($key, $value)
4407 {
4408 $this->dynamicProperties[$key] = $value;
4409 }
4410
4411 /**
4412 * Get dynamically injected value.
4413 *
4414 * @param string $key
4415 * @return mixed
4416 */
4417 public function __get($key)
4418 {
4419 return $this->dynamicProperties[$key] ?? null;
4420 }
4421 }
4422