Completed
Push — master ( 195702...f3dab2 )
by Lars
02:13 queued 15s
created

Result::count()   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
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 bool
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 102
    public function __construct(string $sql, $result, \Closure $mapper = null)
143
    {
144 102
        $this->sql = $sql;
145
146
        if (
147 102
            !$result instanceof \mysqli_result
148
            &&
149 102
            !$result instanceof \Doctrine\DBAL\Statement
150
        ) {
151
            throw new \InvalidArgumentException('$result must be ' . \mysqli_result::class . ' or ' . \Doctrine\DBAL\Statement::class . ' !');
152
        }
153
154 102
        $this->_result = $result;
155
156 102
        if ($this->_result instanceof \Doctrine\DBAL\Statement) {
157
            $doctrineDriver = $this->_result->getWrappedStatement();
158
159
            if ($doctrineDriver instanceof \Doctrine\DBAL\Driver\PDOStatement) {
160
                $this->doctrinePdoStmt = $doctrineDriver;
161
            }
162
163
            if ($doctrineDriver instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliStatement) {
164
                // try to get the mysqli driver from doctrine
165
                $reflectionTmp = new \ReflectionClass($doctrineDriver);
166
                $propertyTmp = $reflectionTmp->getProperty('_stmt');
167
                $propertyTmp->setAccessible(true);
168
                $this->doctrineMySQLiStmt = $propertyTmp->getValue($doctrineDriver);
169
            }
170
171
            $this->num_rows = $this->_result->rowCount();
172
        } else {
173 102
            $this->num_rows = (int) $this->_result->num_rows;
174
        }
175
176 102
        $this->current_row = 0;
177
178 102
        $this->_mapper = $mapper;
179 102
    }
180
181
    /**
182
     * __destruct
183
     */
184 101
    public function __destruct()
185
    {
186 101
        $this->free();
187 101
    }
188
189
    /**
190
     * Runs a user-provided callback with the MySQLi_Result object given as
191
     * argument and returns the result, or returns the MySQLi_Result object if
192
     * called without an argument.
193
     *
194
     * @param callable $callback User-provided callback (optional)
195
     *
196
     * @return \Doctrine\DBAL\Statement|mixed|\mysqli_result
197
     */
198 2
    public function __invoke(callable $callback = null)
199
    {
200 2
        if ($callback !== null) {
201 2
            return $callback($this->_result);
202
        }
203
204 1
        return $this->_result;
205
    }
206
207
    /**
208
     * Get the current "num_rows" as string.
209
     *
210
     * @return string
211
     */
212
    public function __toString()
213
    {
214
        return (string) $this->num_rows;
215
    }
216
217
    /**
218
     * Cast data into int, float or string.
219
     *
220
     * <p>
221
     *   <br />
222
     *   INFO: install / use "mysqlnd"-driver for better performance
223
     * </p>
224
     *
225
     * @param array|object $data
226
     *
227
     * @return array|false|object
228
     *                            <p><strong>false</strong> on error</p>
229
     */
230 71
    private function &cast(&$data)
231
    {
232
        if (
233 71
            !$this->doctrinePdoStmt // pdo only have limited support for types, so we try to improve it
234
            &&
235 71
            Helper::isMysqlndIsUsed()
236
        ) {
237 71
            return $data;
238
        }
239
240
        // init
241
        static $FIELDS_CACHE = [];
242
        static $TYPES_CACHE = [];
243
244
        $result_hash = \spl_object_hash($this->_result);
245
        if (!isset($FIELDS_CACHE[$result_hash])) {
246
            $FIELDS_CACHE[$result_hash] = $this->fetch_fields();
247
        }
248
249
        if (
250
            !isset($FIELDS_CACHE[$result_hash])
251
            ||
252
            $FIELDS_CACHE[$result_hash] === false
253
        ) {
254
            /** @noinspection PhpUnnecessaryLocalVariableInspection */
255
            $dataTmp = false;
256
257
            return $dataTmp;
258
        }
259
260
        if (!isset($TYPES_CACHE[$result_hash])) {
261
            foreach ($FIELDS_CACHE[$result_hash] as $field) {
262
                switch ($field->type) {
263
                    case self::MYSQL_TYPE_BIT:
264
                        $TYPES_CACHE[$result_hash][$field->name] = 'boolean';
265
266
                        break;
267
                    case self::MYSQL_TYPE_TINY:
268
                    case self::MYSQL_TYPE_SHORT:
269
                    case self::MYSQL_TYPE_LONG:
270
                    case self::MYSQL_TYPE_LONGLONG:
271
                    case self::MYSQL_TYPE_INT24:
272
                        $TYPES_CACHE[$result_hash][$field->name] = 'integer';
273
274
                        break;
275
                    case self::MYSQL_TYPE_DOUBLE:
276
                    case self::MYSQL_TYPE_FLOAT:
277
                        $TYPES_CACHE[$result_hash][$field->name] = 'float';
278
279
                        break;
280
                    case self::MYSQL_TYPE_DECIMAL: // INFO: DECIMAL is a "string"-format for numbers
281
                    case self::MYSQL_TYPE_NEWDECIMAL:
282
                    default:
283
                        $TYPES_CACHE[$result_hash][$field->name] = 'string';
284
285
                        break;
286
                }
287
            }
288
        }
289
290
        if (\is_array($data)) {
291 View Code Duplication
            foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
292
                if (isset($data[$type_name])) {
293
                    \settype($data[$type_name], $type);
294
                }
295
            }
296
        } elseif (\is_object($data)) {
297 View Code Duplication
            foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
298
                if (isset($data->{$type_name})) {
299
                    \settype($data->{$type_name}, $type);
300
                }
301
            }
