Completed
Push — master ( dbe3f7...286249 )
by Lars
03:20
created

Result::slice()   C

Complexity

Conditions 7
Paths 18

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 14
cts 14
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 16
nc 18
nop 3
crap 7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\db;
6
7
use Arrayy\Arrayy;
8
use Symfony\Component\PropertyAccess\PropertyAccess;
9
use voku\helper\UTF8;
10
11
/**
12
 * Result: This class can handle the results from the "DB"-class.
13
 *
14
 * @package   voku\db
15
 */
16
final class Result implements \Countable, \SeekableIterator, \ArrayAccess
17
{
18
19
  /**
20
   * @var int
21
   */
22
  public $num_rows;
23
24
  /**
25
   * @var string
26
   */
27
  public $sql;
28
29
  /**
30
   * @var \mysqli_result
31
   */
32
  private $_result;
33
34
  /**
35
   * @var int
36
   */
37
  private $current_row;
38
39
  /**
40
   * @var \Closure|null
41
   */
42
  private $_mapper;
43
44
  /**
45
   * @var string
46
   */
47
  private $_default_result_type = 'object';
48
49
  /**
50
   * Result constructor.
51
   *
52
   * @param string         $sql
53
   * @param \mysqli_result $result
54
   * @param \Closure       $mapper Optional callback mapper for the "fetchCallable()" method
55
   */
56 64
  public function __construct(string $sql = '', \mysqli_result $result, \Closure $mapper = null)
57
  {
58 64
    $this->sql = $sql;
59
60 64
    $this->_result = $result;
61
62 64
    $this->current_row = 0;
63 64
    $this->num_rows = (int)$this->_result->num_rows;
64
65 64
    $this->_mapper = $mapper;
66 64
  }
67
68
  /**
69
   * __destruct
70
   */
71 63
  public function __destruct()
72
  {
73 63
    $this->free();
74 63
  }
75
76
  /**
77
   * Runs a user-provided callback with the MySQLi_Result object given as
78
   * argument and returns the result, or returns the MySQLi_Result object if
79
   * called without an argument.
80
   *
81
   * @param callable $callback User-provided callback (optional)
82
   *
83
   * @return mixed|\mysqli_result
84
   */
85 2
  public function __invoke(callable $callback = null)
86
  {
87 2
    if (null !== $callback) {
88 2
      return $callback($this->_result);
89
    }
90
91 1
    return $this->_result;
92
  }
93
94
  /**
95
   * Get the current "num_rows" as string.
96
   *
97
   * @return string
98
   */
99
  public function __toString()
100
  {
101
    return (string)$this->num_rows;
102
  }
103
104
  /**
105
   * Cast data into int, float or string.
106
   *
107
   * <p>
108
   *   <br />
109
   *   INFO: install / use "mysqlnd"-driver for better performance
110
   * </p>
111
   *
112
   * @param array|object $data
113
   *
114
   * @return array|object|false <p><strong>false</strong> on error</p>
115
   */
116 46
  private function cast(&$data)
117
  {
118 46
    if (Helper::isMysqlndIsUsed() === true) {
119 46
      return $data;
120
    }
121
122
    // init
123
    static $FIELDS_CACHE = [];
124
    static $TYPES_CACHE = [];
125
126
    $result_hash = \spl_object_hash($this->_result);
127
128
    if (!isset($FIELDS_CACHE[$result_hash])) {
129
      $FIELDS_CACHE[$result_hash] = \mysqli_fetch_fields($this->_result);
130
    }
131
132
    if ($FIELDS_CACHE[$result_hash] === false) {
133
      return false;
134
    }
135
136
    if (!isset($TYPES_CACHE[$result_hash])) {
137
      foreach ($FIELDS_CACHE[$result_hash] as $field) {
138
        switch ($field->type) {
139
          case 3:
140
            $TYPES_CACHE[$result_hash][$field->name] = 'int';
141
            break;
142
          case 4:
143
            $TYPES_CACHE[$result_hash][$field->name] = 'float';
144
            break;
145
          default:
146
            $TYPES_CACHE[$result_hash][$field->name] = 'string';
147
            break;
148
        }
149
      }
150
    }
151
152
    if (\is_array($data) === true) {
153 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
154
        if (isset($data[$type_name])) {
155
          settype($data[$type_name], $type);
156
        }
157
      }
158
    } elseif (\is_object($data)) {
159 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
160
        if (isset($data->{$type_name})) {
161
          \settype($data->{$type_name}, $type);
162
        }
163
      }
164
    }
