Completed
Push — feature/player-list ( c5ed99...8b85bb )
by Vladimir
06:36
created

QueryBuilder::createQueryColumns()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 11
nc 3
nop 1
crap 3
1
<?php
2
/**
3
 * This file contains a class to quickly generate database queries for models
4
 *
5
 * @package    BZiON\Models\QueryBuilder
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
9
/**
10
 * This class can be used to search for models with specific characteristics in
11
 * the database.
12
 *
13
 * Note that most methods of this class return itself, so that you can easily
14
 * add a number of different filters.
15
 *
16
 * <code>
17
 *     return Team::getQueryBuilder()
18
 *     ->active()
19
 *     ->where('name')->startsWith('a')
20
 *     ->sortBy('name')->reverse()
21
 *     ->getModels();
22
 * </code>
23
 *
24
 * @package    BZiON\Models\QueryBuilder
25
 */
26
class QueryBuilder implements Countable
27
{
28
    /**
29
     * The type of the model we're building a query for
30
     * @var string
31
     */
32
    protected $type;
33
34
    /**
35
     * The columns that the model provided us
36
     * @var array
37
     */
38
    protected $columns = array('id' => 'id');
39
40
    /**
41
     * Extra columns that are generated from the SQL query (this should be a comma separated string or null)
42
     * @var string|null
43
     */
44
    protected $extraColumns = null;
45
46
    /**
47
     * The conditions to include in WHERE
48
     * @var string[]
49
     */
50
    protected $whereConditions = array();
51
52
    /**
53
     * The conditions to include in HAVING
54
     * @var string[]
55
     */
56
    protected $havingConditions = array();
57
58
    /**
59
     * The MySQL value parameters
60
     * @var array
61
     */
62
    protected $parameters = array();
63
64
    /**
65
     * The MySQL value parameters for pagination
66
     * @var array
67
     */
68
    protected $paginationParameters = array();
69
70
    /**
71
     * Extra MySQL query string to pass
72
     * @var string
73
     */
74
    protected $extras = '';
75
76
    /**
77
     * Extra MySQL query groupby string to pass
78
     * @var string
79
     */
80
    protected $groupQuery = '';
81
82
    /**
83
     * A column based on which we should sort the results
84
     * @var string|null
85
     */
86
    private $sortBy = null;
87
88
    /**
89
     * Whether to reverse the results
90
     * @var bool
91
     */
92
    private $reverseSort = false;
93
94
    /**
95
     * The currently selected column
96
     * @var string|null
97
     */
98
    private $currentColumn = null;
99
100
    /**
101
     * Either 'where' or 'having'
102
     * @var string
103
     */
104
    private $currentColumnMode = '';
105
106
    /**
107
     * The currently selected column without the table name (unless it was
108
     * explicitly provided)
109
     * @var string|null
110
     */
111
    protected $currentColumnRaw = null;
112
113
    /**
114
     * A column to consider the name of the model
115
     * @var string|null
116
     */
117
    private $nameColumn = null;
118
119
    /**
120
     * The page to return
121
     * @var int|null
122
     */
123
    private $page = null;
124
125
    /**
126
     * Whether the ID of the first/last element has been provided
127
     * @var bool
128
     */
129
    private $limited = false;
130
131
    /**
132
     * The number of elements on every page
133
     * @var int
134
     */
135
    protected $resultsPerPage = 30;
136
137
    /**
138
     * Create a new QueryBuilder
139
     *
140
     * A new query builder should be created on a static getQueryBuilder()
141
     * method on each model. The options array can contain the following
142
     * properties:
143
     *
144
     * - `columns`: An associative array - the key of each entry is the column
145
     *   name that will be used by other methods, while the value is
146
     *   is the column name that is used in the database structure
147
     *
148
     * - `activeStatuses`: If the model has a status column, this should be
149
     *                     a list of values that make the entry be considered
150
     *                     "active"
151
     *
152
     * - `name`: The name of the column which represents the name of the object
153
     *
154
     * @param string $type    The type of the Model (e.g. "Player" or "Match")
155
     * @param array  $options The options to pass to the builder (see above)
156
     */
157 1
    public function __construct($type, $options = array())
158
    {
159 1
        $this->type = $type;
160
161 1
        if (isset($options['columns'])) {
162 1
            $this->columns += $options['columns'];
163
        }
164
165 1
        if (isset($options['name'])) {
166 1
            $this->nameColumn = $options['name'];
167
        }
168 1
    }
169
170
    /**
171
     * Select a column
172
     *
173
     * `$queryBuilder->where('username')->equals('administrator');`
174
     *
175
     * @param  string $column The column to select
176
     * @return static
177
     */
178 1
    public function where($column)
179
    {
180 1
        return $this->grabColumn($column, 'where');
181
    }
182
183
    /**
184
     * Select an alias from an aggregate function
185
     *
186
     * `$queryBuilder->where('activity')->greaterThan(0);`
187
     *
188
     * @param  string $column The column to select
189
     * @return static
190
     */
191
    public function having($column)
192
    {
193
        return $this->grabColumn($column, 'having');
194
    }
195
196
    /**
197
     * Request that a column equals a string (case-insensitive)
198
     *
199
     * @param  string $string The string that the column's value should equal to
200
     * @return static
201
     */
202 1
    public function equals($string)
203
    {
204 1
        $this->addColumnCondition("= ?", $string);
205
206 1
        return $this;
207
    }
208
209
    /**
210
     * Request that a column doesNOT equals a string (case-insensitive)
211
     *
212
     * @param  string $string The string that the column's value should equal to
213
     * @return static
214
     */
215 1
    public function notEquals($string)
216
    {
217 1
        $this->addColumnCondition("!= ?", $string);
218
219 1
        return $this;
220
    }
221
222
    /**
223
     * Request that a column is greater than a quantity
224
     *
225
     * @param  string $quantity The quantity to test against
226
     * @return static
227
     */
228
    public function greaterThan($quantity)
229
    {
230
        $this->addColumnCondition("> ?", $quantity);
231
232
        return $this;
233
    }
234
235
    /**
236
     * Request that a column is less than a quantity
237
     *
238
     * @param  string $quantity The quantity to test against
239
     * @return static
240
     */
241
    public function lessThan($quantity)
242
    {
243
        $this->addColumnCondition("< ?", $quantity);
244
245
        return $this;
246
    }
247
248
    /**
249
     * Request that a timestamp is before the specified time
250
     *
251
     * @param string|TimeDate $time      The timestamp to compare to
252
     * @param bool            $inclusive Whether to include the given timestamp
253
     * @param bool            $reverse   Whether to reverse the results
254
     *
255
     * @return static
256
     */
257
    public function isBefore($time, $inclusive = false, $reverse = false)
258
    {
259
        return $this->isAfter($time, $inclusive, !$reverse);
260
    }
261
262
    /**
263
     * Request that a timestamp is after the specified time
264
     *
265
     * @param string|TimeDate $time      The timestamp to compare to
266
     * @param bool            $inclusive Whether to include the given timestamp
267
     * @param bool            $reverse   Whether to reverse the results
268
     *
269
     * @return static
270
     */
271 1
    public function isAfter($time, $inclusive = false, $reverse = false)
272
    {
273 1
        if ($time instanceof TimeDate) {
274 1
            $time = $time->toMysql();
275
        }
276
277 1
        $comparison  = ($reverse)   ? '<' : '>';
278 1
        $comparison .= ($inclusive) ? '=' : '';
279
280 1
        $this->addColumnCondition("$comparison ?",  $time);
281
282 1
        return $this;
283
    }
284
285
    /**
286
     * Request that a column equals a number
287
     *
288
     * @param  int|Model|null $number The number that the column's value should
289
     *                                equal to. If a Model is provided, use the
290
     *                                model's ID, while null values are ignored.
291
     * @return static
292
     */
293 1 View Code Duplication
    public function is($number)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
    {
295 1
        if ($number === null) {
296
            return $this;
297
        }
298
299 1
        if ($number instanceof Model) {
300 1
            $number = $number->getId();
301
        }
302
303 1
        $this->addColumnCondition("= ?", $number);
304
305 1
        return $this;
306
    }
307
308
    /**
309
     * Request that a column equals one of some strings
310
     *
311
     * @todo   Improve for PDO
312
     * @param  string[] $strings The list of accepted values for the column
313
     * @return static
314
     */
315 1
    public function isOneOf($strings)
316
    {
317 1
        $count = count($strings);
318 1
        $questionMarks = str_repeat(',?', $count);
319
320
        // Remove first comma from questionMarks so that MySQL can read our query
321 1
        $questionMarks = ltrim($questionMarks, ',');
322
323 1
        $this->addColumnCondition("IN ($questionMarks)", $strings);
324
325 1
        return $this;
326
    }
327
328
    /**
329
     * Request that a column value starts with a string (case-insensitive)
330
     *
331
     * @param  string $string The substring that the column's value should start with
332
     * @return static
333
     */
334
    public function startsWith($string)
335
    {
336
        $this->addColumnCondition("LIKE CONCAT(?, '%')", $string);
337
338
        return $this;
339
    }
340
341
    /**
342
     * Request that a specific model is not returned
343
     *
344
     * @param  Model|int $model The ID or model you don't want to get
345
     * @return static
346
     */
347 View Code Duplication
    public function except($model)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
348
    {
349
        if ($model instanceof Model) {
350
            $model = $model->getId();
351
        }
352
353
        $this->where('id');
354
        $this->addColumnCondition("!= ?", $model);
355
356
        return $this;
357
    }
358
359
    /**
360
     * Return the results sorted by the value of a column
361
     *
362
     * @param  string $column The column based on which the results should be ordered
363
     * @return static
364
     */
365 1
    public function sortBy($column)
366
    {
367 1
        if (!isset($this->columns[$column])) {
368
            throw new Exception("Unknown column");
369
        }
370
371 1
        $this->sortBy = $this->columns[$column];
372
373 1
        return $this;
374
    }
375
376
    /**
377
     * Reverse the order
378
     *
379
     * Note: This only works if you have specified a column in the sortBy() method
380
     *
381
     * @return static
382
     */
383 1
    public function reverse()
384
    {
385 1
        $this->reverseSort = !$this->reverseSort;
386
387 1
        return $this;
388
    }
389
390
    /**
391
     * Specify the number of results per page
392
     *
393
     * @param  int  $count The number of results
394
     * @return static
395
     */
396 1
    public function limit($count)
397
    {
398 1
        $this->resultsPerPage = $count;
399
400 1
        return $this;
401
    }
402
403
    /**
404
     * Only show results from a specific page
405
     *
406
     * @param  int|null $page The page number (or null to show all pages - counting starts from 0)
407
     * @return static
408
     */
409 1
    public function fromPage($page)
410
    {
411 1
        $this->page = $page;
412
413 1
        return $this;
414
    }
415
416
    /**
417
     * End with a specific result
418
     *
419
     * @param  int|Model $model     The model (or database ID) after the first result
420
     * @param  bool   $inclusive Whether to include the provided model
421
     * @param  bool   $reverse   Whether to reverse the results
422
     * @return static
423
     */
424 1
    public function endAt($model, $inclusive = false, $reverse = false)
425
    {
426 1
        return $this->startAt($model, $inclusive, !$reverse);
427
    }
428
429
    /**
430
     * Start with a specific result
431
     *
432
     * @param  int|Model $model     The model (or database ID) before the first result
433
     * @param  bool   $inclusive Whether to include the provided model
434
     * @param  bool   $reverse   Whether to reverse the results
435
     * @return static
436
     */
437 1
    public function startAt($model, $inclusive = false, $reverse = false)
438
    {
439 1
        if (!$model) {
440 1
            return $this;
441
        } elseif ($model instanceof Model && !$model->isValid()) {
442
            return $this;
443
        }
444
445
        $this->column($this->sortBy);
0 ignored issues
show
Bug introduced by
The call to column() misses a required argument $mode.

This check looks for function calls that miss required arguments.

Loading history...
446
        $this->limited = true;
447
        $column = $this->currentColumn;
448
        $table  = $this->getTable();
449
450
        $comparison  = $this->reverseSort ^ $reverse;
451
        $comparison  = ($comparison) ? '>' : '<';
452
        $comparison .= ($inclusive)  ? '=' : '';
453
        $id = ($model instanceof Model) ? $model->getId() : $model;
454
455
        // Compare an element's timestamp to the timestamp of $model; if it's the
456
        // same, perform the comparison using IDs
457
        $this->addColumnCondition(
458
            "$comparison (SELECT $column FROM $table WHERE id = ?) OR ($column = (SELECT $column FROM $table WHERE id = ?) AND id $comparison ?)",
459
            array($id, $id, $id)
460
        );
461
462
        return $this;
463
    }
464
465
    /**
466
     * Request that only "active" Models should be returned
467
     *
468
     * @return static
469
     */
470 1
    public function active()
471
    {
472 1
        if (!isset($this->columns['status'])) {
473
            return $this;
474
        }
475
476 1
        $type = $this->type;
477
478 1
        return $this->where('status')->isOneOf($type::getActiveStatuses());
479
    }
480
481
    /**
482
     * Make sure that Models invisible to a player are not returned
483
     *
484
     * Note that this method does not take PermissionModel::canBeSeenBy() into
485
     * consideration for performance purposes, so you will have to override this
486
     * in your query builder if necessary.
487
     *
488
     * @param  Player  $player      The player in question
489
     * @param  bool $showDeleted false to hide deleted models even from admins
490
     * @return static
491
     */
492 1
    public function visibleTo($player, $showDeleted = false)
493
    {
494 1
        $type = $this->type;
495
496 1
        if (is_subclass_of($type, "PermissionModel")
497 1
         && $player->hasPermission($type::EDIT_PERMISSION)) {
498
            // The player is an admin who can see hidden models
499 1
            if (!$showDeleted) {
500 1
                if (isset($this->columns['status'])) {
501 1
                    return $this->where('status')->notEquals('deleted');
502
                }
503
            }
504
        } else {
505 1
            return $this->active();
506
        }
507
508
        return $this;
509
    }
510
511
    /**
512
     * Perform the query and get back the results in an array of names
513
     *
514
     * @return string[] An array of the type $id => $name
515
     */
516 1
    public function getNames()
517
    {
518 1
        if (!$this->nameColumn) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->nameColumn of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
519
            throw new Exception("You haven't specified a name column");
520
        }
521
522 1
        $results = $this->getArray($this->nameColumn);
523
524 1
        return array_column($results, $this->nameColumn, 'id');
525
    }