302
        }
303
304
        return $data;
305
    }
306
307
    /**
308
     * Countable interface implementation.
309
     *
310
     * @return int The number of rows in the result
311
     */
312 2
    public function count(): int
313
    {
314 2
        return $this->num_rows;
315
    }
316
317
    /**
318
     * Iterator interface implementation.
319
     *
320
     * @return mixed The current element
321
     */
322 5
    public function current()
323
    {
324 5
        return $this->fetchCallable($this->current_row);
325
    }
326
327
    /**
328
     * Iterator interface implementation.
329
     *
330
     * @return int The current element key (row index; zero-based)
331
     */
332 1
    public function key(): int
333
    {
334 1
        return $this->current_row;
335
    }
336
337
    /**
338
     * Iterator interface implementation.
339
     *
340
     * @return void
341
     */
342 5
    public function next()
343
    {
344 5
        $this->current_row++;
345 5
    }
346
347
    /**
348
     * Iterator interface implementation.
349
     *
350
     * @param int $row Row position to rewind to; defaults to 0
351
     *
352
     * @return void
353
     */
354 12
    public function rewind($row = 0)
355
    {
356 12
        if ($this->seek($row)) {
357 10
            $this->current_row = $row;
358
        }
359 12
    }
360
361
    /**
362
     * Moves the internal pointer to the specified row position.
363
     *
364
     * @param int $row <p>Row position; zero-based and set to 0 by default</p>
365
     *
366
     * @return bool
367
     *              <p>true on success, false otherwise</p>
368
     */
369 20
    public function seek($row = 0): bool
370
    {
371 20
        if (\is_int($row) && $row >= 0 && $row < $this->num_rows) {
372
            if (
373 16
                $this->doctrineMySQLiStmt
374
                &&
375 16
                $this->doctrineMySQLiStmt instanceof \mysqli_stmt
376
            ) {
377
                $this->doctrineMySQLiStmt->data_seek($row);
378
379
                return true;
380
            }
381
382
            if (
383 16
                $this->doctrinePdoStmt
384
                &&
385 16
                $this->doctrinePdoStmt instanceof \Doctrine\DBAL\Driver\PDOStatement
386
            ) {
387
                return true;
388
            }
389
390 16
            if ($this->_result instanceof \mysqli_result) {
391 16
                return \mysqli_data_seek($this->_result, $row);
392
            }
393
        }
394
395 4
        return false;
396
    }
397
398
    /**
399
     * Iterator interface implementation.
400
     *
401
     * @return bool
402
     *              <p>true if the current index is valid, false otherwise</p>
403
     */
404 5
    public function valid(): bool
405
    {
406 5
        return $this->current_row < $this->num_rows;
407
    }
408
409
    /**
410
     * Fetch.
411
     *
412
     * <p>
413
     *   <br />
414
     *   INFO: this will return an object by default, not an array<br />
415
     *   and you can change the behaviour via "Result->setDefaultResultType()"
416
     * </p>
417
     *
418
     * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
419
     *
420
     * @return array|false|object
421
     *                            <p><strong>false</strong> on error</p>
422
     */
423 6
    public function &fetch(bool $reset = false)
424
    {
425
        // init
426 6
        $return = false;
427
428 6
        if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
429 6
            $return = $this->fetchObject(null, null, $reset);
430 6
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
431 6
            $return = $this->fetchArray($reset);
432
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
433
            $return = $this->fetchArrayy($reset);
434
        } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
435
            $return = $this->fetchYield(null, null, $reset);
436
        }
437
438 6
        return $return;
439
    }
440
441
    /**
442
     * Fetch all results.
443
     *
444
     * <p>
445
     *   <br />
446
     *   INFO: this will return an object by default, not an array<br />
447
     *   and you can change the behaviour via "Result->setDefaultResultType()"
448
     * </p>
449
     *
450
     * @return array
451
     */
452 6
    public function &fetchAll(): array
453
    {
454
        // init
455 6
        $return = [];
456
457 6
        if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
458 6
            $return = $this->fetchAllObject();
459 3
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
460 3
            $return = $this->fetchAllArray();
461
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
462
            $return = $this->fetchAllArrayy();
463
        } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
464
            $return = $this->fetchAllYield();
465
        }
466
467 6
        return $return;
468
    }
469
470
    /**
471
     * Fetch all results as array.
472
     *
473
     * @return array
474
     */
475 20 View Code Duplication
    public function &fetchAllArray(): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
476
    {
477
        // init
478 20
        $data = [];
479
480 20
        if ($this->is_empty()) {
481
            return $data;
482
        }
483
484 20
        $this->reset();
485
486
        /** @noinspection PhpAssignmentInConditionInspection */
487 20
        while ($row = $this->fetch_assoc()) {
488 20
            $data[] = $this->cast($row);
489
        }
490
491 20
        return $data;
492
    }
493
494
    /**
495
     * Fetch all results as "Arrayy"-object.
496
     *
497
     * @return Arrayy
498
     */
499 11 View Code Duplication
    public function &fetchAllArrayy(): Arrayy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
