Completed
Push — master ( f141d2...ca688c )
by Lars
03:24
created

Result   F

Complexity

Total Complexity 218

Size/Duplication

Total Lines 1500
Duplicated Lines 15.93 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 71.64%

Importance

Changes 0
Metric Value
wmc 218
lcom 1
cbo 7
dl 239
loc 1500
ccs 336
cts 469
cp 0.7164
rs 0.8
c 0
b 0
f 0

55 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 46 8
A __destruct() 0 4 1
A __invoke() 0 8 2
A __toString() 0 4 1
D cast() 10 74 24
A count() 0 4 1
A current() 0 4 1
A key() 0 4 1
A next() 0 4 1
A rewind() 0 6 2
B seek() 0 26 8
A valid() 0 4 1
A fetch() 0 16 5
A fetchAll() 0 16 5
A fetchAllArray() 0 16 3
A fetchAllArrayy() 0 16 3
A fetchAllColumn() 0 4 1
B fetchAllObject() 12 42 10
B fetchAllYield() 12 40 10
A fetchArray() 17 17 5
A fetchArrayPair() 0 19 4
A fetchArrayy() 17 17 5
B fetchCallable() 0 30 7
B fetchColumn() 25 42 10
A fetchFields() 0 13 2
A fetchGroups() 26 26 5
B fetchObject() 39 39 11
A fetchPairs() 26 26 5
A fetchTranspose() 0 26 5
B fetchYield() 39 39 11
B fetch_assoc() 2 37 7
B fetch_fields() 0 44 8
A first() 0 8 1
A free() 0 29 5
A get() 0 4 1
A getAll() 0 4 1
A getAllColumn() 0 4 1
A getArray() 0 4 1
A getArrayy() 0 4 1
A getColumn() 0 4 1
A getDefaultResultType() 0 4 1
A getObject() 0 4 1
A getYield() 0 4 1
A is_empty() 0 4 1
A json() 0 6 1
A last() 0 8 1
A map() 0 6 1
A num_rows() 0 4 1
A offsetExists() 0 4 3
A offsetGet() 0 8 2
A offsetSet() 0 4 1
A offsetUnset() 0 4 1
B reset() 0 24 6
A setDefaultResultType() 0 14 5
B slice() 0 26 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Result often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Result, and based on these observations, apply Extract Interface, too.

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
435
        }
436
437 6
        return $return;
438
    }
439
440
    /**
441
     * Fetch all results.
442
     *
443
     * <p>
444
     *   <br />
445
     *   INFO: this will return an object by default, not an array<br />
446
     *   and you can change the behaviour via "Result->setDefaultResultType()"
447
     * </p>
448
     *
449
     * @return array
450
     */
451 6
    public function fetchAll(): array
452
    {
453 6
        $return = [];
454
455 6
        if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
456 6
            $return = $this->fetchAllObject();
457 3
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
458 3
            $return = $this->fetchAllArray();
459
        } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
460
            $return = $this->fetchAllArrayy();
461
        } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
462
            $return = $this->fetchAllYield();
463
        }
464
465 6
        return $return;
466
    }
467
468
    /**
469
     * Fetch all results as array.
470
     *
471
     * @return array
472
     */
473 28
    public function fetchAllArray(): array
474
    {
475 28
        if ($this->is_empty()) {
476
            return [];
477
        }
478
479 28
        $this->reset();
480
481 28
        $data = [];
482
        /** @noinspection PhpAssignmentInConditionInspection */
483 28
        while ($row = $this->fetch_assoc()) {
484 28
            $data[] = $this->cast($row);
485
        }
486
487 28
        return $data;
488
    }
489
490
    /**
491
     * Fetch all results as "Arrayy"-object.
492
     *
493
     * @return Arrayy
494
     */
495 10
    public function fetchAllArrayy(): Arrayy
496
    {
497 10
        if ($this->is_empty()) {
498
            return Arrayy::create([]);
499
        }
500
501 10
        $this->reset();
502
503 10
        $data = [];
504
        /** @noinspection PhpAssignmentInConditionInspection */
505 10
        while ($row = $this->fetch_assoc()) {
506 10
            $data[] = $this->cast($row);
507
        }
508
509 10
        return Arrayy::create($data);
510
    }
