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/Grammar.php (25 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 Childish\support\Tools;
5
use Childish\support\Collection;
6
7
/**
8
 * Grammar
9
 *
10
 * @author    Pu ShaoWei <[email protected]>
11
 * @date      2017/12/7
12
 * @package   Childish
13
 * @version   1.0
14
 */
15
abstract class Grammar
16
{
17
    /**
18
     * The grammar table prefix.
19
     *
20
     * @var string
21
     */
22
    protected $tablePrefix = '';
23
24
    /**
25
     * The components that make up a select clause.
26
     *
27
     * @var array
28
     */
29
    protected $selectComponents = [
30
        'aggregate',
31
        'columns',
32
        'from',
33
        'joins',
34
        'wheres',
35
        'groups',
36
        'havings',
37
        'orders',
38
        'limit',
39
        'offset',
40
        'unions',
41
        'lock',
42
    ];
43
44
    /**
45
     * Wrap an array of values.
46
     *
47
     * @param  array $values
48
     * @return array
49
     */
50
    public function wrapArray(array $values)
51
    {
52
        return array_map([$this, 'wrap'], $values);
53
    }
54
55
    /**
56
     * Wrap a table in keyword identifiers.
57
     *
58
     * @param  string $table
59
     * @return string
60
     */
61
    public function wrapTable($table)
62
    {
63
        return $this->wrap($this->tablePrefix . $table, true);
64
    }
65
66
    /**
67
     * Wrap a value in keyword identifiers.
68
     *
69
     * @param  string $value
70
     * @param  bool                                         $prefixAlias
71
     * @return string
72
     */
73
    public function wrap($value, $prefixAlias = false)
74
    {
75
        // If the value being wrapped has a column alias we will need to separate out
76
        // the pieces so we can wrap each of the segments of the expression on it
77
        // own, and then joins them both back together with the "as" connector.
78
        if (strpos(strtolower($value), ' as ') !== false) {
79
            return $this->wrapAliasedValue($value, $prefixAlias);
80
        }
81
82
        return $this->wrapSegments(explode('.', $value));
83
    }
84
85
    /**
86
     * Wrap a value that has an alias.
87
     *
88
     * @param  string $value
89
     * @param  bool   $prefixAlias
90
     * @return string
91
     */
92
    protected function wrapAliasedValue($value, $prefixAlias = false)
93
    {
94
        $segments = preg_split('/\s+as\s+/i', $value);
95
96
        // If we are wrapping a table we need to prefix the alias with the table prefix
97
        // as well in order to generate proper syntax. If this is a column of course
98
        // no prefix is necessary. The condition will be true when from wrapTable.
99
        if ($prefixAlias) {
100
            $segments[1] = $this->tablePrefix . $segments[1];
101
        }
102
103
        return $this->wrap(
104
                $segments[0]) . ' as ' . $this->wrapValue($segments[1]
105
            );
106
    }
107
108
    /**
109
     * Wrap the given value segments.
110
     *
111
     * @param  array $segments
112
     * @return string
113
     */
114
    protected function wrapSegments($segments)
115
    {
116
        return (new Collection($segments))->map(function ($segment, $key) use ($segments) {
117
            return $key == 0 && count($segments) > 1
118
                ? $this->wrapTable($segment)
119
                : $this->wrapValue($segment);
120
        })->implode('.');
121
    }
122
123
    /**
124
     * Wrap a single string in keyword identifiers.
125
     *
126
     * @param  string $value
127
     * @return string
128
     */
129
    protected function wrapValue($value)
130
    {
131
        if ($value !== '*') {
132
            return '"' . str_replace('"', '""', $value) . '"';
133
        }
134
135
        return $value;
136
    }
137
138
    /**
139
     * Convert an array of column names into a delimited string.
140
     *
141
     * @param  array $columns
142
     * @return string
143
     */
144
    public function columnize(array $columns)
145
    {
146
        return implode(', ', array_map([$this, 'wrap'], $columns));
147
    }
148
149
    /**
150
     * Create query parameter place-holders for an array.
151
     *
152
     * @param  array $values
153
     * @return string
154
     */
155
    public function parameterize(array $values)
156
    {
157
        return implode(', ', array_fill(0, count($values), '?'));
158
    }
159
160
    /**
161
     * Get the format for database stored dates.
162
     *
163
     * @return string
164
     */
165
    public function getDateFormat()
166
    {
167
        return 'Y-m-d H:i:s';
168
    }
169
170
    /**
171
     * Get the grammar's table prefix.
172
     *
173
     * @return string
174
     */
175
    public function getTablePrefix()
176
    {
177
        return $this->tablePrefix;
178
    }
179
180
    /**
181
     * Set the grammar's table prefix.
182
     *
183
     * @param  string $prefix
184
     * @return $this
185
     */
186
    public function setTablePrefix($prefix)
187
    {
188
        $this->tablePrefix = $prefix;
189
190
        return $this;
191
    }
192
193
    /**
194
     * Compile a select query into SQL.
195
     *
196
     * @param  \Childish\query\Builder $query
197
     * @return string
198
     */
199
    public function compileSelect(Builder $query)
200
    {
201
        // If the query does not have any columns set, we'll set the columns to the
202
        // * character to just get all of the columns from the database. Then we
203
        // can build the query and concatenate all the pieces together as one.
204
        $original = $query->columns;
205
206
        if (is_null($query->columns)) {
207
            $query->columns = ['*'];
208
        }
209
210
        // To compile the query, we'll spin through each component of the query and
211
        // see if that component exists. If it does we'll just call the compiler
212
        // function for the component which is responsible for making the SQL.
213
        $sql = trim($this->concatenate(
214
            $this->compileComponents($query))
215
        );
216
217
        $query->columns = $original;
218
219
        return $sql;
220
    }
221
222
    /**
223
     * Compile the components necessary for a select clause.
224
     *
225
     * @param  \Childish\query\Builder $query
226
     * @return array
227
     */
228
    protected function compileComponents(Builder $query)
229
    {
230
        $sql = [];
231
232
        foreach ($this->selectComponents as $component) {
233
            // To compile the query, we'll spin through each component of the query and
234
            // see if that component exists. If it does we'll just call the compiler
235
            // function for the component which is responsible for making the SQL.
236
            if (!is_null($query->$component)) {
237
                $method = 'compile' . ucfirst($component);
238
239
                $sql[$component] = $this->$method($query, $query->$component);
240
            }
241
        }
242
243
        return $sql;
244
    }
245
246
    /**
247
     * Compile an aggregated select clause.
248
     *
249
     * @param  \Childish\query\Builder $query
250
     * @param  array                   $aggregate
251
     * @return string
252
     */
253
    protected function compileAggregate(Builder $query, $aggregate)
254
    {
255
        $column = $this->columnize($aggregate['columns']);
256
257
        // If the query has a "distinct" constraint and we're not asking for all columns
258
        // we need to prepend "distinct" onto the column name so that the query takes
259
        // it into account when it performs the aggregating operations on the data.
260
        if ($query->distinct && $column !== '*') {
261
            $column = 'distinct ' . $column;
262
        }
263
264
        return 'select ' . $aggregate['function'] . '(' . $column . ') as aggregate';
265
    }
266
267
    /**
268
     * Compile the "select *" portion of the query.
269
     *
270
     * @param  \Childish\query\Builder $query
271
     * @param  array                   $columns
272
     * @return string|null
273
     */
274
    protected function compileColumns(Builder $query, $columns)
275
    {
276
        // If the query is actually performing an aggregating select, we will let that
277
        // compiler handle the building of the select clauses, as it will need some
278
        // more syntax that is best handled by that function to keep things neat.
279
        if (!is_null($query->aggregate)) {
280
            return;
281
        }
282
283
        $select = $query->distinct ? 'select distinct ' : 'select ';
284
285
        return $select . $this->columnize($columns);
286
    }
287
288
    /**
289
     * Compile the "from" portion of the query.
290
     *
291
     * @param  \Childish\query\Builder $query
292
     * @param  string                  $table
293
     * @return string
294
     */
295
    protected function compileFrom(Builder $query, $table)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
296
    {
297
        return 'from ' . $this->wrapTable($table);
298
    }
299
300
    /**
301
     * Compile the "join" portions of the query.
302
     *
303
     * @param  \Childish\query\Builder $query
304
     * @param  array                   $joins
305
     * @return string
306
     */
307
    protected function compileJoins(Builder $query, $joins)
308
    {
309
        return collect($joins)->map(function ($join) use ($query) {
310
            $table = $this->wrapTable($join->table);
311
312
            return trim("{$join->type} join {$table} {$this->compileWheres($join)}");
313
        })->implode(' ');
314
    }
315
316
    /**
317
     * Compile the "where" portions of the query.
318
     *
319
     * @param  \Childish\query\Builder $query
320
     * @return string
321
     */
322
    protected function compileWheres(Builder $query)
323
    {
324
        // Each type of where clauses has its own compiler function which is responsible
325
        // for actually creating the where clauses SQL. This helps keep the code nice
326
        // and maintainable since each clause has a very small method that it uses.
327
        if (is_null($query->wheres)) {
328
            return '';
329
        }
330
331
        // If we actually have some where clauses, we will strip off the first boolean
332
        // operator, which is added by the query builders for convenience so we can
333
        // avoid checking for the first clauses in each of the compilers methods.
334
        if (count($sql = $this->compileWheresToArray($query)) > 0) {
335
            return $this->concatenateWhereClauses($query, $sql);
336
        }
337
338
        return '';
339
    }
340
341
    /**
342
     * Get an array of all the where clauses for the query.
343
     *
344
     * @param  \Childish\query\Builder $query
345
     * @return array
346
     */
347
    protected function compileWheresToArray($query)
348
    {
349
        return (new Collection($query->wheres))->map(function ($where) use ($query) {
350
            return $where['boolean'] . ' ' . $this->{"where{$where['type']}"}($query, $where);
351
        })->all();
352
    }
353
354
    /**
355
     * Format the where clause statements into one string.
356
     *
357
     * @param  \Childish\query\Builder $query
358
     * @param  array                   $sql
359
     * @return string
360
     */
361
    protected function concatenateWhereClauses($query, $sql)
362
    {
363
        $conjunction = $query instanceof JoinClause ? 'on' : 'where';
0 ignored issues
show
The class Childish\query\JoinClause 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...
364
365
        return $conjunction . ' ' . $this->removeLeadingBoolean(implode(' ', $sql));
366
    }
367
368
    /**
369
     * Compile a raw where clause.
370
     *
371
     * @param  \Childish\query\Builder $query
372
     * @param  array                   $where
373
     * @return string
374
     */
375
    protected function whereRaw(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
376
    {
377
        return $where['sql'];
378
    }
379
380
    /**
381
     * Compile a basic where clause.
382
     *
383
     * @param  \Childish\query\Builder $query
384
     * @param  array                   $where
385
     * @return string
386
     */
387
    protected function whereBasic(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
388
    {
389
        return $this->wrap($where['column']) . ' ' . $where['operator'] . ' ' . '?';
390
    }
391
392
    /**
393
     * Compile a "where in" clause.
394
     *
395
     * @param  \Childish\query\Builder $query
396
     * @param  array                   $where
397
     * @return string
398
     */
399
    protected function whereIn(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
400
    {
401
        if (!empty($where['values'])) {
402
            return $this->wrap($where['column']) . ' in (' . $this->parameterize($where['values']) . ')';
403
        }
404
405
        return '0 = 1';
406
    }
407
408
    /**
409
     * Compile a "where not in" clause.
410
     *
411
     * @param  \Childish\query\Builder $query
412
     * @param  array                   $where
413
     * @return string
414
     */
415
    protected function whereNotIn(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
416
    {
417
        if (!empty($where['values'])) {
418
            return $this->wrap($where['column']) . ' not in (' . $this->parameterize($where['values']) . ')';
419
        }
420
421
        return '1 = 1';
422
    }
423
424
    /**
425
     * Compile a where in sub-select clause.
426
     *
427
     * @param  \Childish\query\Builder $query
428
     * @param  array                   $where
429
     * @return string
430
     */
431
    protected function whereInSub(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
432
    {
433
        return $this->wrap($where['column']) . ' in (' . $this->compileSelect($where['query']) . ')';
434
    }
435
436
    /**
437
     * Compile a where not in sub-select clause.
438
     *
439
     * @param  \Childish\query\Builder $query
440
     * @param  array                   $where
441
     * @return string
442
     */
443
    protected function whereNotInSub(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
444
    {
445
        return $this->wrap($where['column']) . ' not in (' . $this->compileSelect($where['query']) . ')';
446
    }
447
448
    /**
449
     * Compile a "where null" clause.
450
     *
451
     * @param  \Childish\query\Builder $query
452
     * @param  array                   $where
453
     * @return string
454
     */
455
    protected function whereNull(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
456
    {
457
        return $this->wrap($where['column']) . ' is null';
458
    }
459
460
    /**
461
     * Compile a "where not null" clause.
462
     *
463
     * @param  \Childish\query\Builder $query
464
     * @param  array                   $where
465
     * @return string
466
     */
467
    protected function whereNotNull(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
468
    {
469
        return $this->wrap($where['column']) . ' is not null';
470
    }
471
472
    /**
473
     * Compile a "between" where clause.
474
     *
475
     * @param  \Childish\query\Builder $query
476
     * @param  array                   $where
477
     * @return string
478
     */
479
    protected function whereBetween(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
480
    {
481
        $between = $where['not'] ? 'not between' : 'between';
482
483
        return $this->wrap($where['column']) . ' ' . $between . ' ? and ?';
484
    }
485
486
    /**
487
     * Compile a "where date" clause.
488
     *
489
     * @param  \Childish\query\Builder $query
490
     * @param  array                   $where
491
     * @return string
492
     */
493
    protected function whereDate(Builder $query, $where)
494
    {
495
        return $this->dateBasedWhere('date', $query, $where);
496
    }
497
498
    /**
499
     * Compile a "where time" clause.
500
     *
501
     * @param  \Childish\query\Builder $query
502
     * @param  array                   $where
503
     * @return string
504
     */
505
    protected function whereTime(Builder $query, $where)
506
    {
507
        return $this->dateBasedWhere('time', $query, $where);
508
    }
509
510
    /**
511
     * Compile a "where day" clause.
512
     *
513
     * @param  \Childish\query\Builder $query
514
     * @param  array                   $where
515
     * @return string
516
     */
517
    protected function whereDay(Builder $query, $where)
518
    {
519
        return $this->dateBasedWhere('day', $query, $where);
520
    }
521
522
    /**
523
     * Compile a "where month" clause.
524
     *
525
     * @param  \Childish\query\Builder $query
526
     * @param  array                   $where
527
     * @return string
528
     */
529
    protected function whereMonth(Builder $query, $where)
530
    {
531
        return $this->dateBasedWhere('month', $query, $where);
532
    }
533
534
    /**
535
     * Compile a "where year" clause.
536
     *
537
     * @param  \Childish\query\Builder $query
538
     * @param  array                   $where
539
     * @return string
540
     */
541
    protected function whereYear(Builder $query, $where)
542
    {
543
        return $this->dateBasedWhere('year', $query, $where);
544
    }
545
546
    /**
547
     * Compile a date based where clause.
548
     *
549
     * @param  string                  $type
550
     * @param  \Childish\query\Builder $query
551
     * @param  array                   $where
552
     * @return string
553
     */
554
    protected function dateBasedWhere($type, Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
555
    {
556
        return $type . '(' . $this->wrap($where['column']) . ') ' . $where['operator'] . ' ' . '?';
557
    }
558
559
    /**
560
     * Compile a where clause comparing two columns..
561
     *
562
     * @param  \Childish\query\Builder $query
563
     * @param  array                   $where
564
     * @return string
565
     */
566
    protected function whereColumn(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
567
    {
568
        return $this->wrap($where['first']) . ' ' . $where['operator'] . ' ' . $this->wrap($where['second']);
569
    }
570
571
    /**
572
     * Compile a nested where clause.
573
     *
574
     * @param  \Childish\query\Builder $query
575
     * @param  array                   $where
576
     * @return string
577
     */
578
    protected function whereNested(Builder $query, $where)
579
    {
580
        // Here we will calculate what portion of the string we need to remove. If this
581
        // is a join clause query, we need to remove the "on" portion of the SQL and
582
        // if it is a normal query we need to take the leading "where" of queries.
583
        $offset = $query instanceof JoinClause ? 3 : 6;
0 ignored issues
show
The class Childish\query\JoinClause 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...
584
585
        return '(' . substr($this->compileWheres($where['query']), $offset) . ')';
586
    }
587
588
    /**
589
     * Compile a where condition with a sub-select.
590
     *
591
     * @param  \Childish\query\Builder $query
592
     * @param  array                   $where
593
     * @return string
594
     */
595
    protected function whereSub(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
596
    {
597
        $select = $this->compileSelect($where['query']);
598
599
        return $this->wrap($where['column']) . ' ' . $where['operator'] . " ($select)";
600
    }
601
602
    /**
603
     * Compile a where exists clause.
604
     *
605
     * @param  \Childish\query\Builder $query
606
     * @param  array                   $where
607
     * @return string
608
     */
609
    protected function whereExists(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
610
    {
611
        return 'exists (' . $this->compileSelect($where['query']) . ')';
612
    }
613
614
    /**
615
     * Compile a where exists clause.
616
     *
617
     * @param  \Childish\query\Builder $query
618
     * @param  array                   $where
619
     * @return string
620
     */
621
    protected function whereNotExists(Builder $query, $where)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
622
    {
623
        return 'not exists (' . $this->compileSelect($where['query']) . ')';
624
    }
625
626
    /**
627
     * Compile the "group by" portions of the query.
628
     *
629
     * @param  \Childish\query\Builder $query
630
     * @param  array                   $groups
631
     * @return string
632
     */
633
    protected function compileGroups(Builder $query, $groups)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
634
    {
635
        return 'group by ' . $this->columnize($groups);
636
    }
637
638
    /**
639
     * Compile the "having" portions of the query.
640
     *
641
     * @param  \Childish\query\Builder $query
642
     * @param  array                   $havings
643
     * @return string
644
     */
645
    protected function compileHavings(Builder $query, $havings)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
646
    {
647
        $sql = implode(' ', array_map([$this, 'compileHaving'], $havings));
648
649
        return 'having ' . $this->removeLeadingBoolean($sql);
650
    }
651
652
    /**
653
     * Compile a single having clause.
654
     *
655
     * @param  array $having
656
     * @return string
657
     */
658
    protected function compileHaving(array $having)
659
    {
660
        // If the having clause is "raw", we can just return the clause straight away
661
        // without doing any more processing on it. Otherwise, we will compile the
662
        // clause into SQL based on the components that make it up from builder.
663
        if ($having['type'] === 'Raw') {
664
            return $having['boolean'] . ' ' . $having['sql'];
665
        }
666
667
        return $this->compileBasicHaving($having);
668
    }
669
670
    /**
671
     * Compile a basic having clause.
672
     *
673
     * @param  array $having
674
     * @return string
675
     */
676
    protected function compileBasicHaving($having)
677
    {
678
        $column = $this->wrap($having['column']);
679
680
681
        return $having['boolean'] . ' ' . $column . ' ' . $having['operator'] . ' ' . '?';
682
    }
683
684
    /**
685
     * Compile the "order by" portions of the query.
686
     *
687
     * @param  \Childish\query\Builder $query
688
     * @param  array                   $orders
689
     * @return string
690
     */
691
    protected function compileOrders(Builder $query, $orders)
692
    {
693
        if (!empty($orders)) {
694
            return 'order by ' . implode(', ', $this->compileOrdersToArray($query, $orders));
695
        }
696
697
        return '';
698
    }
699
700
    /**
701
     * Compile the query orders to an array.
702
     *
703
     * @param  \Childish\query\Builder
704
     * @param  array $orders
705
     * @return array
706
     */
707
    protected function compileOrdersToArray(Builder $query, $orders)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
708
    {
709
        return array_map(function ($order) {
710
            return !isset($order['sql'])
711
                ? $this->wrap($order['column']) . ' ' . $order['direction']
712
                : $order['sql'];
713
        }, $orders);
714
    }
715
716
    /**
717
     * Compile the random statement into SQL.
718
     *
719
     * @param  string $seed
720
     * @return string
721
     */
722
    public function compileRandom($seed)
723
    {
724
        return 'RANDOM()';
725
    }
726
727
    /**
728
     * Compile the "limit" portions of the query.
729
     *
730
     * @param  \Childish\query\Builder $query
731
     * @param  int                     $limit
732
     * @return string
733
     */
734
    protected function compileLimit(Builder $query, $limit)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
735
    {
736
        return 'limit ' . (int)$limit;
737
    }
738
739
    /**
740
     * Compile the "offset" portions of the query.
741
     *
742
     * @param  \Childish\query\Builder $query
743
     * @param  int                     $offset
744
     * @return string
745
     */
746
    protected function compileOffset(Builder $query, $offset)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
747
    {
748
        return 'offset ' . (int)$offset;
749
    }
750
751
    /**
752
     * Compile the "union" queries attached to the main query.
753
     *
754
     * @param  \Childish\query\Builder $query
755
     * @return string
756
     */
757
    protected function compileUnions(Builder $query)
758
    {
759
        $sql = '';
760
761
        foreach ($query->unions as $union) {
762
            $sql .= $this->compileUnion($union);
763
        }
764
765
        if (!empty($query->unionOrders)) {
766
            $sql .= ' ' . $this->compileOrders($query, $query->unionOrders);
767
        }
768
769
        if (isset($query->unionLimit)) {
770
            $sql .= ' ' . $this->compileLimit($query, $query->unionLimit);
771
        }
772
773
        if (isset($query->unionOffset)) {
774
            $sql .= ' ' . $this->compileOffset($query, $query->unionOffset);
775
        }
776
777
        return ltrim($sql);
778
    }
779
780
    /**
781
     * Compile a single union statement.
782
     *
783
     * @param  array $union
784
     * @return string
785
     */
786
    protected function compileUnion(array $union)
787
    {
788
        $conjuction = $union['all'] ? ' union all ' : ' union ';
789
790
        return $conjuction . $union['query']->toSql();
791
    }
792
793
    /**
794
     * Compile an exists statement into SQL.
795
     *
796
     * @param  \Childish\query\Builder $query
797
     * @return string
798
     */
799
    public function compileExists(Builder $query)
800
    {
801
        $select = $this->compileSelect($query);
802
803
        return "select exists({$select}) as {$this->wrap('exists')}";
804
    }
805
806
    /**
807
     * Compile an insert statement into SQL.
808
     *
809
     * @param  \Childish\query\Builder $query
810
     * @param  array                   $values
811
     * @return string
812
     */
813
    public function compileInsert(Builder $query, array $values)
814
    {
815
        // Essentially we will force every insert to be treated as a batch insert which
816
        // simply makes creating the SQL easier for us since we can utilize the same
817
        // basic routine regardless of an amount of records given to us to insert.
818
        $table = $this->wrapTable($query->from);
819
820
        if (!is_array(reset($values))) {
821
            $values = [$values];
822
        }
823
824
        $columns = $this->columnize(array_keys(reset($values)));
825
826
        // We need to build a list of parameter place-holders of values that are bound
827
        // to the query. Each insert should have the exact same amount of parameter
828
        // bindings so we will loop through the record and parameterize them all.
829
830
        $parameters = (new Collection($values))->map(function ($record) {
831
            return '(' . $this->parameterize($record) . ')';
832
        })->implode(', ');
833
834
        return "insert into $table ($columns) values $parameters";
835
    }
836
837
    /**
838
     * Compile an insert and get ID statement into SQL.
839
     *
840
     * @param  \Childish\query\Builder $query
841
     * @param  array                   $values
842
     * @param  string                  $sequence
843
     * @return string
844
     */
845
    public function compileInsertGetId(Builder $query, $values, $sequence)
0 ignored issues
show
The parameter $sequence is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
846
    {
847
        return $this->compileInsert($query, $values);
848
    }
849
850
    /**
851
     * Compile an update statement into SQL.
852
     *
853
     * @param  \Childish\query\Builder $query
854
     * @param  array                   $values
855
     * @return string
856
     */
857
    public function compileUpdate(Builder $query, $values)
858
    {
859
        $table = $this->wrapTable($query->from);
860
861
        // Each one of the columns in the update statements needs to be wrapped in the
862
        // keyword identifiers, also a place-holder needs to be created for each of
863
        // the values in the list of bindings so we can make the sets statements.
864
        $columns = (new Collection($values))->map(function ($value, $key) {
865
            return $this->wrap($key) . ' = ?' ;
866
        })->implode(', ');
867
868
        // If the query has any "join" clauses, we will setup the joins on the builder
869
        // and compile them so we can attach them to this update, as update queries
870
        // can get join statements to attach to other tables when they're needed.
871
        $joins = '';
872
873
        if (isset($query->joins)) {
874
            $joins = ' ' . $this->compileJoins($query, $query->joins);
875
        }
876
877
        // Of course, update queries may also be constrained by where clauses so we'll
878
        // need to compile the where clauses and attach it to the query so only the
879
        // intended records are updated by the SQL statements we generate to run.
880
        $wheres = $this->compileWheres($query);
881
882
        return trim("update {$table}{$joins} set $columns $wheres");
883
    }
884
885
    /**
886
     * Prepare the bindings for an update statement.
887
     *
888
     * @param  array $bindings
889
     * @param  array $values
890
     * @return array
891
     */
892
    public function prepareBindingsForUpdate(array $bindings, array $values)
893
    {
894
        $cleanBindings = Tools::except($bindings, ['join', 'select']);
895
896
        return array_values(
897
            array_merge($bindings['join'], $values, Tools::flatten($cleanBindings))
898
        );
899
    }
900
901
    /**
902
     * Compile a delete statement into SQL.
903
     *
904
     * @param  \Childish\query\Builder $query
905
     * @return string
906
     */
907
    public function compileDelete(Builder $query)
908
    {
909
        $wheres = is_array($query->wheres) ? $this->compileWheres($query) : '';
910
911
        return trim("delete from {$this->wrapTable($query->from)} $wheres");
912
    }
913
914
    /**
915
     * Compile a truncate table statement into SQL.
916
     *
917
     * @param  \Childish\query\Builder $query
918
     * @return array
919
     */
920
    public function compileTruncate(Builder $query)
921
    {
922
        return ['truncate ' . $this->wrapTable($query->from) => []];
923
    }
924
925
    /**
926
     * Compile the lock into SQL.
927
     *
928
     * @param  \Childish\query\Builder $query
929
     * @param  bool|string             $value
930
     * @return string
931
     */
932
    protected function compileLock(Builder $query, $value)
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
933
    {
934
        return is_string($value) ? $value : '';
935
    }
936
937
    /**
938
     * Determine if the grammar supports savepoints.
939
     *
940
     * @return bool
941
     */
942
    public function supportsSavepoints()
943
    {
944
        return true;
945
    }
946
947
    /**
948
     * Compile the SQL statement to define a savepoint.
949
     *
950
     * @param  string $name
951
     * @return string
952
     */
953
    public function compileSavepoint($name)
954
    {
955
        return 'SAVEPOINT ' . $name;
956
    }
957
958
    /**
959
     * Compile the SQL statement to execute a savepoint rollback.
960
     *
961
     * @param  string $name
962
     * @return string
963
     */
964
    public function compileSavepointRollBack($name)
965
    {
966
        return 'ROLLBACK TO SAVEPOINT ' . $name;
967
    }
968
969
    /**
970
     * Concatenate an array of segments, removing empties.
971
     *
972
     * @param  array $segments
973
     * @return string
974
     */
975
    protected function concatenate($segments)
976
    {
977
        return implode(' ', array_filter($segments, function ($value) {
978
            return (string)$value !== '';
979
        }));
980
    }
981
982
    /**
983
     * Remove the leading boolean from a statement.
984
     *
985
     * @param  string $value
986
     * @return string
987
     */
988
    protected function removeLeadingBoolean($value)
989
    {
990
        return preg_replace('/and |or /i', '', $value, 1);
991
    }
992
993
    /**
994
     * Get the grammar specific operators.
995
     *
996
     * @return array
997
     */
998
    public function getOperators()
999
    {
1000
        return $this->operators;
0 ignored issues
show
The property operators does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
1001
    }
1002
}