500
    {
501
        // init
502 11
        $arrayy = Arrayy::create();
503
504 11
        if ($this->is_empty()) {
505
            return $arrayy;
506
        }
507
508 11
        $this->reset();
509
510
        /** @noinspection PhpAssignmentInConditionInspection */
511 11
        while ($row = $this->fetch_assoc()) {
512 11
            $arrayy[] = $this->cast($row);
513
        }
514
515 11
        return $arrayy;
516
    }
517
518
    /**
519
     * Fetch all results as "Arrayy"-object.
520
     *
521
     * @return Arrayy
522
     */
523 15
    public function fetchAllArrayyYield(): Arrayy
524
    {
525 15
        if ($this->is_empty()) {
526
            return Arrayy::create([]);
527
        }
528
529 15
        return Arrayy::createFromGeneratorFunction(
530
            function () {
531 15
                $this->reset();
532
533
                /** @noinspection PhpAssignmentInConditionInspection */
534 15
                while ($row = $this->fetch_assoc()) {
535 15
                    yield $this->cast($row);
536
                }
537 15
            }
538
        );
539
    }
540
541
    /**
542
     * Fetch a single column as an 1-dimension array.
543
     *
544
     * @param string $column
545
     * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
546
     *
547
     * @return array
548
     *               <p>Return an empty array if the "$column" wasn't found</p>
549
     */
550 5
    public function fetchAllColumn(string $column, bool $skipNullValues = false): array
551
    {
552 5
        $return = $this->fetchColumn(
553 5
            $column,
554 5
            $skipNullValues,
555 5
            true
556
        );
557
558 5
        \assert(\is_array($return));
559
560 5
        return $return;
561
    }
562
563
    /**
564
     * Fetch all results as array with objects.
565
     *
566
     * @param object|string|null $class  <p>
567
     *                              <strong>string</strong>: create a new object (with optional constructor
568
     *                              parameter)<br>
569
     *                              <strong>object</strong>: use a object and fill the the data into
570
     *                              </p>
571
     * @param array|null    $params optional
572
     *                              <p>
573
     *                              An array of parameters to pass to the constructor, used if $class is a
574
     *                              string.
575
     *                              </p>
576
     *
577
     * @return object[]
578
     *
579
     * @psalm-param class-string|object|null $class
580
     * @psalm-param array<int, mixed>|null $params
581
     */
582 9
    public function &fetchAllObject(
583
        $class = null,
584
        array $params = null
585
    ): array
586
    {
587
        // init
588 9
        $data = [];
589
590 9
        foreach ($this->fetchAllYield($class, $params) as $object) {
591 9
            $data[] = $object;
592
        }
593
594 9
        return $data;
595
    }
596
597
    /**
598
     * Fetch all results as "\Generator" via yield.
599
     *
600
     * @param object|string|null $class  <p>
601
     *                              <strong>string</strong>: create a new object (with optional constructor
602
     *                              parameter)<br>
603
     *                              <strong>object</strong>: use a object and fill the the data into
604
     *                              </p>
605
     * @param array|null    $params optional
606
     *                              <p>
607
     *                              An array of parameters to pass to the constructor, used if $class is a
608
     *                              string.
609
     *                              </p>
610
     *
611
     * @return \Generator
612
     *
613
      @psalm-param class-string|object|null $class
614
     * @psalm-param array<int, mixed>|null $params
615
     */
616 17
    public function &fetchAllYield(
617
        $class = null,
618
        array $params = null
619
    ): \Generator
620
    {
621 17
        if ($this->is_empty()) {
622
            return;
623
        }
624
625
        // init
626 17
        $this->reset();
627
628
        // fallback
629 17
        if (!$class || $class === 'stdClass') {
630 17
            $class = \stdClass::class;
631
        }
632
633 17
        if (\is_object($class)) {
634
            $classTmpOrig = $class;
635 17
        } elseif ($class && $params) {
636 3
            $reflectorTmp = new \ReflectionClass($class);
637 3
            $classTmpOrig = $reflectorTmp->newInstanceArgs($params);
638
        } else {
639 17
            $classTmpOrig = new $class();
640
        }
641
642 17
        if ($class === \stdClass::class) {
643 17
            $propertyAccessor = null;
644
        } else {
645 6
            $propertyAccessor = PropertyAccess::createPropertyAccessor();
646
        }
647
648
        /** @noinspection PhpAssignmentInConditionInspection */
649 17
        while ($row = $this->fetch_assoc()) {
650 17
            $classTmp = clone $classTmpOrig;
651
652 17
            $row = $this->cast($row);
653 17
            if ($row !== false) {
654 17
                foreach ($row as $key => $value) {
655 17
                    if ($class === \stdClass::class) {
656 17
                        $classTmp->{$key} = $value;
657
                    } else {
658 6
                        assert($propertyAccessor instanceof \Symfony\Component\PropertyAccess\PropertyAccessor);
659 17
                        $propertyAccessor->setValue($classTmp, $key, $value);
660
                    }
661
                }
662
            }
663
664 17
            yield $classTmp;
665
        }
666 17
    }
667
668
    /**
669
     * Fetch as array.
670
     *
671
     * @param bool $reset
672
     *
673
     * @return array|false
674
     *                     <p><strong>false</strong> on error</p>
675
     */
676 30
    public function fetchArray(bool $reset = false)
677
    {
678 30
        if ($reset) {
679 3
            $this->reset();
680
        }
681
682 30
        $row = $this->fetch_assoc();
683 30
        if ($row) {
684 27
            $return = $this->cast($row);
685
686 27
            \assert(\is_array($return));
687
688 27
            return $return;
689
        }
690
691 5
        if ($row === null || $row === false) {
692 5
            return [];
693
        }
694
695
        return false;
696
    }