165
166
    return $data;
167
  }
168
169
  /**
170
   * Countable interface implementation.
171
   *
172
   * @return int The number of rows in the result
173
   */
174 2
  public function count(): int
175
  {
176 2
    return $this->num_rows;
177
  }
178
179
  /**
180
   * Iterator interface implementation.
181
   *
182
   * @return mixed The current element
183
   */
184 6
  public function current()
185
  {
186 6
    return $this->fetchCallable($this->current_row);
187
  }
188
189
  /**
190
   * Iterator interface implementation.
191
   *
192
   * @return int The current element key (row index; zero-based)
193
   */
194 1
  public function key(): int
195
  {
196 1
    return $this->current_row;
197
  }
198
199
  /**
200
   * Iterator interface implementation.
201
   *
202
   * @return void
203
   */
204 6
  public function next()
205
  {
206 6
    $this->current_row++;
207 6
  }
208
209
  /**
210
   * Iterator interface implementation.
211
   *
212
   * @param int $row Row position to rewind to; defaults to 0
213
   *
214
   * @return void
215
   */
216 10
  public function rewind($row = 0)
217
  {
218 10
    if ($this->seek($row)) {
219 8
      $this->current_row = $row;
220
    }
221 10
  }
222
223
  /**
224
   * Moves the internal pointer to the specified row position.
225
   *
226
   * @param int $row Row position; zero-based and set to 0 by default
227
   *
228
   * @return bool Boolean true on success, false otherwise
229
   */
230 18
  public function seek($row = 0): bool
231
  {
232 18
    if (\is_int($row) && $row >= 0 && $row < $this->num_rows) {
233 14
      return \mysqli_data_seek($this->_result, $row);
234
    }
235
236 4
    return false;
237
  }
238
239
  /**
240
   * Iterator interface implementation.
241
   *
242
   * @return bool Boolean true if the current index is valid, false otherwise
243
   */
244 6
  public function valid(): bool
245
  {
246 6
    return $this->current_row < $this->num_rows;
247
  }
248
249
  /**
250
   * Fetch.
251
   *
252
   * <p>
253
   *   <br />
254
   *   INFO: this will return an object by default, not an array<br />
255
   *   and you can change the behaviour via "Result->setDefaultResultType()"
256
   * </p>
257
   *
258
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
259
   *
260
   * @return array|object|false <p><strong>false</strong> on error</p>
261
   */
262 2
  public function fetch(bool $reset = false)
263
  {
264 2
    $return = false;
265
266 2
    if ($this->_default_result_type === 'object') {
267 2
      $return = $this->fetchObject('', null, $reset);
268 2
    } elseif ($this->_default_result_type === 'array') {
269 2
      $return = $this->fetchArray($reset);
270
    } elseif ($this->_default_result_type === 'Arrayy') {
271
      $return = $this->fetchArrayy($reset);
272
    }
273
274 2
    return $return;
275
  }
276
277
  /**
278
   * Fetch all results.
279
   *
280
   * <p>
281
   *   <br />
282
   *   INFO: this will return an object by default, not an array<br />
283
   *   and you can change the behaviour via "Result->setDefaultResultType()"
284
   * </p>
285
   *
286
   * @return array
287
   */
288 2
  public function fetchAll(): array
