Passed
Push — master ( df539c...d5699a )
by Nestor
02:28
created

Statement::bindValue()   C

Complexity

Conditions 15
Paths 21

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 15
nc 21
nop 3
dl 0
loc 25
rs 5.0504
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Cozy\Database;
4
5
class Statement
6
{
7
    /** @var \PDOStatement */
8
    protected $pdoStatement;
9
    protected $wasExecuted = false;
10
    protected $autoExecute = true;
11
    protected $paramsMap = [];  // ['param_name' => 'type']
12
13
    /**
14
     * Statement constructor that wraps a PDOStatement object.
15
     *
16
     * @param \PDOStatement $pdoStatement
17
     */
18
    public function __construct(\PDOStatement $pdoStatement)
19
    {
20
        $this->pdoStatement = $pdoStatement;
21
    }
22
23
    /**
24
     * Returns the wrapped PDO statement object.
25
     *
26
     * @return \PDOStatement
27
     */
28
    public function getPdoStatement()
29
    {
30
        return $this->pdoStatement;
31
    }
32
33
    /**
34
     * Returns error information about the last operation performed by this statement.
35
     *
36
     * @return array
37
     */
38
    public function getErrorInfo()
39
    {
40
        return $this->pdoStatement->errorInfo();
41
    }
42
43
    /**
44
     * Map out the parameters of this statement.
45
     *
46
     * @param array $params A key pair array where the key is parameter's name and the value is parameter's type.
47
     * @return $this
48
     */
49
    public function mapParams(array $params)
50
    {
51
        $this->paramsMap = [];
52
53
        foreach ($params as $name => $type) {
54
            // The parameters with named placeholders must start with character ':'
55
            if (is_string($name) && strpos($name, ':') !== 0) {
56
                $name = ':' . $name;
57
            }
58
59
            // The parameters with positional ? placeholders must start with number 1
60
            if (array_key_exists(0, $params)) {
61
                $name++;
62
            }
63
64
            if ($type === 'int' || $type === 'integer' || $type === \PDO::PARAM_INT) {
65
                $this->paramsMap[$name] = \PDO::PARAM_INT;
66
            } elseif ($type === 'bool' || $type === 'boolean' || $type === \PDO::PARAM_BOOL) {
67
                $this->paramsMap[$name] = \PDO::PARAM_BOOL;
68
            } elseif ($type === 'lob' || $type === 'blob' || $type === \PDO::PARAM_LOB) {
69
                $this->paramsMap[$name] = \PDO::PARAM_LOB;
70
            } elseif ($type === 'null' || $type === \PDO::PARAM_NULL) {
71
                $this->paramsMap[$name] = \PDO::PARAM_NULL;
72
            } else {
73
                $this->paramsMap[$name] = \PDO::PARAM_STR;
74
            }
75
        }
76
77
        return $this;
78
    }
79
80
    /**
81
     * Binds values to mapped parameters.
82
     *
83
     * @param array $values A key pair array where the key is parameter's name.
84
     * @return $this
85
     */
86
    public function bindValues(array $values)
87
    {
88
        // Validations
89
90
        if (!$this->paramsMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paramsMap of type array 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...
91
            throw new \RuntimeException('No mapped parameters found.');
92
        }
93
94
        if (!$values) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array 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...
95
            throw new \InvalidArgumentException('No values were passed.');
96
        }
97
98
        foreach ($values as $name => $value) {
99
            // The parameters with named placeholders must start with character ':'
100
            if (is_string($name) && strpos($name, ':') !== 0) {
101
                $name = ':' . $name;
102
            }
103
104
            // The parameters with positional ? placeholders must start with number 1
105
            if (array_key_exists(0, $values)) {
106
                $name++;
107
            }
108
109
            if (!isset($this->paramsMap[$name])) {
110
                throw new \InvalidArgumentException("The parameter [{$name}] was not previously mapped.");
111
            }
112
113
            if ($value === null) {
114
                if (!$this->pdoStatement->bindValue($name, null, \PDO::PARAM_NULL)) {
115
                    throw new \RuntimeException("Error binding the parameter [{$name}].");
116
                }
117
            } else {
118
                if (!$this->pdoStatement->bindValue($name, $value, $this->paramsMap[$name])) {
119
                    throw new \RuntimeException("Error binding the parameter [{$name}].");
120
                }
121
            }
122
        }
123
124
        return $this;
125
    }
126
127
    /**
128
     * Binds a value to a parameter.
129
     *
130
     * @param mixed $parameter Parameter identifier.
131
     * @param mixed $value The value to bind to the parameter.
132
     * @param string $data_type [optional] Explicit data type for the parameter.
133
     * @return $this
134
     */
