Issues (99)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/query/Builder.php (10 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Childish\query;
3
4
use Closure;
5
use RuntimeException;
6
use Childish\connection\ConnectionInterface;
7
use Childish\support\Collection;
8
use InvalidArgumentException;
9
use Childish\support\Tools;
10
11
12
/**
13
 * Builder
14
 *
15
 * @author    Pu ShaoWei <[email protected]>
16
 * @date      2017/12/6
17
 * @package   App\db
18
 * @version   1.0
19
 */
20
class Builder
21
{
22
    /**
23
     * The base query builder instance.
24
     *
25
     * @var \Childish\connection\ConnectionInterface
26
     */
27
    public $connection;
28
29
    /**
30
     * The database query grammar instance.
31
     *
32
     * @var \Childish\query\Grammar
33
     */
34
    public $grammar;
35
36
    /**
37
     * The database query post processor instance.
38
     *
39
     * @var \Childish\query\Processor
40
     */
41
    public $processor;
42
43
    /**
44
     * The current query value bindings.
45
     *
46
     * @var array
47
     */
48
    public $bindings = [
49
        'select' => [],
50
        'join'   => [],
51
        'where'  => [],
52
        'having' => [],
53
        'order'  => [],
54
        'union'  => [],
55
    ];
56
57
    /**
58
     * An aggregate function and column to be run.
59
     *
60
     * @var array
61
     */
62
    public $aggregate;
63
64
    /**
65
     * The columns that should be returned.
66
     *
67
     * @var array
68
     */
69
    public $columns;
70
71
    /**
72
     * Indicates if the query returns distinct results.
73
     *
74
     * @var bool
75
     */
76
    public $distinct = false;
77
78
    /**
79
     * The table which the query is targeting.
80
     *
81
     * @var string
82
     */
83
    public $from;
84
85
    /**
86
     * The table joins for the query.
87
     *
88
     * @var array
89
     */
90
    public $joins;
91
92
    /**
93
     * The where constraints for the query.
94
     *
95
     * @var array
96
     */
97
    public $wheres = [];
98
99
    /**
100
     * The groupings for the query.
101
     *
102
     * @var array
103
     */
104
    public $groups;
105
106
    /**
107
     * The having constraints for the query.
108
     *
109
     * @var array
110
     */
111
    public $havings;
112
113
    /**
114
     * The orderings for the query.
115
     *
116
     * @var array
117
     */
118
    public $orders;
119
120
    /**
121
     * The maximum number of records to return.
122
     *
123
     * @var int
124
     */
125
    public $limit;
126
127
    /**
128
     * The number of records to skip.
129
     *
130
     * @var int
131
     */
132
    public $offset;
133
134
    /**
135
     * The query union statements.
136
     *
137
     * @var array
138
     */
139
    public $unions;
140
141
    /**
142
     * The maximum number of union records to return.
143
     *
144
     * @var int
145
     */
146
    public $unionLimit;
147
148
    /**
149
     * The number of union records to skip.
150
     *
151
     * @var int
152
     */
153
    public $unionOffset;
154
155
    /**
156
     * The orderings for the union query.
157
     *
158
     * @var array
159
     */
160
    public $unionOrders;
161
162
    /**
163
     * Indicates whether row locking is being used.
164
     *
165
     * @var string|bool
166
     */
167
    public $lock;
168
169
    /**
170
     * All of the available clause operators.
171
     *
172
     * @var array
173
     */
174
    public $operators = [
175
        '=', '<', '>', '<=', '>=', '<>', '!=', '<=>',
176
        'like', 'like binary', 'not like', 'between', 'ilike',
177
        '&', '|', '^', '<<', '>>',
178
        'rlike', 'regexp', 'not regexp',
179
        '~', '~*', '!~', '!~*', 'similar to',
180
        'not similar to', 'not ilike', '~~*', '!~~*',
181
    ];
182
183
    /**
184
     * Whether use write pdo for select.
185
     *
186
     * @var bool
187
     */
188
    public $useWritePdo = false;
189
190
191
    /**
192
     * Builder constructor.
193
     *
194
     * @param \Childish\connection\ConnectionInterface $connection
195
     * @param \Childish\query\Grammar|null             $grammar
196
     * @param \Childish\query\Processor|null           $processor
197
     */
198
    public function __construct(
199
        ConnectionInterface $connection,
200
        Grammar $grammar = null,
201
        Processor $processor = null)
202
    {
203
        $this->connection = $connection;
204
        $this->grammar    = $grammar;
205
        $this->processor  = $processor;
206
    }
207
208
    /**
209
     * Set the columns to be selected.
210
     *
211
     * @param  array|mixed $columns
212
     * @return $this
213
     */
214
    public function select($columns = ['*'])
215
    {
216
        $this->columns = is_array($columns) ? $columns : func_get_args();
217
218
        return $this;
219
    }
220
221
    /**
222
     * Parse the sub-select query into SQL and bindings.
223
     *
224
     * @param  mixed $query
225
     * @return array
226
     */
227
    protected function parseSubSelect($query)
228
    {
229
        if ($query instanceof self) {
230
            $query->columns = [$query->columns[0]];
231
232
            return [$query->toSql(), $query->getBindings()];
233
        } else if (is_string($query)) {
234
            return [$query, []];
235
        } else {
236
            throw new InvalidArgumentException;
237
        }
238
    }
239
240
    /**
241
     * Add a new select column to the query.
242
     *
243
     * @param  array|mixed $column
244
     * @return $this
245
     */
246
    public function addSelect($column)
247
    {
248
        $column = is_array($column) ? $column : func_get_args();
249
250
        $this->columns = array_merge((array)$this->columns, $column);
251
252
        return $this;
253
    }
254
255
    /**
256
     * Force the query to only return distinct results.
257
     *
258
     * @return $this
259
     */
260
    public function distinct()
261
    {
262
        $this->distinct = true;
263
264
        return $this;
265
    }
266
267
    /**
268
     * Set the table which the query is targeting.
269
     *
270
     * @param  string $table
271
     * @return $this
272
     */
273
    public function from($table)
274
    {
275
        $this->from = $table;
276
277
        return $this;
278
    }
279
280
    /**
281
     * Merge an array of where clauses and bindings.
282
     *
283
     * @param  array $wheres
284
     * @param  array $bindings
285
     * @return void
286
     */
287
    public function mergeWheres($wheres, $bindings)
288
    {
289
        $this->wheres = array_merge($this->wheres, (array)$wheres);
290
291
        $this->bindings['where'] = array_values(
292
            array_merge($this->bindings['where'], (array)$bindings)
293
        );
294
    }
295
296
    /**
297
     * Add a basic where clause to the query.
298
     *
299
     * @param  string|array|\Closure $column
300
     * @param  string|null           $operator
301
     * @param  mixed                 $value
302
     * @param  string                $boolean
303
     * @return $this
304
     */
305
    public function where($column, $operator = null, $value = null, $boolean = 'and')
306
    {
307
        // If the column is an array, we will assume it is an array of key-value pairs
308
        // and can add them each as a where clause. We will maintain the boolean we
309
        // received when the method was called and pass it into the nested where.
310
        if (is_array($column)) {
311
            return $this->addArrayOfWheres($column, $boolean);
312
        }
313
314
        // Here we will make some assumptions about the operator. If only 2 values are
315
        // passed to the method, we will assume that the operator is an equals sign
316
        // and keep going. Otherwise, we'll require the operator to be passed in.
317
        list($value, $operator) = $this->prepareValueAndOperator(
318
            $value, $operator, func_num_args() == 2
319
        );
320
321
        // If the columns is actually a Closure instance, we will assume the developer
322
        // wants to begin a nested where statement which is wrapped in parenthesis.
323
        // We'll add that Closure to the query then return back out immediately.
324
        if ($column instanceof Closure) {
325
            return $this->whereNested($column, $boolean);
326
        }
327
328
        // If the given operator is not found in the list of valid operators we will
329
        // assume that the developer is just short-cutting the '=' operators and
330
        // we will set the operators to '=' and set the values appropriately.
331
        if ($this->invalidOperator($operator)) {
332
            list($value, $operator) = [$operator, '='];
333
        }
334
335
        // If the value is a Closure, it means the developer is performing an entire
336
        // sub-select within the query and we will need to compile the sub-select
337
        // within the where clause to get the appropriate query record results.
338
        if ($value instanceof Closure) {
339
            return $this->whereSub($column, $operator, $value, $boolean);
0 ignored issues
show
The method whereSub() does not seem to exist on object<Childish\query\Builder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
340
        }
341
342
        // If the value is "null", we will just assume the developer wants to add a
343
        // where null clause to the query. So, we will allow a short-cut here to
344
        // that method for convenience so the developer doesn't have to check.
345
        if (is_null($value)) {
346
            return $this->whereNull($column, $boolean, $operator !== '=');
347
        }
348
349
        // Now that we are working with just a simple query we can put the elements
350
        // in our array and add the query binding to our array of bindings that
351
        // will be bound to each SQL statements when it is finally executed.
352
        $type = 'Basic';
353
354
        $this->wheres[] = compact(
355
            'type', 'column', 'operator', 'value', 'boolean'
356
        );
357
358
        $this->addBinding($value, 'where');
359
        return $this;
360
    }
361
362
    /**
363
     * Add an array of where clauses to the query.
364
     *
365
     * @param  array  $column
366
     * @param  string $boolean
367
     * @param  string $method
368
     * @return $this
369
     */
370
    protected function addArrayOfWheres($column, $boolean, $method = 'where')
371
    {
372
        return $this->whereNested(function ($query) use ($column, $method, $boolean) {
373
            foreach ($column as $key => $value) {
374
                if (is_numeric($key) && is_array($value)) {
375
                    $query->{$method}(...array_values($value));
376
                } else {
377
                    $query->$method($key, '=', $value, $boolean);
378
                }
379
            }
380
        }, $boolean);
381
    }
382
383
    /**
384
     * Prepare the value and operator for a where clause.
385
     *
386
     * @param  string $value
387
     * @param  string $operator
388
     * @param  bool   $useDefault
389
     * @return array
390
     * @throws \InvalidArgumentException
391
     */
392
    protected function prepareValueAndOperator($value, $operator, $useDefault = false)
393
    {
394
        if ($useDefault) {
395
            return [$operator, '='];
396
        } else if ($this->invalidOperatorAndValue($operator, $value)) {
397
            throw new InvalidArgumentException('Illegal operator and value combination.');
398
        }
399
400
        return [$value, $operator];
401
    }
402
403
    /**
404
     * Determine if the given operator and value combination is legal.
405
     * Prevents using Null values with invalid operators.
406
     *
407
     * @param  string $operator
408
     * @param  mixed  $value
409
     * @return bool
410
     */
411
    protected function invalidOperatorAndValue($operator, $value)
412
    {
413
        return is_null($value) && in_array($operator, $this->operators) &&
414
               !in_array($operator, ['=', '<>', '!=']);
415
    }
416
417
    /**
418
     * Determine if the given operator is supported.
419
     *
420
     * @param  string $operator
421
     * @return bool
422
     */
423
    protected function invalidOperator($operator)
424
    {
425
        return !in_array(strtolower($operator), $this->operators, true) &&
426
               !in_array(strtolower($operator), $this->grammar->getOperators(), true);
427
    }
428
429
    /**
430
     * Add an "or where" clause to the query.
431
     *
432
     * @param  string|array|\Closure $column
433
     * @param  string|null           $operator
434
     * @param  mixed                 $value
435
     * @return \Childish\query\Builder|static
436
     */
437
    public function orWhere($column, $operator = null, $value = null)
438
    {
439
        return $this->where($column, $operator, $value, 'or');
440
    }
441
442
    /**
443
     * Add a "where" clause comparing two columns to the query.
444
     *
445
     * @param  string|array $first
446
     * @param  string|null  $operator
447
     * @param  string|null  $second
448
     * @param  string|null  $boolean
449
     * @return \Childish\query\Builder|static
450
     */
451
    public function whereColumn($first, $operator = null, $second = null, $boolean = 'and')
452
    {
453
        // If the column is an array, we will assume it is an array of key-value pairs
454
        // and can add them each as a where clause. We will maintain the boolean we
455
        // received when the method was called and pass it into the nested where.
456
        if (is_array($first)) {
457
            return $this->addArrayOfWheres($first, $boolean, 'whereColumn');
458
        }
459
460
        // If the given operator is not found in the list of valid operators we will
461
        // assume that the developer is just short-cutting the '=' operators and
462
        // we will set the operators to '=' and set the values appropriately.
463
        if ($this->invalidOperator($operator)) {
464
            list($second, $operator) = [$operator, '='];
465
        }
466
467
        // Finally, we will add this where clause into this array of clauses that we
468
        // are building for the query. All of them will be compiled via a grammar
469
        // once the query is about to be executed and run against the database.
470
        $type = 'Column';
471
472
        $this->wheres[] = compact(
473
            'type', 'first', 'operator', 'second', 'boolean'
474
        );
475
476
        return $this;
477
    }
478
479
    /**
480
     * Add an "or where" clause comparing two columns to the query.
481
     *
482
     * @param  string|array $first
483
     * @param  string|null  $operator
484
     * @param  string|null  $second
485
     * @return \Childish\query\Builder|static
486
     */
487
    public function orWhereColumn($first, $operator = null, $second = null)
488
    {
489
        return $this->whereColumn($first, $operator, $second, 'or');
490
    }
491
492
    /**
493
     * Add a raw where clause to the query.
494
     *
495
     * @param  string $sql
496
     * @param  mixed  $bindings
497
     * @param  string $boolean
498
     * @return $this
499
     */
500
    public function whereRaw($sql, $bindings = [], $boolean = 'and')
501
    {
502
        $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean];
503
504
        $this->addBinding((array)$bindings, 'where');
505
506
        return $this;
507
    }
508
509
    /**
510
     * Add a raw or where clause to the query.
511
     *
512
     * @param  string $sql
513
     * @param  mixed  $bindings
514
     * @return \Childish\query\Builder|static
515
     */
516
    public function orWhereRaw($sql, $bindings = [])
517
    {
518
        return $this->whereRaw($sql, $bindings, 'or');
519
    }
520
521
    /**
522
     * Add a "where in" clause to the query.
523
     *
524
     * @param  string $column
525
     * @param  mixed  $values
526
     * @param  string $boolean
527
     * @param  bool   $not
528
     * @return $this
529
     */
530
    public function whereIn($column, $values, $boolean = 'and', $not = false)
531
    {
532
        $type = $not ? 'NotIn' : 'In';
533
534
        if ($values instanceof Builder) {
535
            $values = $values->getQuery();
0 ignored issues
show
The method getQuery() does not seem to exist on object<Childish\query\Builder>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
536
        }
537
538
        // If the value is a query builder instance we will assume the developer wants to
539
        // look for any values that exists within this given query. So we will add the
540
        // query accordingly so that this query is properly executed when it is run.
541
        if ($values instanceof self) {
542
            return $this->whereInExistingQuery(
543
                $column, $values, $boolean, $not
544
            );
545
        }
546
547
548
        // If the value of the where in clause is actually a Closure, we will assume that
549
        // the developer is using a full sub-select for this "in" statement, and will
550
        // execute those Closures, then we can re-construct the entire sub-selects.
551
        if ($values instanceof \Closure) {
552
            return $this->whereInSub($column, $values, $boolean, $not);
0 ignored issues
show
The method whereInSub() does not exist on Childish\query\Builder. Did you maybe mean whereIn()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
553
        }
554
        foreach ($values as $value) {
555
            $this->addBinding($value, 'where');
556
        }
557
        $this->wheres[] = compact('type', 'column', 'values', 'boolean');
558
        return $this;
559
    }
560
561
    /**
562
     * Add an "or where in" clause to the query.
563
     *
564
     * @param  string $column
565
     * @param  mixed  $values
566
     * @return \Childish\query\Builder|static
567
     */
568
    public function orWhereIn($column, $values)
569
    {
570
        return $this->whereIn($column, $values, 'or');
571
    }
572
573
    /**
574
     * Add a "where not in" clause to the query.
575
     *
576
     * @param  string $column
577
     * @param  mixed  $values
578
     * @param  string $boolean
579
     * @return \Childish\query\Builder|static
580
     */
581
    public function whereNotIn($column, $values, $boolean = 'and')
582
    {
583
        return $this->whereIn($column, $values, $boolean, true);
584
    }
585
586
    /**
587
     * Add an "or where not in" clause to the query.
588
     *
589
     * @param  string $column
590
     * @param  mixed  $values
591
     * @return \Childish\query\Builder|static
592
     */
593
    public function orWhereNotIn($column, $values)
594
    {
595
        return $this->whereNotIn($column, $values, 'or');
596
    }
597
598
599
    /**
600
     * Add an external sub-select to the query.
601
     *
602
     * @param  string                         $column
603
     * @param  \Childish\query\Builder|static $query
604
     * @param  string                         $boolean
605
     * @param  bool                           $not
606
     * @return $this
607
     */
608
    protected function whereInExistingQuery($column, $query, $boolean, $not)
609
    {
610
        $type = $not ? 'NotInSub' : 'InSub';
611
612
        $this->wheres[] = compact('type', 'column', 'query', 'boolean');
613
614
        $this->addBinding($query->getBindings(), 'where');
615
616
        return $this;
617
    }
618
619
    /**
620
     * Add a "where null" clause to the query.
621
     *
622
     * @param  string $column
623
     * @param  string $boolean
624
     * @param  bool   $not
625
     * @return $this
626
     */
627
    public function whereNull($column, $boolean = 'and', $not = false)
628
    {
629
        $type = $not ? 'NotNull' : 'Null';
630
631
        $this->wheres[] = compact('type', 'column', 'boolean');
632
633
        return $this;
634
    }
635
636
    /**
637
     * Add an "or where null" clause to the query.
638
     *
639
     * @param  string $column
640
     * @return \Childish\query\Builder|static
641
     */
642
    public function orWhereNull($column)
643
    {
644
        return $this->whereNull($column, 'or');
645
    }
646
647
    /**
648
     * Add a "where not null" clause to the query.
649
     *
650
     * @param  string $column
651
     * @param  string $boolean
652
     * @return \Childish\query\Builder|static
653
     */
654
    public function whereNotNull($column, $boolean = 'and')
655
    {
656
        return $this->whereNull($column, $boolean, true);
657
    }
658
659
    /**
660
     * Add a where between statement to the query.
661
     *
662
     * @param  string $column
663
     * @param  array  $values
664
     * @param  string $boolean
665
     * @param  bool   $not
666
     * @return $this
667
     */
668
    public function whereBetween($column, array $values, $boolean = 'and', $not = false)
669
    {
670
        $type = 'between';
671
672
        $this->wheres[] = compact('column', 'type', 'boolean', 'not');
673
674
        $this->addBinding($values, 'where');
675
676
        return $this;
677
    }
678
679
    /**
680
     * Add an or where between statement to the query.
681
     *
682
     * @param  string $column
683
     * @param  array  $values
684
     * @return \Childish\query\Builder|static
685
     */
686
    public function orWhereBetween($column, array $values)
687
    {
688
        return $this->whereBetween($column, $values, 'or');
689
    }
690
691
    /**
692
     * Add a where not between statement to the query.
693
     *
694
     * @param  string $column
695
     * @param  array  $values
696
     * @param  string $boolean
697
     * @return \Childish\query\Builder|static
698
     */
699
    public function whereNotBetween($column, array $values, $boolean = 'and')
700
    {
701
        return $this->whereBetween($column, $values, $boolean, true);
702
    }
703
704
    /**
705
     * Add an or where not between statement to the query.
706
     *
707
     * @param  string $column
708
     * @param  array  $values
709
     * @return \Childish\query\Builder|static
710
     */
711
    public function orWhereNotBetween($column, array $values)
712
    {
713
        return $this->whereNotBetween($column, $values, 'or');
714
    }
715
716
    /**
717
     * Add an "or where not null" clause to the query.
718
     *
719
     * @param  string $column
720
     * @return \Childish\query\Builder|static
721
     */
722
    public function orWhereNotNull($column)
723
    {
724
        return $this->whereNotNull($column, 'or');
725
    }
726
727
    /**
728
     * Add a "where date" statement to the query.
729
     *
730
     * @param  string $column
731
     * @param  string $operator
732
     * @param  mixed  $value
733
     * @param  string $boolean
734
     * @return \Childish\query\Builder|static
735
     */
736
    public function whereDate($column, $operator, $value = null, $boolean = 'and')
737
    {
738
        list($value, $operator) = $this->prepareValueAndOperator(
739
            $value, $operator, func_num_args() == 2
740
        );
741
742
        return $this->addDateBasedWhere('Date', $column, $operator, $value, $boolean);
743
    }
744
745
    /**
746
     * Add an "or where date" statement to the query.
747
     *
748
     * @param  string $column
749
     * @param  string $operator
750
     * @param  string $value
751
     * @return \Childish\query\Builder|static
752
     */
753
    public function orWhereDate($column, $operator, $value)
754
    {
755
        return $this->whereDate($column, $operator, $value, 'or');
756
    }
757
758
    /**
759
     * Add a "where time" statement to the query.
760
     *
761
     * @param  string $column
762
     * @param  string $operator
763
     * @param  int    $value
764
     * @param  string $boolean
765
     * @return \Childish\query\Builder|static
766
     */
767
    public function whereTime($column, $operator, $value, $boolean = 'and')
768
    {
769
        return $this->addDateBasedWhere('Time', $column, $operator, $value, $boolean);
770
    }
771
772
    /**
773
     * Add an "or where time" statement to the query.
774
     *
775
     * @param  string $column
776
     * @param  string $operator
777
     * @param  int    $value
778
     * @return \Childish\query\Builder|static
779
     */
780
    public function orWhereTime($column, $operator, $value)
781
    {
782
        return $this->whereTime($column, $operator, $value, 'or');
783
    }
784
785
    /**
786
     * Add a "where day" statement to the query.
787
     *
788
     * @param  string $column
789
     * @param  string $operator
790
     * @param  mixed  $value
791
     * @param  string $boolean
792
     * @return \Childish\query\Builder|static
793
     */
794
    public function whereDay($column, $operator, $value = null, $boolean = 'and')
795
    {
796
        list($value, $operator) = $this->prepareValueAndOperator(
797
            $value, $operator, func_num_args() == 2
798
        );
799
800
        return $this->addDateBasedWhere('Day', $column, $operator, $value, $boolean);
801
    }
802
803
    /**
804
     * Add a "where month" statement to the query.
805
     *
806
     * @param  string $column
807
     * @param  string $operator
808
     * @param  mixed  $value
809
     * @param  string $boolean
810
     * @return \Childish\query\Builder|static
811
     */
812
    public function whereMonth($column, $operator, $value = null, $boolean = 'and')
813
    {
814
        list($value, $operator) = $this->prepareValueAndOperator(
815
            $value, $operator, func_num_args() == 2
816
        );
817
818
        return $this->addDateBasedWhere('Month', $column, $operator, $value, $boolean);
819
    }
820
821
    /**
822
     * Add a "where year" statement to the query.
823
     *
824
     * @param  string $column
825
     * @param  string $operator
826
     * @param  mixed  $value
827
     * @param  string $boolean
828
     * @return \Childish\query\Builder|static
829
     */
830
    public function whereYear($column, $operator, $value = null, $boolean = 'and')
831
    {
832
        list($value, $operator) = $this->prepareValueAndOperator(
833
            $value, $operator, func_num_args() == 2
834
        );
835
836
        return $this->addDateBasedWhere('Year', $column, $operator, $value, $boolean);
837
    }
838
839
    /**
840
     * Add a date based (year, month, day, time) statement to the query.
841
     *
842
     * @param  string $type
843
     * @param  string $column
844
     * @param  string $operator
845
     * @param  int    $value
846
     * @param  string $boolean
847
     * @return $this
848
     */
849
    protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
850
    {
851
        $this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
852
853
        $this->addBinding($value, 'where');
854
855
        return $this;
856
    }
857
858
    /**
859
     * Add a nested where statement to the query.
860
     *
861
     * @param  \Closure $callback
862
     * @param  string   $boolean
863
     * @return \Childish\query\Builder|static
864
     */
865
    public function whereNested(Closure $callback, $boolean = 'and')
866
    {
867
        call_user_func($callback, $query = $this->forNestedWhere());
868
869
        return $this->addNestedWhereQuery($query, $boolean);
870
    }
871
872
    /**
873
     * Create a new query instance for nested where condition.
874
     *
875
     * @return \Childish\query\Builder
876
     */
877
    public function forNestedWhere()
878
    {
879
        return $this->newQuery()->from($this->from);
880
    }
881
882
    /**
883
     * Add another query builder as a nested where to the query builder.
884
     *
885
     * @param  \Childish\query\Builder|static $query
886
     * @param  string                         $boolean
887
     * @return $this
888
     */
889
    public function addNestedWhereQuery($query, $boolean = 'and')
890
    {
891
        if (count($query->wheres)) {
892
            $type = 'Nested';
893
894
            $this->wheres[] = compact('type', 'query', 'boolean');
895
896
            $this->addBinding($query->getBindings(), 'where');
897
        }
898
899
        return $this;
900
    }
901
902
    /**
903
     * Add an exists clause to the query.
904
     *
905
     * @param  \Closure $callback
906
     * @param  string   $boolean
907
     * @param  bool     $not
908
     * @return $this
909
     */
910
    public function whereExists(Closure $callback, $boolean = 'and', $not = false)
911
    {
912
        $query = $this->forSubQuery();
913
914
        // Similar to the sub-select clause, we will create a new query instance so
915
        // the developer may cleanly specify the entire exists query and we will
916
        // compile the whole thing in the grammar and insert it into the SQL.
917
        call_user_func($callback, $query);
918
919
        return $this->addWhereExistsQuery($query, $boolean, $not);
920
    }
921
922
    /**
923
     * Add an or exists clause to the query.
924
     *
925
     * @param  \Closure $callback
926
     * @param  bool     $not
927
     * @return \Childish\query\Builder|static
928
     */
929
    public function orWhereExists(Closure $callback, $not = false)
930
    {
931
        return $this->whereExists($callback, 'or', $not);
932
    }
933
934
    /**
935
     * Add a where not exists clause to the query.
936
     *
937
     * @param  \Closure $callback
938
     * @param  string   $boolean
939
     * @return \Childish\query\Builder|static
940
     */
941
    public function whereNotExists(Closure $callback, $boolean = 'and')
942
    {
943
        return $this->whereExists($callback, $boolean, true);
944
    }
945
946
    /**
947
     * Add a where not exists clause to the query.
948
     *
949
     * @param  \Closure $callback
950
     * @return \Childish\query\Builder|static
951
     */
952
    public function orWhereNotExists(Closure $callback)
953
    {
954
        return $this->orWhereExists($callback, true);
955
    }
956
957
    /**
958
     * Add an exists clause to the query.
959
     *
960
     * @param  \Childish\query\Builder $query
961
     * @param  string                  $boolean
962
     * @param  bool                    $not
963
     * @return $this
964
     */
965
    public function addWhereExistsQuery(Builder $query, $boolean = 'and', $not = false)
966
    {
967
        $type = $not ? 'NotExists' : 'Exists';
968
969
        $this->wheres[] = compact('type', 'operator', 'query', 'boolean');
970
971
        $this->addBinding($query->getBindings(), 'where');
972
973
        return $this;
974
    }
975
976
977
    /**
978
     * Add a "group by" clause to the query.
979
     *
980
     * @param  array ...$groups
981
     * @return $this
982
     */
983
    public function groupBy(...$groups)
984
    {
985
        foreach ($groups as $group) {
986
            $this->groups = array_merge(
987
                (array)$this->groups,
988
                Tools::wrap($group)
989
            );
990
        }
991
992
        return $this;
993
    }
994
995
    /**
996
     * Add a "having" clause to the query.
997
     *
998
     * @param  string      $column
999
     * @param  string|null $operator
1000
     * @param  string|null $value
1001
     * @param  string      $boolean
1002
     * @return $this
1003
     */
1004
    public function having($column, $operator = null, $value = null, $boolean = 'and')
1005
    {
1006
        $type = 'Basic';
1007
1008
        // Here we will make some assumptions about the operator. If only 2 values are
1009
        // passed to the method, we will assume that the operator is an equals sign
1010
        // and keep going. Otherwise, we'll require the operator to be passed in.
1011
        list($value, $operator) = $this->prepareValueAndOperator(
1012
            $value, $operator, func_num_args() == 2
1013
        );
1014
1015
        // If the given operator is not found in the list of valid operators we will
1016
        // assume that the developer is just short-cutting the '=' operators and
1017
        // we will set the operators to '=' and set the values appropriately.
1018
        if ($this->invalidOperator($operator)) {
1019
            list($value, $operator) = [$operator, '='];
1020
        }
1021
1022
        $this->havings[] = compact('type', 'column', 'operator', 'value', 'boolean');
1023
1024
        if (!$value instanceof Expression) {
0 ignored issues
show
The class Childish\query\Expression does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1025
            $this->addBinding($value, 'having');
1026
        }
1027
1028
        return $this;
1029
    }
1030
1031
    /**
1032
     * Add a "or having" clause to the query.
1033
     *
1034
     * @param  string      $column
1035
     * @param  string|null $operator
1036
     * @param  string|null $value
1037
     * @return \Childish\query\Builder|static
1038
     */
1039
    public function orHaving($column, $operator = null, $value = null)
1040
    {
1041
        return $this->having($column, $operator, $value, 'or');
1042
    }
1043
1044
    /**
1045
     * Add a raw having clause to the query.
1046
     *
1047
     * @param  string $sql
1048
     * @param  array  $bindings
1049
     * @param  string $boolean
1050
     * @return $this
1051
     */
1052
    public function havingRaw($sql, array $bindings = [], $boolean = 'and')
1053
    {
1054
        $type = 'Raw';
1055
1056
        $this->havings[] = compact('type', 'sql', 'boolean');
1057
1058
        $this->addBinding($bindings, 'having');
1059
1060
        return $this;
1061
    }
1062
1063
    /**
1064
     * Add a raw or having clause to the query.
1065
     *
1066
     * @param  string $sql
1067
     * @param  array  $bindings
1068
     * @return \Childish\query\Builder|static
1069
     */
1070
    public function orHavingRaw($sql, array $bindings = [])
1071
    {
1072
        return $this->havingRaw($sql, $bindings, 'or');
1073
    }
1074
1075
    /**
1076
     * Add an "order by" clause to the query.
1077
     *
1078
     * @param  string $column
1079
     * @param  string $direction
1080
     * @return $this
1081
     */
1082
    public function orderBy($column, $direction = 'asc')
1083
    {
1084
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
1085
            'column'    => $column,
1086
            'direction' => strtolower($direction) == 'asc' ? 'asc' : 'desc',
1087
        ];
1088
1089
        return $this;
1090
    }