289
  {
290 2
    $return = [];
291
292 2
    if ($this->_default_result_type === 'object') {
293 2
      $return = $this->fetchAllObject();
294 1
    } elseif ($this->_default_result_type === 'array') {
295 1
      $return = $this->fetchAllArray();
296
    } elseif ($this->_default_result_type === 'Arrayy') {
297
      $return = $this->fetchAllArray();
298
    }
299
300 2
    return $return;
301
  }
302
303
  /**
304
   * Fetch all results as array.
305
   *
306
   * @return array
307
   */
308 14 View Code Duplication
  public function fetchAllArray(): array
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...
309
  {
310
    // init
311 14
    $data = [];
312
313
    if (
314 14
        $this->_result
315
        &&
316 14
        !$this->is_empty()
317
    ) {
318 14
      $this->reset();
319
320
      /** @noinspection PhpAssignmentInConditionInspection */
321 14
      while ($row = \mysqli_fetch_assoc($this->_result)) {
322 14
        $data[] = $this->cast($row);
323
      }
324
    }
325
326 14
    return $data;
327
  }
328
329
  /**
330
   * Fetch all results as "Arrayy"-object.
331
   *
332
   * @return Arrayy
333
   */
334 4 View Code Duplication
  public function fetchAllArrayy(): Arrayy
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...
335
  {
336
    // init
337 4
    $data = [];
338
339
    if (
340 4
        $this->_result
341
        &&
342 4
        !$this->is_empty()
343
    ) {
344 4
      $this->reset();
345
346
      /** @noinspection PhpAssignmentInConditionInspection */
347 4
      while ($row = \mysqli_fetch_assoc($this->_result)) {
348 4
        $data[] = $this->cast($row);
349
      }
350
    }
351
352 4
    return Arrayy::create($data);
353
  }
354
355
  /**
356
   * Fetch a single column as an 1-dimension array.
357
   *
358
   * @param string $column
359
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
360
   *
361
   * @return array <p>Return an empty array if the "$column" wasn't found</p>
362
   */
363 3
  public function fetchAllColumn(string $column, bool $skipNullValues = false): array
364
  {
365 3
    return $this->fetchColumn($column, $skipNullValues, true);
366
  }
367
368
  /**
369
   * Fetch all results as array with objects.
370
   *
371
   * @param object|string $class  <p>
372
   *                              <strong>string</strong>: create a new object (with optional constructor
373
   *                              parameter)<br>
374
   *                              <strong>object</strong>: use a object and fill the the data into
375
   *                              </p>
376
   * @param null|array    $params optional
377
   *                              <p>
378
   *                              An array of parameters to pass to the constructor, used if $class is a
379
   *                              string.
380
   *                              </p>
381
   *
382
   * @return array
383
   */
384 11
  public function fetchAllObject($class = '', array $params = null): array
385
  {
386
387 11
    if ($this->is_empty()) {
388 1
      return [];
389
    }
390
391
    // init
392 10
    $data = [];
393 10
    $this->reset();
394
395 10
    if ($class && \is_object($class)) {
396 7
      $propertyAccessor = PropertyAccess::createPropertyAccessor();
397
      /** @noinspection PhpAssignmentInConditionInspection */
398 7
      while ($row = \mysqli_fetch_assoc($this->_result)) {
399 7
        $classTmp = clone $class;
400 7
        $row = $this->cast($row);
401 7
        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...
402 7
          $propertyAccessor->setValue($classTmp, $key, $value);
403
        }
404 7
        $data[] = $classTmp;
405
      }
406
407 7
      return $data;
408
    }
409
410 3
    if ($class && $params) {
411
      /** @noinspection PhpAssignmentInConditionInspection */
412 1
      while ($row = \mysqli_fetch_object($this->_result, $class, $params)) {
413 1
        $data[] = $this->cast($row);
414
      }
415
416 1
      return $data;
417
    }
