Completed
Push — feature/player-elo-v3 ( e6aec1 )
by Vladimir
02:55
created

QueryBuilder::isOneOf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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