Completed
Push — master ( 06bfdf...e10040 )
by Lars
01:58
created

Result::fetchAllArray()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 7
cts 8
cp 0.875
rs 9.7333
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3.0175
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
final class Result implements \Countable, \SeekableIterator, \ArrayAccess
15
{
16
    const MYSQL_TYPE_BIT = 16;
17
18
    const MYSQL_TYPE_BLOB = 252;
19
20
    const MYSQL_TYPE_DATE = 10;
21
22
    const MYSQL_TYPE_DATETIME = 12;
23
24
    const MYSQL_TYPE_DECIMAL = 0;
25
26
    const MYSQL_TYPE_DOUBLE = 5;
27
28
    const MYSQL_TYPE_ENUM = 247;
29
30
    const MYSQL_TYPE_FLOAT = 4;
31
32
    const MYSQL_TYPE_GEOMETRY = 255;
33
34
    const MYSQL_TYPE_INT24 = 9;
35
36
    const MYSQL_TYPE_JSON = 245;
37
38
    const MYSQL_TYPE_LONG = 3;
39
40
    const MYSQL_TYPE_LONGLONG = 8;
41
42
    const MYSQL_TYPE_LONG_BLOB = 251;
43
44
    const MYSQL_TYPE_MEDIUM_BLOB = 250;
45
46
    const MYSQL_TYPE_NEWDATE = 14;
47
48
    const MYSQL_TYPE_NEWDECIMAL = 246;
49
50
    const MYSQL_TYPE_NULL = 6;
51
52
    const MYSQL_TYPE_SET = 248;
53
54
    const MYSQL_TYPE_SHORT = 2;
55
56
    const MYSQL_TYPE_STRING = 254;
57
58
    const MYSQL_TYPE_TIME = 11;
59
60
    const MYSQL_TYPE_TIMESTAMP = 7;
61
62
    const MYSQL_TYPE_TINY = 1;
63
64
    const MYSQL_TYPE_TINY_BLOB = 249;
65
66
    const MYSQL_TYPE_VARCHAR = 15;
67
68
    const MYSQL_TYPE_VAR_STRING = 253;
69
70
    const MYSQL_TYPE_YEAR = 13;
71
72
    const RESULT_TYPE_ARRAY = 'array';
73
74
    const RESULT_TYPE_ARRAYY = 'Arrayy';
75
76
    const RESULT_TYPE_OBJECT = 'object';
77
78
    const RESULT_TYPE_YIELD = 'yield';
79
80
    /**
81
     * @var int
82
     */
83
    public $num_rows;
84
85
    /**
86
     * @var string
87
     */
88
    public $sql;
89
90
    /**
91
     * @var \Doctrine\DBAL\Statement|\mysqli_result
92
     */
93
    private $_result;
94
95
    /**
96
     * @var int
97
     */
98
    private $current_row;
99
100
    /**
101
     * @var \Closure|null
102
     */
103
    private $_mapper;
104
105
    /**
106
     * @var string
107
     */
108
    private $_default_result_type = self::RESULT_TYPE_OBJECT;
109
110
    /**
111
     * @var \mysqli_stmt|null
112
     */
113
    private $doctrineMySQLiStmt;
114
115
    /**
116
     * @var \Doctrine\DBAL\Driver\PDOStatement|null
117
     */
118
    private $doctrinePdoStmt;
119
120
    /**
121
     * @var int
122
     */
123
    private $doctrinePdoStmtDataSeekFake = 0;
124
125
    /**
126
     * @var int
127
     */
128
    private $doctrinePdoStmtDataSeekInit = false;
129
130
    /**
131
     * @var array
132
     */
133
    private $doctrinePdoStmtDataSeekFakeCache = [];
134
135
    /**
136
     * Result constructor.
137
     *
138
     * @param string                                  $sql
139
     * @param \Doctrine\DBAL\Statement|\mysqli_result $result
140
     * @param \Closure                                $mapper Optional callback mapper for the "fetchCallable()" method
141
     */
142 101
    public function __construct(string $sql, $result, \Closure $mapper = null)
143
    {
144 101
        $this->sql = $sql;
145
146
        if (
147 101
            !$result instanceof \mysqli_result
148
            &&
149 101
            !$result instanceof \Doctrine\DBAL\Statement
150
        ) {
151
            throw new \InvalidArgumentException('$result must be ' . \mysqli_result::class . ' or ' . \Doctrine\DBAL\Statement::class . ' !');
152
        }
153
154 101
        $this->_result = $result;
155
156 101
        if ($this->_result instanceof \Doctrine\DBAL\Statement) {
157
            $doctrineDriver = $this->_result->getWrappedStatement();
158
159
            if (
160
                $doctrineDriver
161
                &&
162
                $doctrineDriver instanceof \Doctrine\DBAL\Driver\PDOStatement
163
            ) {
164
                $this->doctrinePdoStmt = $doctrineDriver;
165
            }
166
167
            if (
168
                $doctrineDriver
169
                &&
170
                $doctrineDriver instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliStatement
171
            ) {
172
                // try to get the mysqli driver from doctrine
173
                $reflectionTmp = new \ReflectionClass($doctrineDriver);
174
                $propertyTmp = $reflectionTmp->getProperty('_stmt');
175
                $propertyTmp->setAccessible(true);
176
                $this->doctrineMySQLiStmt = $propertyTmp->getValue($doctrineDriver);
177
            }
178
179
            $this->num_rows = $this->_result->rowCount();
180
        } else {
181 101
            $this->num_rows = (int) $this->_result->num_rows;
182
        }
183
184 101
        $this->current_row = 0;
185
186 101
        $this->_mapper = $mapper;
187 101
    }
188
189
    /**
190
     * __destruct
191
     */
192 100
    public function __destruct()
193
    {
194 100
        $this->free();
195 100
    }
196
197
    /**
198
     * Runs a user-provided callback with the MySQLi_Result object given as
199
     * argument and returns the result, or returns the MySQLi_Result object if
200
     * called without an argument.
201
     *
202
     * @param callable $callback User-provided callback (optional)
203
     *
204
     * @return \Doctrine\DBAL\Statement|mixed|\mysqli_result
205
     */
206 2
    public function __invoke(callable $callback = null)
207
    {
208 2
        if ($callback !== null) {
209 2
            return $callback($this->_result);
210
        }
211
212 1
        return $this->_result;
213
    }
214
215
    /**
216
     * Get the current "num_rows" as string.
217
     *
218
     * @return string
219
     */
220
    public function __toString()
221
    {
222
        return (string) $this->num_rows;
223
    }
224
225
    /**
226
     * Cast data into int, float or string.
227
     *
228
     * <p>
229
     *   <br />
230
     *   INFO: install / use "mysqlnd"-driver for better performance
231
     * </p>
232
     *
233
     * @param array|object $data
234
     *
235
     * @return array|false|object <p><strong>false</strong> on error</p>
236
     */
237 68
    private function cast(&$data)
238
    {
239
        if (
240 68
            !$this->doctrinePdoStmt // pdo only have limited support for types, so we try to improve it
241
            &&
242 68
            Helper::isMysqlndIsUsed()
243
        ) {
244 68
            return $data;
245
        }
246
247
        // init
248
        static $FIELDS_CACHE = [];
249
        static $TYPES_CACHE = [];
250
251
        $result_hash = \spl_object_hash($this->_result);
252
253
        if (!isset($FIELDS_CACHE[$result_hash])) {
254
            $FIELDS_CACHE[$result_hash] = $this->fetch_fields();
255
        }
256
257
        if (
258
            !isset($FIELDS_CACHE[$result_hash])
259
            ||
260
            $FIELDS_CACHE[$result_hash] === false
261
        ) {
262
            return false;
263
        }
264
265
        if (!isset($TYPES_CACHE[$result_hash])) {
266
            foreach ($FIELDS_CACHE[$result_hash] as $field) {
267
                switch ($field->type) {
268
                    case self::MYSQL_TYPE_BIT:
269
                        $TYPES_CACHE[$result_hash][$field->name] = 'boolean';
270
271
                        break;
272
                    case self::MYSQL_TYPE_TINY:
273
                    case self::MYSQL_TYPE_SHORT:
274
                    case self::MYSQL_TYPE_LONG:
275
                    case self::MYSQL_TYPE_LONGLONG:
276
                    case self::MYSQL_TYPE_INT24:
277
                        $TYPES_CACHE[$result_hash][$field->name] = 'integer';
278
279
                        break;
280
                    case self::MYSQL_TYPE_DOUBLE:
281
                    case self::MYSQL_TYPE_DECIMAL:
282
                    case self::MYSQL_TYPE_NEWDECIMAL:
283
                    case self::MYSQL_TYPE_FLOAT:
284
                        $TYPES_CACHE[$result_hash][$field->name] = 'float';
285
286
                        break;
287
                    default:
288
                        $TYPES_CACHE[$result_hash][$field->name] = 'string';
289
290
                        break;
291
                }
292
            }
293
        }
294
295
        if (\is_array($data)) {
296 View Code Duplication
            foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
297
                if (isset($data[$type_name])) {
298
                    \settype($data[$type_name], $type);
299
                }
300
            }
301
        } elseif (\is_object($data)) {
302 View Code Duplication
            foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
303
                if (isset($data->{$type_name})) {
304
                    \settype($data->{$type_name}, $type);
305
                }
306
            }
307
        }