418
419 3 View Code Duplication
    if ($class) {
420
      /** @noinspection PhpAssignmentInConditionInspection */
421 1
      while ($row = \mysqli_fetch_object($this->_result, $class)) {
422 1
        $data[] = $this->cast($row);
423
      }
424
425 1
      return $data;
426
    }
427
428
    /** @noinspection PhpAssignmentInConditionInspection */
429 3
    while ($row = \mysqli_fetch_object($this->_result)) {
430 3
      $data[] = $this->cast($row);
431
    }
432
433 3
    return $data;
434
  }
435
436
  /**
437
   * Fetch as array.
438
   *
439
   * @param bool $reset
440
   *
441
   * @return array|false <p><strong>false</strong> on error</p>
442
   */
443 16 View Code Duplication
  public function fetchArray(bool $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...
444
  {
445 16
    if ($reset === true) {
446 1
      $this->reset();
447
    }
448
449 16
    $row = \mysqli_fetch_assoc($this->_result);
450 16
    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...
451 15
      return $this->cast($row);
452
    }
453
454 3
    if ($row === null) {
455 3
      return [];
456
    }
457
458
    return false;
459
  }
460
461
  /**
462
   * Fetch data as a key/value pair array.
463
   *
464
   * <p>
465
   *   <br />
466
   *   INFO: both "key" and "value" must exists in the fetched data
467
   *   the key will be the new key of the result-array
468
   *   <br /><br />
469
   * </p>
470
   *
471
   * e.g.:
472
   * <code>
473
   *    fetchArrayPair('some_id', 'some_value');
474
   *    // array(127 => 'some value', 128 => 'some other value')
475
   * </code>
476
   *
477
   * @param string $key
478
   * @param string $value
479
   *
480
   * @return array
481
   */
482 1
  public function fetchArrayPair(string $key, string $value): array
483
  {
484 1
    $arrayPair = [];
485 1
    $data = $this->fetchAllArray();
486
487 1
    foreach ($data as $_row) {
488
      if (
489 1
          \array_key_exists($key, $_row) === true
490
          &&
491 1
          \array_key_exists($value, $_row) === true
492
      ) {
493 1
        $_key = $_row[$key];
494 1
        $_value = $_row[$value];
495 1
        $arrayPair[$_key] = $_value;
496
      }
497
    }
498
499 1
    return $arrayPair;
500
  }
501
502
  /**
503
   * Fetch as "Arrayy"-object.
504
   *
505
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
506
   *
507
   * @return Arrayy|false <p><strong>false</strong> on error</p>
508
   */
509 2 View Code Duplication
  public function fetchArrayy(bool $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...
510
  {
511 2
    if ($reset === true) {
512
      $this->reset();
513
    }
514
515 2
    $row = \mysqli_fetch_assoc($this->_result);
516 2
    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...
517 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...
518
    }
519
520 1
    if ($row === null) {
521 1
      return Arrayy::create();
522
    }
523
524
    return false;
525
  }
526
527
  /**
528
   * Fetch a single column as string (or as 1-dimension array).
529
   *
530
   * @param string $column
531
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
532
   * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
533
   *
534
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
535
   *                      "$asArray"</p>
536
   */
537 5
  public function fetchColumn(string $column = '', bool $skipNullValues = true, bool $asArray = false)