1091
1092
    /**
1093
     * Add a descending "order by" clause to the query.
1094
     *
1095
     * @param  string $column
1096
     * @return $this
1097
     */
1098
    public function orderByDesc($column)
1099
    {
1100
        return $this->orderBy($column, 'desc');
1101
    }
1102
1103
    /**
1104
     * Add a raw "order by" clause to the query.
1105
     *
1106
     * @param  string $sql
1107
     * @param  array  $bindings
1108
     * @return $this
1109
     */
1110
    public function orderByRaw($sql, $bindings = [])
1111
    {
1112
        $type = 'Raw';
1113
1114
        $this->{$this->unions ? 'unionOrders' : 'orders'}[] = compact('type', 'sql');
1115
1116
        $this->addBinding($bindings, 'order');
1117
1118
        return $this;
1119
    }
1120
1121
    /**
1122
     * Alias to set the "offset" value of the query.
1123
     *
1124
     * @param  int $value
1125
     * @return \Childish\query\Builder|static
1126
     */
1127
    public function skip($value)
1128
    {
1129
        return $this->offset($value);
1130
    }
1131
1132
    /**
1133
     * Set the "offset" value of the query.
1134
     *
1135
     * @param  int $value
1136
     * @return $this
1137
     */
1138
    public function offset($value)
