Completed
Push — feature/player-list ( 9f06dd...8e5225 )
by Vladimir
04:50
created

QueryBuilder::getModels()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.025

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 9
cts 10
cp 0.9
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 11
nc 8
nop 1
crap 5.025
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 alias for the table in the FROM query
36
     * @var string|null
37
     */
38
    protected $tableAlias = null;
39
40
    /**
41
     * The columns that the model provided us
42
     * @var array
43
     */
44
    protected $columns = array('id' => 'id');
45
46
    /**
47
     * Extra columns that are generated from the SQL query (this should be a comma separated string or null)
48
     * @var string|null
49
     */
50
    protected $extraColumns = null;
51
52
    /**
53
     * The conditions to include in WHERE
54
     * @var string[]
55
     */
56
    protected $conditions = 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
     * The currently selected column without the table name (unless it was
102
     * explicitly provided)
103
     * @var string|null
104
     */
105
    protected $currentColumnRaw = null;
106
107
    /**
108
     * A column to consider the name of the model
109
     * @var string|null
110
     */
111
    private $nameColumn = null;
112
113
    /**
114
     * The page to return
115
     * @var int|null
116
     */
117
    private $page = null;
118
119
    /**
120
     * Whether the ID of the first/last element has been provided
121
     * @var bool
122
     */
123
    private $limited = false;
124
125
    /**
126
     * The number of elements on every page
127
     * @var int
128
     */
129
    protected $resultsPerPage = 30;
130
131
    /**
132
     * Create a new QueryBuilder
133
     *
134
     * A new query builder should be created on a static getQueryBuilder()
135
     * method on each model. The options array can contain the following
136
     * properties:
137
     *
138
     * - `columns`: An associative array - the key of each entry is the column
139
     *   name that will be used by other methods, while the value is
140
     *   is the column name that is used in the database structure
141
     *
142
     * - `activeStatuses`: If the model has a status column, this should be
143
     *                     a list of values that make the entry be considered
144
     *                     "active"
145
     *
146
     * - `name`: The name of the column which represents the name of the object
147
     *
148
     * @param string $type    The type of the Model (e.g. "Player" or "Match")
149
     * @param array  $options The options to pass to the builder (see above)
150
     */
151 4
    public function __construct($type, $options = array())
152
    {
153 4
        $this->type = $type;
154
155 4
        if (isset($options['columns'])) {
156 4
            $this->columns += $options['columns'];
157
        }
158
159 4
        if (isset($options['name'])) {
160 2
            $this->nameColumn = $options['name'];
161
        }
162 4
    }
163
164
    /**
165
     * Select a column
166
     *
167
     * `$queryBuilder->where('username')->equals('administrator');`
168
     *
169
     * @param  string $column The column to select
170
     * @return static
171
     */
172 4
    public function where($column)
173
    {
174 4
        if (!isset($this->columns[$column])) {
175
            throw new InvalidArgumentException("Unknown column '$column'");
176
        }
177
178 4
        $this->column($this->columns[$column]);
179
180 4
        return $this;
181
    }
182
183
    /**
184
     * Request that a column equals a string (case-insensitive)
185
     *
186
     * @param  string $string The string that the column's value should equal to
187
     * @return static
188
     */
189 2
    public function equals($string)
190
    {
191 2
        $this->addColumnCondition("= ?", $string);
192
193 2
        return $this;
194
    }
195
196
    /**
197
     * Request that a column doesNOT 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 notEquals($string)
203
    {
204 1
        $this->addColumnCondition("!= ?", $string);
205
206 1
        return $this;
207
    }
208
209
    /**
210
     * Request that a column is greater than a quantity
211
     *
212
     * @param  string $quantity The quantity to test against
213
     * @return static
214
     */
215
    public function greaterThan($quantity)
216
    {
217
        $this->addColumnCondition("> ?", $quantity);
218
219
        return $this;
220
    }
221
222
    /**
223
     * Request that a column is less than a quantity
224
     *
225
     * @param  string $quantity The quantity to test against
226
     * @return static
227
     */
228
    public function lessThan($quantity)
229
    {
230
        $this->addColumnCondition("< ?", $quantity);
231
232
        return $this;
233
    }
234
235
    /**
236
     * Request that a timestamp is before the specified time
237
     *
238
     * @param string|TimeDate $time      The timestamp to compare to
239
     * @param bool            $inclusive Whether to include the given timestamp
240
     * @param bool            $reverse   Whether to reverse the results
241
     *
242
     * @return static
243
     */
