Completed
Push — master ( 3efe43...a44fa5 )
by Lars
11:28
created

Result::valid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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
  const MYSQL_TYPE_BIT         = 16;
20
  const MYSQL_TYPE_BLOB        = 252;
21
  const MYSQL_TYPE_DATE        = 10;
22
  const MYSQL_TYPE_DATETIME    = 12;
23
  const MYSQL_TYPE_DECIMAL     = 0;
24
  const MYSQL_TYPE_DOUBLE      = 5;
25
  const MYSQL_TYPE_ENUM        = 247;
26
  const MYSQL_TYPE_FLOAT       = 4;
27
  const MYSQL_TYPE_GEOMETRY    = 255;
28
  const MYSQL_TYPE_INT24       = 9;
29
  const MYSQL_TYPE_JSON        = 245;
30
  const MYSQL_TYPE_LONG        = 3;
31
  const MYSQL_TYPE_LONGLONG    = 8;
32
  const MYSQL_TYPE_LONG_BLOB   = 251;
33
  const MYSQL_TYPE_MEDIUM_BLOB = 250;
34
  const MYSQL_TYPE_NEWDATE     = 14;
35
  const MYSQL_TYPE_NEWDECIMAL  = 246;
36
  const MYSQL_TYPE_NULL        = 6;
37
  const MYSQL_TYPE_SET         = 248;
38
  const MYSQL_TYPE_SHORT       = 2;
39
  const MYSQL_TYPE_STRING      = 254;
40
  const MYSQL_TYPE_TIME        = 11;
41
  const MYSQL_TYPE_TIMESTAMP   = 7;
42
  const MYSQL_TYPE_TINY        = 1;
43
  const MYSQL_TYPE_TINY_BLOB   = 249;
44
  const MYSQL_TYPE_VARCHAR     = 15;
45
  const MYSQL_TYPE_VAR_STRING  = 253;
46
  const MYSQL_TYPE_YEAR        = 13;
47
48
  const RESULT_TYPE_ARRAY  = 'array';
49
  const RESULT_TYPE_ARRAYY = 'Arrayy';
50
  const RESULT_TYPE_OBJECT = 'object';
51
  const RESULT_TYPE_YIELD  = 'yield';
52
53
  /**
54
   * @var int
55
   */
56
  public $num_rows;
57
58
  /**
59
   * @var string
60
   */
61
  public $sql;
62
63
  /**
64
   * @var \mysqli_result|\Doctrine\DBAL\Statement
65
   */
66
  private $_result;
67
68
  /**
69
   * @var int
70
   */
71
  private $current_row;
72
73
  /**
74
   * @var \Closure|null
75
   */
76
  private $_mapper;
77
78
  /**
79
   * @var string
80
   */
81
  private $_default_result_type = self::RESULT_TYPE_OBJECT;
82
83
  /**
84
   * @var \mysqli_stmt|null
85
   */
86
  private $doctrineMySQLiStmt;
87
88
  /**
89
   * @var \Doctrine\DBAL\Driver\PDOStatement|null
90
   */
91
  private $doctrinePdoStmt;
92
93
  /**
94
   * @var int
95
   */
96
  private $doctrinePdoStmtDataSeekFake = 0;
97
98
  /**
99
   * @var int
100
   */
101
  private $doctrinePdoStmtDataSeekInit = false;
102
103
  /**
104
   * @var array
105
   */
106
  private $doctrinePdoStmtDataSeekFakeCache = [];
107
108
  /**
109
   * Result constructor.
110
   *
111
   * @param string                                  $sql
112
   * @param \mysqli_result|\Doctrine\DBAL\Statement $result
113
   * @param \Closure                                $mapper Optional callback mapper for the "fetchCallable()" method
114
   */
115 118
  public function __construct(string $sql, $result, \Closure $mapper = null)
116
  {
117 118
    $this->sql = $sql;
118
119
    if (
120 118
        !$result instanceof \mysqli_result
121
        &&
122 118
        !$result instanceof \Doctrine\DBAL\Statement
123
    ) {
124
      throw new \InvalidArgumentException('$result must be ' . \mysqli_result::class . ' or ' . \Doctrine\DBAL\Statement::class . ' !');
125
    }
126
127 118
    $this->_result = $result;
128
129 118
    if ($this->_result instanceof \Doctrine\DBAL\Statement) {
130
131
      $doctrineDriver = $this->_result->getWrappedStatement();
132
133
      if (
134
          $doctrineDriver
135
          &&
136
          $doctrineDriver instanceof \Doctrine\DBAL\Driver\PDOStatement
137
      ) {
138
        $this->doctrinePdoStmt = $doctrineDriver;
139
      }
140
141
      if (
142
          $doctrineDriver
143
          &&
144
          $doctrineDriver instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliStatement
145
      ) {
146
        // try to get the mysqli driver from doctrine
147
        $reflectionTmp = new \ReflectionClass($doctrineDriver);
148
        $propertyTmp = $reflectionTmp->getProperty('_stmt');
149
        $propertyTmp->setAccessible(true);
150
        $this->doctrineMySQLiStmt = $propertyTmp->getValue($doctrineDriver);
151
      }
152
153
      $this->num_rows = $this->_result->rowCount();
154
    } else {
155 118
      $this->num_rows = (int)$this->_result->num_rows;
156
    }
157
158 118
    $this->current_row = 0;
159
160
161 118
    $this->_mapper = $mapper;
162 118
  }
163
164
  /**
165
   * __destruct
166
   */
167 117
  public function __destruct()
168
  {
169 117
    $this->free();
170 117
  }