308
309
        return $data;
310
    }
311
312
    /**
313
     * Countable interface implementation.
314
     *
315
     * @return int The number of rows in the result
316
     */
317 2
    public function count(): int
318
    {
319 2
        return $this->num_rows;
320
    }
321
322
    /**
323
     * Iterator interface implementation.
324
     *
325
     * @return mixed The current element
326
     */
327 8
    public function current()
328
    {
329 8
        return $this->fetchCallable($this->current_row);
330
    }
331
332
    /**
333
     * Iterator interface implementation.
334
     *
335
     * @return int The current element key (row index; zero-based)
336
     */
337 1
    public function key(): int
338
    {
339 1
        return $this->current_row;
340
    }
341
342
    /**
343
     * Iterator interface implementation.
344
     *
345
     * @return void
346
     */
347 8
    public function next()
348
    {
349 8
        $this->current_row++;
350 8
    }
351
352
    /**
353
     * Iterator interface implementation.
354
     *
355
     * @param int $row Row position to rewind to; defaults to 0
356
     *
357
     * @return void
358
     */
359 12
    public function rewind($row = 0)
360
    {
361 12
        if ($this->seek($row)) {
362 10
            $this->current_row = $row;
363
        }
364 12
    }
365
366
    /**
367
     * Moves the internal pointer to the specified row position.
368
     *
369
     * @param int $row <p>Row position; zero-based and set to 0 by default</p>
370
     *
371
     * @return bool <p>true on success, false otherwise</p>
372
     */
373 20
    public function seek($row = 0): bool
