Completed
Push — master ( d2d838...15d122 )
by Lars
03:19
created

Result::fetchGroups()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 14

Duplication

Lines 28
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 28
loc 28
ccs 0
cts 0
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 14
nc 5
nop 2
crap 30
1
<?php
2
3
namespace voku\db;
4
5
use Arrayy\Arrayy;
6
use Symfony\Component\PropertyAccess\PropertyAccess;
7
use voku\helper\Bootup;
8
use voku\helper\UTF8;
9
10
/**
11
 * Result: This class can handle the results from the "DB"-class.
12
 *
13
 * @package   voku\db
14
 */
15
final class Result implements \Countable, \SeekableIterator, \ArrayAccess
16
{
17
18
  /**
19
   * @var int
20
   */
21
  public $num_rows;
22
23
  /**
24
   * @var string
25
   */
26
  public $sql;
27
28
  /**
29
   * @var \mysqli_result
30
   */
31
  private $_result;
32
33
  /**
34
   * @var int
35
   */
36
  private $current_row;
37
38
  /**
39
   * @var \Closure|null
40
   */
41
  private $_mapper;
42
43 30
  /**
44
   * @var string
45 30
   */
46
  private $_default_result_type = 'object';
47 30
48 30
  /**
49 30
   * Result constructor.
50
   *
51
   * @param string         $sql
52
   * @param \mysqli_result $result
53
   * @param \Closure       $mapper Optional callback mapper for the "fetchCallable()" method
54 1
   */
55
  public function __construct($sql = '', \mysqli_result $result, $mapper = null)
56 1
  {
57
    $this->sql = $sql;
58
59
    $this->_result = $result;
60
61
    $this->current_row = 0;
62
    $this->num_rows = (int)$this->_result->num_rows;
63
64
    $this->_mapper = $mapper;
65
  }
66 2
67
  /**
68
   * __destruct
69
   */
70 2
  public function __destruct()
71
  {
72 2
    $this->free();
73
  }
74 2
75 2
  /**
76 2
   * Runs a user-provided callback with the MySQLi_Result object given as
77 2
   * argument and returns the result, or returns the MySQLi_Result object if
78
   * called without an argument.
79
   *
80
   * @param callable $callback User-provided callback (optional)
81
   *
82
   * @return mixed|\mysqli_result
83
   */
84
  public function __invoke($callback = null)
85
  {
86
    if (isset($callback)) {
87
      return call_user_func($callback, $this->_result);
88
    }
89
90
    return $this->_result;
91
  }
92
93
  /**
94
   * Get the current "num_rows" as string.
95
   *
96
   * @return string
97
   */
98
  public function __toString()
99
  {
100 1
    return (string)$this->num_rows;
101
  }
102 1
103 1
  /**
104
   * Cast data into int, float or string.
105 1
   *
106
   * <p>
107 1
   *   <br />
108 1
   *   INFO: install / use "mysqlnd"-driver for better performance
109 1
   * </p>
110 1
   *
111 1
   * @param array|object $data
112 1
   *
113 1
   * @return array|object|false <p><strong>false</strong> on error</p>
114 1
   */
115 1
  private function cast(&$data)
116
  {
117 1
    if (Helper::isMysqlndIsUsed() === true) {
118
      return $data;
119
    }
120
121
    // init
122
    if (Bootup::is_php('5.4')) {
123
      static $FIELDS_CACHE = array();
124
      static $TYPES_CACHE = array();
125
    } else {
126
      $FIELDS_CACHE = array();
127
      $TYPES_CACHE = array();
128
    }
129
130
    $result_hash = spl_object_hash($this->_result);
131
132 26
    if (!isset($FIELDS_CACHE[$result_hash])) {
133
      $FIELDS_CACHE[$result_hash] = \mysqli_fetch_fields($this->_result);
134 26
    }
135 26
136
    if ($FIELDS_CACHE[$result_hash] === false) {
137
      return false;
138
    }
139
140
    if (!isset($TYPES_CACHE[$result_hash])) {
141
      foreach ($FIELDS_CACHE[$result_hash] as $field) {
142
        switch ($field->type) {
143
          case 3:
144
            $TYPES_CACHE[$result_hash][$field->name] = 'int';
145
            break;
146
          case 4:
147
            $TYPES_CACHE[$result_hash][$field->name] = 'float';
148
            break;
149
          default:
150
            $TYPES_CACHE[$result_hash][$field->name] = 'string';
151
            break;
152
        }
153
      }
154
    }
155
156
    if (is_array($data) === true) {
157 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
158
        if (isset($data[$type_name])) {
159
          settype($data[$type_name], $type);
160
        }
161
      }
162
    } elseif (is_object($data)) {
163 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
164
        if (isset($data->{$type_name})) {
165
          settype($data->{$type_name}, $type);
166
        }
167
      }
168
    }