697
698
    /**
699
     * Fetch data as a key/value pair array.
700
     *
701
     * <p>
702
     *   <br />
703
     *   INFO: both "key" and "value" must exists in the fetched data
704
     *   the key will be the new key of the result-array
705
     *   <br /><br />
706
     * </p>
707
     *
708
     * e.g.:
709
     * <code>
710
     *    fetchArrayPair('some_id', 'some_value');
711
     *    // array(127 => 'some value', 128 => 'some other value')
712
     * </code>
713
     *
714
     * @param string $key
715
     * @param string $value
716
     *
717
     * @return Arrayy
718
     */
719 3
    public function fetchArrayPair(string $key, string $value): Arrayy
720
    {
721
        // init
722 3
        $arrayPair = new Arrayy();
723 3
        $data = $this->fetchAllArrayyYield();
724
725 3
        foreach ($data as $_row) {
726
            if (
727 3
                \array_key_exists($key, $_row)
728
                &&
729 3
                \array_key_exists($value, $_row)
730
            ) {
731 3
                $_key = $_row[$key];
732 3
                $_value = $_row[$value];
733 3
                $arrayPair[$_key] = $_value;
734
            }
735
        }
736
737 3
        return $arrayPair;
738
    }
739
740
    /**
741
     * Fetch as "Arrayy"-object.
742
     *
743
     * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
744
     *
745
     * @return Arrayy|false
746
     *                      <p><strong>false</strong> on error</p>
747
     */
748 6
    public function fetchArrayy(bool $reset = false)
749
    {
750 6
        if ($reset) {
751
            $this->reset();
752
        }
753
754 6
        $row = $this->fetch_assoc();
755 6
        if ($row) {
756 3
            return Arrayy::create($this->cast($row));
757
        }
758
759 3
        if ($row === null || $row === false) {
760 3
            return Arrayy::create();
761
        }
762
763
        return false;
764
    }
765
766
    /**
767
     * Fetches a row or a single column within a row. Returns null if there are
768
     * no more rows in the result.
769
     *
770
     * @param int    $row    The row number (optional)
771
     * @param string $column The column name (optional)
772
     *
773
     * @return mixed An associative array or a scalar value
774
     */
775 14
    public function fetchCallable(int $row = null, string $column = null)
776
    {
777 14
        if (!$this->num_rows) {
778 2
            return null;
779
        }
780
781 12
        if ($row !== null) {
782 11
            $this->seek($row);
783
        }
784
785 12
        $rows = $this->fetch_assoc();
786
787 12
        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...
788
            if (
789 5
                \is_array($rows)
790
                &&
791 5
                isset($rows[$column])
792
            ) {
793 5
                return $rows[$column];
794
            }
795
796
            return null;
797
        }
798
799 11
        if (\is_callable($this->_mapper)) {
800 1
            return \call_user_func($this->_mapper, $rows);
801
        }
802
803 11
        return $rows;
804
    }
805
806
    /**
807
     * Fetch a single column as string (or as 1-dimension array).
808
     *
809
     * @param string $column
810
     * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
811
     * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
812
     *
813
     * @return array|string
814
     *                      <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
815
     *                      "$asArray"</p>
816
     */
817 9
    public function &fetchColumn(
818
        string $column = '',
819
        bool $skipNullValues = true,
820
        bool $asArray = false
821
    ) {
822 9
        if (!$asArray) {
823
            // init
824 7
            $columnData = '';
825
826 7
            $data = $this->fetchAllArrayy()->reverse()->getArray();
827 7 View Code Duplication
            foreach ($data as &$_row) {
828 7
                if ($skipNullValues) {
829 7
                    if (!isset($_row[$column])) {
830 7
                        continue;
831
                    }
832 3
                } elseif (!\array_key_exists($column, $_row)) {
833 3
                    break;
834
                }
835
836 7
                $columnData = $_row[$column];
837
838 7
                break;
839
            }
840
841 7
            return $columnData;
842
        }
843
844
        // -- return as array -->
845
846
        // init
847 5
        $columnData = [];
848
849 5 View Code Duplication
        foreach ($this->fetchAllYield() as $_row) {
850 5
            if ($skipNullValues) {
851 3
                if (!isset($_row->{$column})) {
852 3
                    continue;
853
                }
854 5
            } elseif (!\property_exists($_row, $column)) {
855 3
                break;
856
            }
857
858 5
            $columnData[] = $_row->{$column};
859
        }
860
861 5
        return $columnData;
862
    }
863
864
    /**
865
     * Return rows of field information in a result set.
866
     *
867
     * @param bool $as_array Return each field info as array; defaults to false
868
     *
869
     * @return array
870
     *               <p>Array of field information each as an associative array.</p>
871
     */
872 1
    public function &fetchFields(bool $as_array = false): array
873
    {
874 1
        $fields = $this->fetch_fields();
875 1
        if ($fields === false) {
876
            $fields = [];
877
878
            return $fields;
879
        }
880
881 1
        if ($as_array) {
882 1
            $fields = \array_map(
883
                static function ($object) {
884 1
                    return (array) $object;
885 1
                },
886 1
                $fields
887
            );
888
889 1
            return $fields;
890
        }
891
892
        return $fields;
893
    }