244
    public function isBefore($time, $inclusive = false, $reverse = false)
245
    {
246
        return $this->isAfter($time, $inclusive, !$reverse);
247
    }
248
249
    /**
250
     * Request that a timestamp is after the specified time
251
     *
252
     * @param string|TimeDate $time      The timestamp to compare to
253
     * @param bool            $inclusive Whether to include the given timestamp
254
     * @param bool            $reverse   Whether to reverse the results
255
     *
256
     * @return static
257
     */
258 1
    public function isAfter($time, $inclusive = false, $reverse = false)
259
    {
260 1
        if ($time instanceof TimeDate) {
261 1
            $time = $time->toMysql();
262
        }
263
264 1
        $comparison  = ($reverse)   ? '<' : '>';
265 1
        $comparison .= ($inclusive) ? '=' : '';
266
267 1
        $this->addColumnCondition("$comparison ?",  $time);
268
269 1
        return $this;
270
    }
271
272
    /**
273
     * Request that a column equals a number
274
     *
275
     * @param  int|Model|null $number The number that the column's value should
276
     *                                equal to. If a Model is provided, use the
277
     *                                model's ID, while null values are ignored.
278
     * @return static
279
     */
280 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...
281
    {
282 1
        if ($number === null) {
283
            return $this;
284
        }
285
286 1
        if ($number instanceof Model) {
287 1
            $number = $number->getId();
288
        }
289
290 1
        $this->addColumnCondition("= ?", $number);
291
292 1
        return $this;
293
    }
294
295
    /**
296
     * Request that a column equals one of some strings
297
     *
298
     * @todo   Improve for PDO
299
     * @param  string[] $strings The list of accepted values for the column
300
     * @return static
301
     */
302 3
    public function isOneOf($strings)
303
    {
304 3
        $count = count($strings);
305 3
        $questionMarks = str_repeat(',?', $count);
306
307
        // Remove first comma from questionMarks so that MySQL can read our query
308 3
        $questionMarks = ltrim($questionMarks, ',');
309
310 3
        $this->addColumnCondition("IN ($questionMarks)", $strings);
311
312 3
        return $this;
313
    }
314
315
    /**
316
     * Request that a column value starts with a string (case-insensitive)
317
     *
318
     * @param  string $string The substring that the column's value should start with
319
     * @return static
320
     */
321
    public function startsWith($string)
322
    {
323
        $this->addColumnCondition("LIKE CONCAT(?, '%')", $string);
324
325
        return $this;
326
    }
327
328
    /**
329
     * Request that a specific model is not returned
330
     *
331
     * @param  Model|int $model The ID or model you don't want to get
332
     * @return static
333
     */
334 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...
335
    {
336
        if ($model instanceof Model) {
337
            $model = $model->getId();
338
        }
339
340
        $this->where('id');
341
        $this->addColumnCondition("!= ?", $model);
342
343
        return $this;
344
    }
345
346
    /**
347
     * Return the results sorted by the value of a column
348
     *
349
     * @param  string $column The column based on which the results should be ordered
350
     * @return static
351
     */
352 2
    public function sortBy($column)
353
    {
354 2
        if (!isset($this->columns[$column])) {
355
            throw new Exception("Unknown column");
356
        }
357
358 2
        $this->sortBy = $this->columns[$column];
359
360 2
        return $this;
361
    }
362
363
    /**
364
     * Reverse the order
365
     *
366
     * Note: This only works if you have specified a column in the sortBy() method
367
     *
368
     * @return static
369
     */
370 2
    public function reverse()
371
    {
372 2
        $this->reverseSort = !$this->reverseSort;
373
374 2
        return $this;
375
    }
376
377
    /**
378
     * Specify the number of results per page
379
     *
380
     * @param  int  $count The number of results
381
     * @return static
382
     */
383 2
    public function limit($count)
384
    {
385 2
        $this->resultsPerPage = $count;
386
387 2
        return $this;
388
    }
389
390
    /**
391
     * Only show results from a specific page
392
     *
393
     * @param  int|null $page The page number (or null to show all pages - counting starts from 0)
394
     * @return static
395
     */
396 2
    public function fromPage($page)
397
    {
398 2
        $this->page = $page;
399
400 2
        return $this;
401
    }