511
512
    /**
513
     * Fetch a single column as an 1-dimension array.
514
     *
515
     * @param string $column
516
     * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
517
     *
518
     * @return array <p>Return an empty array if the "$column" wasn't found</p>
519
     */
520 5
    public function fetchAllColumn(string $column, bool $skipNullValues = false): array
521
    {
522 5
        return $this->fetchColumn($column, $skipNullValues, true);
523
    }
524
525
    /**
526
     * Fetch all results as array with objects.
527
     *
528
     * @param object|string $class  <p>
529
     *                              <strong>string</strong>: create a new object (with optional constructor
530
     *                              parameter)<br>
531
     *                              <strong>object</strong>: use a object and fill the the data into
532
     *                              </p>
533
     * @param array|null    $params optional
534
     *                              <p>
535
     *                              An array of parameters to pass to the constructor, used if $class is a
536
     *                              string.
537
     *                              </p>
538
     *
539
     * @return array
540
     */
541 9
    public function fetchAllObject($class = '', array $params = null): array
542
    {
543 9
        if ($this->is_empty()) {
544
            return [];
545
        }
546
547
        // fallback
548 9
        if (!$class || $class === 'stdClass') {
549
            /** @noinspection ClassConstantCanBeUsedInspection */
550 9
            $class = '\stdClass';
551
        }
552
553
        // init
554 9
        $data = [];
555 9
        $this->reset();
556
557 9
        if (\is_object($class)) {
558
            $classTmpOrig = new $class();
559 9
        } elseif ($class && $params) {
560 3
            $reflectorTmp = new \ReflectionClass($class);
561 3
            $classTmpOrig = $reflectorTmp->newInstanceArgs($params);
562
        } else {
563 9
            $classTmpOrig = new $class();
564
        }
565
566 9
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
567
        /** @noinspection PhpAssignmentInConditionInspection */
568 9 View Code Duplication
        while ($row = $this->fetch_assoc()) {
569 9
            $classTmp = clone $classTmpOrig;
570 9
            $row = $this->cast($row);
571 9
            foreach ($row as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $row of type array|object|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
572 9
                if ($class === '\stdClass') {
573 9
                    $classTmp->{$key} = $value;
574
                } else {
575 9
                    $propertyAccessor->setValue($classTmp, $key, $value);
576
                }
577
            }
578 9
            $data[] = $classTmp;
579
        }
580
581 9
        return $data;
582
    }
583
584
    /**
585
     * Fetch all results as "\Generator" via yield.
586
     *
587
     * @param object|string $class  <p>
588
     *                              <strong>string</strong>: create a new object (with optional constructor
589
     *                              parameter)<br>
590
     *                              <strong>object</strong>: use a object and fill the the data into
591
     *                              </p>
592
     * @param array|null    $params optional
593
     *                              <p>
594
     *                              An array of parameters to pass to the constructor, used if $class is a
595
     *                              string.
596
     *                              </p>
597
     *
598
     * @return \Generator
599
     */
600 8
    public function fetchAllYield($class = '', array $params = null): \Generator
601
    {
602 8
        if ($this->is_empty()) {
603
            return;
604
        }
605
606
        // init
607 8
        $this->reset();
608
609
        // fallback
610 8
        if (!$class || $class === 'stdClass') {
611
            /** @noinspection ClassConstantCanBeUsedInspection */
612 8
            $class = '\stdClass';
613
        }
614
615 8
        if (\is_object($class)) {
616
            $classTmpOrig = $class;
617 8
        } elseif ($class && $params) {
618
            $reflectorTmp = new \ReflectionClass($class);
619
            $classTmpOrig = $reflectorTmp->newInstanceArgs($params);
620
        } else {
621 8
            $classTmpOrig = new $class();
622
        }
623
624 8
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
625
        /** @noinspection PhpAssignmentInConditionInspection */
626 8 View Code Duplication
        while ($row = $this->fetch_assoc()) {
627 8
            $classTmp = clone $classTmpOrig;
628 8
            $row = $this->cast($row);
629 8
            foreach ($row as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $row of type array|object|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
630 8
                if ($class === '\stdClass') {
631 8
                    $classTmp->{$key} = $value;
632
                } else {
633 8
                    $propertyAccessor->setValue($classTmp, $key, $value);
634
                }
635
            }
636
637 8
            yield $classTmp;
638
        }
639 8
    }