171
172
  /**
173
   * Runs a user-provided callback with the MySQLi_Result object given as
174
   * argument and returns the result, or returns the MySQLi_Result object if
175
   * called without an argument.
176
   *
177
   * @param callable $callback User-provided callback (optional)
178
   *
179
   * @return mixed|\Doctrine\DBAL\Statement|\mysqli_result
180
   */
181 2
  public function __invoke(callable $callback = null)
182
  {
183 2
    if (null !== $callback) {
184 2
      return $callback($this->_result);
185
    }
186
187 1
    return $this->_result;
188
  }
189
190
  /**
191
   * Get the current "num_rows" as string.
192
   *
193
   * @return string
194
   */
195
  public function __toString()
196
  {
197
    return (string)$this->num_rows;
198
  }
199
200
  /**
201
   * Cast data into int, float or string.
202
   *
203
   * <p>
204
   *   <br />
205
   *   INFO: install / use "mysqlnd"-driver for better performance
206
   * </p>
207
   *
208
   * @param array|object $data
209
   *
210
   * @return array|object|false <p><strong>false</strong> on error</p>
211
   */
212 82
  private function cast(&$data)
213
  {
214
    if (
215 82
        !$this->doctrinePdoStmt // pdo only have limited support for types, so we try to improve it
216
        &&
217 82
        Helper::isMysqlndIsUsed() === true
218
    ) {
219 82
      return $data;
220
    }
221
222
    // init
223
    static $FIELDS_CACHE = [];
224
    static $TYPES_CACHE = [];
225
226
    $result_hash = \spl_object_hash($this->_result);
227
228
    if (!isset($FIELDS_CACHE[$result_hash])) {
229
      $FIELDS_CACHE[$result_hash] = $this->fetch_fields();
230
    }
231
232
    if (
233
        !isset($FIELDS_CACHE[$result_hash])
234
        ||
235
        $FIELDS_CACHE[$result_hash] === false
236
    ) {
237
      return false;
238
    }
239
240
    if (!isset($TYPES_CACHE[$result_hash])) {
241
      foreach ($FIELDS_CACHE[$result_hash] as $field) {
242
        switch ($field->type) {
243
          case self::MYSQL_TYPE_BIT:
244
            $TYPES_CACHE[$result_hash][$field->name] = 'boolean';
245
            break;
246
          case self::MYSQL_TYPE_TINY:
247
          case self::MYSQL_TYPE_SHORT:
248
          case self::MYSQL_TYPE_LONG:
249
          case self::MYSQL_TYPE_LONGLONG:
250
          case self::MYSQL_TYPE_INT24:
251
            $TYPES_CACHE[$result_hash][$field->name] = 'integer';
252
            break;
253
          case self::MYSQL_TYPE_DOUBLE:
254
          case self::MYSQL_TYPE_DECIMAL:
255
          case self::MYSQL_TYPE_NEWDECIMAL:
256
          case self::MYSQL_TYPE_FLOAT:
257
            $TYPES_CACHE[$result_hash][$field->name] = 'float';
258
            break;
259
          default:
260
            $TYPES_CACHE[$result_hash][$field->name] = 'string';
261
            break;
262
        }
263
      }
264
    }
265
266
    if (\is_array($data) === true) {
267 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
268
        if (isset($data[$type_name])) {
269
          \settype($data[$type_name], $type);
270
        }
271
      }
272
    } elseif (\is_object($data)) {
273 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
274
        if (isset($data->{$type_name})) {
275
          \settype($data->{$type_name}, $type);
276
        }
277
      }
278
    }
279
280
    return $data;
281
  }
282
283
  /**
284
   * Countable interface implementation.
285
   *
286
   * @return int The number of rows in the result
287
   */
288 2
  public function count(): int
289
  {
290 2
    return $this->num_rows;
291
  }
292
293
  /**
294
   * Iterator interface implementation.
295
   *
296
   * @return mixed The current element
297
   */
298 8
  public function current()
299
  {
300 8
    return $this->fetchCallable($this->current_row);
301
  }
302
303
  /**
304
   * Iterator interface implementation.
305
   *
306
   * @return int The current element key (row index; zero-based)
307
   */
308 1
  public function key(): int
309
  {
310 1
    return $this->current_row;
311
  }
312
313
  /**
314
   * Iterator interface implementation.
315
   *
316
   * @return void
317
   */
318 8
  public function next()
319
  {
320 8
    $this->current_row++;
321 8
  }
322
323
  /**
324
   * Iterator interface implementation.
325
   *
326
   * @param int $row Row position to rewind to; defaults to 0
327
   *
328
   * @return void
329
   */
330 12
  public function rewind($row = 0)
331
  {
332 12
    if ($this->seek($row)) {
333 10
      $this->current_row = $row;
334
    }
335 12
  }
336
337
  /**
338
   * Moves the internal pointer to the specified row position.
339
   *
340
   * @param int $row <p>Row position; zero-based and set to 0 by default</p>
341
   *
342
   * @return bool <p>true on success, false otherwise</p>
343
   */
344 20
  public function seek($row = 0): bool
345
  {
346 20
    if (\is_int($row) && $row >= 0 && $row < $this->num_rows) {
347
348
      if (
349 16
          $this->doctrineMySQLiStmt
350
          &&
351 16
          $this->doctrineMySQLiStmt instanceof \mysqli_stmt
352
      ) {
353
        $this->doctrineMySQLiStmt->data_seek($row);
354
355
        return true;
356
      }
357
358
      if (
359 16
          $this->doctrinePdoStmt
360
          &&
361 16
          $this->doctrinePdoStmt instanceof \Doctrine\DBAL\Driver\PDOStatement
362
      ) {
363
        return true;
364
      }
365
366 16
      return \mysqli_data_seek($this->_result, $row);
367
    }
368
369 4
    return false;
370
  }