135
    public function bindValue($parameter, $value, string $data_type = 'str')
136
    {
137
        if ($value === null) {
138
            if (!$this->pdoStatement->bindValue($parameter, null, \PDO::PARAM_NULL)) {
139
                throw new \RuntimeException("Error binding the parameter [{$parameter}].");
140
            }
141
        }
142
143
        $type = \PDO::PARAM_STR;
144
145
        if ($data_type === 'int' || $data_type === 'integer' || $data_type === \PDO::PARAM_INT) {
146
            $type = \PDO::PARAM_INT;
147
        } elseif ($data_type === 'bool' || $data_type === 'boolean' || $data_type === \PDO::PARAM_BOOL) {
148
            $type = \PDO::PARAM_BOOL;
149
        } elseif ($data_type === 'lob' || $data_type === 'blob' || $data_type === \PDO::PARAM_LOB) {
150
            $type = \PDO::PARAM_LOB;
151
        } elseif ($data_type === 'null' || $data_type === \PDO::PARAM_NULL) {
152
            $type = \PDO::PARAM_NULL;
153
        }
154
155
        if (!$this->pdoStatement->bindValue($parameter, $value, $type)) {
156
            throw new \RuntimeException("Error binding the parameter [{$parameter}].");
157
        }
158
159
        return $this;
160
    }
161
162
    /**
163
     * Define if the statement will execute automatically when trying to fetch data.
164
     *
165
     * @param bool $flag
166
     * @return $this
167
     */
168
    public function setAutoExecute(bool $flag)
169
    {
170
        $this->autoExecute = $flag;
171
172
        return $this;
173
    }
174
175
    /**
176
     * Execute the statement and return the number of rows that were modified or deleted.
177
     * If no rows were affected, this method returns 0. This method may return Boolean FALSE, but may also return a
178
     * non-Boolean value which evaluates to FALSE, so use the === operator for testing the return value of this method.
179
     *
180
     * @param array|null $valuesToBind A key pair array where the key is parameter's name.
181
     * @return int|false
182
     */
183
    public function execute(array $valuesToBind = null)
184
    {
185
        if ($valuesToBind) {
186
            $this->bindValues($valuesToBind);
187
        }
188
189
        $this->wasExecuted = true;
190
191
        if ($this->pdoStatement->execute()) {
192
            return $this->pdoStatement->rowCount();
193
        }
194
195
        return false;
196
    }
197
198
    /**
199
     * Returns the number of rows affected by the last SQL statement.
200
     * If there is no result set, returns 0.
201
     *
202
     * @return int
203
     */
204
    public function getRowCount(): int
205
    {
206
        return $this->pdoStatement->rowCount();
207
    }
208
209
    /**
210
     * Returns the number of columns in the result set.
211
     * If there is no result set, returns 0.
212
     *
213
     * @return int
214
     */
215
    public function getColumnCount(): int
216
    {
217
        return $this->pdoStatement->columnCount();
218
    }
219
220
    /**
221
     * Returns metadata for a column in a result set.
222
     * Returns FALSE if the requested column does not exist in the result set, or if no result set exists.
223
     *
224
     * @param int $columnNumber
225
     * @return array|false
226
     */
227
    public function getColumnMeta(int $columnNumber)
228
    {
229
        // Validations
230
231
        if ($columnNumber < 0) {
232
            throw new \InvalidArgumentException('The argument $columnNumber is not a valid integer.');
233
        }
234
235
        return $this->pdoStatement->getColumnMeta($columnNumber);
236
    }
237
238
    // CUSTOM FETCH METHODS
239
240
    /**
241
     * Fetches the first row from the result set and returns it as an associative array.
242
     *
243
     * @return bool|array
244
     */
245
    public function fetchFirstAsArray()
246
    {
247
        // Auto execute block
248
249
        if (!$this->wasExecuted || $this->autoExecute) {
250
            if ($this->execute() === false) {
251
                return false;
252
            }
253
        }
254
255
        // Fetch the first row
256
257
        $row = $this->pdoStatement->fetch(\PDO::FETCH_ASSOC);
258
259
        // Clear state and return result
260
261
        $this->pdoStatement->closeCursor();
262
263
        if ($row === false && $this->pdoStatement->errorCode() === '00000') {
264
            return null;
265
        }
266
267
        return $row;
268
    }
269
270
    /**
271
     * Fetches the first row from the result set and returns it as an object.
272
     *
273
     * @param string $className Name of the created class.
274
     * @param array|null $arguments Elements of this array are passed to the constructor.
275
     * @return bool|object
276
     */