526
527
    /**
528
     * Perform the query and get back the results in a list of arrays
529
     *
530
     * @todo   Play with partial models?
531
     * @param  string|string[] $columns The column(s) that should be returned
532
     * @return array[]
533
     */
534 1
    public function getArray($columns)
535
    {
536 1
        if (!is_array($columns)) {
537 1
            $columns = array($columns);
538
        }
539
540 1
        $db = Database::getInstance();
541
542 1
        return $db->query($this->createQuery($columns), $this->getParameters());
543
    }
544
545
    /**
546
     * An alias for QueryBuilder::getModels(), with fast fetching on by default
547
     * and no return of results
548
     *
549
     * @param  bool $fastFetch Whether to perform one query to load all
550
     *                            the model data instead of fetching them
551
     *                            one by one
552
     * @return void
553
     */
554
    public function addToCache($fastFetch = true)
555
    {
556
        $this->getModels($fastFetch);
557
    }
558
559
    /**
560
     * Perform the query and get the results as Models
561
     *
562
     * @todo Fix fast fetch for queries with multiple tables
563
     * @param  bool $fastFetch Whether to perform one query to load all
564
     *                            the model data instead of fetching them
565
     *                            one by one (ignores cache)
566
     * @return array
567
     */
568 1
    public function getModels($fastFetch = false)