371
372
  /**
373
   * Iterator interface implementation.
374
   *
375
   * @return bool <p>true if the current index is valid, false otherwise</p>
376
   */
377 8
  public function valid(): bool
378
  {
379 8
    return $this->current_row < $this->num_rows;
380
  }
381
382
  /**
383
   * Fetch.
384
   *
385
   * <p>
386
   *   <br />
387
   *   INFO: this will return an object by default, not an array<br />
388
   *   and you can change the behaviour via "Result->setDefaultResultType()"
389
   * </p>
390
   *
391
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
392
   *
393
   * @return array|object|false <p><strong>false</strong> on error</p>
394
   */
395 6
  public function fetch(bool $reset = false)
396
  {
397 6
    $return = false;
398
399 6
    if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
400 6
      $return = $this->fetchObject('', null, $reset);
401 6
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
402 6
      $return = $this->fetchArray($reset);
403
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
404
      $return = $this->fetchArrayy($reset);
405
    } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
406
      $return = $this->fetchYield($reset);
0 ignored issues
show
Documentation introduced by
$reset is of type boolean, but the function expects a string|object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
407
    }
408
409 6
    return $return;
410
  }
411
412
  /**
413
   * Fetch all results.
414
   *
415
   * <p>
416
   *   <br />
417
   *   INFO: this will return an object by default, not an array<br />
418
   *   and you can change the behaviour via "Result->setDefaultResultType()"
419
   * </p>
420
   *
421
   * @return array
422
   */
423 6
  public function fetchAll(): array
424
  {
425 6
    $return = [];
426
427 6
    if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
428 6
      $return = $this->fetchAllObject();
429 3
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
430 3
      $return = $this->fetchAllArray();
431
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
432
      $return = $this->fetchAllArrayy();
433
    } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
434
      $return = $this->fetchAllYield();
435
    }
436
437 6
    return $return;
438
  }
439
440
  /**
441
   * Fetch all results as array.
442
   *
443
   * @return array
444
   */
445 28
  public function fetchAllArray(): array
446
  {
447 28
    if ($this->is_empty()) {
448
      return [];
449
    }
450
451 28
    $this->reset();
452
453 28
    $data = [];
454
    /** @noinspection PhpAssignmentInConditionInspection */
455 28
    while ($row = $this->fetch_assoc()) {
456 28
      $data[] = $this->cast($row);
457
    }
458
459 28
    return $data;
460
  }
461
462
  /**
463
   * Fetch all results as "Arrayy"-object.
464
   *
465
   * @return Arrayy
466
   */
467 10
  public function fetchAllArrayy(): Arrayy
468
  {
469 10
    if ($this->is_empty()) {
470
      return Arrayy::create([]);
471
    }
472
473 10
    $this->reset();
474
475 10
    $data = [];
476
    /** @noinspection PhpAssignmentInConditionInspection */
477 10
    while ($row = $this->fetch_assoc()) {
478 10
      $data[] = $this->cast($row);
479
    }
480
481 10
    return Arrayy::create($data);
482
  }
483
484
  /**
485
   * Fetch a single column as an 1-dimension array.
486
   *
487
   * @param string $column
488
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
489
   *
490
   * @return array <p>Return an empty array if the "$column" wasn't found</p>
491
   */
492 5
  public function fetchAllColumn(string $column, bool $skipNullValues = false): array
493
  {
494 5
    return $this->fetchColumn($column, $skipNullValues, true);
495
  }
496
497
  /**
498
   * Fetch all results as array with objects.
499
   *
500
   * @param object|string $class  <p>
501
   *                              <strong>string</strong>: create a new object (with optional constructor
502
   *                              parameter)<br>
503
   *                              <strong>object</strong>: use a object and fill the the data into
504
   *                              </p>
505
   * @param null|array    $params optional
506
   *                              <p>
507
   *                              An array of parameters to pass to the constructor, used if $class is a
508
   *                              string.
509
   *                              </p>
510
   *
511
   * @return array
512
   */
513 17
  public function fetchAllObject($class = '', array $params = null): array
514
  {
515 17
    if ($this->is_empty()) {
516 1
      return [];
517
    }
518
519
    // fallback
520 16
    if (!$class || $class === 'stdClass') {
521
      /** @noinspection ClassConstantCanBeUsedInspection */
522 9
      $class = '\stdClass';
523
    }
524
525
    // init
526 16
    $data = [];
527 16
    $this->reset();
528
529 16
    if (\is_object($class)) {
530
531 7
      $classTmpOrig = new $class;
532
533 9
    } elseif ($class && $params) {
534
535 3
      $reflectorTmp = new \ReflectionClass($class);
536 3
      $classTmpOrig = $reflectorTmp->newInstanceArgs($params);
537
538
    } else {
539
540 9
      $classTmpOrig = new $class;
541
542
    }
543
544 16
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
545
    /** @noinspection PhpAssignmentInConditionInspection */
546 16 View Code Duplication
    while ($row = $this->fetch_assoc()) {
547 16
      $classTmp = clone $classTmpOrig;
548 16
      $row = $this->cast($row);
549 16
      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...
550 16
        if ($class === '\stdClass') {
551 9
          $classTmp->{$key} = $value;
552
        } else {
553 16
          $propertyAccessor->setValue($classTmp, $key, $value);
554
        }
555
      }
556 16
      $data[] = $classTmp;
557
    }
