PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.20
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.20
2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 1.45 All 41 releases
fluent-boards / vendor / wpfluent / framework / src / WPFluent / Database / Query / Builder.php

Builder.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.20, at vendor/wpfluent/framework/src/WPFluent/Database/Query/Builder.php

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