538
  {
539 5
    if ($asArray === false) {
540 3
      $columnData = '';
541
542 3
      $data = $this->fetchAllArrayy()->reverse();
543 3 View Code Duplication
      foreach ($data as $_row) {
544
545 3
        if ($skipNullValues === true) {
546 3
          if (isset($_row[$column]) === false) {
547 3
            continue;
548
          }
549
        } else {
550 1
          if (\array_key_exists($column, $_row) === false) {
551 1
            break;
552
          }
553
        }
554
555 3
        $columnData = $_row[$column];
556 3
        break;
557
      }
558
559 3
      return $columnData;
560
    }
561
562
    // -- return as array -->
563
564 3
    $columnData = [];
565
566 3
    $data = $this->fetchAllArray();
567
568 3 View Code Duplication
    foreach ($data as $_row) {
569
570 3
      if ($skipNullValues === true) {
571 1
        if (isset($_row[$column]) === false) {
572 1
          continue;
573
        }
574
      } else {
575 3
        if (\array_key_exists($column, $_row) === false) {
576 1
          break;
577
        }
578
      }
579
580 3
      $columnData[] = $_row[$column];
581
    }
582
583 3
    return $columnData;
584
  }
585
586
  /**
587
   * Fetch as object.
588
   *
589
   * @param object|string $class  <p>
590
   *                              <strong>string</strong>: create a new object (with optional constructor
591
   *                              parameter)<br>
592
   *                              <strong>object</strong>: use a object and fill the the data into
593
   *                              </p>
594
   * @param null|array    $params optional
595
   *                              <p>
596
   *                              An array of parameters to pass to the constructor, used if $class is a
597
   *                              string.
598
   *                              </p>
599
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
600
   *
601
   * @return object|false <p><strong>false</strong> on error</p>
602
   */
603 18
  public function fetchObject($class = '', array $params = null, bool $reset = false)
604
  {
605 18
    if ($reset === true) {
606 14
      $this->reset();
607
    }
608
609 18
    if ($class && \is_object($class)) {
610 12
      $row = \mysqli_fetch_assoc($this->_result);
611 12
      $row = $row ? $this->cast($row) : false;
612
613 12
      if (!$row) {
614 3
        return false;
615
      }
616
617 10
      $propertyAccessor = PropertyAccess::createPropertyAccessor();
618 10
      foreach ($row as $key => $value) {
619 10
        $propertyAccessor->setValue($class, $key, $value);
620
      }
621
622 10
      return $class;
623
    }
624
625 7 View Code Duplication
    if ($class && $params) {
626 1
      $row = \mysqli_fetch_object($this->_result, $class, $params);
627
628 1
      return $row ? $this->cast($row) : false;
629
    }
630
631 7 View Code Duplication
    if ($class) {
632 1
      $row = \mysqli_fetch_object($this->_result, $class);
633
634 1
      return $row ? $this->cast($row) : false;
635
    }
636
637 7
    $row = \mysqli_fetch_object($this->_result);
638
639 7
    return $row ? $this->cast($row) : false;
640
  }
641
642
  /**
643
   * Fetches a row or a single column within a row. Returns null if there are
644
   * no more rows in the result.
645
   *
646
   * @param int    $row    The row number (optional)
647
   * @param string $column The column name (optional)
648
   *
649
   * @return mixed An associative array or a scalar value
650
   */
651 15
  public function fetchCallable(int $row = null, string $column = null)
652
  {
653 15
    if (!$this->num_rows) {
654 2
      return null;
655
    }
656
657 13
    if (null !== $row) {
658 12
      $this->seek($row);
659
    }
660
661 13
    $rows = \mysqli_fetch_assoc($this->_result);
662
663 13
    if ($column) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $column 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 5
      return \is_array($rows) && isset($rows[$column]) ? $rows[$column] : null;
665
    }
666
667 12
    return \is_callable($this->_mapper) ? \call_user_func($this->_mapper, $rows) : $rows;
668
  }
669
670
  /**
671
   * Return rows of field information in a result set. This function is a
672
   * basically a wrapper on the native mysqli_fetch_fields function.
673
   *
674
   * @param bool $as_array Return each field info as array; defaults to false
675
   *
676
   * @return array Array of field information each as an associative array
677
   */
678 1
  public function fetchFields(bool $as_array = false): array
679
  {
680 1
    if ($as_array) {
681 1
      return \array_map(
682 1
          function ($object) {
683 1
            return (array)$object;
684 1
          },
685 1
          \mysqli_fetch_fields($this->_result)
686
      );
687
    }
688
689
    return \mysqli_fetch_fields($this->_result);
690
  }