277
    public function fetchFirstAsObject($className = 'stdClass', array $arguments = null)
278
    {
279
        // Validations
280
281
        if (!is_string($className) || $className == '') {
282
            throw new \InvalidArgumentException('The argument $className is not a valid string.');
283
        }
284
285
        // Auto execute block
286
287
        if (!$this->wasExecuted || $this->autoExecute) {
288
            if ($this->execute() === false) {
289
                return false;
290
            }
291
        }
292
293
        // Fetch the first row as object
294
295
        $row = $this->pdoStatement->fetchObject($className, $arguments);
0 ignored issues
show
Bug introduced by
It seems like $arguments can also be of type null; however, parameter $ctor_args of PDOStatement::fetchObject() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

295
        $row = $this->pdoStatement->fetchObject($className, /** @scrutinizer ignore-type */ $arguments);
Loading history...
296
297
        // Clear state and return result
298
299
        $this->pdoStatement->closeCursor();
300
301
        if ($row === false && $this->pdoStatement->errorCode() === '00000') {
302
            return null;
303
        }
304
305
        return $row;
306
    }
307
308
    /**
309
     * Fetches the first row from the result set and updates an existing object, mapping the columns as named properties.
310
     *
311
     * @param string $object Object to update.
312
     * @return bool|object
313
     */
314
    public function fetchFirstIntoObject($object)
315
    {
316
        // Validations
317
318
        if (!is_object($object)) {
319
            throw new \InvalidArgumentException('The argument $object is not a valid object.');
320
        }
321
322
        // Auto execute block
323
324
        if (!$this->wasExecuted || $this->autoExecute) {
325
            if ($this->execute() === false) {
326
                return false;
327
            }
328
        }
329
330
        // Fetch the first fow as object
331
332
        $this->pdoStatement->setFetchMode(\PDO::FETCH_INTO, $object);
333
        $row = $this->pdoStatement->fetch();
334
335
        // Clear state and return result
336
337
        $this->pdoStatement->closeCursor();
338
339
        if ($row === false && $this->pdoStatement->errorCode() === '00000') {
340
            return null;
341
        }
342
343
        return $row;
344
    }
345
346
    /**
347
     * Returns the value of a single column from the first row of the result set.
348
     *
349
     * @param string $column Name of column you wish to retrieve.
350
     * @return mixed
351
     */
352
    public function fetchFirstAsColumn($column)
353
    {
354
        // Validations
355
356
        if (!is_string($column) || $column == '') {
357
            throw new \InvalidArgumentException('The argument $column is not a valid string.');
358
        }
359
360
        // Auto execute block
361
362
        if (!$this->wasExecuted || $this->autoExecute) {
363
            if ($this->execute() === false) {
364
                return false;
365
            }
366
        }
367
368
        // Fetch the first row
369
        $row = $this->pdoStatement->fetch(\PDO::FETCH_ASSOC);
370
371
        // More validations
372
373
        if ($row === false) {
374
            $this->pdoStatement->closeCursor();
375
376
            if ($this->pdoStatement->errorCode() === '00000') {
377
                return null;
378
            }
379
380
            return false;
381
        }
382
383
        if (!isset($row[$column])) {
384
            throw new \InvalidArgumentException('The column to fetch is not present in the result set.');
385
        }
386
387
        // Clear state and return result
388
389
        $this->pdoStatement->closeCursor();
390
391
        return $row[$column];
392
    }
393
394
    /**
395
     * Returns an array containing values of a single column retrieved from the result set rows.
396
     *
397
     * @param string $column Name of column you wish to retrieve.
398
     * @param string $indexBy Name of the column you want to assign as a row key.
399
     * @return array|bool
400
     */
401
    public function fetchAllAsColumn($column, $indexBy = null)