402
403
    /**
404
     * End with a specific result
405
     *
406
     * @param  int|Model $model     The model (or database ID) after the first result
407
     * @param  bool   $inclusive Whether to include the provided model
408
     * @param  bool   $reverse   Whether to reverse the results
409
     * @return static
410
     */
411 1
    public function endAt($model, $inclusive = false, $reverse = false)
412
    {
413 1
        return $this->startAt($model, $inclusive, !$reverse);
414
    }
415
416
    /**
417
     * Start with a specific result
418
     *
419
     * @param  int|Model $model     The model (or database ID) before 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 startAt($model, $inclusive = false, $reverse = false)
425
    {
426 1
        if (!$model) {
427 1
            return $this;
428
        } elseif ($model instanceof Model && !$model->isValid()) {
429
            return $this;
430
        }
431
432
        $this->column($this->sortBy);
433
        $this->limited = true;
434
        $column = $this->currentColumn;
435
        $table  = $this->getTable();
436
437
        $comparison  = $this->reverseSort ^ $reverse;
438
        $comparison  = ($comparison) ? '>' : '<';
439
        $comparison .= ($inclusive)  ? '=' : '';
440
        $id = ($model instanceof Model) ? $model->getId() : $model;
441
442
        // Compare an element's timestamp to the timestamp of $model; if it's the
443
        // same, perform the comparison using IDs
444
        $this->addColumnCondition(
445
            "$comparison (SELECT $column FROM $table WHERE id = ?) OR ($column = (SELECT $column FROM $table WHERE id = ?) AND id $comparison ?)",
446
            array($id, $id, $id)
447
        );
448
449
        return $this;
450
    }
451
452
    /**
453
     * Request that only "active" Models should be returned
454
     *
455
     * @return static
456
     */
457 3
    public function active()
458
    {
459 3
        if (!isset($this->columns['status'])) {
460
            return $this;
461
        }
462
463 3
        $type = $this->type;
464
465 3
        return $this->where('status')->isOneOf($type::getActiveStatuses());
466
    }
467
468
    /**
469
     * Make sure that Models invisible to a player are not returned
470
     *
471
     * Note that this method does not take PermissionModel::canBeSeenBy() into
472
     * consideration for performance purposes, so you will have to override this
473
     * in your query builder if necessary.
474
     *
475
     * @param  Player  $player      The player in question
476
     * @param  bool $showDeleted false to hide deleted models even from admins
477
     * @return static
478
     */
479 1
    public function visibleTo($player, $showDeleted = false)
480
    {
481 1
        $type = $this->type;
482
483 1
        if (is_subclass_of($type, "PermissionModel")
484 1
         && $player->hasPermission($type::EDIT_PERMISSION)) {
485
            // The player is an admin who can see hidden models
486 1
            if (!$showDeleted) {
487 1
                if (isset($this->columns['status'])) {
488 1
                    return $this->where('status')->notEquals('deleted');
489
                }
490
            }
491
        } else {
492 1
            return $this->active();
493
        }
494
495
        return $this;
496
    }
497
498
    /**
499
     * Perform the query and get back the results in an array of names
500
     *
501
     * @return string[] An array of the type $id => $name
502
     */
503 1
    public function getNames()
504
    {
505 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...
506
            throw new Exception("You haven't specified a name column");
507
        }
508
509 1
        $results = $this->getArray($this->nameColumn);
510
511 1
        return array_column($results, $this->nameColumn, 'id');
512
    }
513
514
    /**
515
     * Perform the query and get back the results in a list of arrays
516
     *
517
     * @todo   Play with partial models?
518
     * @param  string|string[] $columns The column(s) that should be returned
519
     * @return array[]
520
     */
521 1
    public function getArray($columns)
522
    {
523 1
        if (!is_array($columns)) {
524 1
            $columns = array($columns);
525
        }
526
527 1
        $db = Database::getInstance();
528
529 1
        return $db->query($this->createQuery($columns), $this->getParameters());
530
    }
531
532
    /**
533
     * An alias for QueryBuilder::getModels(), with fast fetching on by default
534
     * and no return of results
535
     *
536
     * @param  bool $fastFetch Whether to perform one query to load all
537
     *                            the model data instead of fetching them
538
     *                            one by one
539
     * @return void
540
     */
541
    public function addToCache($fastFetch = true)
542
    {
543
        $this->getModels($fastFetch);
544
    }