691
692
  /**
693
   * Returns all rows at once as a grouped array of scalar values or arrays.
694
   *
695
   * @param string $group  The column name to use for grouping
696
   * @param string $column The column name to use as values (optional)
697
   *
698
   * @return array A grouped array of scalar values or arrays
699
   */
700 1 View Code Duplication
  public function fetchGroups(string $group, string $column = null): array
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...
701
  {
702
    // init
703 1
    $groups = [];
704 1
    $pos = $this->current_row;
705
706 1
    foreach ($this as $row) {
707
708 1
      if (!\array_key_exists($group, $row)) {
709
        continue;
710
      }
711
712 1
      if (null !== $column) {
713
714 1
        if (!\array_key_exists($column, $row)) {
715
          continue;
716
        }
717
718 1
        $groups[$row[$group]][] = $row[$column];
719
      } else {
720 1
        $groups[$row[$group]][] = $row;
721
      }
722
    }
723
724 1
    $this->rewind($pos);
725
726 1
    return $groups;
727
  }
728
729
  /**
730
   * Returns all rows at once as key-value pairs.
731
   *
732
   * @param string $key    The column name to use as keys
733
   * @param string $column The column name to use as values (optional)
734
   *
735
   * @return array An array of key-value pairs
736
   */
737 1 View Code Duplication
  public function fetchPairs(string $key, string $column = null): array
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...
738
  {
739
    // init
740 1
    $pairs = [];
741 1
    $pos = $this->current_row;
742
743 1
    foreach ($this as $row) {
744
745 1
      if (!\array_key_exists($key, $row)) {
746
        continue;
747
      }
748
749 1
      if (null !== $column) {
750
751 1
        if (!\array_key_exists($column, $row)) {
752
          continue;
753
        }
754
755 1
        $pairs[$row[$key]] = $row[$column];
756
      } else {
757 1
        $pairs[$row[$key]] = $row;
758
      }
759
    }
760
761 1
    $this->rewind($pos);
762
763 1
    return $pairs;
764
  }
765
766
  /**
767
   * Returns all rows at once, transposed as an array of arrays. Instead of
768
   * returning rows of columns, this method returns columns of rows.
769
   *
770
   * @param string $column The column name to use as keys (optional)
771
   *
772
   * @return mixed A transposed array of arrays
773
   */
774 1
  public function fetchTranspose(string $column = null)
775
  {
776
    // init
777 1
    $keys = null !== $column ? $this->fetchAllColumn($column) : [];
778 1
    $rows = [];
779 1
    $pos = $this->current_row;
780
781 1
    foreach ($this as $row) {
782 1
      foreach ($row as $key => $value) {
783 1
        $rows[$key][] = $value;
784
      }
785
    }
786
787 1
    $this->rewind($pos);
788
789 1
    if (empty($keys)) {
790 1
      return $rows;
791
    }
792
793 1
    return \array_map(
794 1
        function ($values) use ($keys) {
795 1
          return \array_combine($keys, $values);
796 1
        }, $rows
797
    );
798
  }
799
800
  /**
801
   * Returns the first row element from the result.
802
   *
803
   * @param string $column The column name to use as value (optional)
804
   *
805
   * @return mixed A row array or a single scalar value
806
   */
807 3
  public function first(string $column = null)
808
  {
809 3
    $pos = $this->current_row;
810 3
    $first = $this->fetchCallable(0, $column);
811 3
    $this->rewind($pos);
812
813 3
    return $first;
814
  }
815
816
  /**
817
   * free the memory
818
   */
819 63
  public function free()
820
  {
821 63
    if (!empty($this->_result)) {
822
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
823 63
      @\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...
824 63
      $this->_result = null;
825
826 63
      return true;
827
    }
828
829 1
    return false;
830
  }