169
170
    return $data;
171
  }
172
173
  /**
174
   * Countable interface implementation.
175
   *
176
   * @return int The number of rows in the result
177
   */
178
  public function count()
179
  {
180
    return $this->num_rows;
181
  }
182
183
  /**
184
   * Iterator interface implementation.
185
   *
186
   * @return mixed The current element
187
   */
188
  public function current()
189
  {
190
    return $this->fetchCallable($this->current_row);
191
  }
192
193
  /**
194
   * Iterator interface implementation.
195 10
   *
196
   * @return int The current element key (row index; zero-based)
197
   */
198 10
  public function key()
199
  {
200
    return $this->current_row;
201 10
  }
202 10
203 10
  /**
204 10
   * Iterator interface implementation.
205 10
   *
206
   * @return void
207
   */
208 10
  public function next()
209 10
  {
210 10
    $this->current_row++;
211 10
  }
212
213 10
  /**
214
   * Iterator interface implementation.
215
   *
216
   * @param int $row Row position to rewind to; defaults to 0
217
   *
218
   * @return void
219
   */
220
  public function rewind($row = 0)
221 3
  {
222
    if ($this->seek($row)) {
223
      $this->current_row = $row;
224 3
    }
225
  }
226
227 3
  /**
228 3
   * Moves the internal pointer to the specified row position.
229 3
   *
230 3
   * @param int $row Row position; zero-based and set to 0 by default
231 3
   *
232
   * @return bool Boolean true on success, false otherwise
233
   */
234 3
  public function seek($row = 0)
235 3
  {
236 3
    if (is_int($row) && $row >= 0 && $row < $this->num_rows) {
237 3
      return mysqli_data_seek($this->_result, $row);
238
    }
239 3
240
    return false;
241
  }
242
243
  /**
244
   * Iterator interface implementation.
245
   *
246
   * @return bool Boolean true if the current index is valid, false otherwise
247 12
   */
248
  public function valid()
249 12
  {
250 12
    return $this->current_row < $this->num_rows;
251
  }
252
253 1
  /**
254
   * Fetch.
255
   *
256
   * <p>
257
   *   <br />
258
   *   INFO: this will return an object by default, not an array<br />
259
   *   and you can change the behaviour via "Result->setDefaultResultType()"
260
   * </p>
261 11
   *
262
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
263 11
   *
264 11
   * @return array|object|false <p><strong>false</strong> on error</p>
265 11
   */
266
  public function fetch($reset = false)
267 11
  {
268
    $return = false;
269
270
    if ($this->_default_result_type === 'object') {
271
      $return = $this->fetchObject('', '', $reset);
272
    } elseif ($this->_default_result_type === 'array') {
273
      $return = $this->fetchArray($reset);
274
    } elseif ($this->_default_result_type === 'Arrayy') {
275 1
      $return = $this->fetchArrayy($reset);
276
    }
277 1
278
    return $return;
279 1
  }
280
281
  /**
282
   * Fetch all results.
283
   *
284
   * <p>
285
   *   <br />
286 30
   *   INFO: this will return an object by default, not an array<br />
287
   *   and you can change the behaviour via "Result->setDefaultResultType()"
288 30
   * </p>
289 30
   *
290
   * @return array
291
   */
292
  public function fetchAll()
293
  {
294 30
    $return = array();
295
296 30
    if ($this->_default_result_type === 'object') {
297 30
      $return = $this->fetchAllObject();
298
    } elseif ($this->_default_result_type === 'array') {
299
      $return = $this->fetchAllArray();
300
    } elseif ($this->_default_result_type === 'Arrayy') {
301
      $return = $this->fetchAllArray();
302
    }
303
304
    return $return;
305
  }
306 1
307
  /**
308 1
   * Fetch all results as array.
309
   *
310
   * @return array
311
   */