374
    {
375 20
        if (\is_int($row) && $row >= 0 && $row < $this->num_rows) {
376
            if (
377 16
                $this->doctrineMySQLiStmt
378
                &&
379 16
                $this->doctrineMySQLiStmt instanceof \mysqli_stmt
380
            ) {
381
                $this->doctrineMySQLiStmt->data_seek($row);
382
383
                return true;
384
            }
385
386
            if (
387 16
                $this->doctrinePdoStmt
388
                &&
389 16
                $this->doctrinePdoStmt instanceof \Doctrine\DBAL\Driver\PDOStatement
390
            ) {
391
                return true;
392
            }
393
394 16
            return \mysqli_data_seek($this->_result, $row);
395
        }
396
397 4
        return false;
398
    }
399
400
    /**
401
     * Iterator interface implementation.
402
     *
403
     * @return bool <p>true if the current index is valid, false otherwise</p>
404
     */
405 8
    public function valid(): bool
406
    {
407 8
        return $this->current_row < $this->num_rows;
408
    }
409
410
    /**
411
     * Fetch.
412
     *
413
     * <p>
414
     *   <br />
415
     *   INFO: this will return an object by default, not an array<br />
416
     *   and you can change the behaviour via "Result->setDefaultResultType()"
417
     * </p>
418
     *
419
     * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
420
     *
421
     * @return array|false|object <p><strong>false</strong> on error</p>
422
     */
423 6
    public function fetch(bool $reset = false)
424
    {
425 6
        $return = false;
426
427 6
        if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
428 6
            $return = $this->fetchObject('', null, $reset);
429 6
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
430 6
            $return = $this->fetchArray($reset);
431
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
432
            $return = $this->fetchArrayy($reset);
433
        } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
434
            $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...
435
        }
436
437 6
        return $return;
438
    }
439
440
    /**
441
     * Fetch all results.
442
     *
443
     * <p>
444
     *   <br />
445
     *   INFO: this will return an object by default, not an array<br />
446
     *   and you can change the behaviour via "Result->setDefaultResultType()"
447
     * </p>
448
     *
449
     * @return array
450
     */
451 6
    public function fetchAll(): array
452
    {
453 6
        $return = [];
454
455 6
        if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
456 6
            $return = $this->fetchAllObject();
457 3
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
458 3
            $return = $this->fetchAllArray();
459
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
460
            $return = $this->fetchAllArrayy();
461
        } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
462
            $return = $this->fetchAllYield();
463
        }
464
465 6
        return $return;
466
    }
467
468
    /**
469
     * Fetch all results as array.
470
     *
471
     * @return array
472
     */
473 28
    public function fetchAllArray(): array
474
    {
475 28
        if ($this->is_empty()) {
476
            return [];
477
        }
478
479 28
        $this->reset();
480
481 28
        $data = [];
482
        /** @noinspection PhpAssignmentInConditionInspection */
483 28
        while ($row = $this->fetch_assoc()) {
484 28
            $data[] = $this->cast($row);
485
        }
486
487 28
        return $data;
488
    }
489
490
    /**
491
     * Fetch all results as "Arrayy"-object.
492
     *
493
     * @return Arrayy
494
     */
495 10
    public function fetchAllArrayy(): Arrayy
496
    {
497 10
        if ($this->is_empty()) {
498
            return Arrayy::create([]);
499
        }
500
501 10
        $this->reset();
502
503 10
        $data = [];
504
        /** @noinspection PhpAssignmentInConditionInspection */
505 10
        while ($row = $this->fetch_assoc()) {
506 10
            $data[] = $this->cast($row);
507
        }
508
509 10
        return Arrayy::create($data);
510
    }
511
512
    /**
513
     * Fetch a single column as an 1-dimension array.
514
     *
515
     * @param string $column
516
     * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
517
     *
518
     * @return array <p>Return an empty array if the "$column" wasn't found</p>
519
     */
520 5
    public function fetchAllColumn(string $column, bool $skipNullValues = false): array
521
    {
522 5
        return $this->fetchColumn($column, $skipNullValues, true);
523
    }
524
525
    /**
526
     * Fetch all results as array with objects.
527
     *
528
     * @param object|string $class  <p>
529
     *                              <strong>string</strong>: create a new object (with optional constructor
530
     *                              parameter)<br>
531
     *                              <strong>object</strong>: use a object and fill the the data into
532
     *                              </p>
533
     * @param array|null    $params optional
534
     *                              <p>
535
     *                              An array of parameters to pass to the constructor, used if $class is a
536
     *                              string.
537
     *                              </p>
538
     *
539
     * @return array
540
     */
541 9
    public function fetchAllObject($class = '', array $params = null): array
542
    {
543 9
        if ($this->is_empty()) {
544
            return [];
545
        }
546
547
        // fallback
548 9
        if (!$class || $class === 'stdClass') {
549
            /** @noinspection ClassConstantCanBeUsedInspection */
550 9
            $class = '\stdClass';
551
        }
552
553
        // init
554 9
        $data = [];
555 9
        $this->reset();
556
557 9
        if (\is_object($class)) {
558
            $classTmpOrig = new $class();
559 9
        } elseif ($class && $params) {
560 3
            $reflectorTmp = new \ReflectionClass($class);
561 3
            $classTmpOrig = $reflectorTmp->newInstanceArgs($params);
562
        } else {
563 9
            $classTmpOrig = new $class();
564
        }
565
566 9
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
567
        /** @noinspection PhpAssignmentInConditionInspection */
568 9 View Code Duplication
        while ($row = $this->fetch_assoc()) {
569 9
            $classTmp = clone $classTmpOrig;
570 9
            $row = $this->cast($row);
571 9
            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...
572 9
                if ($class === '\stdClass') {
573 9
                    $classTmp->{$key} = $value;
574
                } else {
575 3
                    $propertyAccessor->setValue($classTmp, $key, $value);
576
                }
577
            }
578 9
            $data[] = $classTmp;
579
        }