569
    {
570 1
        $db   = Database::getInstance();
571 1
        $type = $this->type;
572
573 1
        $columns = ($fastFetch) ? $type::getEagerColumns($this->getFromAlias()) : array();
574
575 1
        if (is_string($columns) && !empty($this->extraColumns)) {
576
            $columns .= ',' . $this->extraColumns;
577
        }
578
579 1
        $results = $db->query($this->createQuery($columns), $this->getParameters());
580
581 1
        if ($fastFetch) {
582 1
            return $type::createFromDatabaseResults($results);
583
        } else {
584 1
            return $type::arrayIdToModel(array_column($results, 'id'));
585
        }
586
    }
587
588
    /**
589
     * Count the results
590
     *
591
     * @return int
592
     */
593 1
    public function count()
594
    {
595 1
        $table  = $this->getTable();
596 1
        $params = $this->createQueryParams(false);
597 1
        $db     = Database::getInstance();
598 1
        $query  = "SELECT COUNT(*) FROM $table $params";
599
600
        // We don't want pagination to affect our results so don't use the functions that combine
601
        // pagination results
602 1
        $results = $db->query($query, $this->parameters);
603
604 1
        return $results[0]['COUNT(*)'];
605
    }
606
607
    /**
608
     * Count the number of pages that all the models could be separated into
609
     */