312 View Code Duplication
  public function fetchAllArray()
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...
313
  {
314
    // init
315
    $data = array();
316
317
    if (
318
        $this->_result
319
        &&
320
        !$this->is_empty()
321
    ) {
322
      $this->reset();
323
324 2
      /** @noinspection PhpAssignmentInConditionInspection */
325
      while ($row = \mysqli_fetch_assoc($this->_result)) {
326 2
        $data[] = $this->cast($row);
327
      }
328 2
    }
329 2
330 2
    return $data;
331 2
  }
332 2
333
  /**
334
   * Fetch all results as "Arrayy"-object.
335
   *
336 2
   * @return Arrayy
337
   */
338 View Code Duplication
  public function fetchAllArrayy()
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...
339
  {
340
    // init
341
    $data = array();
342
343
    if (
344
        $this->_result
345
        &&
346
        !$this->is_empty()
347
    ) {
348 7
      $this->reset();
349
350 7
      /** @noinspection PhpAssignmentInConditionInspection */
351 1
      while ($row = \mysqli_fetch_assoc($this->_result)) {
352 1
        $data[] = $this->cast($row);
353
      }
354 7
    }
355 1
356
    return Arrayy::create($data);
357
  }
358 7
359 1
  /**
360
   * Fetch a single column as an 1-dimension array.
361
   *
362 7
   * @param string $column
363
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
364
   *
365
   * @return array <p>Return an empty array if the "$column" wasn't found</p>
366
   */
367
  public function fetchAllColumn($column, $skipNullValues = false)
368
  {
369
    return $this->fetchColumn($column, $skipNullValues, true);
370
  }
371
372 14
  /**
373
   * Fetch all results as array with objects.
374 14
   *
375 1
   * @param object|string $class  <p>
376 1
   *                              <strong>string</strong>: create a new object (with optional constructor
377
   *                              parameter)<br>
378 14
   *                              <strong>object</strong>: use a object and fill the the data into
379 14
   *                              </p>
380 13
   * @param null|array    $params optional
381
   *                              <p>
382
   *                              An array of parameters to pass to the constructor, used if $class is a
383 3
   *                              string.
384 3
   *                              </p>
385
   *
386
   * @return array
387
   */
388
  public function fetchAllObject($class = '', $params = null)
389
  {
390
391
    if ($this->is_empty()) {
392
      return array();
393
    }
394
395
    // init
396
    $data = array();
397 2
    $this->reset();
398
399 2
    if ($class && is_object($class)) {
400
      /** @noinspection PhpAssignmentInConditionInspection */
401
      while ($row = \mysqli_fetch_assoc($this->_result)) {
402
        $classTmp = clone $class;
403 2
        $row = $this->cast($row);
404 2
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
405 1
        foreach ($row as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $row of type array|object|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
406
          $propertyAccessor->setValue($classTmp, $key, $value);
407
        }
408 1
        $data[] = $classTmp;
409 1
      }
410
411
      return $data;
412
    }
413
414
    if ($class && $params) {
415
      /** @noinspection PhpAssignmentInConditionInspection */
416
      while ($row = \mysqli_fetch_object($this->_result, $class, $params)) {
417
        $data[] = $this->cast($row);
418
      }
419
420
      return $data;
421
    }
422 1
423 View Code Duplication
    if ($class) {
424 1
      /** @noinspection PhpAssignmentInConditionInspection */
425
      while ($row = \mysqli_fetch_object($this->_result, $class)) {
426
        $data[] = $this->cast($row);
427
      }
428
429
      return $data;
430
    }
431
432
    /** @noinspection PhpAssignmentInConditionInspection */
433
    while ($row = \mysqli_fetch_object($this->_result)) {
434
      $data[] = $this->cast($row);
435
    }
436
437
    return $data;
438 2
  }
439
440 2
  /**
441
   * Fetch as array.
442 2
   *
443 2
   * @param bool $reset
444 2
   *
445 1
   * @return array|false <p><strong>false</strong> on error</p>
446 1
   */
447 View Code Duplication
  public function fetchArray($reset = false)
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...
448
  {
449
    if ($reset === true) {
450 2
      $this->reset();
451
    }
452
453
    $row = \mysqli_fetch_assoc($this->_result);
454
    if ($row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row 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...
455
      return $this->cast($row);
456
    }
457
458
    if ($row === null) {
459
      return array();
460
    }
461 3
462
    return false;
463
  }