580
581 9
        return $data;
582
    }
583
584
    /**
585
     * Fetch all results as "\Generator" via yield.
586
     *
587
     * @param object|string $class  <p>
588
     *                              <strong>string</strong>: create a new object (with optional constructor
589
     *                              parameter)<br>
590
     *                              <strong>object</strong>: use a object and fill the the data into
591
     *                              </p>
592
     * @param array|null    $params optional
593
     *                              <p>
594
     *                              An array of parameters to pass to the constructor, used if $class is a
595
     *                              string.
596
     *                              </p>
597
     *
598
     * @return \Generator
599
     */
600 8
    public function fetchAllYield($class = '', array $params = null): \Generator
601
    {
602 8
        if ($this->is_empty()) {
603
            return;
604
        }
605
606
        // init
607 8
        $this->reset();
608
609
        // fallback
610 8
        if (!$class || $class === 'stdClass') {
611
            /** @noinspection ClassConstantCanBeUsedInspection */
612 8
            $class = '\stdClass';
613
        }
614
615 8
        if (\is_object($class)) {
616
            $classTmpOrig = $class;
617 8
        } elseif ($class && $params) {
618
            $reflectorTmp = new \ReflectionClass($class);
619
            $classTmpOrig = $reflectorTmp->newInstanceArgs($params);
620
        } else {
621 8
            $classTmpOrig = new $class();
622
        }
623
624 8
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
625
        /** @noinspection PhpAssignmentInConditionInspection */
626 8 View Code Duplication
        while ($row = $this->fetch_assoc()) {
627 8
            $classTmp = clone $classTmpOrig;
628 8
            $row = $this->cast($row);
629 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...
630 8
                if ($class === '\stdClass') {
631 8
                    $classTmp->{$key} = $value;
632
                } else {
633 3
                    $propertyAccessor->setValue($classTmp, $key, $value);
634
                }
635
            }
636
637 8
            yield $classTmp;
638
        }
639 8
    }
640
641
    /**
642
     * Fetch as array.
643
     *
644
     * @param bool $reset
645
     *
646
     * @return array|false <p><strong>false</strong> on error</p>
647
     */
648 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...
649
    {
650 30
        if ($reset) {
651 3
            $this->reset();
652
        }
653
654 30
        $row = $this->fetch_assoc();
655 30
        if ($row) {
656 27
            return $this->cast($row);
657
        }
658
659 5
        if ($row === null || $row === false) {
660 5
            return [];
661
        }
662
663
        return false;
664
    }
665
666
    /**
667
     * Fetch data as a key/value pair array.
668
     *
669
     * <p>
670
     *   <br />
671
     *   INFO: both "key" and "value" must exists in the fetched data
672
     *   the key will be the new key of the result-array
673
     *   <br /><br />
674
     * </p>
675
     *
676
     * e.g.:
677
     * <code>
678
     *    fetchArrayPair('some_id', 'some_value');
679
     *    // array(127 => 'some value', 128 => 'some other value')
680
     * </code>
681
     *
682
     * @param string $key
683
     * @param string $value
684
     *
685
     * @return array
686
     */
687 3
    public function fetchArrayPair(string $key, string $value): array
688
    {
689 3
        $arrayPair = [];
690 3
        $data = $this->fetchAllArray();
691
692 3
        foreach ($data as &$_row) {
693
            if (
694 3
                \array_key_exists($key, $_row)
695
                &&
696 3
                \array_key_exists($value, $_row)
697
            ) {
698 3
                $_key = $_row[$key];
699 3
                $_value = $_row[$value];
700 3
                $arrayPair[$_key] = $_value;
701
            }
702
        }
703
704 3
        return $arrayPair;
705
    }
706
707
    /**
708
     * Fetch as "Arrayy"-object.
709
     *
710
     * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
711
     *
712
     * @return Arrayy|false <p><strong>false</strong> on error</p>
713
     */
714 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...
715
    {
716 6
        if ($reset) {
717
            $this->reset();
718
        }
719
720 6
        $row = $this->fetch_assoc();
721 6
        if ($row) {
722 3
            return Arrayy::create($this->cast($row));
723
        }
724
725 3
        if ($row === null || $row === false) {
726 3
            return Arrayy::create();
727
        }
728
729
        return false;
730
    }
731
732
    /**
733
     * Fetches a row or a single column within a row. Returns null if there are
734
     * no more rows in the result.
735
     *
736
     * @param int    $row    The row number (optional)
737
     * @param string $column The column name (optional)
738
     *
739
     * @return mixed An associative array or a scalar value
740
     */
741 17
    public function fetchCallable(int $row = null, string $column = null)
742
    {
743 17
        if (!$this->num_rows) {
744 2
            return null;
745
        }
746
747 15
        if ($row !== null) {
748 14
            $this->seek($row);
749
        }
750
751 15
        $rows = $this->fetch_assoc();
752
753 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...
754
            if (
755 5
                \is_array($rows)
756
                &&
757 5
                isset($rows[$column])
758
            ) {
759 5
                return $rows[$column];
760
            }
761
762
            return null;
763
        }