610
    public function countPages()
611
    {
612
        return ceil($this->count() / $this->getResultsPerPage());
613
    }
614
615
    /**
616
     * Find if there is any result
617
     *
618
     * @return bool
619
     */
620 1
    public function any()
621
    {
622
        // Make sure that we don't mess with the user's options
623 1
        $query = clone $this;
624
625 1
        $query->limit(1);
626
627 1
        return $query->count() > 0;
628
    }
629
630
    /**
631
     * Get the amount of results that are returned per page
632
     * @return int
633
     */
634
    public function getResultsPerPage()
635
    {
636
        return $this->resultsPerPage;
637
    }
638
639
    /**
640
     * Select a column to perform opeations on
641
     *
642
     * This is identical to the `where()` method, except that the column is
643
     * specified as a MySQL column and not as a column name given by the model
644
     *
645
     * @param  string $column The column to select
646
     * @return static
647
     */
648 1
    protected function column($column, $mode)
649
    {
650 1
        $this->currentColumnMode = $mode;
651
652 1
        if (strpos($column, '.') === false) {
653
            // Add the table name to the column if it isn't there already so that
654
            // MySQL knows what to do when handling multiple tables
655 1
            $this->currentColumn = ($this->currentColumnMode == 'having') ? "$column" : "`{$this->getFromAlias()}`.`$column`";
656
        } else {
657
            $this->currentColumn = $column;
658
        }
659
660 1
        $this->currentColumnRaw = $column;
661
662 1
        return $this;
663
    }
