Completed
Push — feature/player-list ( 8e5225...c69c4b )
by Vladimir
05:41
created

QueryBuilder::grabColumn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

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