764
765 14
        if (\is_callable($this->_mapper)) {
766 1
            return \call_user_func($this->_mapper, $rows);
767
        }
768
769 14
        return $rows;
770
    }
771
772
    /**
773
     * Fetch a single column as string (or as 1-dimension array).
774
     *
775
     * @param string $column
776
     * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
777
     * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
778
     *
779
     * @return array|string <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
780
     *                      "$asArray"</p>
781
     */
782 9
    public function fetchColumn(string $column = '', bool $skipNullValues = true, bool $asArray = false)
783
    {
784 9
        if (!$asArray) {
785 7
            $columnData = '';
786
787 7
            $data = $this->fetchAllArrayy()->reverse()->getArray();
788 7 View Code Duplication
            foreach ($data as $_row) {
789 7
                if ($skipNullValues) {
790 7
                    if (!isset($_row[$column])) {
791 7
                        continue;
792
                    }
793 3
                } elseif (!\array_key_exists($column, $_row)) {
794 3
                    break;
795
                }
796
797 7
                $columnData = $_row[$column];
798
799 7
                break;
800
            }
801
802 7
            return $columnData;
803
        }
804
805
        // -- return as array -->
806
807 5
        $columnData = [];
808
809 5 View Code Duplication
        foreach ($this->fetchAllYield() as $_row) {
810 5
            if ($skipNullValues) {
811 3
                if (!isset($_row->{$column})) {
812 3
                    continue;
813
                }
814 5
            } elseif (!\array_key_exists($column, $_row)) {
815 3
                break;
816
            }
817
818 5
            $columnData[] = $_row->{$column};
819
        }
820
821 5
        return $columnData;
822
    }
823
824
    /**
825
     * Return rows of field information in a result set.
826
     *
827
     * @param bool $as_array Return each field info as array; defaults to false
828
     *
829
     * @return array Array of field information each as an associative array
830
     */
831 1
    public function fetchFields(bool $as_array = false): array
832
    {
833 1
        if ($as_array) {
834 1
            return \array_map(
835
                static function ($object) {
836 1
                    return (array) $object;
837 1
                },
838 1
                $this->fetch_fields()
839
            );
840
        }
841
842
        return $this->fetch_fields();
843
    }
844
845
    /**
846
     * Returns all rows at once as a grouped array of scalar values or arrays.
847
     *
848
     * @param string $group  The column name to use for grouping
849
     * @param string $column The column name to use as values (optional)
850
     *
851
     * @return array A grouped array of scalar values or arrays
852
     */
853 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...
854
    {
855
        // init
856 1
        $groups = [];
857 1
        $pos = $this->current_row;
858
859 1
        foreach ($this as $row) {
860 1
            if (!\array_key_exists($group, $row)) {
861
                continue;
862
            }
863
864 1
            if ($column !== null) {
865 1
                if (!\array_key_exists($column, $row)) {
866
                    continue;
867
                }
868
869 1
                $groups[$row[$group]][] = $row[$column];
870
            } else {
871 1
                $groups[$row[$group]][] = $row;
872
            }
873
        }
874
875 1
        $this->rewind($pos);
876
877 1
        return $groups;
878
    }
879
880
    /**
881
     * Fetch as object.
882
     *
883
     * @param object|string $class  <p>
884
     *                              <strong>string</strong>: create a new object (with optional constructor
885
     *                              parameter)<br>
886
     *                              <strong>object</strong>: use a object and fill the the data into
887
     *                              </p>
888
     * @param array|null    $params optional
889
     *                              <p>
890
     *                              An array of parameters to pass to the constructor, used if $class is a
891
     *                              string.
892
     *                              </p>
893
     * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
894
     *
895
     * @return false|object <p><strong>false</strong> on error</p>
896
     */
897 21 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...
898
    {
899 21
        if ($reset) {
900 9
            $this->reset();
901
        }
902
903
        // fallback
904 21
        if (!$class || $class === 'stdClass') {
905
            /** @noinspection ClassConstantCanBeUsedInspection */
906 21
            $class = '\stdClass';
907
        }
908
909 21
        $row = $this->fetch_assoc();
910 21
        $row = $row ? $this->cast($row) : false;
911
912 21
        if (!$row) {
913 3
            return false;
914
        }
915
916 21
        if (\is_object($class)) {
917 3
            $classTmp = $class;
918 21
        } elseif ($class && $params) {
919 3
            $reflectorTmp = new \ReflectionClass($class);
920 3
            $classTmp = $reflectorTmp->newInstanceArgs($params);
921
        } else {
922 21
            $classTmp = new $class();
923
        }
924
925 21
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
926 21
        foreach ($row as $key => $value) {
927 21
            if ($class === '\stdClass') {
928 21
                $classTmp->{$key} = $value;
929
            } else {
930 3
                $propertyAccessor->setValue($classTmp, $key, $value);
931
            }
932
        }
933
934 21
        return $classTmp;
935
    }
936
937
    /**
938
     * Returns all rows at once as key-value pairs.
939
     *
940
     * @param string $key    The column name to use as keys
941
     * @param string $column The column name to use as values (optional)
942
     *
943
     * @return array An array of key-value pairs
944
     */
945 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...
946
    {
947
        // init
948 1
        $pairs = [];
949 1
        $pos = $this->current_row;
950
951 1
        foreach ($this as $row) {
952 1
            if (!\array_key_exists($key, $row)) {
953
                continue;
954
            }
955
956 1
            if ($column !== null) {
957 1
                if (!\array_key_exists($column, $row)) {
958
                    continue;
959
                }
960
961 1
                $pairs[$row[$key]] = $row[$column];
962
            } else {
963 1
                $pairs[$row[$key]] = $row;
964
            }
965
        }
966
967 1
        $this->rewind($pos);
968
969 1
        return $pairs;
970
    }