664
665
    /**
666
     * Add a condition for the column
667
     * @param  string $condition The MySQL condition
668
     * @param  mixed  $value     Value(s) to pass to MySQL
669
     * @return void
670
     */
671 1
    protected function addColumnCondition($condition, $value)
672
    {
673 1
        if (!$this->currentColumn) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentColumn of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
674
            throw new Exception("You haven't selected a column!");
675
        }
676
677 1
        if (!is_array($value)) {
678 1
            $value = array($value);
679
        }
680
681 1
        $array = $this->currentColumnMode . 'Conditions';
682 1
        $this->{$array}[] = "{$this->currentColumn} $condition";
683 1
        $this->parameters   = array_merge($this->parameters, $value);
684
685 1
        $this->currentColumn = null;
686 1
        $this->currentColumnRaw = null;
687 1
    }
688
689
    /**
690
     * Get the MySQL extra parameters
691
     *
692
     * @param  bool $respectPagination Whether to respect pagination or not; useful for when pagination should be ignored such as count
693
     * @return string
694
     */
695 1
    protected function createQueryParams($respectPagination = true)
696
    {
697 1
        $extras     = $this->extras;
698 1
        $conditions = $this->createQueryConditions('where');
699 1
        $groupQuery = $this->groupQuery;
700 1
        $havingClause = $this->createQueryConditions('having');
701 1
        $order      = $this->createQueryOrder();
702 1
        $pagination = "";
703
704 1
        if ($respectPagination) {
705 1
            $pagination = $this->createQueryPagination();
706
        }
707
708 1
        return "$extras $conditions $groupQuery $havingClause $order $pagination";
709
    }
710
711
    /**
712
     * Get the query parameters
713
     *
714
     * @return array
715
     */
716 1
    protected function getParameters()
717
    {
718 1
        return array_merge($this->parameters, $this->paginationParameters);
719
    }
720
721
    /**
722
     * Get the alias used for the table in the FROM clause
723
     *
724
     * @return null|string
725
     */
726 1
    protected function getFromAlias()
727
    {
728 1
        return $this->getTable();
729
    }
730
731
    /**
732
     * Get the table of the model
733
     *
734
     * @return string
735
     */
736 1
    protected function getTable()