1139
    {
1140
        $property = $this->unions ? 'unionOffset' : 'offset';
1141
1142
        $this->$property = max(0, $value);
1143
1144
        return $this;
1145
    }
1146
1147
    /**
1148
     * Alias to set the "limit" value of the query.
1149
     *
1150
     * @param  int $value
1151
     * @return \Childish\query\Builder|static
1152
     */
1153
    public function take($value)
1154
    {
1155
        return $this->limit($value);
1156
    }
1157
1158
    /**
1159
     * Set the "limit" value of the query.
1160
     *
1161
     * @param  int $value
1162
     * @return $this
1163
     */
1164
    public function limit($value)
1165
    {
1166
        $property = $this->unions ? 'unionLimit' : 'limit';
1167
1168
        if ($value >= 0) {
1169
            $this->$property = $value;
1170
        }
1171
1172
        return $this;
1173
    }
1174
1175
    /**
1176
     * Set the limit and offset for a given page.
1177
     *
1178
     * @param  int $page
1179
     * @param  int $perPage
1180
     * @return \Childish\query\Builder|static
1181
     */
1182
    public function forPage($page, $perPage = 15)
1183
    {
1184
        return $this->skip(($page - 1) * $perPage)->take($perPage);
1185
    }
1186
1187
    /**
1188
     * Add a union statement to the query.
1189
     *
1190
     * @param  \Childish\query\Builder|\Closure $query
1191
     * @param  bool                             $all
1192
     * @return \Childish\query\Builder|static
1193
     */