971
972
    /**
973
     * Returns all rows at once, transposed as an array of arrays. Instead of
974
     * returning rows of columns, this method returns columns of rows.
975
     *
976
     * @param string $column The column name to use as keys (optional)
977
     *
978
     * @return mixed A transposed array of arrays
979
     */
980 1
    public function fetchTranspose(string $column = null)
981
    {
982
        // init
983 1
        $keys = $column !== null ? $this->fetchAllColumn($column) : [];
984 1
        $rows = [];
985 1
        $pos = $this->current_row;
986
987 1
        foreach ($this as $row) {
988 1
            foreach ($row as $key => $value) {
989 1
                $rows[$key][] = $value;
990
            }
991
        }
992
993 1
        $this->rewind($pos);
994
995 1
        if (empty($keys)) {
996 1
            return $rows;
997
        }
998
999 1
        return \array_map(
1000
            static function ($values) use ($keys) {
1001 1
                return \array_combine($keys, $values);
1002 1
            },
1003 1
            $rows
1004
        );
1005
    }
1006
1007
    /**
1008
     * Fetch as "\Generator" via yield.
1009
     *
1010
     * @param object|string $class  <p>
1011
     *                              <strong>string</strong>: create a new object (with optional constructor
1012
     *                              parameter)<br>
1013
     *                              <strong>object</strong>: use a object and fill the the data into
1014
     *                              </p>
1015
     * @param array|null    $params optional
1016
     *                              <p>
1017
     *                              An array of parameters to pass to the constructor, used if $class is a
1018
     *                              string.
1019
     *                              </p>
1020
     * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
1021
     *
1022
     * @return \Generator
1023
     */
1024 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...
1025
    {
1026 4
        if ($reset) {
1027
            $this->reset();
1028
        }
1029
1030
        // fallback
1031 4
        if (!$class || $class === 'stdClass') {
1032
            /** @noinspection ClassConstantCanBeUsedInspection */
1033 3
            $class = '\stdClass';
1034
        }
1035
1036 4
        if (\is_object($class)) {
1037
            $classTmp = $class;
1038 4
        } elseif ($class && $params) {
1039
            $reflectorTmp = new \ReflectionClass($class);
1040
            $classTmp = $reflectorTmp->newInstanceArgs($params);
1041
        } else {
1042 4
            $classTmp = new $class();
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 1
                $propertyAccessor->setValue($classTmp, $key, $value);
1058
            }
1059
        }
1060
1061 4
        yield $classTmp;
1062 4
    }
1063
1064
    /**
1065
     * @return mixed
1066
     */
1067 78
    private function fetch_assoc()
1068
    {
1069 78
        if ($this->_result instanceof \Doctrine\DBAL\Statement) {
1070
            if (
1071
                $this->doctrinePdoStmt
1072
                &&
1073
                $this->doctrinePdoStmt instanceof \Doctrine\DBAL\Driver\PDOStatement
1074
            ) {
1075
                if ($this->doctrinePdoStmtDataSeekInit === false) {
1076
                    $this->doctrinePdoStmtDataSeekInit = true;
1077
1078
                    $this->doctrinePdoStmtDataSeekFakeCache = $this->_result->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
1079
                }
1080
1081
                $return = ($this->doctrinePdoStmtDataSeekFakeCache[$this->doctrinePdoStmtDataSeekFake] ?? null);
1082
1083
                $this->doctrinePdoStmtDataSeekFake++;
1084
1085
                return $return;
1086
            }
1087
1088
            if (
1089
                $this->doctrineMySQLiStmt
1090
                &&
1091
                $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1092
            ) {
1093
                return $this->_result->fetch(
1094
                    \Doctrine\DBAL\FetchMode::ASSOCIATIVE,
1095
                    0 // FETCH_ORI_NEXT
1096
                );
1097
            }
1098
1099
            return null;
1100
        }
1101
1102 78
        return \mysqli_fetch_assoc($this->_result);
1103
    }
1104
1105
    /**
1106
     * @return array|bool
1107
     */
1108 1
    private function fetch_fields()
1109
    {
1110 1
        if ($this->_result instanceof \mysqli_result) {
1111 1
            return \mysqli_fetch_fields($this->_result);
1112
        }
1113
1114
        if ($this->doctrineMySQLiStmt) {
1115
            $metadataTmp = $this->doctrineMySQLiStmt->result_metadata();
1116
1117
            return $metadataTmp->fetch_fields();
1118
        }
1119
1120
        if ($this->doctrinePdoStmt) {
1121
            $fields = [];
1122
1123
            static $THIS_CLASS_TMP = null;
1124
            if ($THIS_CLASS_TMP === null) {
1125
                $THIS_CLASS_TMP = new \ReflectionClass(__CLASS__);
1126
            }
1127
1128
            $totalColumnsTmp = $this->doctrinePdoStmt->columnCount();
1129
            for ($counterTmp = 0; $counterTmp < $totalColumnsTmp; $counterTmp++) {
1130
                $metadataTmp = $this->doctrinePdoStmt->getColumnMeta($counterTmp);
1131
                $fieldTmp = new \stdClass();
1132
                foreach ($metadataTmp as $metadataTmpKey => $metadataTmpValue) {
1133
                    $fieldTmp->{$metadataTmpKey} = $metadataTmpValue;
1134
                }
1135
1136
                $typeNativeTmp = 'MYSQL_TYPE_' . $metadataTmp['native_type'];
1137
                $typeTmp = $THIS_CLASS_TMP->getConstant($typeNativeTmp);
1138
                if ($typeTmp) {
1139
                    $fieldTmp->type = $typeTmp;
1140
                } else {
1141
                    $fieldTmp->type = '';
1142
                }
1143
1144
                $fields[] = $fieldTmp;
1145
            }
1146
1147
            return $fields;
1148
        }
1149
1150
        return false;
1151
    }