402
    {
403
        // Validations
404
405
        if (!is_string($column) || $column == '') {
406
            throw new \InvalidArgumentException('The argument $column is not a valid string.');
407
        }
408
409
        if (isset($indexBy) && (!is_string($indexBy) || $indexBy == '')) {
410
            throw new \InvalidArgumentException('The argument $indexBy is not a valid string.');
411
        }
412
413
        // Auto execute block
414
415
        if (!$this->wasExecuted || $this->autoExecute) {
416
            if ($this->execute() === false) {
417
                return false;
418
            }
419
        }
420
421
        // Set initial values
422
423
        $result = [];
424
        $row = $this->pdoStatement->fetch(\PDO::FETCH_ASSOC);
425
426
        // More validations
427
428
        if ($row === false) {
429
            $this->pdoStatement->closeCursor();
430
431
            if ($this->pdoStatement->errorCode() === '00000') {
432
                return null;
433
            }
434
435
            return false;
436
        }
437
438
        if (!isset($row[$column])) {
439
            throw new \InvalidArgumentException('The column to fetch is not present in the result set.');
440
        }
441
442
        if ($indexBy && !isset($row[$indexBy])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
443
            throw new \InvalidArgumentException('The column to index by is not present in the result set.');
444
        }
445
446
        // Traversing the remaining rows
447
448
        while ($row) {
449
            if ($indexBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
450
                $result[$row[$indexBy]] = $row[$column];
451
            } else {
452
                $result[] = $row[$column];
453
            }
454
455
            $row = $this->pdoStatement->fetch(\PDO::FETCH_ASSOC);
456
        }
457
458
        // Clear state and return result
459
460
        $this->pdoStatement->closeCursor();
461
462
        return $result;
463
    }
464
465
    /**
466
     * Returns an associative array containing all of the result set.
467
     *
468
     * @param string $indexBy Name of the column you want to assign as a row key.
469
     * @param string $groupBy Name of the columns with which you want to group the result. You can include max. 3 columns by separating them with commas.
470
     * @return array|bool
471
     */
472
    public function fetchAllAsArray($indexBy = null, $groupBy = null)
473
    {
474
        // Validations
475
476
        if (isset($indexBy) && (!is_string($indexBy) || $indexBy == '')) {
477
            throw new \InvalidArgumentException('The argument $indexBy is not a valid string.');
478
        }
479
480
        if (isset($groupBy) && (!is_string($groupBy) || $groupBy == '')) {
481
            throw new \InvalidArgumentException('The argument $groupBy is not a valid string.');
482
        }
483
484
        // Auto execute block
485
486
        if (!$this->wasExecuted || $this->autoExecute) {
487
            if ($this->execute() === false) {
488
                return false;
489
            }
490
        }
491
492
        // Set initial values
493
494
        $result = [];
495
        $groupByCount = 0;
496
        $row = $this->pdoStatement->fetch(\PDO::FETCH_ASSOC);
497
498
        // More validations
499
500
        if ($row === false) {
501
            $this->pdoStatement->closeCursor();
502
503
            if ($this->pdoStatement->errorCode() === '00000') {
504
                return null;
505
            }
506
507
            return false;
508
        }
509
510
        if ($indexBy && !isset($row[$indexBy])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
511
            throw new \InvalidArgumentException('The column to index-by is not present in the result set.');
512
        }
513
514
        if ($groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupBy of type null|string 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...
515
            $groupBy = explode(',', str_replace(' ', '', $groupBy));
516
            $groupByCount = count($groupBy);
517
518
            if ($groupByCount > 3) {
519
                throw new \InvalidArgumentException('You have exceeded the limit of 3 columns to group-by.');
520
            }
521
522
            foreach ($groupBy as $column) {
523
                $columnErr = [];
524
525
                if (!isset($row[$column])) {
526
                    $columnErr[] = $column;
527
                }
528
529
                if ($columnErr) {
530
                    throw new \InvalidArgumentException('Some columns to group-by (' .
531
                        implode(', ', $columnErr)
532
                        . ') are not present in the result set.');
533
                }
534
            }
535
        }
536
537
        // Traversing the remaining rows
538
539
        while ($row) {
540
            if (!$indexBy && !$groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
541
                $result[] = $row;
542
            } elseif ($indexBy && !$groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
543
                $result[$row[$indexBy]] = $row;
544
            } elseif ($indexBy && $groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
545
                switch ($groupByCount) {
546
                    case 3:
547
                        $result[$row[$groupBy[0]]][$row[$groupBy[1]]][$row[$groupBy[2]]][$row[$indexBy]] = $row;
548
                        break;
549
                    case 2:
550
                        $result[$row[$groupBy[0]]][$row[$groupBy[1]]][$row[$indexBy]] = $row;
551
                        break;
552
                    case 1:
553
                        $result[$row[$groupBy[0]]][$row[$indexBy]] = $row;
554
                        break;
555
                }
556
            } elseif (!$indexBy && $groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
557
                switch ($groupByCount) {
558
                    case 3:
559
                        $result[$row[$groupBy[0]]][$row[$groupBy[1]]][$row[$groupBy[2]]][] = $row;
560
                        break;
561
                    case 2:
562
                        $result[$row[$groupBy[0]]][$row[$groupBy[1]]][] = $row;
563
                        break;
564
                    case 1:
565
                        $result[$row[$groupBy[0]]][] = $row;
566
                        break;
567
                }
568
            }
569
570
            $row = $this->pdoStatement->fetch(\PDO::FETCH_ASSOC);
571
        }
572
573
        // Clear state and return result
574
575
        $this->pdoStatement->closeCursor();
576
577
        return $result;
578
    }