894
895
    /**
896
     * Returns all rows at once as a grouped array of scalar values or arrays.
897
     *
898
     * @param string $group  The column name to use for grouping
899
     * @param string $column The column name to use as values (optional)
900
     *
901
     * @return array
902
     *               <p>A grouped array of scalar values or arrays.</p>
903
     */
904 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...
905
    {
906
        // init
907 1
        $groups = [];
908 1
        $pos = $this->current_row;
909
910 1
        foreach ($this->fetchAllArrayyYield() as $row) {
911 1
            if (!\array_key_exists($group, $row)) {
912
                continue;
913
            }
914
915 1
            if ($column !== null) {
916 1
                if (!\array_key_exists($column, $row)) {
917
                    continue;
918
                }
919
920 1
                $groups[$row[$group]][] = $row[$column];
921
            } else {
922 1
                $groups[$row[$group]][] = $row;
923
            }
924
        }
925
926 1
        $this->rewind($pos);
927
928 1
        return $groups;
929
    }
930
931
    /**
932
     * Fetch as object.
933
     *
934
     * @param object|string|null $class  <p>
935
     *                              <strong>string</strong>: create a new object (with optional constructor
936
     *                              parameter)<br>
937
     *                              <strong>object</strong>: use a object and fill the the data into
938
     *                              </p>
939
     * @param array|null    $params optional
940
     *                              <p>
941
     *                              An array of parameters to pass to the constructor, used if $class is a
942
     *                              string.
943
     *                              </p>
944
     * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
945
     *
946
     * @return false|object
947
     *                      <p><strong>false</strong> on error</p>
948
     *
949
     * @psalm-param class-string|object|null $class
950
     * @psalm-param array<int, mixed>|null $params
951
     */
952 21 View Code Duplication
    public function &fetchObject(
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...
953
        $class = null,
954
        array $params = null,
955
        bool $reset = false
956
    ) {
957 21
        if ($reset) {
958 9
            $this->reset();
959
        }
960
961
        // fallback
962 21
        if (!$class || $class === 'stdClass') {
963 21
            $class = \stdClass::class;
964
        }
965
966 21
        $row = $this->fetch_assoc();
967 21
        $row = $row ? $this->cast($row) : false;
968
969 21
        if (!$row) {
970
            /** @noinspection PhpUnnecessaryLocalVariableInspection */
971 3
            $dataTmp = false;
972
973 3
            return $dataTmp;
974
        }
975
976 21
        if (\is_object($class)) {
977 3
            $classTmp = $class;
978 21
        } elseif ($class && $params) {
979 3
            $reflectorTmp = new \ReflectionClass($class);
980 3
            $classTmp = $reflectorTmp->newInstanceArgs($params);
981
        } else {
982 21
            $classTmp = new $class();
983
        }
984
985 21
        if ($class === \stdClass::class) {
986 21
            $propertyAccessor = null;
987
        } else {
988 3
            $propertyAccessor = PropertyAccess::createPropertyAccessor();
989
        }
990
991 21
        foreach ($row as $key => &$value) {
992 21
            if ($class === \stdClass::class) {
993 21
                $classTmp->{$key} = $value;
994
            } else {
995 3
                assert($propertyAccessor instanceof \Symfony\Component\PropertyAccess\PropertyAccessor);
996 21
                $propertyAccessor->setValue($classTmp, $key, $value);
997
            }
998
        }
999
1000 21
        return $classTmp;
1001
    }
1002
1003
    /**
1004
     * Returns all rows at once as key-value pairs.
1005
     *
1006
     * @param string $key    The column name to use as keys
1007
     * @param string $column The column name to use as values (optional)
1008
     *
1009
     * @return array
1010
     *               <p>An array of key-value pairs.</p>
1011
     */
1012 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...
1013
    {
1014
        // init
1015 1
        $pairs = [];
1016 1
        $pos = $this->current_row;
1017
1018 1
        foreach ($this->fetchAllArrayyYield() as $row) {
1019 1
            if (!\array_key_exists($key, $row)) {
1020
                continue;
1021
            }
1022
1023 1
            if ($column !== null) {
1024 1
                if (!\array_key_exists($column, $row)) {
1025
                    continue;
1026
                }
1027
1028 1
                $pairs[$row[$key]] = $row[$column];
1029
            } else {
1030 1
                $pairs[$row[$key]] = $row;
1031
            }
1032
        }
1033
1034 1
        $this->rewind($pos);
1035
1036 1
        return $pairs;
1037
    }
1038
1039
    /**
1040
     * Returns all rows at once, transposed as an array of arrays. Instead of
1041
     * returning rows of columns, this method returns columns of rows.
1042
     *
1043
     * @param string $column The column name to use as keys (optional)
1044
     *
1045
     * @return array
1046
     *               <p>A transposed array of arrays</p>
1047
     */
1048 1
    public function fetchTranspose(string $column = null)
1049
    {
1050
        // init
1051 1
        $keys = $column !== null ? $this->fetchAllColumn($column) : [];
1052 1
        $rows = [];
1053 1
        $pos = $this->current_row;
1054
1055 1
        foreach ($this->fetchAllYield() as $row) {
1056 1
            foreach ($row as $key => &$value) {
1057 1
                $rows[$key][] = $value;
1058
            }
1059
        }
1060
1061 1
        $this->rewind($pos);
1062
1063 1
        if (empty($keys)) {
1064 1
            return $rows;
1065
        }
1066
1067 1
        return \array_map(
1068
            static function ($values) use ($keys) {
1069 1
                return \array_combine($keys, $values);
1070 1
            },
1071 1
            $rows
1072
        );
1073
    }