464 3
465
  /**
466 3
   * Fetch data as a key/value pair array.
467 3
   *
468
   * <p>
469 3
   *   <br />
470
   *   INFO: both "key" and "value" must exists in the fetched data
471 1
   *   the key will be the new key of the result-array
472 1
   *   <br /><br />
473 1
   * </p>
474 3
   *
475
   * e.g.:
476 1
   * <code>
477 1
   *    fetchArrayPair('some_id', 'some_value');
478 1
   *    // array(127 => 'some value', 128 => 'some other value')
479 1
   * </code>
480
   *
481 3
   * @param string $key
482 3
   * @param string $value
483 3
   *
484
   * @return array
485 3
   */
486
  public function fetchArrayPair($key, $value)
487 3
  {
488
    $arrayPair = array();
489
    $data = $this->fetchAllArray();
490
491
    foreach ($data as $_row) {
492
      if (
493
          array_key_exists($key, $_row) === true
494
          &&
495
          array_key_exists($value, $_row) === true
496
      ) {
497 1
        $_key = $_row[$key];
498
        $_value = $_row[$value];
499 1
        $arrayPair[$_key] = $_value;
500
      }
501
    }
502
503
    return $arrayPair;
504
  }
505
506
  /**
507
   * Fetch as "Arrayy"-object.
508
   *
509
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
510
   *
511
   * @return Arrayy|false <p><strong>false</strong> on error</p>
512
   */
513 View Code Duplication
  public function fetchArrayy($reset = false)
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...
514
  {
515
    if ($reset === true) {
516
      $this->reset();
517
    }
518
519
    $row = \mysqli_fetch_assoc($this->_result);
520
    if ($row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row 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...
521 1
      return Arrayy::create($this->cast($row));
0 ignored issues
show
Bug introduced by
It seems like $this->cast($row) targeting voku\db\Result::cast() can also be of type false or object; however, Arrayy\Arrayy::create() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
522
    }
523 1
524
    if ($row === null) {
525
      return Arrayy::create();
526
    }
527
528
    return false;
529
  }
530
531
  /**
532
   * Fetch a single column as string (or as 1-dimension array).
533
   *
534 1
   * @param string $column
535
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
536 1
   * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
537
   *
538
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
539
   *                      "$asArray"</p>
540
   */
541
  public function fetchColumn($column = '', $skipNullValues = true, $asArray = false)
542
  {
543
    if ($asArray === false) {
544
      $columnData = '';
545
546
      $data = $this->fetchAllArrayy()->reverse();
547 View Code Duplication
      foreach ($data as $_row) {
548
549
        if ($skipNullValues === true) {
550
          if (isset($_row[$column]) === false) {
551
            continue;
552
          }
553
        } else {
554
          if (array_key_exists($column, $_row) === false) {
555
            break;
556
          }
557
        }
558
559
        $columnData = $_row[$column];
560
        break;
561
      }
562
563
      return $columnData;
564
    }
565
566 1
    // -- return as array -->
567
568 1
    $columnData = array();
569
570
    $data = $this->fetchAllArray();
571
572 View Code Duplication
    foreach ($data as $_row) {
573
574
      if ($skipNullValues === true) {
575
        if (isset($_row[$column]) === false) {
576
          continue;
577
        }
578
      } else {
579
        if (array_key_exists($column, $_row) === false) {
580
          break;
581 2
        }
582
      }
583 2
584 2
      $columnData[] = $_row[$column];
585
    }
586 2
587 2
    return $columnData;
588
  }
589 2
590 2
  /**
591 1
   * Fetch as object.
592
   *
593 2
   * @param object|string $class  <p>
594 1
   *                              <strong>string</strong>: create a new object (with optional constructor
595 1
   *                              parameter)<br>
596
   *                              <strong>object</strong>: use a object and fill the the data into
597
   *                              </p>
598
   * @param null|array    $params optional
599 2
   *                              <p>
600 2
   *                              An array of parameters to pass to the constructor, used if $class is a
601 2
   *                              string.
602
   *                              </p>
603 2
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
604
   *
605
   * @return object|false <p><strong>false</strong> on error</p>
606
   */
607
  public function fetchObject($class = '', $params = null, $reset = false)