737
    {
738 1
        $type = $this->type;
739
740 1
        return $type::TABLE;
741
    }
742
743
    /**
744
     * Get a MySQL query string in the requested format
745
     * @param  string|string[] $columns The columns that should be included
746
     *                                  (without the ID, if an array is provided)
747
     * @return string The query
748
     */
749 1
    protected function createQuery($columns = array())
750
    {
751 1
        $type     = $this->type;
752 1
        $table    = $type::TABLE;
753 1
        $params   = $this->createQueryParams();
754
755 1
        if (is_array($columns)) {
756 1
            $columns = $this->createQueryColumns($columns);
757
        } elseif (empty($columns)) {
758
            $columns = $this->createQueryColumns();
759
        }
760
761 1
        return "SELECT $columns FROM $table $params";
762
    }
763
764
    /**
765
     * Generate the columns for the query
766
     * @param  string[] $columns The columns that should be included (without the ID)
767
     * @return string
768
     */
769 1
    private function createQueryColumns($columns = array())
770
    {
771 1
        $type = $this->type;
772 1
        $table = $type::TABLE;
773 1
        $columnStrings = array("`$table`.id");
774
775 1
        foreach ($columns as $returnName) {
776 1
            if (strpos($returnName, ' ') === false) {
777 1
                $dbName = $this->columns[$returnName];
778 1
                $columnStrings[] = "`$table`.`$dbName` as `$returnName`";
779
            } else {
780
                // "Column" contains a space, pass it as is
781 1
                $columnStrings[] = $returnName;
782
            }
783
        }
784
785 1
        return implode(',', $columnStrings);
786
    }
787
788
    /**
789
     * Generates all the WHERE conditions for the query
790
     * @return string
791
     */
792 1
    private function createQueryConditions($mode)
793
    {
794 1
        $array = $mode . 'Conditions';
795
796 1
        if ($this->{$array}) {
797
            // Add parentheses around the conditions to prevent conflicts due
798
            // to the order of operations
799
            $conditions = array_map(function ($value) { return "($value)"; }, $this->{$array});
800
801 1
            return strtoupper($mode) . ' ' . implode(' AND ', $conditions);
802
        }
803
804 1
        return '';
805
    }
806
807
    /**
808
     * Generates the sorting instructions for the query
809
     * @return string
810
     */
811 1
    private function createQueryOrder()
812
    {
813 1
        if ($this->sortBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sortBy of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
814 1
            $order = 'ORDER BY ' . $this->sortBy;
815
816
            // Sort by ID if the sorting columns are equal
817 1
            $id = "`{$this->getFromAlias()}`.`id`";
818 1
            if ($this->reverseSort) {
819 1
                $order .= " DESC, $id DESC";
820
            } else {
821 1
                $order .= ", $id";
822
            }
823
        } else {
824 1
            $order = '';
825
        }
826
827 1
        return $order;
828
    }
829
830
    /**
831
     * Generates the pagination instructions for the query
832
     * @return string
833
     */
834 1
    private function createQueryPagination()
835
    {
836
        // Reset mysqli params just in case createQueryParagination()
837
        // had been called earlier
838 1
        $this->paginationParameters = array();
839
840 1
        if (!$this->page && !$this->limited) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->page of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
841 1
            return '';
842
        }
843
844 1
        $offset = '';
845 1
        if ($this->page) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->page of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
846 1
            $firstElement = ($this->page - 1) * $this->resultsPerPage;
847 1
            $this->paginationParameters[] = $firstElement;
848
849 1
            $offset = '?,';
850
        }
851
852 1
        $this->paginationParameters[] = $this->resultsPerPage;
853
854 1
        return "LIMIT $offset ?";
855
    }
856
857
    /**
858
     * Set the current column we're working on
859
     *
860
     * @param string $column The column we're selecting
861
     * @param string $mode   Either 'where' or 'having'
862
     *
863
     * @return $this
864
     */
865 1
    private function grabColumn($column, $mode)
866
    {
867 1
        if (!isset($this->columns[$column])) {
868
            throw new InvalidArgumentException("Unknown column '$column'");
869
        }
870
871 1
        $this->column($this->columns[$column], $mode);
872
873 1
        return $this;
874
    }
875
}
876