Issues (17)

lib/Query/SelectBase.php (3 issues)

1
<?php declare(strict_types=1);
2
3
/** 
4
 *  ___      _        _
5
 * | _ \__ _| |_ __ _| |__  __ _ ___ ___
6
 * |  _/ _` |  _/ _` | '_ \/ _` (_-</ -_)
7
 * |_| \__,_|\__\__,_|_.__/\__,_/__/\___|
8
 * 
9
 * This file is part of Kristuff\Patabase.
10
 * (c) Kristuff <[email protected]>
11
 *
12
 * For the full copyright and license information, please view the LICENSE
13
 * file that was distributed with this source code.
14
 *
15
 * @version    1.0.1
16
 * @copyright  2017-2022 Christophe Buliard
17
 */
18
19
namespace Kristuff\Patabase\Query;
20
21
use Kristuff\Patabase\Driver\DatabaseDriver;
22
use Kristuff\Patabase\Query;
23
use Kristuff\Patabase\Query\QueryBuilder;
24
25
/**
26
 * Class SelectBase
27
 * 
28
 * Abstract base class for Select
29
 */
30
abstract class SelectBase extends QueryBuilder
31
{
32
33
    /**
34
     * Use DISTINCT or not?
35
     *
36
     * @access protected
37
     * @var boolean
38
     */
39
    protected $distinct = false;
40
41
    /**
42
     * Columns list for SELECT query
43
     *
44
     * @access protected
45
     * @var array
46
     */
47
    protected $columns = array();
48
49
    /**
50
     * Table source for SELECT query
51
     *
52
     * @access protected
53
     * @var string
54
     */
55
    protected $fromTable = '';
56
57
    /**
58
     * SQL JOINS internal list
59
     *
60
     * @access protected
61
     * @var array
62
     */
63
    protected $joins = array();
64
65
    /**
66
     * SQL GROUP BY internal list
67
     *
68
     * @access protected
69
     * @var array
70
     */
71
    protected $groupBy = array();
72
73
    /**
74
     * SQL ORDER BY internal list
75
     *
76
     * @access protected
77
     * @var array
78
     */
79
    protected $orderBy = array();
80
81
    /**
82
     * Limit for the SELECT query
83
     *
84
     * @access protected
85
     * @var int
86
     */
87
    protected $limit = 0;
88
89
    /**
90
     * Offset for the SELECT query
91
     *
92
     * @access protected
93
     * @var int
94
     */
95
    protected $offset = 0;
96
97
    /**
98
     * The top QueryBuilder instance, in case of subquery
99
     *
100
     * @access protected
101
     * @var QueryBuilder
102
     */
103
     protected $topQuery = null;
104
105
    /**
106
     * Constructor
107
     *
108
     * @access public
109
     * @param DatabaseDriver    $driver   The driver instance
110
     * @param Query             $query    The top query parent in case of subquery. Default is NULL
111
     * @param array             $args     Columns arguments. Default is empty array
112
     */
113
    public function __construct(DatabaseDriver $driver, $query = null, $args = array())
114
    {
115
        parent::__construct($driver);
116
        $this->topQuery = $query;
117
118
        // columns arguments
119
        if (! empty($args)) {
120
            $this->parseColumnsArguments($args);
121
        }
122
    }
123
124
   /**
125
     * Parse the columns arguments for the select query
126
     *
127
     * @access protected
128
     * @param mixed             $args       The output columns argument
129
     *
130
     * @return void
131
     */
132
    protected function parseColumnsArguments(array $args)
133
    {
134
        // args could be list of name, or one argument indexed array name => alias
135
        $cols = (count($args) === 1 && is_array($args[0])) ? $args[0] : $args;
136
137
        // parse column
138
        foreach ($cols as $key => $value){
139
            
140
            // Each arg could be a non indexed array of name, or 
141
            // an indexed array name => alias
142
            $column = !is_int($key) ? $key : $value;
143
            $columnValue = !is_int($key) ? $value : null;
144
            $this->column($column, $columnValue);
145
        }
146
    }
147
148
    /**
149
     * Define the used of DISTINCT keyword 
150
     *
151
     * @access public
152
     * @return $this
153
     */
154
    public function distinct()
155
    {
156
        $this->distinct = true;
157
        return $this;
158
    }
159
160
    /**
161
     * Add an output column for the select
162
     *
163
     * @access public
164
     * @param string   $column    The column name, could be Table.ColumnName format
165
     * @param string   $alias     The alias for this column
166
     * 
167
     * @return $this
168
     */
169
    public function column(string $column, ?string $alias = null)
170
    {
171
        $this->columns[] = array(
172
            'type'  => 'column',
173
            'name'  => $column, 
174
            'alias' => $alias);
175
        return $this;
176
    }
177
178
    /**
179
     * Define the outputs columns for the select
180
     *
181
     * @access public
182
     * @return $this
183
     */
184
    public function columns()
185
    {
186
        // args could be list of name, of one argument indexed array name => alias
187
        $args = func_get_args();
188
        $this->parseColumnsArguments($args);
189
        return $this;
190
    }
191
192
    /**
193
     * Add a COUNT(*) column for the select
194
     *
195
     * @access public
196
     * @param string   $alias     The alias for this column
197
     * 
198
     * @return $this
199
     */
200
    public function count(string $alias)
201
    {
202
         $this->columns[] = array(
203
            'type'  => 'count',
204
            'alias' => $alias
205
        );
206
        return $this;
207
    }
208
209
    /**
210
     * Add a SUM(column) for the select
211
     *
212
     * @access public
213
     * @param string $column   The column to sum
214
     * @param string $alias    The alias for this column 
215
     * @return $this
216
     */
217
    public function sum(string $column, string $alias)
218
    {
219
         $this->columns[] = array(
220
            'type'  => 'sum',
221
            'name'  => $column,
222
            'alias' => $alias
223
        );
224
        return $this;
225
    }
226
227
    /**
228
     * Add a MIN(column) for the select
229
     *
230
     * @access public
231
     * @param string $column   The column to sum
232
     * @param string $alias    The alias for this column 
233
     * @return $this
234
     */
235
    public function min(string $column, string $alias)
236
    {
237
         $this->columns[] = array(
238
            'type'  => 'min',
239
            'name'  => $column,
240
            'alias' => $alias
241
        );
242
        return $this;
243
    }
244
245
    /**
246
     * Add a MAX(column) for the select
247
     *
248
     * @access public
249
     * @param string $column   The column to sum
250
     * @param string $alias    The alias for this column 
251
     * @return $this
252
     */
253
    public function max(string $column, string $alias)
254
    {
255
         $this->columns[] = array(
256
            'type'  => 'max',
257
            'name'  => $column,
258
            'alias' => $alias
259
        );
260
        return $this;
261
    }
262
263
    /**
264
     * Create and returns a new sub Select instance
265
     *
266
     * @access public
267
     * @param string $alias    The alias for this query 
268
     *
269
     * @return Query\Select 
270
     */
271
    public function select(string $alias)
272
    {
273
        $query = new Select($this->driver, $this);
274
        $this->columns[] = array(
275
            'type' => 'sub_query',
276
            'query' => $query,
277
            'alias' => $alias,
278
        );
279
        return $query;
280
    }
281
282
    /**
283
     * Define the FROM tableName
284
     *
285
     * @access public
286
     * @param string   $tableName      The table name
287
     *
288
     * @return $this
289
     */
290
    public function from(string $tableName)
291
    {
292
        $this->fromTable = $tableName;
293
        return $this;       
294
    }
295
   
296
    /**
297
     * Left join
298
     *
299
     * @access public
300
     * @param string   $externalTable    Join table
301
     * @param string   $externalColumn   Foreign key on the join table
302
     * @param string   $localTable       Local table
303
     * @param string   $localColumn      Local column
304
     *
305
     * @return $this
306
     */
307
    public function leftJoin(string $externalTable, string $externalColumn, string $localTable, string $localColumn)
308
    {
309
        $this->joins[] = sprintf(
310
            'LEFT OUTER JOIN %s ON %s=%s',
311
            $this->driver->escape($externalTable),
312
            $this->driver->escape($localTable).'.'.$this->driver->escape($localColumn),
313
            $this->driver->escape($externalTable).'.'.$this->driver->escape($externalColumn)
314
        );
315
        return $this;
316
    }
317
318
    /**
319
     * Right join
320
     *
321
     * @access public
322
     * @param string   $externalTable    Join table
323
     * @param string   $externalColumn   Foreign key on the join table
324
     * @param string   $localTable       Local table
325
     * @param string   $localColumn      Local column
326
     *
327
     * @return $this
328
     */
329
    public function rightJoin(string $externalTable, string $externalColumn, string $localTable, string $localColumn)
330
    {
331
        $this->joins[] = sprintf(
332
            'RIGHT OUTER JOIN %s ON %s=%s',
333
            $this->driver->escape($externalTable),
334
            $this->driver->escape($localTable).'.'.$this->driver->escape($localColumn),
335
            $this->driver->escape($externalTable).'.'.$this->driver->escape($externalColumn)
336
        );
337
        return $this;
338
    }
339
340
    /**
341
     * Full join
342
     *
343
     * @access public
344
     * @param string   $externalTable    Join table
345
     * @param string   $externalColumn   Foreign key on the join table
346
     * @param string   $localTable       Local table
347
     * @param string   $localColumn      Local column
348
     *
349
     * @return $this
350
     */
351
    public function fullJoin(string $externalTable, string $externalColumn, string $localTable, string $localColumn)
352
    {
353
        $this->joins[] = sprintf(
354
            'FULL OUTER JOIN %s ON %s=%s',
355
            $this->driver->escape($externalTable),
356
            $this->driver->escape($localTable).'.'.$this->driver->escape($localColumn),
357
            $this->driver->escape($externalTable).'.'.$this->driver->escape($externalColumn)
358
        );
359
        return $this;
360
    }       
361
    
362
    /**
363
     * Inner join
364
     *
365
     * @access public
366
     * @param string   $externalTable    Join table
367
     * @param string   $externalColumn   Foreign key on the join table
368
     * @param string   $localTable       Local table
369
     * @param string   $localColumn      Local column
370
     *
371
     * @return $this
372
     */
373
    public function innerJoin(string $externalTable, string $externalColumn, string $localTable, string $localColumn)
374
    {
375
        $this->joins[] = sprintf(
376
            'INNER JOIN %s ON %s=%s',
377
            $this->driver->escape($externalTable),
378
            $this->driver->escape($localTable).'.'.$this->driver->escape($localColumn),
379
            $this->driver->escape($externalTable).'.'.$this->driver->escape($externalColumn)
380
        );
381
        return $this;
382
    }
383
384
    /**
385
     * join (alias for innerJoin)
386
     *
387
     * @access public
388
     * @param string   $externalTable    Join table
389
     * @param string   $externalColumn   Foreign key on the join table
390
     * @param string   $localTable       Local table
391
     * @param string   $localColumn      Local column
392
     *
393
     * @return $this
394
     */
395
    public function join(string $externalTable, string $externalColumn, string $localTable, string $localColumn)
396
    {
397
        return $this->innerJoin($externalTable, $externalColumn, $localTable, $localColumn);
398
    }
399
400
    /**
401
     * Get a WHERE statement object
402
     *
403
     * @access public
404
     * @return Where
405
     */
406
    public function where(): Where
407
    {
408
        if (!isset($this->where)){
409
            $this->where = new Query\Where($this, $this->driver, $this->topQuery);
410
        }
411
        return $this->where; 
412
    }
413
414
    /**
415
     * Add a WHERE column = value condition
416
     * It's an alias for ->where()->equal($column, $value)
417
     * 
418
     * @access public
419
     * @param string    $column     The column name
420
     * @param mixed     $value      The condition value
421
     * 
422
     * @return $this
423
     */
424
    public function whereEqual(string $column, $value)
425
    {
426
        $this->where()->equal($column, $value);
427
        return $this;
428
    }
429
    
430
    /**
431
     * Get an HAVING statement object
432
     *
433
     * @access public
434
     * @return Having
435
     */
436
    public function having(): Having
437
    {
438
        if (!isset($this->having)){
439
            $this->having = new Query\Having($this, $this->driver, $this->topQuery);
440
        }
441
        return $this->having; 
442
    }
443
444
    /**
445
     * Define the GROUP BY 
446
     *
447
     * @access public
448
     * @param  mixed 
449
     * @return $this
450
     */
451
    public function groupBy()
452
    {
453
        $this->groupBy = func_get_args();
454
        return $this;
455
    }
456
        
457
    /**
458
     * Add an ORDER BY statement
459
     *
460
     * @access public
461
     * @param string   $column    Column name
462
     * @param string   $order     Direction ASC or DESC or custom function
463
     *
464
     * @return $this
465
     */
466
    public function orderBy($column, $order = self::SORT_ASC)
467
    {
468
        $this->orderBy[] = array(
469
            'column' => $column,
470
            'order'  => $order
471
        );
472
        return $this;
473
    }
474
        
475
    /**
476
     * Add an ORDER BY [X] ASC statement
477
     *
478
     * @access public
479
     * @param string   $column    The column name
480
     * @return $this
481
     */
482
    public function orderAsc($column)
483
    {   
484
        $this->orderBy($column, self::SORT_ASC);
485
        return $this;
486
    }
487
488
    /**
489
     * Add an ORDER BY [X] DESC statement
490
     *
491
     * @access public
492
     * @param string   $column    The column name
493
     * @return $this
494
     */
495
    public function orderDesc($column)
496
    {
497
        $this->orderBy($column, self::SORT_DESC);
498
        return $this;
499
    }
500
501
    /**
502
     * Add an ORDER BY *random function* statement
503
     *
504
     * @access public
505
     * @param  int      $seed    (optional) The random seed.
506
     *
507
     * @return $this
508
     */
509
    public function orderRand($seed = null)
510
    {
511
        $this->orderBy(NULL, $this->driver->sqlRandom($seed));
0 ignored issues
show
The call to Kristuff\Patabase\Driver...baseDriver::sqlRandom() has too many arguments starting with $seed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

511
        $this->orderBy(NULL, $this->driver->/** @scrutinizer ignore-call */ sqlRandom($seed));

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. Please note the @ignore annotation hint above.

Loading history...
512
        return $this;
513
    }
514
515
    /**
516
     * Define the query LIMIT
517
     *
518
     * @access public
519
     * @param  int       $value    The limit value
520
     * 
521
     * @return $this
522
     */
523
    public function limit($value)
524
    {
525
        if (! is_null($value)) {
0 ignored issues
show
The condition is_null($value) is always false.
Loading history...
526
            $this->limit = (int) $value;
527
        }
528
        return $this;
529
    }
530
531
    /**
532
     * Define the query OFFSET
533
     *
534
     * @access public
535
     * @param  int      $value      The offset value
536
     *
537
     * @return $this
538
     */
539
    public function offset($value)
540
    {
541
        if (! is_null($value)) {
0 ignored issues
show
The condition is_null($value) is always false.
Loading history...
542
            $this->offset = (int) $value;
543
        }
544
        return $this;
545
    }
546
}