Completed
Push — master ( 4b3e9c...46623e )
by Lars
02:02
created

Result::valid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\db;
6
7
/**
8
 * Result: This class can handle the results from the "DB"-class.
9
 */
10
final class Result implements \Countable, \SeekableIterator, \ArrayAccess
11
{
12
    const MYSQL_TYPE_BIT = 16;
13
14
    const MYSQL_TYPE_BLOB = 252;
15
16
    const MYSQL_TYPE_DATE = 10;
17
18
    const MYSQL_TYPE_DATETIME = 12;
19
20
    const MYSQL_TYPE_DECIMAL = 0;
21
22
    const MYSQL_TYPE_DOUBLE = 5;
23
24
    const MYSQL_TYPE_ENUM = 247;
25
26
    const MYSQL_TYPE_FLOAT = 4;
27
28
    const MYSQL_TYPE_GEOMETRY = 255;
29
30
    const MYSQL_TYPE_INT24 = 9;
31
32
    const MYSQL_TYPE_JSON = 245;
33
34
    const MYSQL_TYPE_LONG = 3;
35
36
    const MYSQL_TYPE_LONGLONG = 8;
37
38
    const MYSQL_TYPE_LONG_BLOB = 251;
39
40
    const MYSQL_TYPE_MEDIUM_BLOB = 250;
41
42
    const MYSQL_TYPE_NEWDATE = 14;
43
44
    const MYSQL_TYPE_NEWDECIMAL = 246;
45
46
    const MYSQL_TYPE_NULL = 6;
47
48
    const MYSQL_TYPE_SET = 248;
49
50
    const MYSQL_TYPE_SHORT = 2;
51
52
    const MYSQL_TYPE_STRING = 254;
53
54
    const MYSQL_TYPE_TIME = 11;
55
56
    const MYSQL_TYPE_TIMESTAMP = 7;
57
58
    const MYSQL_TYPE_TINY = 1;
59
60
    const MYSQL_TYPE_TINY_BLOB = 249;
61
62
    const MYSQL_TYPE_VARCHAR = 15;
63
64
    const MYSQL_TYPE_VAR_STRING = 253;
65
66
    const MYSQL_TYPE_YEAR = 13;
67
68
    const RESULT_TYPE_ARRAY = 'array';
69
70
    const RESULT_TYPE_ARRAYY = 'Arrayy';
71
72
    const RESULT_TYPE_OBJECT = 'object';
73
74
    const RESULT_TYPE_YIELD = 'yield';
75
76
    /**
77
     * @var int
78
     */
79
    public $num_rows;
80
81
    /**
82
     * @var string
83
     */
84
    public $sql;
85
86
    /**
87
     * @var \Doctrine\DBAL\Driver\Statement|\mysqli_result
88
     */
89
    private $_result;
90
91
    /**
92
     * @var int
93
     */
94
    private $current_row;
95
96
    /**
97
     * @var \Closure|null
98
     */
99
    private $_mapper;
100
101
    /**
102
     * @var string
103
     */
104
    private $_default_result_type = self::RESULT_TYPE_OBJECT;
105
106
    /**
107
     * @var \mysqli_stmt|null
108
     */
109
    private $doctrineMySQLiStmt;
110
111
    /**
112
     * @var \Doctrine\DBAL\Driver\PDOStatement|null
113
     */
114
    private $doctrinePdoStmt;
115
116
    /**
117
     * @var int
118
     */
119
    private $doctrinePdoStmtDataSeekFake = 0;
120
121
    /**
122
     * @var bool
123
     */
124
    private $doctrinePdoStmtDataSeekInit = false;
125
126
    /**
127
     * @var array
128
     */
129
    private $doctrinePdoStmtDataSeekFakeCache = [];
130
131
    /**
132
     * Result constructor.
133
     *
134
     * @param string                                  $sql
135
     * @param \Doctrine\DBAL\Driver\Statement|\mysqli_result $result
136
     * @param \Closure                                $mapper Optional callback mapper for the "fetchCallable()" method
137
     */
138 102
    public function __construct(string $sql, $result, \Closure $mapper = null)
139
    {
140 102
        $this->sql = $sql;
141
142
        if (
143 102
            !$result instanceof \mysqli_result
144
            &&
145 102
            !$result instanceof \Doctrine\DBAL\Driver\Statement
146
        ) {
147
            throw new \InvalidArgumentException('$result must be ' . \mysqli_result::class . ' or ' . \Doctrine\DBAL\Driver\Statement::class . ' !');
148
        }
149
150 102
        $this->_result = $result;
151
152 102
        if ($this->_result instanceof \Doctrine\DBAL\Driver\Statement) {
153
            if (\method_exists($this->_result, 'getWrappedStatement')) {
154
                throw new \InvalidArgumentException('$result (' . \Doctrine\DBAL\Driver\Statement::class . ') must implement "getWrappedStatement()"!');
155
            }
156
157
            $doctrineDriver = $this->_result->getWrappedStatement();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\DBAL\Driver\Statement as the method getWrappedStatement() does only exist in the following implementations of said interface: Doctrine\DBAL\Statement.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

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