1194
    public function union($query, $all = false)
1195
    {
1196
        if ($query instanceof Closure) {
1197
            call_user_func($query, $query = $this->newQuery());
1198
        }
1199
1200
        $this->unions[] = compact('query', 'all');
1201
1202
        $this->addBinding($query->getBindings(), 'union');
1203
1204
        return $this;
1205
    }
1206
1207
    /**
1208
     * Add a union all statement to the query.
1209
     *
1210
     * @param  \Childish\query\Builder|\Closure $query
1211
     * @return \Childish\query\Builder|static
1212
     */
1213
    public function unionAll($query)
1214
    {
1215
        return $this->union($query, true);
1216
    }
1217
1218
    /**
1219
     * Lock the selected rows in the table.
1220
     *
1221
     * @param  string|bool $value
1222
     * @return $this
1223
     */
1224
    public function lock($value = true)
1225
    {
1226
        $this->lock = $value;
1227
1228
        if (!is_null($this->lock)) {
1229
            $this->useWritePdo();
1230
        }
1231
1232
        return $this;
1233
    }
1234
1235
    /**
1236
     * Lock the selected rows in the table for updating.
1237
     *
1238
     * @return \Childish\query\Builder
1239
     */
1240
    public function lockForUpdate()
1241
    {
1242
        return $this->lock(true);
1243
    }