1074
1075
    /**
1076
     * Fetch as "\Generator" via yield.
1077
     *
1078
     * @param object|string|null $class  <p>
1079
     *                              <strong>string</strong>: create a new object (with optional constructor
1080
     *                              parameter)<br>
1081
     *                              <strong>object</strong>: use a object and fill the the data into
1082
     *                              </p>
1083
     * @param array|null    $params optional
1084
     *                              <p>
1085
     *                              An array of parameters to pass to the constructor, used if $class is a
1086
     *                              string.
1087
     *                              </p>
1088
     * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
1089
     *
1090
     * @return \Generator
1091
     *
1092
     * @psalm-param class-string|object|null $class
1093
     * @psalm-param array<int, mixed>|null $params
1094
     */
1095 4 View Code Duplication
    public function fetchYield(
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...
1096
        $class = null,
1097
        array $params = null,
1098
        bool $reset = false
1099
    ): \Generator {
1100 4
        if ($reset) {
1101
            $this->reset();
1102
        }
1103
1104
        // fallback
1105 4
        if (!$class || $class === 'stdClass') {
1106 3
            $class = \stdClass::class;
1107
        }
1108
1109 4
        if (\is_object($class)) {
1110
            $classTmp = $class;
1111 4
        } elseif ($class && $params) {
1112
            $reflectorTmp = new \ReflectionClass($class);
1113
            $classTmp = $reflectorTmp->newInstanceArgs($params);
1114
        } else {
1115 4
            $classTmp = new $class();
1116
        }
1117
1118 4
        $row = $this->fetch_assoc();
1119 4
        $row = $row ? $this->cast($row) : false;
1120
1121 4
        if (!$row) {
1122
            return;
1123
        }
1124
1125 4
        if ($class === \stdClass::class) {
1126 3
            $propertyAccessor = null;
1127
        } else {
1128 1
            $propertyAccessor = PropertyAccess::createPropertyAccessor();
1129
        }
1130
1131
        /** @noinspection AlterInForeachInspection */
1132 4
        foreach ($row as $key => &$value) {
1133 4
            if ($class === \stdClass::class) {
1134 3
                $classTmp->{$key} = $value;
1135
            } else {
1136 1
                assert($propertyAccessor instanceof \Symfony\Component\PropertyAccess\PropertyAccessor);
1137 4
                $propertyAccessor->setValue($classTmp, $key, $value);
1138
            }
1139
        }
1140
1141 4
        yield $classTmp;
1142 4
    }
1143
1144
    /**
1145
     * @return mixed|null
1146
     */
1147 79
    private function fetch_assoc()
1148
    {
1149 79
        if ($this->_result instanceof \Doctrine\DBAL\Statement) {
1150
            if (
1151
                $this->doctrinePdoStmt
1152
                &&
1153
                $this->doctrinePdoStmt instanceof \Doctrine\DBAL\Driver\PDOStatement
1154
            ) {
1155
                if ($this->doctrinePdoStmtDataSeekInit === false) {
1156
                    $this->doctrinePdoStmtDataSeekInit = true;
1157
1158
                    $this->doctrinePdoStmtDataSeekFakeCache = $this->_result->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
1159
                }
1160
1161
                $return = ($this->doctrinePdoStmtDataSeekFakeCache[$this->doctrinePdoStmtDataSeekFake] ?? null);
1162
1163
                $this->doctrinePdoStmtDataSeekFake++;
1164
1165
                return $return;
1166
            }
1167
1168
            if (
1169
                $this->doctrineMySQLiStmt
1170
                &&
1171
                $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1172
            ) {
1173
                return $this->_result->fetch(
1174
                    \Doctrine\DBAL\FetchMode::ASSOCIATIVE,
1175
                    0 // FETCH_ORI_NEXT
1176
                );
1177
            }
1178
1179
            return null;
1180
        }
1181
1182 79
        return \mysqli_fetch_assoc($this->_result);
1183
    }
1184
1185
    /**
1186
     * @return array|false
1187
     */
1188 1
    private function fetch_fields()
1189
    {
1190 1
        if ($this->_result instanceof \mysqli_result) {
1191 1
            return \mysqli_fetch_fields($this->_result);
1192
        }
1193
1194
        if ($this->doctrineMySQLiStmt) {
1195
            $metadataTmp = $this->doctrineMySQLiStmt->result_metadata();
1196
            if ($metadataTmp === false) {
1197
                return [];
1198
            }
1199
1200
            return $metadataTmp->fetch_fields();
1201
        }
1202
1203
        if ($this->doctrinePdoStmt) {
1204
            $fields = [];
1205
1206
            static $THIS_CLASS_TMP = null;
1207
            if ($THIS_CLASS_TMP === null) {
1208
                $THIS_CLASS_TMP = new \ReflectionClass(__CLASS__);
1209
            }
1210
1211
            $totalColumnsTmp = $this->doctrinePdoStmt->columnCount();
1212
            for ($counterTmp = 0; $counterTmp < $totalColumnsTmp; $counterTmp++) {
1213
                $metadataTmp = $this->doctrinePdoStmt->getColumnMeta($counterTmp);
1214
                $fieldTmp = new \stdClass();
1215
                foreach ($metadataTmp as $metadataTmpKey => $metadataTmpValue) {
1216
                    $fieldTmp->{$metadataTmpKey} = $metadataTmpValue;
1217
                }
1218
1219
                $typeNativeTmp = 'MYSQL_TYPE_' . $metadataTmp['native_type'];
1220
                $typeTmp = $THIS_CLASS_TMP->getConstant($typeNativeTmp);
1221
                if ($typeTmp) {
1222
                    $fieldTmp->type = $typeTmp;
1223
                } else {
1224
                    $fieldTmp->type = '';
1225
                }
1226
1227
                $fields[] = $fieldTmp;
1228
            }
1229
1230
            return $fields;
1231
        }
1232
1233
        return false;
1234
    }