608 1
  {
609
    if ($reset === true) {
610 1
      $this->reset();
611
    }
612 1
613
    if ($class && is_object($class)) {
614 1
      $row = \mysqli_fetch_assoc($this->_result);
615 1
      $row = $row ? $this->cast($row) : false;
616 1
617
      if (!$row) {
618 1
        return false;
619 1
      }
620 1
621
      $propertyAccessor = PropertyAccess::createPropertyAccessor();
622
      foreach ($row as $key => $value) {
623
        $propertyAccessor->setValue($class, $key, $value);
624 1
      }
625 1
626
      return $class;
627 1
    }
628
629 View Code Duplication
    if ($class && $params) {
630
      $row = \mysqli_fetch_object($this->_result, $class, $params);
631
632
      return $row ? $this->cast($row) : false;
633
    }
634
635 View Code Duplication
    if ($class) {
636
      $row = \mysqli_fetch_object($this->_result, $class);
637
638
      return $row ? $this->cast($row) : false;
639
    }
640
641
    $row = \mysqli_fetch_object($this->_result);
642
643
    return $row ? $this->cast($row) : false;
644
  }
645
646
  /**
647
   * Fetches a row or a single column within a row. Returns null if there are
648
   * no more rows in the result.
649
   *
650
   * @param int    $row    The row number (optional)
651
   * @param string $column The column name (optional)
652
   *
653
   * @return mixed An associative array or a scalar value
654
   */
655
  public function fetchCallable($row = null, $column = null)
656
  {
657
    if (!$this->num_rows) {
658
      return null;
659
    }
660
661
    if (isset($row)) {
662
      $this->seek($row);
663
    }
664
665
    $rows = \mysqli_fetch_assoc($this->_result);
666
667
    if ($column) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $column 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...
668
      return is_array($rows) && isset($rows[$column]) ? $rows[$column] : null;
669
    }
670
671
    return is_callable($this->_mapper) ? call_user_func($this->_mapper, $rows) : $rows;
672
  }
673
674
  /**
675
   * Return rows of field information in a result set. This function is a
676
   * basically a wrapper on the native mysqli_fetch_fields function.
677
   *
678
   * @param bool $as_array Return each field info as array; defaults to false
679
   *
680
   * @return array Array of field information each as an associative array
681
   */
682
  public function fetchFields($as_array = false)
683
  {
684
    if ($as_array) {
685
      return array_map(
686
          function ($object) {
687
            return (array)$object;
688
          },
689
          \mysqli_fetch_fields($this->_result)
690
      );
691
    }
692
693
    return \mysqli_fetch_fields($this->_result);
694
  }
695
696
  /**
697
   * Returns all rows at once as a grouped array of scalar values or arrays.
698
   *
699
   * @param string $group  The column name to use for grouping
700
   * @param string $column The column name to use as values (optional)
701
   *
702
   * @return array A grouped array of scalar values or arrays
703
   */
704 View Code Duplication
  public function fetchGroups($group, $column = null)
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...
705
  {
706
    // init
707
    $groups = array();
708
    $pos = $this->current_row;
709
710
    foreach ($this as $row) {
711
712
      if (!array_key_exists($group, $row)) {
713
        continue;
714
      }
715
716
      if (isset($column)) {
717
718
        if (!array_key_exists($column, $row)) {
719
          continue;
720
        }
721
722
        $groups[$row[$group]][] = $row[$column];
723
      } else {
724
        $groups[$row[$group]][] = $row;
725
      }
726
    }
727
728
    $this->rewind($pos);
729
730
    return $groups;
731
  }
732
733
  /**
734
   * Returns all rows at once as key-value pairs.
735
   *
736
   * @param string $key    The column name to use as keys
737
   * @param string $column The column name to use as values (optional)
738
   *
739
   * @return array An array of key-value pairs
740
   */
741 View Code Duplication
  public function fetchPairs($key, $column = null)
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...
742
  {
743
    // init
744
    $pairs = array();
745
    $pos = $this->current_row;
746
747
    foreach ($this as $row) {
748
749
      if (!array_key_exists($key, $row)) {
750
        continue;
751
      }
752
753
      if (isset($column)) {
754
755
        if (!array_key_exists($column, $row)) {
756
          continue;
757
        }
758
759
        $pairs[$row[$key]] = $row[$column];
760
      } else {
761
        $pairs[$row[$key]] = $row;
762
      }
763
    }
764
765
    $this->rewind($pos);
766
767
    return $pairs;
768
  }
769
770
  /**
771
   * Returns all rows at once, transposed as an array of arrays. Instead of
772
   * returning rows of columns, this method returns columns of rows.
773
   *
774
   * @param string $column The column name to use as keys (optional)
775
   *
776
   * @return mixed A transposed array of arrays
777
   */