1244
1245
    /**
1246
     * Share lock the selected rows in the table.
1247
     *
1248
     * @return \Childish\query\Builder
1249
     */
1250
    public function sharedLock()
1251
    {
1252
        return $this->lock(false);
1253
    }
1254
1255
    /**
1256
     * Get the SQL representation of the query.
1257
     *
1258
     * @return string
1259
     */
1260
    public function toSql()
1261
    {
1262
        return $this->grammar->compileSelect($this);
1263
    }
1264
1265
    /**
1266
     * Execute a query for a single record by ID.
1267
     *
1268
     * @param  int   $id
1269
     * @param  array $columns
1270
     * @return mixed|static
1271
     */
1272
    public function find($id, $columns = ['*'])
1273
    {
1274
        return $this->where('id', '=', $id)->first($columns);
1275
    }
1276
1277
    /**
1278
     * Get a single column's value from the first result of a query.
1279
     *
1280
     * @param  string $column
1281
     * @return mixed
1282
     */
1283
    public function value($column)
1284
    {
1285
        $result = (array)$this->first([$column]);
1286
1287
        return count($result) > 0 ? reset($result) : null;
1288
    }
1289
1290
    /**
1291
     * Execute the query as a "select" statement.
1292
     *
1293
     * @param  array $columns
1294
     * @return \childish\support\Collection
1295
     */
1296
    public function get($columns = ['*'])
1297
    {
1298
        $original = $this->columns;
1299
1300
        if (is_null($original)) {
1301
            $this->columns = $columns;
1302
        }
1303
1304
        $results = $this->processor->processSelect($this, $this->runSelect());
1305
1306
        $this->columns = $original;
0 ignored issues
show
Documentation Bug introduced by
It seems like $original can be null. However, the property $columns is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
1307
1308
        return new Collection($results);
1309
    }
1310
1311
    /**
1312
     * Run the query as a "select" statement against the connection.
1313
     *
1314
     * @return array
1315
     */
1316
    protected function runSelect()
1317
    {
1318
        return $this->connection->select(
1319
            $this->toSql(), $this->getBindings(), !$this->useWritePdo
0 ignored issues
show
The call to ConnectionInterface::select() has too many arguments starting with !$this->useWritePdo.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1320
        );
1321
    }