1235
1236
    /**
1237
     * Returns the first row element from the result.
1238
     *
1239
     * @param string $column The column name to use as value (optional)
1240
     *
1241
     * @return mixed
1242
     *               <p>A row array or a single scalar value</p>
1243
     */
1244 3
    public function first(string $column = null)
1245
    {
1246 3
        $pos = $this->current_row;
1247 3
        $first = $this->fetchCallable(0, $column);
1248 3
        $this->rewind($pos);
1249
1250 3
        return $first;
1251
    }
1252
1253
    /**
1254
     * free the memory
1255
     *
1256
     * @return bool
1257
     */
1258 101
    public function free()
1259
    {
1260 101
        if ($this->_result instanceof \mysqli_result) {
1261
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
1262 101
            @\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...
1263
1264 101
            return true;
1265
        }
1266
1267
        if (
1268
            $this->doctrineMySQLiStmt
1269
            &&
1270
            $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1271
        ) {
1272
            $this->doctrineMySQLiStmt->free_result();
1273
1274
            return true;
1275
        }
1276
1277
        return false;
1278
    }
1279
1280
    /**
1281
     * alias for "Result->fetch()"
1282
     *
1283
     * @return array|false|object
1284
     *                            <p><strong>false</strong> on error</p>
1285
     *
1286
     * @see Result::fetch()
1287
     */
1288 3
    public function get()
1289
    {
1290 3
        return $this->fetch();
1291
    }
1292
1293
    /**
1294
     * alias for "Result->fetchAll()"
1295
     *
1296
     * @return array
1297
     *
1298
     * @see Result::fetchAll()
1299
     */
1300 3
    public function getAll(): array
1301
    {
1302 3
        return $this->fetchAll();
1303
    }
1304
1305
    /**
1306
     * alias for "Result->fetchAllColumn()"
1307
     *
1308
     * @param string $column
1309
     * @param bool   $skipNullValues
1310
     *
1311
     * @return array
1312
     *
1313
     * @see Result::fetchAllColumn()
1314
     */
1315
    public function getAllColumn(string $column, bool $skipNullValues = false): array
1316
    {
1317
        return $this->fetchAllColumn($column, $skipNullValues);
1318
    }
1319
1320
    /**
1321
     * alias for "Result->fetchAllArray()"
1322
     *
1323
     * @return array
1324
     *
1325
     * @see Result::fetchAllArray()
1326
     */
1327 4
    public function getArray(): array
1328
    {
1329 4
        return $this->fetchAllArray();
1330
    }
1331
1332
    /**
1333
     * alias for "Result->fetchAllArrayy()"
1334
     *
1335
     * @return Arrayy
1336
     *
1337
     * @see Result::fetchAllArrayy()
1338
     */
1339
    public function getArrayy(): Arrayy
1340
    {
1341
        return $this->fetchAllArrayy();
1342
    }
1343
1344
    /**
1345
     * alias for "Result->fetchColumn()"
1346
     *
1347
     * @param string $column
1348
     * @param bool   $asArray
1349
     * @param bool   $skipNullValues
1350
     *
1351
     * @return array|string
1352
     *                      <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
1353
     *                      "$asArray"</p>
1354
     *
1355
     * @see Result::fetchColumn()
1356
     */
1357 3
    public function getColumn(
1358
        string $column,
1359
        bool $skipNullValues = true,
1360
        bool $asArray = false
1361
    ) {
1362 3
        return $this->fetchColumn(
1363 3
            $column,
1364 3
            $skipNullValues,
1365 3
            $asArray
1366
        );
1367
    }
1368
1369
    /**
1370
     * @return string
1371
     */
1372 3
    public function getDefaultResultType(): string
1373
    {
1374 3
        return $this->_default_result_type;
1375
    }
1376
1377
    /**
1378
     * alias for "Result->fetchAllObject()"
1379
     *
1380
     * @return array of mysql-objects
1381
     *
1382
     * @see Result::fetchAllObject()
1383
     */
1384 3
    public function getObject(): array
1385
    {
1386 3
        return $this->fetchAllObject();
1387
    }
1388
1389
    /**
1390
     * alias for "Result->fetchAllYield()"
1391
     *
1392
     * @return \Generator
1393
     *
1394
     * @see Result::fetchAllYield()
1395
     */
1396 1
    public function getYield(): \Generator
1397
    {
1398 1
        return $this->fetchAllYield();
1399
    }
1400
1401
    /**
1402
     * Check if the result is empty.
1403
     *
1404
     * @return bool
1405
     */
1406 44
    public function is_empty(): bool
1407
    {
1408 44
        return !($this->num_rows > 0);
1409
    }
1410
1411
    /**
1412
     * Fetch all results as "json"-string.
1413
     *
1414
     * @return false|string
1415
     */
1416 3
    public function json()
1417
    {
1418 3
        $data = $this->fetchAllArray();
1419
1420 3
        return UTF8::json_encode($data);
1421
    }