831
832
  /**
833
   * alias for "Result->fetch()"
834
   *
835
   * @see Result::fetch()
836
   *
837
   * @return array|object|false <p><strong>false</strong> on error</p>
838
   */
839 1
  public function get()
840
  {
841 1
    return $this->fetch();
842
  }
843
844
  /**
845
   * alias for "Result->fetchAll()"
846
   *
847
   * @see Result::fetchAll()
848
   *
849
   * @return array
850
   */
851 1
  public function getAll(): array
852
  {
853 1
    return $this->fetchAll();
854
  }
855
856
  /**
857
   * alias for "Result->fetchAllColumn()"
858
   *
859
   * @see Result::fetchAllColumn()
860
   *
861
   * @param string $column
862
   * @param bool   $skipNullValues
863
   *
864
   * @return array
865
   */
866
  public function getAllColumn(string $column, bool $skipNullValues = false): array
867
  {
868
    return $this->fetchAllColumn($column, $skipNullValues);
869
  }
870
871
  /**
872
   * alias for "Result->fetchAllArray()"
873
   *
874
   * @see Result::fetchAllArray()
875
   *
876
   * @return array
877
   */
878 1
  public function getArray(): array
879
  {
880 1
    return $this->fetchAllArray();
881
  }
882
883
  /**
884
   * alias for "Result->fetchAllArrayy()"
885
   *
886
   * @see Result::fetchAllArrayy()
887
   *
888
   * @return Arrayy
889
   */
890
  public function getArrayy(): \Arrayy\Arrayy
891
  {
892
    return $this->fetchAllArrayy();
893
  }
894
895
  /**
896
   * alias for "Result->fetchColumn()"
897
   *
898
   * @see Result::fetchColumn()
899
   *
900
   * @param string $column
901
   * @param bool   $asArray
902
   * @param bool   $skipNullValues
903
   *
904
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
905
   *                      "$asArray"</p>
906
   */
907 1
  public function getColumn(string $column, bool $skipNullValues = true, bool $asArray = false)
908
  {
909 1
    return $this->fetchColumn($column, $skipNullValues, $asArray);
910
  }
911
912
  /**
913
   * @return string
914
   */
915 1
  public function getDefaultResultType(): string
916
  {
917 1
    return $this->_default_result_type;
918
  }
919
920
  /**
921
   * alias for "Result->fetchAllObject()"
922
   *
923
   * @see Result::fetchAllObject()
924
   *
925
   * @return array of mysql-objects
926
   */
927 1
  public function getObject(): array
928
  {
929 1
    return $this->fetchAllObject();
930
  }
931
932
  /**
933
   * Check if the result is empty.
934
   *
935
   * @return bool
936
   */
937 34
  public function is_empty(): bool
938
  {
939 34
    return !($this->num_rows > 0);
940
  }
941
942
  /**
943
   * Fetch all results as "json"-string.
944
   *
945
   * @return string
946
   */
947 1
  public function json(): string
948
  {
949 1
    $data = $this->fetchAllArray();
950
951 1
    return UTF8::json_encode($data);
952
  }
953
954
  /**
955
   * Returns the last row element from the result.
956
   *
957
   * @param string $column The column name to use as value (optional)
958
   *
959
   * @return mixed A row array or a single scalar value
960
   */
961 3
  public function last(string $column = null)
962
  {
963 3
    $pos = $this->current_row;
964 3
    $last = $this->fetchCallable($this->num_rows - 1, $column);
965 3
    $this->rewind($pos);
966
967 3
    return $last;
968
  }
969
970
  /**
971
   * Set the mapper...
972
   *
973
   * @param \Closure $callable
974
   *
975
   * @return $this
976
   */
977 1
  public function map(\Closure $callable)
978
  {
979 1
    $this->_mapper = $callable;
980
981 1
    return $this;
982
  }