545
546
    /**
547
     * Perform the query and get the results as Models
548
     *
549
     * @todo Fix fast fetch for queries with multiple tables
550
     * @param  bool $fastFetch Whether to perform one query to load all
551
     *                            the model data instead of fetching them
552
     *                            one by one (ignores cache)
553
     * @return array
554
     */
555 4
    public function getModels($fastFetch = false)
556
    {
557 4
        $db   = Database::getInstance();
558 4
        $type = $this->type;
559
560 4
        $columns = ($fastFetch) ? $type::getEagerColumns($this->tableAlias) : array();
561
562 4
        if (is_string($columns) && !empty($this->extraColumns)) {
563
            $columns .= ',' . $this->extraColumns;
564
        }
565
566 4
        $results = $db->query($this->createQuery($columns), $this->getParameters());
567
568 4
        if ($fastFetch) {
569 2
            return $type::createFromDatabaseResults($results);
570
        } else {
571 3
            return $type::arrayIdToModel(array_column($results, 'id'));
572
        }
573
    }
574
575
    /**
576
     * Count the results
577
     *
578
     * @return int
579
     */
580 1
    public function count()
581
    {
582 1
        $table  = $this->getTable();
583 1
        $params = $this->createQueryParams(false);
584 1
        $db     = Database::getInstance();
585 1
        $query  = "SELECT COUNT(*) FROM $table $params";
586
587
        // We don't want pagination to affect our results so don't use the functions that combine
588
        // pagination results
589 1
        $results = $db->query($query, $this->parameters);
590
591 1
        return $results[0]['COUNT(*)'];
592
    }
593
594
    /**
595
     * Count the number of pages that all the models could be separated into
596
     */
597 1
    public function countPages()
598
    {
599 1
        return ceil($this->count() / $this->getResultsPerPage());
600
    }
601
602
    /**
603
     * Find if there is any result
604
     *
605
     * @return bool
606
     */
607 1
    public function any()
608
    {
609
        // Make sure that we don't mess with the user's options
610 1
        $query = clone $this;
611
612 1
        $query->limit(1);
613
614 1
        return $query->count() > 0;
615
    }
616
617
    /**
618
     * Get the amount of results that are returned per page
619
     * @return int
620
     */
621 1
    public function getResultsPerPage()
622
    {
623 1
        return $this->resultsPerPage;
624
    }
625
626
    /**
627
     * Select a column to perform opeations on
628
     *
629
     * This is identical to the `where()` method, except that the column is
630
     * specified as a MySQL column and not as a column name given by the model
631
     *
632
     * @param  string $column The column to select
633
     * @return static
634
     */
635 4
    protected function column($column)
636
    {
637 4
        if (strpos($column, '.') === false) {
638
            // Add the table name to the column if it isn't there already so that
639
            // MySQL knows what to do when handling multiple tables
640 4
            $this->currentColumn = "`{$this->getFromAlias()}`.`$column`";
641
        } else {
642 1
            $this->currentColumn = $column;
643
        }
644
645 4
        $this->currentColumnRaw = $column;
646
647 4
        return $this;
648
    }
649
650
    /**
651
     * Add a condition for the column
652
     * @param  string $condition The MySQL condition
653
     * @param  mixed  $value     Value(s) to pass to MySQL
654
     * @return void
655
     */
656 4
    protected function addColumnCondition($condition, $value)
657
    {
658 4
        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...
659
            throw new Exception("You haven't selected a column!");
660
        }
661
662 4
        if (!is_array($value)) {
663 2
            $value = array($value);
664
        }
665
666 4
        $this->conditions[] = "{$this->currentColumn} $condition";
667 4
        $this->parameters   = array_merge($this->parameters, $value);
668
669 4
        $this->currentColumn = null;
670 4
        $this->currentColumnRaw = null;
671 4
    }
672
673
    /**
674
     * Get the MySQL extra parameters
675
     *
676
     * @param  bool $respectPagination Whether to respect pagination or not; useful for when pagination should be ignored such as count
677
     * @return string
678
     */
679 4
    protected function createQueryParams($respectPagination = true)
680
    {
681 4
        $extras     = $this->extras;
682 4
        $conditions = $this->createQueryConditions();
683 4
        $groupQuery = $this->groupQuery;
684 4
        $order      = $this->createQueryOrder();
685 4
        $pagination = "";
686
687 4
        if ($respectPagination) {
688 4
            $pagination = $this->createQueryPagination();
689
        }
690
691 4
        return "$extras $conditions $groupQuery $order $pagination";
692
    }