1152
1153
    /**
1154
     * Returns the first row element from the result.
1155
     *
1156
     * @param string $column The column name to use as value (optional)
1157
     *
1158
     * @return mixed A row array or a single scalar value
1159
     */
1160 3
    public function first(string $column = null)
1161
    {
1162 3
        $pos = $this->current_row;
1163 3
        $first = $this->fetchCallable(0, $column);
1164 3
        $this->rewind($pos);
1165
1166 3
        return $first;
1167
    }
1168
1169
    /**
1170
     * free the memory
1171
     */
1172 100
    public function free()
1173
    {
1174
        if (
1175 100
            $this->_result
1176
            &&
1177 100
            $this->_result instanceof \mysqli_result
1178
        ) {
1179
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
1180 100
            @\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...
1181 100
            $this->_result = null;
1182
1183 100
            return true;
1184
        }
1185
1186
        if (
1187 1
            $this->doctrineMySQLiStmt
1188
            &&
1189 1
            $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1190
        ) {
1191
            $this->doctrineMySQLiStmt->free_result();
1192
            $this->_result = null;
1193
1194
            return true;
1195
        }
1196
1197 1
        $this->_result = null;
1198
1199 1
        return false;
1200
    }
1201
1202
    /**
1203
     * alias for "Result->fetch()"
1204
     *
1205
     * @see Result::fetch()
1206
     *
1207
     * @return array|false|object <p><strong>false</strong> on error</p>
1208
     */
1209 3
    public function get()
1210
    {
1211 3
        return $this->fetch();
1212
    }
1213
1214
    /**
1215
     * alias for "Result->fetchAll()"
1216
     *
1217
     * @see Result::fetchAll()
1218
     *
1219
     * @return array
1220
     */
1221 3
    public function getAll(): array
1222
    {
1223 3
        return $this->fetchAll();
1224
    }
1225
1226
    /**
1227
     * alias for "Result->fetchAllColumn()"
1228
     *
1229
     * @see Result::fetchAllColumn()
1230
     *
1231
     * @param string $column
1232
     * @param bool   $skipNullValues
1233
     *
1234
     * @return array
1235
     */
1236
    public function getAllColumn(string $column, bool $skipNullValues = false): array
1237
    {
1238
        return $this->fetchAllColumn($column, $skipNullValues);
1239
    }
1240
1241
    /**
1242
     * alias for "Result->fetchAllArray()"
1243
     *
1244
     * @see Result::fetchAllArray()
1245
     *
1246
     * @return array
1247
     */
1248 3
    public function getArray(): array
1249
    {
1250 3
        return $this->fetchAllArray();
1251
    }
1252
1253
    /**
1254
     * alias for "Result->fetchAllArrayy()"
1255
     *
1256
     * @see Result::fetchAllArrayy()
1257
     *
1258
     * @return Arrayy
1259
     */
1260
    public function getArrayy(): Arrayy
1261
    {
1262
        return $this->fetchAllArrayy();
1263
    }
1264
1265
    /**
1266
     * alias for "Result->fetchColumn()"
1267
     *
1268
     * @see Result::fetchColumn()
1269
     *
1270
     * @param string $column
1271
     * @param bool   $asArray
1272
     * @param bool   $skipNullValues
1273
     *
1274
     * @return array|string <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
1275
     *                      "$asArray"</p>
1276
     */
1277 3
    public function getColumn(string $column, bool $skipNullValues = true, bool $asArray = false)
1278
    {
1279 3
        return $this->fetchColumn($column, $skipNullValues, $asArray);
1280
    }
1281
1282
    /**
1283
     * @return string
1284
     */
1285 3
    public function getDefaultResultType(): string
1286
    {
1287 3
        return $this->_default_result_type;
1288
    }
1289
1290
    /**
1291
     * alias for "Result->fetchAllObject()"
1292
     *
1293
     * @see Result::fetchAllObject()
1294
     *
1295
     * @return array of mysql-objects
1296
     */
1297 3
    public function getObject(): array
1298
    {
1299 3
        return $this->fetchAllObject();
1300
    }
1301
1302
    /**
1303
     * alias for "Result->fetchAllYield()"
1304
     *
1305
     * @see Result::fetchAllYield()
1306
     *
1307
     * @param bool $asArray
1308
     *
1309
     * @return \Generator
1310
     */
1311 1
    public function getYield($asArray = false): \Generator
1312
    {
1313 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...
1314
    }
1315
1316
    /**
1317
     * Check if the result is empty.
1318
     *
1319
     * @return bool
1320
     */
1321 41
    public function is_empty(): bool
1322
    {
1323 41
        return !($this->num_rows > 0);
1324
    }
1325
1326
    /**
1327
     * Fetch all results as "json"-string.
1328
     *
1329
     * @return string
1330
     */
1331 3
    public function json(): string