558
559 16
    return $data;
560
  }
561
562
  /**
563
   * Fetch all results as "\Generator" via yield.
564
   *
565
   * @param object|string $class  <p>
566
   *                              <strong>string</strong>: create a new object (with optional constructor
567
   *                              parameter)<br>
568
   *                              <strong>object</strong>: use a object and fill the the data into
569
   *                              </p>
570
   * @param null|array    $params optional
571
   *                              <p>
572
   *                              An array of parameters to pass to the constructor, used if $class is a
573
   *                              string.
574
   *                              </p>
575
   *
576
   * @return \Generator
577
   */
578 8
  public function fetchAllYield($class = '', array $params = null): \Generator
579
  {
580 8
    if ($this->is_empty()) {
581
      return;
582
    }
583
584
    // init
585 8
    $this->reset();
586
587
    // fallback
588 8
    if (!$class || $class === 'stdClass') {
589
      /** @noinspection ClassConstantCanBeUsedInspection */
590 8
      $class = '\stdClass';
591
    }
592
593 8
    if (\is_object($class)) {
594
595
      $classTmpOrig = $class;
596
597 8
    } elseif ($class && $params) {
598
599
      $reflectorTmp = new \ReflectionClass($class);
600
      $classTmpOrig = $reflectorTmp->newInstanceArgs($params);
601
602
    } else {
603
604 8
      $classTmpOrig = new $class;
605
606
    }
607
608 8
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
609
    /** @noinspection PhpAssignmentInConditionInspection */
610 8 View Code Duplication
    while ($row = $this->fetch_assoc()) {
611 8
      $classTmp = clone $classTmpOrig;
612 8
      $row = $this->cast($row);
613 8
      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...
614 8
        if ($class === '\stdClass') {
615 8
          $classTmp->{$key} = $value;
616
        } else {
617 8
          $propertyAccessor->setValue($classTmp, $key, $value);
618
        }
619
      }
620
621 8
      yield $classTmp;
622
    }
623 8
  }
624
625
  /**
626
   * Fetch as array.
627
   *
628
   * @param bool $reset
629
   *
630
   * @return array|false <p><strong>false</strong> on error</p>
631
   */
632 30 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...
633
  {
634 30
    if ($reset === true) {
635 3
      $this->reset();
636
    }
637
638 30
    $row = $this->fetch_assoc();
639 30
    if ($row) {
640 27
      return $this->cast($row);
641
    }
642
643 5
    if ($row === null || $row === false) {
644 5
      return [];
645
    }
646
647
    return false;
648
  }
649
650
  /**
651
   * Fetch data as a key/value pair array.
652
   *
653
   * <p>
654
   *   <br />
655
   *   INFO: both "key" and "value" must exists in the fetched data
656
   *   the key will be the new key of the result-array
657
   *   <br /><br />
658
   * </p>
659
   *
660
   * e.g.:
661
   * <code>
662
   *    fetchArrayPair('some_id', 'some_value');
663
   *    // array(127 => 'some value', 128 => 'some other value')
664
   * </code>
665
   *
666
   * @param string $key
667
   * @param string $value
668
   *
669
   * @return array
670
   */
671 3
  public function fetchArrayPair(string $key, string $value): array
672
  {
673 3
    $arrayPair = [];
674 3
    $data = $this->fetchAllArray();
675
676 3
    foreach ($data as &$_row) {
677
      if (
678 3
          \array_key_exists($key, $_row) === true
679
          &&
680 3
          \array_key_exists($value, $_row) === true
681
      ) {
682 3
        $_key = $_row[$key];
683 3
        $_value = $_row[$value];
684 3
        $arrayPair[$_key] = $_value;
685
      }
686
    }
687
688 3
    return $arrayPair;
689
  }
690
691
  /**
692
   * Fetch as "Arrayy"-object.
693
   *
694
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
695
   *
696
   * @return Arrayy|false <p><strong>false</strong> on error</p>
697
   */
698 6 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...
699
  {
700 6
    if ($reset === true) {
701
      $this->reset();
702
    }
703
704 6
    $row = $this->fetch_assoc();
705 6
    if ($row) {
706 3
      return Arrayy::create($this->cast($row));
707
    }
708
709 3
    if ($row === null || $row === false) {
710 3
      return Arrayy::create();
711
    }
712
713
    return false;
714
  }
715
716
  /**
717
   * Fetches a row or a single column within a row. Returns null if there are
718
   * no more rows in the result.
719
   *
720
   * @param int    $row    The row number (optional)
721
   * @param string $column The column name (optional)
722
   *
723
   * @return mixed An associative array or a scalar value
724
   */
725 17
  public function fetchCallable(int $row = null, string $column = null)
726
  {
727 17
    if (!$this->num_rows) {
728 2
      return null;
729
    }
730
731 15
    if ($row !== null) {
732 14
      $this->seek($row);
733
    }
734
735 15
    $rows = $this->fetch_assoc();
736
737 15
    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...
738
      if (
739 5
          \is_array($rows)
740
          &&
741 5
          isset($rows[$column])
742
      ) {
743 5
        return $rows[$column];
744
      }
745
746
      return null;
747
    }
748
749 14
    if (\is_callable($this->_mapper)) {
750 1
      return \call_user_func($this->_mapper, $rows);
751
    }
752
753 14
    return $rows;
754
  }
755
756
  /**
757
   * Fetch a single column as string (or as 1-dimension array).
758
   *
759
   * @param string $column
760
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
761
   * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
762
   *
763
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
764
   *                      "$asArray"</p>
765
   */
766 9
  public function fetchColumn(string $column = '', bool $skipNullValues = true, bool $asArray = false)