640
641
    /**
642
     * Fetch as array.
643
     *
644
     * @param bool $reset
645
     *
646
     * @return array|false <p><strong>false</strong> on error</p>
647
     */
648 30 View Code Duplication
    public function fetchArray(bool $reset = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
649
    {
650 30
        if ($reset === true) {
651 3
            $this->reset();
652
        }
653
654 30
        $row = $this->fetch_assoc();
655 30
        if ($row) {
656 27
            return $this->cast($row);
657
        }
658
659 5
        if ($row === null || $row === false) {
660 5
            return [];
661
        }
662
663
        return false;
664
    }
665
666
    /**
667
     * Fetch data as a key/value pair array.
668
     *
669
     * <p>
670
     *   <br />
671
     *   INFO: both "key" and "value" must exists in the fetched data
672
     *   the key will be the new key of the result-array
673
     *   <br /><br />
674
     * </p>
675
     *
676
     * e.g.:
677
     * <code>
678
     *    fetchArrayPair('some_id', 'some_value');
679
     *    // array(127 => 'some value', 128 => 'some other value')
680
     * </code>
681
     *
682
     * @param string $key
683
     * @param string $value
684
     *
685
     * @return array
686
     */
687 3
    public function fetchArrayPair(string $key, string $value): array
688
    {
689 3
        $arrayPair = [];
690 3
        $data = $this->fetchAllArray();
691
692 3
        foreach ($data as &$_row) {
693
            if (
694 3
                \array_key_exists($key, $_row) === true
695
                &&
696 3
                \array_key_exists($value, $_row) === true
697
            ) {
698 3
                $_key = $_row[$key];
699 3
                $_value = $_row[$value];
700 3
                $arrayPair[$_key] = $_value;
701
            }
702
        }
703
704 3
        return $arrayPair;
705
    }
706
707
    /**
708
     * Fetch as "Arrayy"-object.
709
     *
710
     * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
711
     *
712
     * @return Arrayy|false <p><strong>false</strong> on error</p>
713
     */
714 6 View Code Duplication
    public function fetchArrayy(bool $reset = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
715
    {
716 6
        if ($reset === true) {
717
            $this->reset();
718
        }
719
720 6
        $row = $this->fetch_assoc();
721 6
        if ($row) {
722 3
            return Arrayy::create($this->cast($row));
723
        }
724
725 3
        if ($row === null || $row === false) {
726 3
            return Arrayy::create();
727
        }
728
729
        return false;
730
    }
731
732
    /**
733
     * Fetches a row or a single column within a row. Returns null if there are
734
     * no more rows in the result.
735
     *
736
     * @param int    $row    The row number (optional)
737
     * @param string $column The column name (optional)
738
     *
739
     * @return mixed An associative array or a scalar value
740
     */
741 17
    public function fetchCallable(int $row = null, string $column = null)
742
    {
743 17
        if (!$this->num_rows) {
744 2
            return null;
745
        }
746
747 15
        if ($row !== null) {
748 14
            $this->seek($row);
749
        }
750
751 15
        $rows = $this->fetch_assoc();
752
753 15
        if ($column) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $column of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
754
            if (
755 5
                \is_array($rows)
756
                &&
757 5
                isset($rows[$column])
758
            ) {
759 5
                return $rows[$column];
760
            }
761
762
            return null;
763
        }
764
765 14
        if (\is_callable($this->_mapper)) {
766 1
            return \call_user_func($this->_mapper, $rows);
767
        }
768
769 14
        return $rows;
770
    }
771
772
    /**
773
     * Fetch a single column as string (or as 1-dimension array).
774
     *
775
     * @param string $column
776
     * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
777
     * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
778
     *
779
     * @return array|string <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
780
     *                      "$asArray"</p>
781
     */
782 9
    public function fetchColumn(string $column = '', bool $skipNullValues = true, bool $asArray = false)
783
    {
784 9
        if ($asArray === false) {
785 7
            $columnData = '';
786
787 7
            $data = $this->fetchAllArrayy()->reverse()->getArray();
788 7 View Code Duplication
            foreach ($data as $_row) {
789
790 7
                if ($skipNullValues === true) {
791 7
                    if (isset($_row[$column]) === false) {
792 7
                        continue;
793
                    }
794 3
                } elseif (\array_key_exists($column, $_row) === false) {
795 3
                    break;
796
                }
797
798 7
                $columnData = $_row[$column];
799
800 7
                break;
801
            }
802
803 7
            return $columnData;
804
        }
805
806
        // -- return as array -->
807
808 5
        $columnData = [];
809
810 5 View Code Duplication
        foreach ($this->fetchAllYield() as $_row) {
811 5
            if ($skipNullValues === true) {
812 3
                if (isset($_row->{$column}) === false) {
813 3
                    continue;
814
                }
815 5
            } elseif (\array_key_exists($column, $_row) === false) {
816 3
                break;
817
            }
818
819 5
            $columnData[] = $_row->{$column};
820
        }
821
822 5
        return $columnData;
823
    }
824
825
    /**
826
     * Return rows of field information in a result set.
827
     *
828
     * @param bool $as_array Return each field info as array; defaults to false
829
     *
830
     * @return array Array of field information each as an associative array
831
     */
832 1
    public function fetchFields(bool $as_array = false): array
833
    {
834 1
        if ($as_array) {
835 1
            return \array_map(
836 1
                function ($object) {
837 1
                    return (array) $object;
838 1
                },
839 1
                $this->fetch_fields()
840
            );
841
        }
842
843
        return $this->fetch_fields();
844
    }
845
846
    /**
847
     * Returns all rows at once as a grouped array of scalar values or arrays.
848
     *
849
     * @param string $group  The column name to use for grouping
850
     * @param string $column The column name to use as values (optional)
851
     *
852
     * @return array A grouped array of scalar values or arrays
853
     */
854 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...
855
    {
856
        // init
857 1
        $groups = [];
858 1
        $pos = $this->current_row;
859
860 1
        foreach ($this as $row) {
861 1
            if (!\array_key_exists($group, $row)) {
862
                continue;
863
            }
864
865 1
            if ($column !== null) {
866 1
                if (!\array_key_exists($column, $row)) {
867
                    continue;
868
                }
869
870 1
                $groups[$row[$group]][] = $row[$column];
871
            } else {
872 1
                $groups[$row[$group]][] = $row;
873
            }
874
        }
875
876 1
        $this->rewind($pos);
877
878 1
        return $groups;
879
    }
880
881
    /**
882
     * Fetch as object.
883
     *
884
     * @param object|string $class  <p>
885
     *                              <strong>string</strong>: create a new object (with optional constructor
886
     *                              parameter)<br>
887
     *                              <strong>object</strong>: use a object and fill the the data into
888
     *                              </p>
889
     * @param array|null    $params optional
890
     *                              <p>
891
     *                              An array of parameters to pass to the constructor, used if $class is a
892
     *                              string.
893
     *                              </p>
894
     * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
895
     *
896
     * @return false|object <p><strong>false</strong> on error</p>
897
     */
898 21 View Code Duplication
    public function fetchObject($class = '', array $params = null, bool $reset = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
899
    {
900 21
        if ($reset === true) {
901 9
            $this->reset();
902
        }
903
904
        // fallback
905 21
        if (!$class || $class === 'stdClass') {
906
            /** @noinspection ClassConstantCanBeUsedInspection */
907 21
            $class = '\stdClass';
908
        }
909
910 21
        $row = $this->fetch_assoc();
911 21
        $row = $row ? $this->cast($row) : false;
912
913 21
        if (!$row) {
914 3
            return false;
915
        }
916
917 21
        if (\is_object($class)) {
918 3
            $classTmp = $class;
919 21
        } elseif ($class && $params) {
920 3
            $reflectorTmp = new \ReflectionClass($class);
921 3
            $classTmp = $reflectorTmp->newInstanceArgs($params);
922
        } else {
923 21
            $classTmp = new $class();
924
        }
925
926 21
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
927 21
        foreach ($row as $key => $value) {
928 21
            if ($class === '\stdClass') {
929 21
                $classTmp->{$key} = $value;
930
            } else {
931 21
                $propertyAccessor->setValue($classTmp, $key, $value);
932
            }
933
        }
934
935 21
        return $classTmp;
936
    }
937
938
    /**
939
     * Returns all rows at once as key-value pairs.
940
     *
941
     * @param string $key    The column name to use as keys
942
     * @param string $column The column name to use as values (optional)
943
     *
944
     * @return array An array of key-value pairs
945
     */
946 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...
947
    {
948
        // init
949 1
        $pairs = [];
950 1
        $pos = $this->current_row;
951
952 1
        foreach ($this as $row) {
953 1
            if (!\array_key_exists($key, $row)) {
954
                continue;
955
            }
956
957 1
            if ($column !== null) {
958 1
                if (!\array_key_exists($column, $row)) {
959
                    continue;
960
                }
961
962 1
                $pairs[$row[$key]] = $row[$column];
963
            } else {
964 1
                $pairs[$row[$key]] = $row;
965
            }
966
        }
967
968 1
        $this->rewind($pos);
969
970 1
        return $pairs;
971
    }
972
973
    /**
974
     * Returns all rows at once, transposed as an array of arrays. Instead of
975
     * returning rows of columns, this method returns columns of rows.
976
     *
977
     * @param string $column The column name to use as keys (optional)
978
     *
979
     * @return mixed A transposed array of arrays
980
     */
981 1
    public function fetchTranspose(string $column = null)
982
    {
983
        // init
984 1
        $keys = $column !== null ? $this->fetchAllColumn($column) : [];
985 1
        $rows = [];
986 1
        $pos = $this->current_row;
987
988 1
        foreach ($this as $row) {
989 1
            foreach ($row as $key => $value) {
990 1
                $rows[$key][] = $value;
991
            }
992
        }
993
994 1
        $this->rewind($pos);
995
996 1
        if (empty($keys)) {
997 1
            return $rows;
998
        }
999
1000 1
        return \array_map(
1001 1
            function ($values) use ($keys) {
1002 1
                return \array_combine($keys, $values);
1003 1
            },
1004 1
            $rows
1005
        );
1006
    }
1007
1008
    /**
1009
     * Fetch as "\Generator" via yield.
1010
     *
1011
     * @param object|string $class  <p>
1012
     *                              <strong>string</strong>: create a new object (with optional constructor
1013
     *                              parameter)<br>
1014
     *                              <strong>object</strong>: use a object and fill the the data into
1015
     *                              </p>
1016
     * @param array|null    $params optional
1017
     *                              <p>
1018
     *                              An array of parameters to pass to the constructor, used if $class is a
1019
     *                              string.
1020
     *                              </p>
1021
     * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
1022
     *
1023
     * @return \Generator
1024
     */
1025 4 View Code Duplication
    public function fetchYield($class = '', array $params = null, bool $reset = false): \Generator
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1026
    {
1027 4
        if ($reset === true) {
1028
            $this->reset();
1029
        }
1030
1031
        // fallback
1032 4
        if (!$class || $class === 'stdClass') {
1033
            /** @noinspection ClassConstantCanBeUsedInspection */
1034 3
            $class = '\stdClass';
1035
        }
1036
1037 4
        if (\is_object($class)) {
1038
            $classTmp = $class;
1039 4
        } elseif ($class && $params) {
1040
            $reflectorTmp = new \ReflectionClass($class);
1041
            $classTmp = $reflectorTmp->newInstanceArgs($params);
1042
        } else {
1043 4
            $classTmp = new $class();
1044
        }
1045
1046 4
        $row = $this->fetch_assoc();
1047 4
        $row = $row ? $this->cast($row) : false;
1048
1049 4
        if (!$row) {
1050
            return;
1051
        }
1052
1053 4
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1054 4
        foreach ($row as $key => $value) {
1055 4
            if ($class === '\stdClass') {
1056 3
                $classTmp->{$key} = $value;
1057
            } else {
1058 4
                $propertyAccessor->setValue($classTmp, $key, $value);
1059
            }
1060
        }
1061
1062 4
        yield $classTmp;
1063 4
    }
1064
1065
    /**
1066
     * @return mixed
1067
     */
1068 78
    private function fetch_assoc()
1069
    {
1070 78
        if ($this->_result instanceof \Doctrine\DBAL\Statement) {
1071
            if (
1072
                $this->doctrinePdoStmt
1073
                &&
1074
                $this->doctrinePdoStmt instanceof \Doctrine\DBAL\Driver\PDOStatement
1075
            ) {
1076
                if ($this->doctrinePdoStmtDataSeekInit === false) {
1077
                    $this->doctrinePdoStmtDataSeekInit = true;
1078
1079
                    $this->doctrinePdoStmtDataSeekFakeCache = $this->_result->fetchAll(\Doctrine\DBAL\FetchMode::ASSOCIATIVE);
1080
                }
1081
1082
                $return = ($this->doctrinePdoStmtDataSeekFakeCache[$this->doctrinePdoStmtDataSeekFake] ?? null);
1083
1084
                $this->doctrinePdoStmtDataSeekFake++;
1085
1086
                return $return;
1087
            }
1088
1089
            if (
1090
                $this->doctrineMySQLiStmt
1091
                &&
1092
                $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1093
            ) {
1094
                return $this->_result->fetch(
1095
                    \Doctrine\DBAL\FetchMode::ASSOCIATIVE,
1096
                    0 // FETCH_ORI_NEXT
1097
                );
1098
            }
1099
1100
            return null;
1101
        }
1102
1103 78
        return \mysqli_fetch_assoc($this->_result);
1104
    }
1105
1106
    /**
1107
     * @return array|bool
1108
     */
1109 1
    private function fetch_fields()
1110
    {
1111 1
        if ($this->_result instanceof \mysqli_result) {
1112 1
            return \mysqli_fetch_fields($this->_result);
1113
        }
1114
1115
        if ($this->doctrineMySQLiStmt) {
1116
            $metadataTmp = $this->doctrineMySQLiStmt->result_metadata();
1117
1118
            return $metadataTmp->fetch_fields();
1119
        }
1120
1121
        if ($this->doctrinePdoStmt) {
1122
            $fields = [];
1123
1124
            static $THIS_CLASS_TMP = null;
1125
            if ($THIS_CLASS_TMP === null) {
1126
                $THIS_CLASS_TMP = new \ReflectionClass(__CLASS__);
1127
            }
1128
1129
            $totalColumnsTmp = $this->doctrinePdoStmt->columnCount();
1130
            for ($counterTmp = 0; $counterTmp < $totalColumnsTmp; $counterTmp++) {
1131
                $metadataTmp = $this->doctrinePdoStmt->getColumnMeta($counterTmp);
1132
                $fieldTmp = new \stdClass();
1133
                foreach ($metadataTmp as $metadataTmpKey => $metadataTmpValue) {
1134
                    $fieldTmp->{$metadataTmpKey} = $metadataTmpValue;
1135
                }
1136
1137
                $typeNativeTmp = 'MYSQL_TYPE_' . $metadataTmp['native_type'];
1138
                $typeTmp = $THIS_CLASS_TMP->getConstant($typeNativeTmp);
1139
                if ($typeTmp) {
1140
                    $fieldTmp->type = $typeTmp;
1141
                } else {
1142
                    $fieldTmp->type = '';
1143
                }
1144
1145
                $fields[] = $fieldTmp;
1146
            }
1147
1148
            return $fields;
1149
        }
1150
1151
        return false;
1152
    }
1153
1154
    /**
1155
     * Returns the first row element from the result.
1156
     *
1157
     * @param string $column The column name to use as value (optional)
1158
     *
1159
     * @return mixed A row array or a single scalar value
1160
     */
1161 3
    public function first(string $column = null)
1162
    {
1163 3
        $pos = $this->current_row;
1164 3
        $first = $this->fetchCallable(0, $column);
1165 3
        $this->rewind($pos);
1166
1167 3
        return $first;
1168
    }
1169
1170
    /**
1171
     * free the memory
1172
     */
1173 100
    public function free()
1174
    {
1175
        if (
1176 100
            $this->_result
1177
            &&
1178 100
            $this->_result instanceof \mysqli_result
1179
        ) {
1180
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
1181 100
            @\mysqli_free_result($this->_result);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1182 100
            $this->_result = null;
1183
1184 100
            return true;
1185
        }
1186
1187
        if (
1188 1
            $this->doctrineMySQLiStmt
1189
            &&
1190 1
            $this->doctrineMySQLiStmt instanceof \mysqli_stmt
1191
        ) {
1192
            $this->doctrineMySQLiStmt->free_result();
1193
            $this->_result = null;
1194
1195
            return true;
1196
        }
1197
1198 1
        $this->_result = null;
1199
1200 1
        return false;
1201
    }
1202
1203
    /**
1204
     * alias for "Result->fetch()"
1205
     *
1206
     * @see Result::fetch()
1207
     *
1208
     * @return array|false|object <p><strong>false</strong> on error</p>
1209
     */
1210 3
    public function get()
1211
    {
1212 3
        return $this->fetch();
1213
    }
1214
1215
    /**
1216
     * alias for "Result->fetchAll()"
1217
     *
1218
     * @see Result::fetchAll()
1219
     *
1220
     * @return array
1221
     */
1222 3
    public function getAll(): array
1223
    {
1224 3
        return $this->fetchAll();
1225
    }
1226
1227
    /**
1228
     * alias for "Result->fetchAllColumn()"
1229
     *
1230
     * @see Result::fetchAllColumn()
1231
     *
1232
     * @param string $column
1233
     * @param bool   $skipNullValues
1234
     *
1235
     * @return array
1236
     */
1237
    public function getAllColumn(string $column, bool $skipNullValues = false): array
1238
    {
1239
        return $this->fetchAllColumn($column, $skipNullValues);
1240
    }
1241
1242
    /**
1243
     * alias for "Result->fetchAllArray()"
1244
     *
1245
     * @see Result::fetchAllArray()
1246
     *
1247
     * @return array
1248
     */
1249 3
    public function getArray(): array
1250
    {
1251 3
        return $this->fetchAllArray();
1252
    }
1253
1254
    /**
1255
     * alias for "Result->fetchAllArrayy()"
1256
     *
1257
     * @see Result::fetchAllArrayy()
1258
     *
1259
     * @return Arrayy
1260
     */
1261
    public function getArrayy(): Arrayy
1262
    {
1263
        return $this->fetchAllArrayy();
1264
    }
1265
1266
    /**
1267
     * alias for "Result->fetchColumn()"
1268
     *
1269
     * @see Result::fetchColumn()
1270
     *
1271
     * @param string $column
1272
     * @param bool   $asArray
1273
     * @param bool   $skipNullValues
1274
     *
1275
     * @return array|string <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
1276
     *                      "$asArray"</p>
1277
     */
1278 3
    public function getColumn(string $column, bool $skipNullValues = true, bool $asArray = false)
1279
    {
1280 3
        return $this->fetchColumn($column, $skipNullValues, $asArray);
1281
    }
1282
1283
    /**
1284
     * @return string
1285
     */
1286 3
    public function getDefaultResultType(): string
1287
    {
1288 3
        return $this->_default_result_type;
1289
    }
1290
1291
    /**
1292
     * alias for "Result->fetchAllObject()"
1293
     *
1294
     * @see Result::fetchAllObject()
1295
     *
1296
     * @return array of mysql-objects
1297
     */
1298 3
    public function getObject(): array
1299
    {
1300 3
        return $this->fetchAllObject();
1301
    }
1302
1303
    /**
1304
     * alias for "Result->fetchAllYield()"
1305
     *
1306
     * @see Result::fetchAllYield()
1307
     *
1308
     * @param bool $asArray
1309
     *
1310
     * @return \Generator
1311
     */
1312 1
    public function getYield($asArray = false): \Generator
1313
    {
1314 1
        return $this->fetchAllYield($asArray);
0 ignored issues
show
Documentation introduced by
$asArray is of type boolean, but the function expects a string|object.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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