1332
    {
1333 3
        $data = $this->fetchAllArray();
1334
1335 3
        return UTF8::json_encode($data);
1336
    }
1337
1338
    /**
1339
     * Returns the last row element from the result.
1340
     *
1341
     * @param string $column The column name to use as value (optional)
1342
     *
1343
     * @return mixed A row array or a single scalar value
1344
     */
1345 3
    public function last(string $column = null)
1346
    {
1347 3
        $pos = $this->current_row;
1348 3
        $last = $this->fetchCallable($this->num_rows - 1, $column);
1349 3
        $this->rewind($pos);
1350
1351 3
        return $last;
1352
    }
1353
1354
    /**
1355
     * Set the mapper...
1356
     *
1357
     * @param \Closure $callable
1358
     *
1359
     * @return $this
1360
     */
1361 1
    public function map(\Closure $callable): self
1362
    {
1363 1
        $this->_mapper = $callable;
1364
1365 1
        return $this;
1366
    }
1367
1368
    /**
1369
     * Alias of count(). Deprecated.
1370
     *
1371
     * @return int The number of rows in the result
1372
     */
1373 1
    public function num_rows(): int
1374
    {
1375 1
        return $this->count();
1376
    }
1377
1378
    /**
1379
     * ArrayAccess interface implementation.
1380
     *
1381
     * @param int $offset <p>Offset number</p>
1382
     *
1383
     * @return bool <p>true if offset exists, false otherwise</p>
1384
     */
1385 1
    public function offsetExists($offset): bool
1386
    {
1387 1
        return \is_int($offset) && $offset >= 0 && $offset < $this->num_rows;
1388
    }
1389
1390
    /**
1391
     * ArrayAccess interface implementation.
1392
     *
1393
     * @param int $offset Offset number
1394
     *
1395
     * @return mixed
1396
     */
1397 1
    public function offsetGet($offset)
1398
    {
1399 1
        if ($this->offsetExists($offset)) {
1400 1
            return $this->fetchCallable($offset);
1401
        }
1402
1403
        throw new \OutOfBoundsException("undefined offset (${offset})");
1404
    }
1405
1406
    /**
1407
     * ArrayAccess interface implementation. Not implemented by design.
1408
     *
1409
     * @param mixed $offset
1410
     * @param mixed $value
1411
     */
1412
    public function offsetSet($offset, $value)
1413
    {
1414
        /** @noinspection UselessReturnInspection */
1415
    }
1416
1417
    /**
1418
     * ArrayAccess interface implementation. Not implemented by design.
1419
     *
1420
     * @param mixed $offset
1421
     */
1422
    public function offsetUnset($offset)
1423
    {
1424
        /** @noinspection UselessReturnInspection */
1425
    }
1426
1427
    /**
1428
     * Reset the offset (data_seek) for the results.
1429
     *
1430
     * @return Result
1431
     */
1432 38
    public function reset(): self
1433
    {
1434 38
        $this->doctrinePdoStmtDataSeekFake = 0;
1435
1436 38
        if (!$this->is_empty()) {
1437
            if (
1438 38
                $this->doctrineMySQLiStmt
1439
                &&
1440 38
                $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1441
            ) {
1442
                $this->doctrineMySQLiStmt->data_seek(0);
1443
            }
1444
1445
            if (
1446 38
                $this->_result
1447
                &&
1448 38
                $this->_result instanceof \mysqli_result
1449
            ) {
1450 38
                \mysqli_data_seek($this->_result, 0);
1451
            }
1452
        }
1453
1454 38
        return $this;
1455
    }
1456
1457
    /**
1458
     * You can set the default result-type to Result::RESULT_TYPE_*.
1459
     *
1460
     * INFO: used for "fetch()" and "fetchAll()"
1461
     *
1462
     * @param string $default_result_type
1463
     */
1464 6
    public function setDefaultResultType(string $default_result_type = self::RESULT_TYPE_OBJECT)
1465
    {
1466
        if (
1467 6
            $default_result_type === self::RESULT_TYPE_OBJECT
1468
            ||
1469 6
            $default_result_type === self::RESULT_TYPE_ARRAY
1470
            ||
1471
            $default_result_type === self::RESULT_TYPE_ARRAYY
1472
            ||
1473 6
            $default_result_type === self::RESULT_TYPE_YIELD
1474
        ) {
1475 6
            $this->_default_result_type = $default_result_type;
1476
        }
1477 6
    }
1478
1479
    /**
1480
     * @param int      $offset
1481
     * @param int|null $length
1482
     * @param bool     $preserve_keys
1483
     *
1484
     * @return array
1485
     */
1486 1
    public function slice(int $offset = 0, int $length = null, bool $preserve_keys = false): array
1487
    {
1488
        // init
1489 1
        $slice = [];
1490
1491 1
        if ($offset < 0) {
1492 1
            if (\abs($offset) > $this->num_rows) {
1493 1
                $offset = 0;
1494
            } else {
1495 1
                $offset = $this->num_rows - (int) \abs($offset);
1496
            }
1497
        }
1498
1499 1
        $length = $length !== null ? (int) $length : $this->num_rows;
1500 1
        $n = 0;
1501 1
        for ($i = $offset; $i < $this->num_rows && $n < $length; $i++) {
1502 1
            if ($preserve_keys) {
1503 1
                $slice[$i] = $this->fetchCallable($i);
1504
            } else {
1505 1
                $slice[] = $this->fetchCallable($i);
1506
            }
1507 1
            ++$n;
1508
        }
1509
1510 1
        return $slice;
1511
    }
1512
}
1513