767
  {
768 9
    if ($asArray === false) {
769 7
      $columnData = '';
770
771 7
      $data = $this->fetchAllArrayy()->reverse();
772 7 View Code Duplication
      foreach ($data as $_row) {
773
774 7
        if ($skipNullValues === true) {
775 7
          if (isset($_row[$column]) === false) {
776 7
            continue;
777
          }
778 3
        } elseif (\array_key_exists($column, $_row) === false) {
779 3
          break;
780
        }
781
782 7
        $columnData = $_row[$column];
783 7
        break;
784
      }
785
786 7
      return $columnData;
787
    }
788
789
    // -- return as array -->
790
791 5
    $columnData = [];
792
793 5 View Code Duplication
    foreach ($this->fetchAllYield() as $_row) {
794
795 5
      if ($skipNullValues === true) {
796 3
        if (isset($_row->{$column}) === false) {
797 3
          continue;
798
        }
799 5
      } elseif (\array_key_exists($column, $_row) === false) {
800 3
        break;
801
      }
802
803 5
      $columnData[] = $_row->{$column};
804
    }
805
806 5
    return $columnData;
807
  }
808
809
  /**
810
   * Return rows of field information in a result set.
811
   *
812
   * @param bool $as_array Return each field info as array; defaults to false
813
   *
814
   * @return array Array of field information each as an associative array
815
   */
816 1
  public function fetchFields(bool $as_array = false): array
817
  {
818 1
    if ($as_array) {
819 1
      return \array_map(
820 1
          function ($object) {
821 1
            return (array)$object;
822 1
          },
823 1
          $this->fetch_fields()
824
      );
825
    }
826
827
    return $this->fetch_fields();
828
  }
829
830
  /**
831
   * Returns all rows at once as a grouped array of scalar values or arrays.
832
   *
833
   * @param string $group  The column name to use for grouping
834
   * @param string $column The column name to use as values (optional)
835
   *
836
   * @return array A grouped array of scalar values or arrays
837
   */
838 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...
839
  {
840
    // init
841 1
    $groups = [];
842 1
    $pos = $this->current_row;
843
844 1
    foreach ($this as $row) {
845
846 1
      if (!\array_key_exists($group, $row)) {
847
        continue;
848
      }
849
850 1
      if (null !== $column) {
851
852 1
        if (!\array_key_exists($column, $row)) {
853
          continue;
854
        }
855
856 1
        $groups[$row[$group]][] = $row[$column];
857
      } else {
858 1
        $groups[$row[$group]][] = $row;
859
      }
860
    }
861
862 1
    $this->rewind($pos);
863
864 1
    return $groups;
865
  }
866
867
  /**
868
   * Fetch as object.
869
   *
870
   * @param object|string $class  <p>
871
   *                              <strong>string</strong>: create a new object (with optional constructor
872
   *                              parameter)<br>
873
   *                              <strong>object</strong>: use a object and fill the the data into
874
   *                              </p>
875
   * @param null|array    $params optional
876
   *                              <p>
877
   *                              An array of parameters to pass to the constructor, used if $class is a
878
   *                              string.
879
   *                              </p>
880
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
881
   *
882
   * @return object|false <p><strong>false</strong> on error</p>
883
   */