983
984
  /**
985
   * Alias of count(). Deprecated.
986
   *
987
   * @return int The number of rows in the result
988
   */
989 1
  public function num_rows(): int
990
  {
991 1
    return $this->count();
992
  }
993
994
  /**
995
   * ArrayAccess interface implementation.
996
   *
997
   * @param int $offset Offset number
998
   *
999
   * @return bool Boolean true if offset exists, false otherwise
1000
   */
1001 1
  public function offsetExists($offset): bool
1002
  {
1003 1
    return \is_int($offset) && $offset >= 0 && $offset < $this->num_rows;
1004
  }
1005
1006
  /**
1007
   * ArrayAccess interface implementation.
1008
   *
1009
   * @param int $offset Offset number
1010
   *
1011
   * @return mixed
1012
   */
1013 1
  public function offsetGet($offset)
1014
  {
1015 1
    if ($this->offsetExists($offset)) {
1016 1
      return $this->fetchCallable($offset);
1017
    }
1018
1019
    throw new \OutOfBoundsException("undefined offset ($offset)");
1020
  }
1021
1022
  /**
1023
   * ArrayAccess interface implementation. Not implemented by design.
1024
   *
1025
   * @param mixed $offset
1026
   * @param mixed $value
1027
   */
1028
  public function offsetSet($offset, $value)
1029
  {
1030
    /** @noinspection UselessReturnInspection */
1031
    return;
1032
  }
1033
1034
  /**
1035
   * ArrayAccess interface implementation. Not implemented by design.
1036
   *
1037
   * @param mixed $offset
1038
   */
1039
  public function offsetUnset($offset)
1040
  {
1041
    /** @noinspection UselessReturnInspection */
1042
    return;
1043
  }
1044
1045
  /**
1046
   * Reset the offset (data_seek) for the results.
1047
   *
1048
   * @return Result
1049
   */
1050 32
  public function reset(): self
1051
  {
1052 32
    if (!$this->is_empty()) {
1053 30
      \mysqli_data_seek($this->_result, 0);
1054
    }
1055
1056 32
    return $this;
1057
  }
1058
1059
  /**
1060
   * You can set the default result-type to 'object', 'array' or 'Arrayy'.
1061
   *
1062
   * INFO: used for "fetch()" and "fetchAll()"
1063
   *
1064
   * @param string $default_result_type
1065
   */
1066 2
  public function setDefaultResultType(string $default_result_type = 'object')
1067
  {
1068
    if (
1069 2
        $default_result_type === 'object'
1070
        ||
1071 2
        $default_result_type === 'array'
1072
        ||
1073 2
        $default_result_type === 'Arrayy'
1074
    ) {
1075 2
      $this->_default_result_type = $default_result_type;
1076
    }
1077 2
  }
1078
1079
  /**
1080
   * @param int      $offset
1081
   * @param null|int $length
1082
   * @param bool     $preserve_keys
1083
   *
1084
   * @return array
1085
   */
1086 1
  public function slice(int $offset = 0, int $length = null, bool $preserve_keys = false): array
1087
  {
1088
    // init
1089 1
    $slice = [];
1090
1091 1
    if ($offset < 0) {
1092 1
      if (\abs($offset) > $this->num_rows) {
1093 1
        $offset = 0;
1094
      } else {
1095 1
        $offset = $this->num_rows - (int)\abs($offset);
1096
      }
1097
    }
1098
1099 1
    $length = null !== $length ? (int)$length : $this->num_rows;
1100 1
    $n = 0;
1101 1
    for ($i = $offset; $i < $this->num_rows && $n < $length; $i++) {
1102 1
      if ($preserve_keys) {
1103 1
        $slice[$i] = $this->fetchCallable($i);
1104
      } else {
1105 1
        $slice[] = $this->fetchCallable($i);
1106
      }
1107 1
      ++$n;
1108
    }
1109
1110 1
    return $slice;
1111
  }
1112
}
1113