1322
1323
1324
    /**
1325
     * Remove the column aliases since they will break count queries.
1326
     *
1327
     * @param  array $columns
1328
     * @return array
1329
     */
1330
    protected function withoutSelectAliases(array $columns)
1331
    {
1332
        return array_map(function ($column) {
1333
            return is_string($column) && ($aliasPosition = strpos(strtolower($column), ' as ')) !== false
1334
                ? substr($column, 0, $aliasPosition) : $column;
0 ignored issues
show
The variable $aliasPosition does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1335
        }, $columns);
1336
    }
1337
1338
    /**
1339
     * Get a generator for the given query.
1340
     *
1341
     * @return \Generator
1342
     */
1343
    public function cursor()
1344
    {
1345
        if (is_null($this->columns)) {
1346
            $this->columns = ['*'];
1347
        }
1348
1349
        return $this->connection->cursor(
1350
            $this->toSql(), $this->getBindings(), !$this->useWritePdo
1351
        );
1352
    }
1353
1354
    /**
1355
     * Chunk the results of a query by comparing numeric IDs.
1356
     *
1357
     * @param  int      $count
1358
     * @param  callable $callback
1359
     * @param  string   $column
1360
     * @param  string   $alias
1361
     * @return bool
1362
     */
1363
    public function chunkById($count, callable $callback, $column = 'id', $alias = null)
1364
    {
1365
        $alias = $alias ? : $column;
1366
1367
        $lastId = 0;
1368
1369
        do {
1370
            $clone = clone $this;
1371
1372
            // We'll execute the query for the given page and get the results. If there are
1373
            // no results we can just break and return from here. When there are results
1374
            // we will call the callback with the current chunk of these results here.
1375
            $results = $clone->forPageAfterId($count, $lastId, $column)->get();
0 ignored issues
show
The method forPageAfterId() does not exist on Childish\query\Builder. Did you maybe mean forPage()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1376
1377
            $countResults = $results->count();
1378
1379
            if ($countResults == 0) {
1380
                break;
1381
            }
1382
1383
            // On each chunk result set, we will pass them to the callback and then let the
1384
            // developer take care of everything within the callback, which allows us to
1385
            // keep the memory low for spinning through large result sets for working.
1386
            if ($callback($results) === false) {
1387
                return false;
1388
            }
1389
1390
            $lastId = $results->last()->{$alias};
1391
1392
            unset($results);
1393
        } while ($countResults == $count);
1394
1395
        return true;
1396
    }
1397
1398
    /**
1399
     * Throw an exception if the query doesn't have an orderBy clause.
1400
     *
1401
     * @return void
1402
     * @throws \RuntimeException
1403
     */
1404
    protected function enforceOrderBy()
1405
    {
1406
        if (empty($this->orders) && empty($this->unionOrders)) {
1407
            throw new RuntimeException('You must specify an orderBy clause when using this function.');
1408
        }
1409
    }
1410
1411
    /**
1412
     * Get an array with the values of a given column.
1413
     *
1414
     * @param  string      $column
1415
     * @param  string|null $key
1416
     * @return \Childish\support\Collection
1417
     */
1418
    public function pluck($column, $key = null)
1419
    {
1420
        $results = $this->get(is_null($key) ? [$column] : [$column, $key]);
1421
1422
        // If the columns are qualified with a table or have an alias, we cannot use
1423
        // those directly in the "pluck" operations since the results from the DB
1424
        // are only keyed by the column itself. We'll strip the table out here.
1425
        return $results->pluck(
1426
            $this->stripTableForPluck($column),
1427
            $this->stripTableForPluck($key)
1428
        );
1429
    }
1430
1431
    /**
1432
     * Strip off the table name or alias from a column identifier.
1433
     *
1434
     * @param  string $column
1435
     * @return string|null
1436
     */
1437
    protected function stripTableForPluck($column)
1438
    {
1439
        return is_null($column) ? $column : last(preg_split('~\.| ~', $column));
1440
    }
1441
1442
    /**
1443
     * Concatenate values of a given column as a string.
1444
     *
1445
     * @param  string $column
1446
     * @param  string $glue
1447
     * @return string
1448
     */
1449
    public function implode($column, $glue = '')
1450
    {
1451
        return $this->pluck($column)->implode($glue);
1452
    }
1453
1454
    /**
1455
     * Determine if any rows exist for the current query.
1456
     *
1457
     * @return bool
1458
     */
1459
    public function exists()
1460
    {
1461
        $results = $this->connection->select(
1462
            $this->grammar->compileExists($this), $this->getBindings(), !$this->useWritePdo
0 ignored issues
show
The call to ConnectionInterface::select() has too many arguments starting with !$this->useWritePdo.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1463
        );
1464
1465
        // If the results has rows, we will get the row and see if the exists column is a
1466
        // boolean true. If there is no results for this query we will return false as
1467
        // there are no rows for this query at all and we can return that info here.
1468
        if (isset($results[0])) {
1469
            $results = (array)$results[0];
1470
1471
            return (bool)$results['exists'];
1472
        }
1473
1474
        return false;
1475
    }
1476
1477
    /**
1478
     * Retrieve the "count" result of the query.
1479
     *
1480
     * @param  string $columns
1481
     * @return int
1482
     */
1483
    public function count($columns = '*')
1484
    {
1485
        return (int)$this->aggregate(__FUNCTION__, Tools::wrap($columns));
1486
    }
1487
1488
    /**
1489
     * Retrieve the minimum value of a given column.
1490
     *
1491
     * @param  string $column
1492
     * @return mixed
1493
     */
1494
    public function min($column)
1495
    {
1496
        return $this->aggregate(__FUNCTION__, [$column]);
1497
    }
1498
1499
    /**
1500
     * Retrieve the maximum value of a given column.
1501
     *
1502
     * @param  string $column
1503
     * @return mixed
1504
     */
1505
    public function max($column)
1506
    {
1507
        return $this->aggregate(__FUNCTION__, [$column]);
1508
    }
1509
1510
    /**
1511
     * Retrieve the sum of the values of a given column.
1512
     *
1513
     * @param  string $column
1514
     * @return mixed
1515
     */
1516
    public function sum($column)
1517
    {
1518
        $result = $this->aggregate(__FUNCTION__, [$column]);
1519
1520
        return $result ? : 0;
1521
    }
1522
1523
    /**
1524
     * Retrieve the average of the values of a given column.
1525
     *
1526
     * @param  string $column
1527
     * @return mixed
1528
     */
1529
    public function avg($column)
1530
    {
1531
        return $this->aggregate(__FUNCTION__, [$column]);
1532
    }
1533
1534
    /**
1535
     * Alias for the "avg" method.
1536
     *
1537
     * @param  string $column
1538
     * @return mixed
1539
     */
1540
    public function average($column)
1541
    {
1542
        return $this->avg($column);
1543
    }
1544
1545
    /**
1546
     * Execute an aggregate function on the database.
1547
     *
1548
     * @param  string $function
1549
     * @param  array  $columns
1550
     * @return mixed
1551
     */
1552
    public function aggregate($function, $columns = ['*'])
1553
    {
1554
        $results = $this->cloneWithout(['columns'])
1555
                        ->cloneWithoutBindings(['select'])
1556
                        ->setAggregate($function, $columns)
1557
                        ->get($columns);
1558
1559
        if (!$results->isEmpty()) {
1560
            return array_change_key_case((array)$results[0])['aggregate'];
1561
        }
1562
    }
1563
1564
    /**
1565
     * Clone the query without the given properties.
1566
     *
1567
     * @param  array $properties
1568
     * @return static
1569
     */