693
694
    /**
695
     * Get the query parameters
696
     *
697
     * @return array
698
     */
699 4
    protected function getParameters()
700
    {
701 4
        return array_merge($this->parameters, $this->paginationParameters);
702
    }
703
704
    /**
705
     * Get the alias used for the table in the FROM clause
706
     *
707
     * @return null|string
708
     */
709 4
    protected function getFromAlias()
710
    {
711 4
        if ($this->tableAlias === null) {
712 4
            return $this->getTable();
713
        }
714
715
        return $this->tableAlias;
716
    }
717
718
    /**
719
     * Get the table of the model
720
     *
721
     * @return string
722
     */
723 4
    protected function getTable()
724
    {
725 4
        $type = $this->type;
726
727 4
        return $type::TABLE;
728
    }
729
730
    /**
731
     * Get a MySQL query string in the requested format
732
     * @param  string|string[] $columns The columns that should be included
733
     *                                  (without the ID, if an array is provided)
734
     * @return string The query
735
     */
736 4
    protected function createQuery($columns = array())
737
    {
738 4
        $type     = $this->type;
739 4
        $table    = $type::TABLE;
740 4
        $params   = $this->createQueryParams();
741
742 4
        if (is_array($columns)) {
743 3
            $columns = $this->createQueryColumns($columns);
744
        } elseif (empty($columns)) {
745
            $columns = $this->createQueryColumns();
746
        }
747
748 4
        return "SELECT $columns FROM $table {$this->tableAlias} $params";
749
    }
750
751
    /**
752
     * Generate the columns for the query
753
     * @param  string[] $columns The columns that should be included (without the ID)
754
     * @return string
755
     */
756 3
    private function createQueryColumns($columns = array())
757
    {
758 3
        $type = $this->type;
759 3
        $table = $type::TABLE;
760 3
        $columnStrings = array("`$table`.id");
761
762 3
        foreach ($columns as $returnName) {
763 1
            if (strpos($returnName, ' ') === false) {
764 1
                $dbName = $this->columns[$returnName];
765 1
                $columnStrings[] = "`$table`.`$dbName` as `$returnName`";
766
            } else {
767
                // "Column" contains a space, pass it as is
768 1
                $columnStrings[] = $returnName;
769
            }
770
        }
771
772 3
        return implode(',', $columnStrings);
773
    }
774
775
    /**
776
     * Generates all the WHERE conditions for the query
777
     * @return string
778
     */
779 4
    private function createQueryConditions()
780
    {
781 4
        if ($this->conditions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->conditions of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
782
            // Add parentheses around the conditions to prevent conflicts due
783
            // to the order of operations
784
            $conditions = array_map(function ($value) { return "($value)"; }, $this->conditions);
785
786 4
            return 'WHERE ' . implode(' AND ', $conditions);
787
        }
788
789
        return '';
790
    }
791
792
    /**
793
     * Generates the sorting instructions for the query
794
     * @return string
795
     */
796 4
    private function createQueryOrder()
797
    {
798 4
        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...
799 2
            $order = 'ORDER BY ' . $this->sortBy;
800
801
            // Sort by ID if the sorting columns are equal
802 2
            $id = "`{$this->getFromAlias()}`.`id`";
803 2
            if ($this->reverseSort) {
804 2
                $order .= " DESC, $id DESC";
805
            } else {
806 2
                $order .= ", $id";
807
            }
808
        } else {
809 3
            $order = '';
810
        }
811
812 4
        return $order;
813
    }
814
815
    /**
816
     * Generates the pagination instructions for the query
817
     * @return string
818
     */
819 4
    private function createQueryPagination()
820
    {
821
        // Reset mysqli params just in case createQueryParagination()
822
        // had been called earlier
823 4
        $this->paginationParameters = array();
824
825 4
        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...
826 3
            return '';
827
        }
828
829 2
        $offset = '';
830 2
        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...
831 2
            $firstElement = ($this->page - 1) * $this->resultsPerPage;
832 2
            $this->paginationParameters[] = $firstElement;
833
834 2
            $offset = '?,';
835
        }
836
837 2
        $this->paginationParameters[] = $this->resultsPerPage;
838
839 2
        return "LIMIT $offset ?";
840
    }
841
}
842