778
  public function fetchTranspose($column = null)
779
  {
780
    // init
781
    $keys = isset($column) ? $this->fetchAllColumn($column) : array();
782
    $rows = array();
783
    $pos = $this->current_row;
784
785
    foreach ($this as $row) {
786
      foreach ($row as $key => $value) {
787
        $rows[$key][] = $value;
788
      }
789
    }
790
791
    $this->rewind($pos);
792
793
    if (empty($keys)) {
794
      return $rows;
795
    }
796
797
    return array_map(
798
        function ($values) use ($keys) {
799
          return array_combine($keys, $values);
800
        }, $rows
801
    );
802
  }
803
804
  /**
805
   * Returns the first row element from the result.
806
   *
807
   * @param string $column The column name to use as value (optional)
808
   *
809
   * @return mixed A row array or a single scalar value
810
   */
811
  public function first($column = null)
812
  {
813
    $pos = $this->current_row;
814
    $first = $this->fetchCallable(0, $column);
815
    $this->rewind($pos);
816
817
    return $first;
818
  }
819
820
  /**
821
   * free the memory
822
   */
823
  public function free()
824
  {
825
    if (isset($this->_result) && $this->_result) {
826
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
827
      /** @noinspection UsageOfSilenceOperatorInspection */
828
      @\mysqli_free_result($this->_result);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
829
      $this->_result = null;
830
831
      return true;
832
    }
833
834
    return false;
835
  }
836
837
  /**
838
   * alias for "Result->fetch()"
839
   *
840
   * @see Result::fetch()
841
   *
842
   * @return array|object|false <p><strong>false</strong> on error</p>
843
   */
844
  public function get()
845
  {
846
    return $this->fetch();
847
  }
848
849
  /**
850
   * alias for "Result->fetchAll()"
851
   *
852
   * @see Result::fetchAll()
853
   *
854
   * @return array
855
   */
856
  public function getAll()
857
  {
858
    return $this->fetchAll();
859
  }
860
861
  /**
862
   * alias for "Result->fetchAllColumn()"
863
   *
864
   * @see Result::fetchAllColumn()
865
   *
866
   * @param string $column
867
   * @param bool   $skipNullValues
868
   *
869
   * @return array
870
   */
871
  public function getAllColumn($column, $skipNullValues = false)
872
  {
873
    return $this->fetchAllColumn($column, $skipNullValues);
874
  }
875
876
  /**
877
   * alias for "Result->fetchAllArray()"
878
   *
879
   * @see Result::fetchAllArray()
880
   *
881
   * @return array
882
   */
883
  public function getArray()
884
  {
885
    return $this->fetchAllArray();
886
  }
887
888
  /**
889
   * alias for "Result->fetchAllArrayy()"
890
   *
891
   * @see Result::fetchAllArrayy()
892
   *
893
   * @return Arrayy
894
   */
895
  public function getArrayy()
896
  {
897
    return $this->fetchAllArrayy();
898
  }
899
900
  /**
901
   * alias for "Result->fetchColumn()"
902
   *
903
   * @see Result::fetchColumn()
904
   *
905
   * @param $column
906
   * @param $asArray
907
   * @param $skipNullValues
908
   *
909
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
910
   *                      "$asArray"</p>
911
   */
912
  public function getColumn($column, $skipNullValues = true, $asArray = false)
913
  {
914
    return $this->fetchColumn($column, $skipNullValues, $asArray);
915
  }
916
917
  /**
918
   * @return string
919
   */
920
  public function getDefaultResultType()
921
  {
922
    return $this->_default_result_type;
923
  }
924
925
  /**
926
   * alias for "Result->fetchAllObject()"
927
   *
928
   * @see Result::fetchAllObject()
929
   *
930
   * @return array of mysql-objects
931
   */
932
  public function getObject()
933
  {
934
    return $this->fetchAllObject();
935
  }
936
937
  /**
938
   * Check if the result is empty.
939
   *
940
   * @return bool
941
   */
942
  public function is_empty()
943
  {
944
    if ($this->num_rows > 0) {
945
      return false;
946
    }
947
948
    return true;
949
  }
950
951
  /**
952
   * Fetch all results as "json"-string.
953
   *
954
   * @return string
955
   */
956
  public function json()
957
  {
958
    $data = $this->fetchAllArray();
959
960
    return UTF8::json_encode($data);
961
  }