1570
    public function cloneWithout(array $properties)
1571
    {
1572
        return $this->higherOrderTap(clone $this, function ($clone) use ($properties) {
1573
            foreach ($properties as $property) {
1574
                $clone->{$property} = null;
1575
            }
1576
        });
1577
    }
1578
1579
    /**
1580
     * Clone the query without the given bindings.
1581
     *
1582
     * @param  array $except
1583
     * @return static
1584
     */
1585
    public function cloneWithoutBindings(array $except)
1586
    {
1587
        return $this->higherOrderTap(clone $this, function ($clone) use ($except) {
1588
            foreach ($except as $type) {
1589
                $clone->bindings[$type] = [];
1590
            }
1591
        });
1592
    }
1593
1594
    /**
1595
     * Call the given Closure with the given value then return the value.
1596
     *
1597
     * @param  mixed         $value
1598
     * @param  callable|null $callback
1599
     * @return mixed
1600
     */
1601
    public function higherOrderTap($value, $callback = null)
1602
    {
1603
        if (is_null($callback) === false) {
1604
            $callback($value);
1605
        }
1606
        return $value;
1607
    }
1608
1609
    /**
1610
     * Execute a numeric aggregate function on the database.
1611
     *
1612
     * @param  string $function
1613
     * @param  array  $columns
1614
     * @return float|int
1615
     */
1616
    public function numericAggregate($function, $columns = ['*'])
1617
    {
1618
        $result = $this->aggregate($function, $columns);
1619
1620
        // If there is no result, we can obviously just return 0 here. Next, we will check
1621
        // if the result is an integer or float. If it is already one of these two data
1622
        // types we can just return the result as-is, otherwise we will convert this.
1623
        if (!$result) {
1624
            return 0;
1625
        }
1626
1627
        if (is_int($result) || is_float($result)) {
1628
            return $result;
1629
        }
1630
1631
        // If the result doesn't contain a decimal place, we will assume it is an int then
1632
        // cast it to one. When it does we will cast it to a float since it needs to be
1633
        // cast to the expected data type for the developers out of pure convenience.
1634
        return strpos((string)$result, '.') === false
1635
            ? (int)$result : (float)$result;
1636
    }
1637
1638
    /**
1639
     * Set the aggregate property without running the query.
1640
     *
1641
     * @param  string $function
1642
     * @param  array  $columns
1643
     * @return $this
1644
     */
1645
    protected function setAggregate($function, $columns)
1646
    {
1647
        $this->aggregate = compact('function', 'columns');
1648
1649
        if (empty($this->groups)) {
1650
            $this->orders = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $orders.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1651
1652
            $this->bindings['order'] = [];
1653
        }
1654
1655
        return $this;
1656
    }
1657
1658
    /**
1659
     * Insert a new record into the database.
1660
     *
1661
     * @param  array $values
1662
     * @return bool
1663
     */
1664
    public function insert(array $values)
1665
    {
1666
        // Since every insert gets treated like a batch insert, we will make sure the
1667
        // bindings are structured in a way that is convenient when building these
1668
        // inserts statements by verifying these elements are actually an array.
1669
        if (empty($values)) {
1670
            return true;
1671
        }
1672
1673
        if (!is_array(reset($values))) {
1674
            $values = [$values];
1675
        }
1676
1677
        // Here, we will sort the insert keys for every record so that each insert is
1678
        // in the same order for the record. We need to make sure this is the case
1679
        // so there are not any errors or problems when inserting these records.
1680
        else {
1681
            foreach ($values as $key => $value) {
1682
                ksort($value);
1683
1684
                $values[$key] = $value;
1685
            }
1686
        }
1687
1688
        // Finally, we will run this query against the database connection and return
1689
        // the results. We will need to also flatten these bindings before running
1690
        // the query so they are all in one huge, flattened array for execution.
1691
        return $this->connection->insert(
1692
            $this->grammar->compileInsert($this, $values),
1693
            $this->cleanBindings(Tools::flatten($values, 1))
1694
        );
1695
    }
1696
1697
    /**
1698
     * Insert a new record and get the value of the primary key.
1699
     *
1700
     * @param  array       $values
1701
     * @param  string|null $sequence
1702
     * @return int
1703
     */
1704
    public function insertGetId(array $values, $sequence = null)
1705
    {
1706
        $sql = $this->grammar->compileInsertGetId($this, $values, $sequence);
1707
1708
        $values = $this->cleanBindings($values);
1709
1710
        return $this->processor->processInsertGetId($this, $sql, $values, $sequence);
1711
    }
1712
1713
    /**
1714
     * Update a record in the database.
1715
     *
1716
     * @param  array $values
1717
     * @return int
1718
     */
1719
    public function update(array $values)
1720
    {
1721
        $sql = $this->grammar->compileUpdate($this, $values);
1722
        return $this->connection->update($sql, $this->cleanBindings(
1723
            $this->grammar->prepareBindingsForUpdate($this->bindings, $values)
1724
        ));
1725
    }
1726
1727
    /**
1728
     * Insert or update a record matching the attributes, and fill it with values.
1729
     *
1730
     * @param  array $attributes
1731
     * @param  array $values
1732
     * @return bool
1733
     */
1734
    public function updateOrInsert(array $attributes, array $values = [])
1735
    {
1736
        if (!$this->where($attributes)->exists()) {
1737
            return $this->insert(array_merge($attributes, $values));
1738
        }
1739
1740
        return (bool)$this->take(1)->update($values);
1741
    }
1742
1743
    /**
1744
     * Increment a column's value by a given amount.
1745
     *
1746
     * @param  string $column
1747
     * @param  int    $amount
1748
     * @param  array  $extra
1749
     * @return int
1750
     */
1751
    public function increment($column, $amount = 1, array $extra = [])
1752
    {
1753
        if (!is_numeric($amount)) {
1754
            throw new InvalidArgumentException('Non-numeric value passed to increment method.');
1755
        }
1756
1757
        $wrapped = $this->grammar->wrap($column);
1758
1759
        $columns = array_merge([$column => $this->raw("$wrapped + $amount")], $extra);
1760
1761
        return $this->update($columns);
1762
    }
1763
1764
    /**
1765
     * Decrement a column's value by a given amount.
1766
     *
1767
     * @param  string $column
1768
     * @param  int    $amount
1769
     * @param  array  $extra
1770
     * @return int
1771
     */
1772
    public function decrement($column, $amount = 1, array $extra = [])
1773
    {
1774
        if (!is_numeric($amount)) {
1775
            throw new InvalidArgumentException('Non-numeric value passed to decrement method.');
1776
        }
1777
1778
        $wrapped = $this->grammar->wrap($column);
1779
1780
        $columns = array_merge([$column => $this->raw("$wrapped - $amount")], $extra);
1781
1782
        return $this->update($columns);
1783
    }
1784
1785
    /**
1786
     * Delete a record from the database.
1787
     *
1788
     * @param  mixed $id
1789
     * @return int
1790
     */
1791
    public function delete($id = null)
1792
    {
1793
        // If an ID is passed to the method, we will set the where clause to check the
1794
        // ID to let developers to simply and quickly remove a single row from this
1795
        // database without manually specifying the "where" clauses on the query.
1796
        if (!is_null($id)) {
1797
            $this->where($this->from . '.id', '=', $id);
1798
        }
1799
1800
        return $this->connection->delete(
1801
            $this->grammar->compileDelete($this), $this->getBindings()
1802
        );
1803
    }
1804
1805
    /**
1806
     * Run a truncate statement on the table.
1807
     *
1808
     * @return void
1809
     */
1810
    public function truncate()