579
580
    /**
581
     * Returns an array of objects containing all of the result set.
582
     *
583
     * @param string $className Name of the class you want to instantiate.
584
     * @param array $classArgs Elements of this array are passed to the constructor of the class instantiated.
585
     * @param string $indexBy Name of the column you want to assign as a row key.
586
     * @param string $groupBy Name of the columns with which you want to group the result. You can include max. 3 columns by separating them with commas.
587
     * @return array|bool
588
     */
589
    public function fetchAllAsObject($className = 'stdClass', array $classArgs = null, $indexBy = null, $groupBy = null)
590
    {
591
        // Validations
592
593
        if (!is_string($className) || $className === '') {
594
            throw new \InvalidArgumentException('The argument $className is not a valid string.');
595
        }
596
597
        if (isset($indexBy) && (!is_string($indexBy) || $indexBy == '')) {
598
            throw new \InvalidArgumentException('The argument $indexBy is not a valid string.');
599
        }
600
601
        if (isset($groupBy) && (!is_string($groupBy) || $groupBy == '')) {
602
            throw new \InvalidArgumentException('The argument $groupBy is not a valid string.');
603
        }
604
605
        // Auto execute block
606
607
        if (!$this->wasExecuted || $this->autoExecute) {
608
            if ($this->execute() === false) {
609
                return false;
610
            }
611
        }
612
613
        // Set initial values
614
615
        $result = [];
616
        $groupByCount = 0;
617
        $row = $this->pdoStatement->fetchObject($className, (array)$classArgs);
618
619
        // More validations
620
621
        if ($row === false) {
622
            $this->pdoStatement->closeCursor();
623
624
            if ($this->pdoStatement->errorCode() === '00000') {
625
                return null;
626
            }
627
628
            return false;
629
        }
630
631
        if ($indexBy && !property_exists($row, $indexBy)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
632
            throw new \InvalidArgumentException('The column to index-by is not present in the result set.');
633
        }
634
635
        if ($groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groupBy of type null|string 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...
636
            $groupBy = explode(',', str_replace(' ', '', $groupBy));
637
            $groupByCount = count($groupBy);
638
639
            if ($groupByCount > 3) {
640
                throw new \InvalidArgumentException('You have exceeded the limit of 3 columns to group-by.');
641
            }
642
643
            foreach ($groupBy as $column) {
644
                $columnErr = [];
645
646
                if (!property_exists($row, $column)) {
647
                    $columnErr[] = $column;
648
                }
649
650
                if ($columnErr) {
651
                    throw new \InvalidArgumentException('Some columns to group-by (' .
652
                        implode(', ', $columnErr)
653
                        . ') are not present in the result set.');
654
                }
655
            }
656
        }
657
658
        // Traversing the remaining rows
659
660
        while ($row) {
661
            if (!$indexBy && !$groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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
                $result[] = $row;
663
            } elseif ($indexBy && !$groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
664
                $result[$row->{$indexBy}] = $row;
665
            } elseif ($indexBy && $groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
666
                switch ($groupByCount) {
667
                    case 3:
668
                        $result[$row->{$groupBy[0]}][$row->{$groupBy[1]}][$row->{$groupBy[2]}][$row->{$indexBy}] = $row;
669
                        break;
670
                    case 2:
671
                        $result[$row->{$groupBy[0]}][$row->{$groupBy[1]}][$row->{$indexBy}] = $row;
672
                        break;
673
                    case 1:
674
                        $result[$row->{$groupBy[0]}][$row->{$indexBy}] = $row;
675
                        break;
676
                }
677
            } elseif (!$indexBy && $groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexBy of type null|string 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...
678
                switch ($groupByCount) {
679
                    case 3:
680
                        $result[$row->{$groupBy[0]}][$row->{$groupBy[1]}][$row->{$groupBy[2]}][] = $row;
681
                        break;
682
                    case 2:
683
                        $result[$row->{$groupBy[0]}][$row->{$groupBy[1]}][] = $row;
684
                        break;
685
                    case 1:
686
                        $result[$row->{$groupBy[0]}][] = $row;
687
                        break;
688
                }
689
            }
690
691
            $row = $this->pdoStatement->fetchObject($className, (array)$classArgs);
692
        }
693
694
        // Clear state and return result
695
696
        $this->pdoStatement->closeCursor();
697
698
        return $result;
699
    }
700
}
701