Completed
Push — 2.0 ( bddf1c )
by Vermeulen
02:18
created

SqlSelect::generateLimit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
1
<?php
2
3
namespace BfwSql;
4
5
use \PDO;
6
7
/**
8
 * Class to write SELECT queries
9
 * 
10
 * @package bfw-sql
11
 * @author Vermeulen Maxime <[email protected]>
12
 * @version 2.0
13
 */
14
class SqlSelect extends SqlActions
15
{
16
    /**
17
     * @var string $returnType PHP Type used for return result
18
     */
19
    protected $returnType = '';
20
    
21
    /**
22
     * @var object $from Informations about main table. Used for FROM part
23
     */
24
    protected $table;
25
    
26
    /**
27
     * @var array $subQueries All sub-queries
28
     */
29
    protected $subQueries = [];
30
    
31
    /**
32
     * @var array $join List of all INNER JOIN
33
     */
34
    protected $join = [];
35
    
36
    /**
37
     * @var array $joinLeft List of all LEFT JOIN
38
     */
39
    protected $joinLeft = [];
40
    
41
    /**
42
     * @var array $joinRight List of all RIGHT JOIN
43
     */
44
    protected $joinRight = [];
45
    
46
    /**
47
     * @var string[] $order All columns used for ORDER BY part
48
     */
49
    protected $order = [];
50
    
51
    /**
52
     * @var string $limit The LIMIT part
53
     */
54
    protected $limit = '';
55
    
56
    /**
57
     * @var string[] $group The GROUP BY part
58
     */
59
    protected $group = [];
60
    
61
    /**
62
     * Constructor
63
     * 
64
     * @param \BfwSql\SqlConnect $sqlConnect Instance of SGBD connexion
65
     * @param string             $returnType PHP type used for return result
66
     */
67
    public function __construct(SqlConnect $sqlConnect, $returnType)
68
    {
69
        parent::__construct($sqlConnect);
70
        $this->returnType = $returnType;
71
    }
72
    
73
    /**
74
     * Define object used to save informations about a table
75
     * This object will be used to write query
76
     * 
77
     * @param string|array $table Tables informations
78
     * 
79
     * @return \stdClass
80
     */
81
    protected function obtainTableInfos($table)
82
    {
83
        if (!is_array($table) && !is_string($table)) {
84
            throw new Exception('table information is not in the right format.');
85
        }
86
        
87
        if (is_array($table)) {
88
            $tableName = reset($table);
89
            $shortcut  = key($table);
90
        } else {
91
            $tableName = $table;
92
            $shortcut  = null;
93
        }
94
        
95
        $prefix = $this->sqlConnect->getConnectionInfos()->tablePrefix;
96
        
97
        return (object) [
98
            'tableName' => $prefix.$tableName,
99
            'shortcut'  => $shortcut
100
        ];
101
    }
102
    
103
    /**
104
     * Add columns for select
105
     * 
106
     * @param array|string $columns   Columns to add
107
     * @param string       $tableName Table name for will be columns added
108
     * 
109
     * @return void
110
     */
111
    protected function addColumnsForSelect($columns, $tableName)
112
    {
113
        if (!is_array($columns)) {
114
            $columns = (array) $columns;
115
        }
116
        
117
        foreach ($columns as $columnShortcut => $columnName) {
118
            //If value is a sql function, not add quote
119
            if (strpos($columnName, '(') === false) {
120
                //Add quote only if a column has been declared
121
                if ($columnName !== '*') {
122
                    $columnName = '`'.$columnName.'`';
123
                }
124
                
125
                $columnName = '`'.$tableName.'`.'.$columnName;
126
            }
127
            
128
            //If a column shortcut is declared
129
            if (is_string($columnShortcut)) {
130
                $this->select[] = (object) [
0 ignored issues
show
Bug introduced by
The property select does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
131
                    'column'   => $columnName,
132
                    'shortcut' => $columnShortcut
133
                ];
134
            } else {
135
                $this->select[] = (object) [
136
                    'column'   => $columnName,
137
                    'shortcut' => null
138
                ];
139
            }
140
        }
141
    }
142
    
143
    /**
144
     * Declare information for FROM part and column will be get for main table
145
     * 
146
     * @param string|array $table   Table name.
147
     *  It can be an array if a table shortcut is declared.
148
     *  In array mode, the format is ['asValue' => 'tableName']
149
     * @param string|array $columns (default: "*") Columns will be get for
150
     *  the table declared in first argument
151
     * 
152
     * @return \BfwSql\SqlSelect
153
     */
154
    public function from($table, $columns='*')
155
    {
156
        $this->from = $this->obtainTableInfos($table);
0 ignored issues
show
Bug introduced by
The property from does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
157
        
158
        $tableName = $this->from->tableName;
159
        if ($this->from->shortcut !== null) {
160
            $tableName = $this->from->shortcut;
161
        }
162
        
163
        $this->addColumnsForSelect($columns, $tableName);
164
        
165
        return $this;
166
    }
167
    
168
    /**
169
     * Add a sub-query in the SELECT part on the request
170
     * 
171
     * @param \BfwSql\SqlActions|string $subRequest The sub-request
172
     * @param string                    $shortcut   The shortcut to use for
173
     *  this query in SELECT part
174
     * 
175
     * @return \BfwSql\SqlSelect
176
     */
177
    public function subQuery($subRequest, $shortcut)
178
    {
179
        if (
180
            is_object($subRequest)
181
            && $subRequest instanceof \BfwSql\SqlActions
182
        ) {
183
            $subQuery = $subRequest->assemble();
184
        } elseif (is_string($subRequest)) {
185
            $subQuery = $subRequest;
186
        } else {
187
            throw new Exception(
188
                'subRequest passed in parameters must be an instance of '
189
                .'BfwSql System or a string.'
190
            );
191
        }
192
        
193
        $this->subQueries[] = (object) [
194
            'query'    => $subQuery,
195
            'shortcut' => $shortcut
196
        ];
197
        
198
        return $this;
199
    }
200
    
201
    /**
202
     * Add a (inner|left|right) join to the request
203
     * 
204
     * @param string       $joinPropertyName The name of the property in this
205
     *  class where the join is add
206
     * @param string|array $table            Name of the table concerned by
207
     * the join. Or an array with the table shortcut in key.
208
     * @param string       $joinOn           SQL part "ON" for this join
209
     * @param string|array $joinColumns      Columns from the table joined to
210
     *  add in the SELECT part of the request
211
     * 
212
     * @return \BfwSql\SqlSelect
213
     */
214
    protected function createJoin(
215
        $joinPropertyName,
216
        $table,
217
        $joinOn,
218
        $joinColumns
219
    )
220
    {
221
        $tableInfos     = $this->obtainTableInfos($table);
222
        $tableInfos->on = $joinOn;
223
        
224
        $tableName = $tableInfos->tableName;
225
        if ($tableInfos->shortcut !== null) {
226
            $tableName = $tableInfos->shortcut;
227
        }
228
        
229
        $this->{$joinPropertyName} = $tableInfos;
230
        $this->addColumnsForSelect($joinColumns, $tableName);
231
        
232
        return $this;
233
    }
234
    
235
    /**
236
     * Add a INNER JOIN to the request
237
     * 
238
     * @param string|array $table       Name of the table concerned by the
239
     *  join. Or an array with the table shortcut in key.
240
     * @param string       $joinOn      SQL part "ON" for this join
241
     * @param string|array $joinColumns Columns from the table joined to add
242
     *  in the SELECT part of the request
243
     * 
244
     * @return \BfwSql\SqlSelect
245
     */
246
    public function join($table, $joinOn, $joinColumns='*')
247
    {
248
        return $this->createJoin('join', $table, $joinOn, $joinColumns);
249
    }
250
    
251
    /**
252
     * Add a LEFT JOIN to the request
253
     * 
254
     * @param string|array $table       Name of the table concerned by the
255
     *  join. Or an array with the table shortcut in key.
256
     * @param string       $joinOn      SQL part "ON" for this join
257
     * @param string|array $joinColumns Columns from the table joined to add
258
     *  in the SELECT part of the request
259
     * 
260
     * @return \BfwSql\SqlSelect
261
     */
262
    public function joinLeft($table, $joinOn, $joinColumns='*')
263
    {
264
        return $this->createJoin('joinLeft', $table, $joinOn, $joinColumns);
265
    }
266
    
267
    /**
268
     * Add a RIGHT JOIN to the request
269
     * 
270
     * @param string|array $table       Name of the table concerned by the
271
     *  join. Or an array with the table shortcut in key.
272
     * @param string       $joinOn      SQL part "ON" for this join
273
     * @param string|array $joinColumns Columns from the table joined to add
274
     *  in the SELECT part of the request
275
     * 
276
     * @return \BfwSql\SqlSelect
277
     */
278
    public function joinRight($table, $joinOn, $joinColumns='*')
279
    {
280
        return $this->createJoin('joinRight', $table, $joinOn, $joinColumns);
281
    }
282
    
283
    /**
284
     * Add a order condition to the request for the ORDER BY part
285
     * 
286
     * @param string $condition The new condition
287
     * 
288
     * @return \BfwSql\SqlSelect
289
     */
290
    public function order($condition)
291
    {
292
        $this->order[] = (string) $condition;
293
        return $this;
294
    }
295
    
296
    /**
297
     * Add information about the LIMIT part in request
298
     * 
299
     * @param array|integer $limit If it's a integer, the number of row to
300
     *  return. If an array, the format is [numberToStart, numberOfRowToReturn]
301
     * 
302
     * @return \BfwSql\SqlSelect
303
     */
304
    public function limit($limit)
305
    {
306
        if (!is_array($limit)) {
307
            $limit = (array) $limit;
308
        }
309
        
310
        if (isset($limit[1])) {
311
            $this->limit = $limit[0].', '.$limit[1];
312
        } else {
313
            $this->limit = $limit[0];
314
        }
315
        
316
        return $this;
317
    }
318
    
319
    /**
320
     * Add a GROUP BY part to the request
321
     * 
322
     * @param string $condition The condition to use in GROUP BY
323
     * 
324
     * @return \BfwSql\SqlSelect
325
     */
326
    public function group($condition)
327
    {
328
        $this->group[] = $condition;
329
        return $this;
330
    }
331
    
332
    /**
333
     * Return the PDO constant for the returnType declared
334
     * 
335
     * @return integer
336
     */
337
    protected function obtainPdoFetchType()
338
    {
339
        if ($this->typeResult === 'object') {
0 ignored issues
show
Bug introduced by
The property typeResult does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
340
            return PDO::FETCH_OBJ;
341
        }
342
        
343
        return PDO::FETCH_ASSOC;
344
    }
345
    
346
    /**
347
     * Fetch one row of the result
348
     * 
349
     * @return mixed
350
     */
351
    public function fetchRow()
352
    {
353
        $req = $this->execute();
354
        return $req->fetch($this->getPdoFetchType());
0 ignored issues
show
Bug introduced by
The method getPdoFetchType() does not seem to exist on object<BfwSql\SqlSelect>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
355
    }
356
    
357
    /**
358
     * Fetch all rows returned by the request
359
     * 
360
     * @return mixed
361
     */
362
    public function fetchAll()
363
    {
364
        $result  = [];
365
        $request = $this->execute(); //throw an Exception if error
366
        
367
        while($row = $request->fetch($this->getPdoFetchType()))
0 ignored issues
show
Bug introduced by
The method getPdoFetchType() does not seem to exist on object<BfwSql\SqlSelect>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
368
        {
369
            $result[] = $row;
370
        }
371
        
372
        return $result;
373
    }
374
    
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function assembleRequest()
379
    {
380
        $this->assembledRequest = 'SELECT '.$this->generateSelect()
381
            .' FROM '.$this->generateFrom()
382
            .$this->generateJoin('join')
383
            .$this->generateJoin('joinLeft')
384
            .$this->generateJoin('joinRight')
385
            .$this->generateWhere()
386
            .$this->generateGroupBy()
387
            .$this->generateOrderBy()
388
            .$this->generateLimit();
389
        
390
        $this->callObserver();
391
    }
392
    
393
    /**
394
     * Write the SELECT part of the request
395
     * 
396
     * @return string
397
     */
398
    protected function generateSelect()
399
    {
400
        $select = '';
401
        foreach($this->select as $columnInfos)
402
        {
403
            if ($select != '') {
404
                $select .= ', ';
405
            }
406
            
407
            $select .= $columnInfos->column;
408
            if ($columnInfos->shortcut !== null) {
409
                $select. ' AS '.$columnInfos->shortcut;
410
            }
411
        }
412
        
413
        foreach($this->subQuery as $subQueryInfos)
0 ignored issues
show
Bug introduced by
The property subQuery does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
414
        {
415
            if ($select != '') {
416
                $select .= ', ';
417
            }
418
419
            $select .= $subQueryInfos->query
420
                .' AS `'.$subQueryInfos->shortcut.'`';
421
        }
422
        
423
        return $select;
424
    }
425
    
426
    /**
427
     * Write the FROM part of the request
428
     * 
429
     * @return string
430
     */
431
    protected function generateFrom()
432
    {
433
        $from = '`'.$this->from->tableName.'`';
434
        
435
        if ($this->from->shortcut !== null) {
436
            $from .= ' AS `'.$this->from->shortcut.'`';
437
        }
438
        
439
        return $from;
440
    }
441
    
442
    /**
443
     * Write a (inner|left|right) join in the request
444
     * 
445
     * @param string $joinProperty The join property name
446
     * 
447
     * @return string
448
     */
449
    protected function generateJoin($joinProperty)
450
    {
451
        $join = '';
452
        if (count($this->{$joinProperty}) === 0) {
453
            return $join;
454
        }
455
        
456
        if ($joinProperty == 'join') {
457
            $joinSqlName = ' INNER JOIN ';
458
        } elseif ($joinProperty == 'joinLeft') {
459
            $joinSqlName = ' LEFT JOIN ';
460
        } elseif ($joinProperty == 'joinRight') {
461
            $joinSqlName = ' RIGHT JOIN ';
462
        }
463
464
        foreach($this->{$joinProperty} as $joinInfos)
465
        {
466
            $join .= $joinSqlName.'`'.$joinInfos->tableName.'`';
1 ignored issue
show
Bug introduced by
The variable $joinSqlName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
467
            if ($joinInfos->shortcut !== null) {
468
                $join .= ' AS `'.$joinInfos->shortcut.'`';
469
            }
470
471
            $join .= ' ON '.$joinInfos->on;
472
        }
473
        
474
        return $join;
475
    }
476
    
477
    /**
478
     * Write the ORDER BY part for the request
479
     * 
480
     * @return string
481
     */
482
    protected function generateOrderBy()
483
    {
484
        if (count($this->order) === 0) {
485
            return '';
486
        }
487
        
488
        $order = ' ORDER BY ';
489
        foreach ($this->order as $orderCondition) {
490
            if ($order != ' ORDER BY ') {
491
                $order .= ', ';
492
            }
493
            
494
            $order .= $orderCondition;
495
        }
496
        
497
        return $order;
498
    }
499
    
500
    /**
501
     * Write the GRUOP BY part for the request
502
     * 
503
     * @return string
504
     */
505
    protected function generateGroupBy()
506
    {
507
        if (count($this->group) === 0) {
508
            return '';
509
        }
510
        
511
        $group = ' GROUP BY ';
512
        foreach($this->group as $groupCondition)
513
        {
514
            if ($group != ' GROUP BY ') {
515
                $group .= ', ';
516
            }
517
            
518
            $group .= $groupCondition;
519
        }
520
        
521
        return $group;
522
    }
523
    
524
    /**
525
     * Write the LIMIT part for the request
526
     * 
527
     * @return string
528
     */
529
    protected function generateLimit()
530
    {
531
        $limit = '';
532
        if ($this->limit !== '') {
533
            $limit = ' LIMIT '.$this->limit;
534
        }
535
        
536
        return $limit;
537
    }
538
} 
539