Completed
Push — feature/player-list ( ee00f1...3478af )
by Vladimir
04:05
created

QueryBuilder   D

Complexity

Total Complexity 83

Size/Duplication

Total Lines 805
Duplicated Lines 3.11 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 75.8%

Importance

Changes 0
Metric Value
wmc 83
lcom 1
cbo 4
dl 25
loc 805
ccs 166
cts 219
cp 0.758
rs 4.4444
c 0
b 0
f 0

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 3
A where() 0 10 2
A equals() 0 6 1
A notEquals() 0 6 1
A greaterThan() 0 6 1
A lessThan() 0 6 1
A isBefore() 0 4 1
A isAfter() 0 13 4
A is() 14 14 3
A isOneOf() 0 12 1
A startsWith() 0 6 1
A except() 11 11 2
A sortBy() 0 10 2
A reverse() 0 6 1
A limit() 0 6 1
A fromPage() 0 6 1
A endAt() 0 4 1
C startAt() 0 27 7
A active() 0 10 2
B visibleTo() 0 18 5
A getNames() 0 10 2
A getArray() 0 10 2
A addToCache() 0 4 1
C getModels() 0 29 8
A count() 0 13 1
A any() 0 9 1
A column() 0 15 2
A addColumnCondition() 0 16 3
A createQueryParams() 0 14 2
A getParameters() 0 4 1
A getTable() 0 6 1
A createQuery() 0 14 3
A createQueryColumns() 0 18 3
A createQueryConditions() 0 12 2
B createQueryPagination() 0 22 4
A countPages() 0 4 1
A getResultsPerPage() 0 4 1
A createQueryOrder() 0 18 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like QueryBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBuilder, and based on these observations, apply Extract Interface, too.

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