884 32 View Code Duplication
  public function fetchObject($class = '', array $params = null, 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...
885
  {
886 32
    if ($reset === true) {
887 20
      $this->reset();
888
    }
889
890
    // fallback
891 32
    if (!$class || $class === 'stdClass') {
892
      /** @noinspection ClassConstantCanBeUsedInspection */
893 21
      $class = '\stdClass';
894
    }
895
896 32
    $row = $this->fetch_assoc();
897 32
    $row = $row ? $this->cast($row) : false;
898
899 32
    if (!$row) {
900 6
      return false;
901
    }
902
903 30
    if (\is_object($class)) {
904
905 12
      $classTmp = $class;
906
907 21
    } elseif ($class && $params) {
908
909 3
      $reflectorTmp = new \ReflectionClass($class);
910 3
      $classTmp = $reflectorTmp->newInstanceArgs($params);
911
912
    } else {
913
914 21
      $classTmp = new $class;
915
916
    }
917
918 30
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
919 30
    foreach ($row as $key => $value) {
920 30
      if ($class === '\stdClass') {
921 21
        $classTmp->{$key} = $value;
922
      } else {
923 30
        $propertyAccessor->setValue($classTmp, $key, $value);
924
      }
925
    }
926
927 30
    return $classTmp;
928
  }
929
930
  /**
931
   * Returns all rows at once as key-value pairs.
932
   *
933
   * @param string $key    The column name to use as keys
934
   * @param string $column The column name to use as values (optional)
935
   *
936
   * @return array An array of key-value pairs
937
   */
938 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...
939
  {
940
    // init
941 1
    $pairs = [];
942 1
    $pos = $this->current_row;
943
944 1
    foreach ($this as $row) {
945
946 1
      if (!\array_key_exists($key, $row)) {
947
        continue;
948
      }
949
950 1
      if (null !== $column) {
951
952 1
        if (!\array_key_exists($column, $row)) {
953
          continue;
954
        }
955
956 1
        $pairs[$row[$key]] = $row[$column];
957
      } else {
958 1
        $pairs[$row[$key]] = $row;
959
      }
960
    }
961
962 1
    $this->rewind($pos);
963
964 1
    return $pairs;
965
  }
966
967
  /**
968
   * Returns all rows at once, transposed as an array of arrays. Instead of
969
   * returning rows of columns, this method returns columns of rows.
970
   *
971
   * @param string $column The column name to use as keys (optional)
972
   *
973
   * @return mixed A transposed array of arrays
974
   */
975 1
  public function fetchTranspose(string $column = null)
976
  {
977
    // init
978 1
    $keys = null !== $column ? $this->fetchAllColumn($column) : [];
979 1
    $rows = [];
980 1
    $pos = $this->current_row;
981
982 1
    foreach ($this as $row) {
983 1
      foreach ($row as $key => $value) {
984 1
        $rows[$key][] = $value;
985
      }
986
    }
987
988 1
    $this->rewind($pos);
989
990 1
    if (empty($keys)) {
991 1
      return $rows;
992
    }
993
994 1
    return \array_map(
995 1
        function ($values) use ($keys) {
996 1
          return \array_combine($keys, $values);
997 1
        }, $rows
998
    );
999
  }
1000
1001
  /**
1002
   * Fetch as "\Generator" via yield.
1003
   *
1004
   * @param object|string $class  <p>
1005
   *                              <strong>string</strong>: create a new object (with optional constructor
1006
   *                              parameter)<br>
1007
   *                              <strong>object</strong>: use a object and fill the the data into
1008
   *                              </p>
1009
   * @param null|array    $params optional
1010
   *                              <p>
1011
   *                              An array of parameters to pass to the constructor, used if $class is a
1012
   *                              string.
1013
   *                              </p>
1014
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
1015
   *
1016
   * @return \Generator
1017
   */
1018 4 View Code Duplication
  public function fetchYield($class = '', array $params = null, bool $reset = false): \Generator
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...
1019
  {
1020 4
    if ($reset === true) {
1021
      $this->reset();
1022
    }
1023
1024
    // fallback
1025 4
    if (!$class || $class === 'stdClass') {
1026
      /** @noinspection ClassConstantCanBeUsedInspection */
1027 3
      $class = '\stdClass';
1028
    }
1029
1030 4
    if (\is_object($class)) {
1031
1032
      $classTmp = $class;
1033
1034 4
    } elseif ($class && $params) {
1035
1036
      $reflectorTmp = new \ReflectionClass($class);
1037
      $classTmp = $reflectorTmp->newInstanceArgs($params);
1038
1039
    } else {
1040
1041 4
      $classTmp = new $class;
1042
1043
    }
1044
1045 4
    $row = $this->fetch_assoc();
1046 4
    $row = $row ? $this->cast($row) : false;
1047
1048 4
    if (!$row) {
1049
      return;
1050
    }
1051
1052 4
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
1053 4
    foreach ($row as $key => $value) {
1054 4
      if ($class === '\stdClass') {
1055 3
        $classTmp->{$key} = $value;
1056
      } else {
1057 4
        $propertyAccessor->setValue($classTmp, $key, $value);
1058
      }
1059
    }
1060
1061 4
    yield $classTmp;
1062 4
  }
1063
1064
  /**
1065
   * @return mixed
1066
   */
1067 94
  private function fetch_assoc()
1068
  {
1069 94
    if ($this->_result instanceof \Doctrine\DBAL\Statement) {
1070
1071
      if (
1072
          $this->doctrinePdoStmt
1073
          &&
1074
          $this->doctrinePdoStmt instanceof \Doctrine\DBAL\Driver\PDOStatement
1075
      ) {
1076
        if ($this->doctrinePdoStmtDataSeekInit === false) {
1077
          $this->doctrinePdoStmtDataSeekInit = true;
1078
1079
          $this->doctrinePdoStmtDataSeekFakeCache = $this->_result->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
1080
        }
1081
1082
        $return = ($this->doctrinePdoStmtDataSeekFakeCache[$this->doctrinePdoStmtDataSeekFake] ?? null);
1083
1084
        $this->doctrinePdoStmtDataSeekFake++;
1085
1086
        return $return;
1087
      }
1088
1089
      if (
1090
          $this->doctrineMySQLiStmt
1091
          &&
1092
          $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1093
      ) {
1094
        $object = $this->_result->fetch(
1095
            \Doctrine\DBAL\FetchMode::ASSOCIATIVE,
1096
            0 // FETCH_ORI_NEXT
1097
        );
1098
1099
        return $object;
1100
      }
1101
1102
      return null;
1103
    }
1104
1105 94
    return \mysqli_fetch_assoc($this->_result);
1106
  }
1107
1108
  /**
1109
   * @return array|bool
1110
   */
1111 1
  private function fetch_fields()
1112
  {
1113 1
    if ($this->_result instanceof \mysqli_result) {
1114 1
      return \mysqli_fetch_fields($this->_result);
1115
    }
1116
1117
    if ($this->doctrineMySQLiStmt) {
1118
      $metadataTmp = $this->doctrineMySQLiStmt->result_metadata();
1119
1120
      return $metadataTmp->fetch_fields();
1121
    }
1122
1123
    if ($this->doctrinePdoStmt) {
1124
      $fields = [];
1125
1126
      static $THIS_CLASS_TMP = null;
1127
      if ($THIS_CLASS_TMP === null) {
1128
        $THIS_CLASS_TMP = new \ReflectionClass(__CLASS__);
1129
      }
1130
1131
      $totalColumnsTmp = $this->doctrinePdoStmt->columnCount();
1132
      for ($counterTmp = 0; $counterTmp < $totalColumnsTmp; $counterTmp++) {
1133
        $metadataTmp = $this->doctrinePdoStmt->getColumnMeta($counterTmp);
1134
        $fieldTmp = new \stdClass();
1135
        foreach ($metadataTmp as $metadataTmpKey => $metadataTmpValue) {
1136
          $fieldTmp->{$metadataTmpKey} = $metadataTmpValue;
1137
        }
1138
1139
        $typeNativeTmp = 'MYSQL_TYPE_' . $metadataTmp['native_type'];
1140
        $typeTmp = $THIS_CLASS_TMP->getConstant($typeNativeTmp);
1141
        if ($typeTmp) {
1142
          $fieldTmp->type = $typeTmp;
1143
        } else {
1144
          $fieldTmp->type = '';
1145
        }
1146
1147
        $fields[] = $fieldTmp;
1148
      }
1149
1150
      return $fields;
1151
    }
1152
1153
    return false;
1154
  }
1155
1156
  /**
1157
   * Returns the first row element from the result.
1158
   *
1159
   * @param string $column The column name to use as value (optional)
1160
   *
1161
   * @return mixed A row array or a single scalar value
1162
   */
1163 3
  public function first(string $column = null)
1164
  {
1165 3
    $pos = $this->current_row;
1166 3
    $first = $this->fetchCallable(0, $column);
1167 3
    $this->rewind($pos);
1168
1169 3
    return $first;
1170
  }
1171
1172
  /**
1173
   * free the memory
1174
   */
1175 117
  public function free()
1176
  {
1177
    if (
1178 117
        $this->_result
1179
        &&
1180 117
        $this->_result instanceof \mysqli_result
1181
    ) {
1182
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1183 117
      @\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...
1184 117
      $this->_result = null;
1185
1186 117
      return true;
1187
    }
1188
1189
    if (
1190 1
        $this->doctrineMySQLiStmt
1191
        &&
1192 1
        $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1193
    ) {
1194
      $this->doctrineMySQLiStmt->free_result();
1195
      $this->_result = null;
1196
1197
      return true;
1198
    }
1199
1200 1
    $this->_result = null;
1201
1202 1
    return false;
1203
  }
1204
1205
  /**
1206
   * alias for "Result->fetch()"
1207
   *
1208
   * @see Result::fetch()
1209
   *
1210
   * @return array|object|false <p><strong>false</strong> on error</p>
1211
   */
1212 3
  public function get()
1213
  {
1214 3
    return $this->fetch();
1215
  }
1216
1217
  /**
1218
   * alias for "Result->fetchAll()"
1219
   *
1220
   * @see Result::fetchAll()
1221
   *
1222
   * @return array
1223
   */
1224 3
  public function getAll(): array
1225
  {
1226 3
    return $this->fetchAll();
1227
  }
1228
1229
  /**
1230
   * alias for "Result->fetchAllColumn()"
1231
   *
1232
   * @see Result::fetchAllColumn()
1233
   *
1234
   * @param string $column
1235
   * @param bool   $skipNullValues
1236
   *
1237
   * @return array
1238
   */
1239
  public function getAllColumn(string $column, bool $skipNullValues = false): array
1240
  {
1241
    return $this->fetchAllColumn($column, $skipNullValues);
1242
  }
1243
1244
  /**
1245
   * alias for "Result->fetchAllArray()"
1246
   *
1247
   * @see Result::fetchAllArray()
1248
   *
1249
   * @return array
1250
   */
1251 3
  public function getArray(): array
1252
  {
1253 3
    return $this->fetchAllArray();
1254
  }
1255
1256
  /**
1257
   * alias for "Result->fetchAllArrayy()"
1258
   *
1259
   * @see Result::fetchAllArrayy()
1260
   *
1261
   * @return Arrayy
1262
   */
1263
  public function getArrayy(): Arrayy
1264
  {
1265
    return $this->fetchAllArrayy();
1266
  }
1267
1268
  /**
1269
   * alias for "Result->fetchColumn()"
1270
   *
1271
   * @see Result::fetchColumn()
1272
   *
1273
   * @param string $column
1274
   * @param bool   $asArray
1275
   * @param bool   $skipNullValues
1276
   *
1277
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
1278
   *                      "$asArray"</p>
1279
   */
1280 3
  public function getColumn(string $column, bool $skipNullValues = true, bool $asArray = false)
1281
  {
1282 3
    return $this->fetchColumn($column, $skipNullValues, $asArray);
1283
  }
1284
1285
  /**
1286
   * @return string
1287
   */
1288 3
  public function getDefaultResultType(): string
1289
  {
1290 3
    return $this->_default_result_type;
1291
  }
1292
1293
  /**
1294
   * alias for "Result->fetchAllObject()"
1295
   *
1296
   * @see Result::fetchAllObject()
1297
   *
1298
   * @return array of mysql-objects
1299
   */
1300 3
  public function getObject(): array
1301
  {
1302 3
    return $this->fetchAllObject();
1303
  }
1304
1305
  /**
1306
   * alias for "Result->fetchAllYield()"
1307
   *
1308
   * @see Result::fetchAllYield()
1309
   *
1310
   * @param bool $asArray
1311
   *
1312
   * @return \Generator
1313
   */
1314 1
  public function getYield($asArray = false): \Generator
1315
  {
1316 1
    return $this->fetchAllYield($asArray);
0 ignored issues
show
Documentation introduced by
$asArray is of type boolean, but the function expects a string|object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1317
  }
1318
1319
  /**
1320
   * Check if the result is empty.
1321
   *
1322
   * @return bool
1323
   */
1324 58
  public function is_empty(): bool
1325
  {
1326 58
    return !($this->num_rows > 0);
1327
  }
1328
1329
  /**
1330
   * Fetch all results as "json"-string.
1331
   *
1332
   * @return string
1333
   */
1334 3
  public function json(): string
1335
  {
1336 3
    $data = $this->fetchAllArray();
1337
1338 3
    return UTF8::json_encode($data);
1339
  }
1340
1341
  /**
1342
   * Returns the last row element from the result.
1343
   *
1344
   * @param string $column The column name to use as value (optional)
1345
   *
1346
   * @return mixed A row array or a single scalar value
1347
   */
1348 3
  public function last(string $column = null)
1349
  {
1350 3
    $pos = $this->current_row;
1351 3
    $last = $this->fetchCallable($this->num_rows - 1, $column);
1352 3
    $this->rewind($pos);
1353
1354 3
    return $last;
1355
  }
1356
1357
  /**
1358
   * Set the mapper...
1359
   *
1360
   * @param \Closure $callable
1361
   *
1362
   * @return $this
1363
   */
1364 1
  public function map(\Closure $callable): self
1365
  {
1366 1
    $this->_mapper = $callable;
1367
1368 1
    return $this;
1369
  }
1370
1371
  /**
1372
   * Alias of count(). Deprecated.
1373
   *
1374
   * @return int The number of rows in the result
1375
   */
1376 1
  public function num_rows(): int
1377
  {
1378 1
    return $this->count();
1379
  }
1380
1381
  /**
1382
   * ArrayAccess interface implementation.
1383
   *
1384
   * @param int $offset <p>Offset number</p>
1385
   *
1386
   * @return bool <p>true if offset exists, false otherwise</p>
1387
   */
1388 1
  public function offsetExists($offset): bool
1389
  {
1390 1
    return \is_int($offset) && $offset >= 0 && $offset < $this->num_rows;
1391
  }
1392
1393
  /**
1394
   * ArrayAccess interface implementation.
1395
   *
1396
   * @param int $offset Offset number
1397
   *
1398
   * @return mixed
1399
   */
1400 1
  public function offsetGet($offset)
1401
  {
1402 1
    if ($this->offsetExists($offset)) {
1403 1
      return $this->fetchCallable($offset);
1404
    }
1405
1406
    throw new \OutOfBoundsException("undefined offset ($offset)");
1407
  }
1408
1409
  /**
1410
   * ArrayAccess interface implementation. Not implemented by design.
1411
   *
1412
   * @param mixed $offset
1413
   * @param mixed $value
1414
   */
1415
  public function offsetSet($offset, $value)
1416
  {
1417
    /** @noinspection UselessReturnInspection */
1418
    return;
1419
  }
1420
1421
  /**
1422
   * ArrayAccess interface implementation. Not implemented by design.
1423
   *
1424
   * @param mixed $offset
1425
   */
1426
  public function offsetUnset($offset)
1427
  {
1428
    /** @noinspection UselessReturnInspection */
1429
    return;
1430
  }
1431
1432
  /**
1433
   * Reset the offset (data_seek) for the results.
1434
   *
1435
   * @return Result
1436
   */
1437 54
  public function reset(): self
1438
  {
1439 54
    $this->doctrinePdoStmtDataSeekFake = 0;
1440
1441 54
    if (!$this->is_empty()) {
1442
1443
      if (
1444 52
          $this->doctrineMySQLiStmt
1445
          &&
1446 52
          $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1447
      ) {
1448
        $this->doctrineMySQLiStmt->data_seek(0);
1449
      }
1450
1451
      if (
1452 52
          $this->_result
1453
          &&
1454 52
          $this->_result instanceof \mysqli_result
1455
      ) {
1456 52
        \mysqli_data_seek($this->_result, 0);
1457
      }
1458
    }
1459
1460 54
    return $this;
1461
  }
1462
1463
  /**
1464
   * You can set the default result-type to Result::RESULT_TYPE_*.
1465
   *
1466
   * INFO: used for "fetch()" and "fetchAll()"
1467
   *
1468
   * @param string $default_result_type
1469
   */
1470 6
  public function setDefaultResultType(string $default_result_type = self::RESULT_TYPE_OBJECT)
1471
  {
1472
    if (
1473 6
        $default_result_type === self::RESULT_TYPE_OBJECT
1474
        ||
1475 6
        $default_result_type === self::RESULT_TYPE_ARRAY
1476
        ||
1477
        $default_result_type === self::RESULT_TYPE_ARRAYY
1478
        ||
1479 6
        $default_result_type === self::RESULT_TYPE_YIELD
1480
    ) {
1481 6
      $this->_default_result_type = $default_result_type;
1482
    }
1483 6
  }
1484
1485
  /**
1486
   * @param int      $offset
1487
   * @param null|int $length
1488
   * @param bool     $preserve_keys
1489
   *
1490
   * @return array
1491
   */
1492 1
  public function slice(int $offset = 0, int $length = null, bool $preserve_keys = false): array
1493
  {
1494
    // init
1495 1
    $slice = [];
1496
1497 1
    if ($offset < 0) {
1498 1
      if (\abs($offset) > $this->num_rows) {
1499 1
        $offset = 0;
1500
      } else {
1501 1
        $offset = $this->num_rows - (int)\abs($offset);
1502
      }
1503
    }
1504
1505 1
    $length = null !== $length ? (int)$length : $this->num_rows;
1506 1
    $n = 0;
1507 1
    for ($i = $offset; $i < $this->num_rows && $n < $length; $i++) {
1508 1
      if ($preserve_keys) {
1509 1
        $slice[$i] = $this->fetchCallable($i);
1510
      } else {
1511 1
        $slice[] = $this->fetchCallable($i);
1512
      }
1513 1
      ++$n;
1514
    }
1515
1516 1
    return $slice;
1517
  }
1518
}
1519