962
963
  /**
964
   * Returns the last row element from the result.
965
   *
966
   * @param string $column The column name to use as value (optional)
967
   *
968
   * @return mixed A row array or a single scalar value
969
   */
970
  public function last($column = null)
971
  {
972
    $pos = $this->current_row;
973
    $last = $this->fetchCallable($this->num_rows - 1, $column);
974
    $this->rewind($pos);
975
976
    return $last;
977
  }
978
979
  /**
980
   * Set the mapper...
981
   *
982
   * @param \Closure $callable
983
   *
984
   * @return $this
985
   */
986
  public function map(\Closure $callable)
987
  {
988
    $this->_mapper = $callable;
989
990
    return $this;
991
  }
992
993
  /**
994
   * Alias of count(). Deprecated.
995
   *
996
   * @return int The number of rows in the result
997
   */
998
  public function num_rows()
999
  {
1000
    return $this->count();
1001
  }
1002
1003
  /**
1004
   * ArrayAccess interface implementation.
1005
   *
1006
   * @param int $offset Offset number
1007
   *
1008
   * @return bool Boolean true if offset exists, false otherwise
1009
   */
1010
  public function offsetExists($offset)
1011
  {
1012
    return is_int($offset) && $offset >= 0 && $offset < $this->num_rows;
1013
  }
1014
1015
  /**
1016
   * ArrayAccess interface implementation.
1017
   *
1018
   * @param int $offset Offset number
1019
   *
1020
   * @return mixed
1021
   */
1022
  public function offsetGet($offset)
1023
  {
1024
    if ($this->offsetExists($offset)) {
1025
      return $this->fetchCallable($offset);
1026
    }
1027
1028
    throw new \OutOfBoundsException("undefined offset ($offset)");
1029
  }
1030
1031
  /**
1032
   * ArrayAccess interface implementation. Not implemented by design.
1033
   *
1034
   * @param mixed $offset
1035
   * @param mixed $value
1036
   */
1037
  public function offsetSet($offset, $value)
1038
  {
1039
    /** @noinspection UselessReturnInspection */
1040
    return;
1041
  }
1042
1043
  /**
1044
   * ArrayAccess interface implementation. Not implemented by design.
1045
   *
1046
   * @param mixed $offset
1047
   */
1048
  public function offsetUnset($offset)
1049
  {
1050
    /** @noinspection UselessReturnInspection */
1051
    return;
1052
  }
1053
1054
  /**
1055
   * Reset the offset (data_seek) for the results.
1056
   *
1057
   * @return Result
1058
   */
1059
  public function reset()
1060
  {
1061
    if (!$this->is_empty()) {
1062
      \mysqli_data_seek($this->_result, 0);
1063
    }
1064
1065
    return $this;
1066
  }
1067
1068
  /**
1069
   * You can set the default result-type to 'object', 'array' or 'Arrayy'.
1070
   *
1071
   * INFO: used for "fetch()" and "fetchAll()"
1072
   *
1073
   * @param string $default_result_type
1074
   */
1075
  public function setDefaultResultType($default_result_type = 'object')
1076
  {
1077
    if (
1078
        $default_result_type === 'object'
1079
        ||
1080
        $default_result_type === 'array'
1081
        ||
1082
        $default_result_type === 'Arrayy'
1083
    ) {
1084
      $this->_default_result_type = $default_result_type;
1085
    }
1086
  }
1087
1088
  /**
1089
   * @param int      $offset
1090
   * @param null|int $length
1091
   * @param bool     $preserve_keys
1092
   *
1093
   * @return array
1094
   */
1095
  public function slice($offset = 0, $length = null, $preserve_keys = false)
1096
  {
1097
    // init
1098
    $slice = array();
1099
    $offset = (int)$offset;
1100
1101
    if ($offset < 0) {
1102
      if (abs($offset) > $this->num_rows) {
1103
        $offset = 0;
1104
      } else {
1105
        $offset = $this->num_rows - abs($offset);
1106
      }
1107
    }
1108
1109
    $length = isset($length) ? (int)$length : $this->num_rows;
1110
    $n = 0;
1111
    for ($i = $offset; $i < $this->num_rows && $n < $length; $i++) {
1112
      if ($preserve_keys) {
1113
        $slice[$i] = $this->fetchCallable($i);
1114
      } else {
1115
        $slice[] = $this->fetchCallable($i);
1116
      }
1117
      ++$n;
1118
    }
1119
1120
    return $slice;
1121
  }
1122
}
1123