1811
    {
1812
        foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) {
1813
            $this->connection->statement($sql, $bindings);
1814
        }
1815
    }
1816
1817
    /**
1818
     * Get a new instance of the query builder.
1819
     *
1820
     * @return \Childish\query\Builder
1821
     */
1822
    public function newQuery()
1823
    {
1824
        return new static($this->connection, $this->grammar, $this->processor);
1825
    }
1826
1827
    /**
1828
     * Create a new query instance for a sub-query.
1829
     *
1830
     * @return \Childish\query\Builder
1831
     */
1832
    protected function forSubQuery()
1833
    {
1834
        return $this->newQuery();
1835
    }
1836
1837
    /**
1838
     * Create a raw database expression.
1839
     *
1840
     * @param $value
1841
     * @return mixed
1842
     */
1843
    public function raw($value)
1844
    {
1845
        return $value;
1846
    }
1847
1848
    /**
1849
     * Get the current query value bindings in a flattened array.
1850
     *
1851
     * @return array
1852
     */
1853
    public function getBindings()
1854
    {
1855
        return Tools::flatten($this->bindings);
1856
    }
1857
1858
    /**
1859
     * Get the raw array of bindings.
1860
     *
1861
     * @return array
1862
     */
1863
    public function getRawBindings()
1864
    {
1865
        return $this->bindings;
1866
    }
1867
1868
    /**
1869
     * Set the bindings on the query builder.
1870
     *
1871
     * @param  array  $bindings
1872
     * @param  string $type
1873
     * @return $this
1874
     * @throws \InvalidArgumentException
1875
     */
1876
    public function setBindings(array $bindings, $type = 'where')
1877
    {
1878
        if (!array_key_exists($type, $this->bindings)) {
1879
            throw new InvalidArgumentException("Invalid binding type: {$type}.");
1880
        }
1881
1882
        $this->bindings[$type] = $bindings;
1883
1884
        return $this;
1885
    }
1886
1887
    /**
1888
     * Add a binding to the query.
1889
     *
1890
     * @param  mixed  $value
1891
     * @param  string $type
1892
     * @return $this
1893
     * @throws \InvalidArgumentException
1894
     */
1895
    public function addBinding($value, $type = 'where')
1896
    {
1897
        if (!array_key_exists($type, $this->bindings)) {
1898
            throw new InvalidArgumentException("Invalid binding type: {$type}.");
1899
        }
1900
1901
        if (is_array($value)) {
1902
            $this->bindings[$type] = array_values(array_merge($this->bindings[$type], $value));
1903
        } else {
1904
            $this->bindings[$type][] = $value;
1905
        }
1906
1907
        return $this;
1908
    }
1909
1910
    /**
1911
     * Merge an array of bindings into our bindings.
1912
     *
1913
     * @param  \Childish\query\Builder $query
1914
     * @return $this
1915
     */
1916
    public function mergeBindings(Builder $query)
1917
    {
1918
        $this->bindings = array_merge_recursive($this->bindings, $query->bindings);
1919
1920
        return $this;
1921
    }
1922
1923
    /**
1924
     * Remove all of the expressions from a list of bindings.
1925
     *
1926
     * @param  array $bindings
1927
     * @return array
1928
     */
1929
    protected function cleanBindings(array $bindings)
1930
    {
1931
        return array_values($bindings);
1932
    }
1933
1934
    /**
1935
     * Get the database connection instance.
1936
     *
1937
     * @return \Childish\connection\ConnectionInterface
1938
     */
1939
    public function getConnection()
1940
    {
1941
        return $this->connection;
1942
    }
1943
1944
    /**
1945
     * Get the database query processor instance.
1946
     *
1947
     * @return \Childish\query\Processor
1948
     */
1949
    public function getProcessor()
1950
    {
1951
        return $this->processor;
1952
    }
1953
1954
    /**
1955
     * Get the query grammar instance.
1956
     *
1957
     * @return \Childish\query\Grammar
1958
     */
1959
    public function getGrammar()
1960
    {
1961
        return $this->grammar;
1962
    }
1963
1964
    /**
1965
     * Use the write pdo for query.
1966
     *
1967
     * @return $this
1968
     */
1969
    public function useWritePdo()
1970
    {
1971
        $this->useWritePdo = true;
1972
1973
        return $this;
1974
    }
1975
1976
    /**
1977
     * Chunk the results of the query.
1978
     *
1979
     * @param  int      $count
1980
     * @param  callable $callback
1981
     * @return bool
1982
     */
1983
    public function chunk($count, callable $callback)
1984
    {
1985
        $this->enforceOrderBy();
1986
1987
        $page = 1;
1988
1989
        do {
1990
            // We'll execute the query for the given page and get the results. If there are
1991
            // no results we can just break and return from here. When there are results
1992
            // we will call the callback with the current chunk of these results here.
1993
            $results = $this->forPage($page, $count)->get();
1994
1995
            $countResults = $results->count();
1996
1997
            if ($countResults == 0) {
1998
                break;
1999
            }
2000
2001
            // On each chunk result set, we will pass them to the callback and then let the
2002
            // developer take care of everything within the callback, which allows us to
2003
            // keep the memory low for spinning through large result sets for working.
2004
            if ($callback($results, $page) === false) {
2005
                return false;
2006
            }
2007
2008
            unset($results);
2009
2010
            $page++;
2011
        } while ($countResults == $count);
2012
2013
        return true;
2014
    }
2015
2016
    /**
2017
     * Execute a callback over each item while chunking.
2018
     *
2019
     * @param  callable $callback
2020
     * @param  int      $count
2021
     * @return bool
2022
     */
2023
    public function each(callable $callback, $count = 1000)
2024
    {
2025
        return $this->chunk($count, function ($results) use ($callback) {
2026
            foreach ($results as $key => $value) {
2027
                if ($callback($value, $key) === false) {
2028
                    return false;
2029
                }
2030
            }
2031
        });
2032
    }
2033
2034
    /**
2035
     * Execute the query and get the first result.
2036
     *
2037
     * @param  array $columns
2038
     * @return \childish\support\Collection
2039
     */
2040
    public function first($columns = ['*'])
2041
    {
2042
        return $this->take(1)->get($columns)->first();
2043
    }
2044
2045
    /**
2046
     * Apply the callback's query changes if the given "value" is true.
2047
     *
2048
     * @param  mixed    $value
2049
     * @param  callable $callback
2050
     * @param  callable $default
2051
     * @return mixed
2052
     */
2053
    public function when($value, $callback, $default = null)
2054
    {
2055
        if ($value) {
2056
            return $callback($this, $value) ? : $this;
2057
        } else if ($default) {
2058
            return $default($this, $value) ? : $this;
2059
        }
2060
2061
        return $this;
2062
    }
2063
2064
    /**
2065
     * Pass the query to a given callback.
2066
     *
2067
     * @param  \Closure $callback
2068
     * @return \Childish\query\Builder
2069
     */
2070
    public function tap($callback)
2071
    {
2072
        return $this->when(true, $callback);
2073
    }
2074
2075
    /**
2076
     * Apply the callback's query changes if the given "value" is false.
2077
     *
2078
     * @param  mixed    $value
2079
     * @param  callable $callback
2080
     * @param  callable $default
2081
     * @return mixed
2082
     */
2083
    public function unless($value, $callback, $default = null)
2084
    {
2085
        if (!$value) {
2086
            return $callback($this, $value) ? : $this;
2087
        } else if ($default) {
2088
            return $default($this, $value) ? : $this;
2089
        }
2090
2091
        return $this;
2092
    }
2093
}
2094