1422
1423
    /**
1424
     * Returns the last row element from the result.
1425
     *
1426
     * @param string $column The column name to use as value (optional)
1427
     *
1428
     * @return mixed A row array or a single scalar value
1429
     */
1430 3
    public function last(string $column = null)
1431
    {
1432 3
        $pos = $this->current_row;
1433 3
        $last = $this->fetchCallable($this->num_rows - 1, $column);
1434 3
        $this->rewind($pos);
1435
1436 3
        return $last;
1437
    }
1438
1439
    /**
1440
     * Set the mapper...
1441
     *
1442
     * @param \Closure $callable
1443
     *
1444
     * @return $this
1445
     */
1446 1
    public function map(\Closure $callable): self
1447
    {
1448 1
        $this->_mapper = $callable;
1449
1450 1
        return $this;
1451
    }
1452
1453
    /**
1454
     * Alias of count(). Deprecated.
1455
     *
1456
     * @return int
1457
     *             <p>The number of rows in the result.</p>
1458
     */
1459 1
    public function num_rows(): int
1460
    {
1461 1
        return $this->count();
1462
    }
1463
1464
    /**
1465
     * ArrayAccess interface implementation.
1466
     *
1467
     * @param int $offset <p>Offset number</p>
1468
     *
1469
     * @return bool
1470
     *              <p>true if offset exists, false otherwise.</p>
1471
     */
1472 1
    public function offsetExists($offset): bool
1473
    {
1474 1
        return \is_int($offset) && $offset >= 0 && $offset < $this->num_rows;
1475
    }
1476
1477
    /**
1478
     * ArrayAccess interface implementation.
1479
     *
1480
     * @param int $offset Offset number
1481
     *
1482
     * @return mixed
1483
     */
1484 1
    public function offsetGet($offset)
1485
    {
1486 1
        if ($this->offsetExists($offset)) {
1487 1
            return $this->fetchCallable($offset);
1488
        }
1489
1490
        throw new \OutOfBoundsException("undefined offset (${offset})");
1491
    }
1492
1493
    /**
1494
     * ArrayAccess interface implementation. Not implemented by design.
1495
     *
1496
     * @param mixed $offset
1497
     * @param mixed $value
1498
     *
1499
     * @return void
1500
     */
1501
    public function offsetSet($offset, $value)
1502
    {
1503
    }
1504
1505
    /**
1506
     * ArrayAccess interface implementation. Not implemented by design.
1507
     *
1508
     * @param mixed $offset
1509
     *
1510
     * @return void
1511
     */
1512
    public function offsetUnset($offset)
1513
    {
1514
    }
1515
1516
    /**
1517
     * Reset the offset (data_seek) for the results.
1518
     *
1519
     * @return Result
1520
     */
1521 41
    public function reset(): self
1522
    {
1523 41
        $this->doctrinePdoStmtDataSeekFake = 0;
1524
1525 41
        if (!$this->is_empty()) {
1526 41
            if ($this->doctrineMySQLiStmt instanceof \mysqli_stmt) {
1527
                $this->doctrineMySQLiStmt->data_seek(0);
1528
            }
1529
1530 41
            if ($this->_result instanceof \mysqli_result) {
1531 41
                \mysqli_data_seek($this->_result, 0);
1532
            }
1533
        }
1534
1535 41
        return $this;
1536
    }
1537
1538
    /**
1539
     * You can set the default result-type to Result::RESULT_TYPE_*.
1540
     *
1541
     * INFO: used for "fetch()" and "fetchAll()"
1542
     *
1543
     * @param string $default_result_type
1544
     *
1545
     * @return void
1546
     */
1547 6
    public function setDefaultResultType(string $default_result_type = self::RESULT_TYPE_OBJECT)
1548
    {
1549
        if (
1550 6
            $default_result_type === self::RESULT_TYPE_OBJECT
1551
            ||
1552 6
            $default_result_type === self::RESULT_TYPE_ARRAY
1553
            ||
1554
            $default_result_type === self::RESULT_TYPE_ARRAYY
1555
            ||
1556 6
            $default_result_type === self::RESULT_TYPE_YIELD
1557
        ) {
1558 6
            $this->_default_result_type = $default_result_type;
1559
        }
1560 6
    }
1561
1562
    /**
1563
     * @param int      $offset
1564
     * @param int|null $length
1565
     * @param bool     $preserve_keys
1566
     *
1567
     * @return array
1568
     */
1569 1
    public function &slice(
1570
        int $offset = 0,
1571
        int $length = null,
1572
        bool $preserve_keys = false
1573
    ): array {
1574
        // init
1575 1
        $slice = [];
1576
1577 1
        if ($offset < 0) {
1578 1
            if (\abs($offset) > $this->num_rows) {
1579 1
                $offset = 0;
1580
            } else {
1581 1
                $offset = $this->num_rows - (int) \abs($offset);
1582
            }
1583
        }
1584
1585 1
        $length = $length !== null ? (int) $length : $this->num_rows;
1586 1
        $n = 0;
1587 1
        for ($i = $offset; $i < $this->num_rows && $n < $length; $i++) {
1588 1
            if ($preserve_keys) {
1589 1
                $slice[$i] = $this->fetchCallable($i);
1590
            } else {
1591 1
                $slice[] = $this->fetchCallable($i);
1592
            }
1593 1
            ++$n;
1594
        }
1595
1596 1
        return $slice;
1597
    }
1598
}
1599