Completed
Push — master ( a781c6...24e936 )
by Lars
06:09
created

Result::json()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 1
b 0
f 0
cc 1
eloc 3
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\db;
6
7
use Arrayy\Arrayy;
8
use Doctrine\DBAL\FetchMode;
9
use Symfony\Component\PropertyAccess\PropertyAccess;
10
use voku\helper\UTF8;
11
12
/**
13
 * Result: This class can handle the results from the "DB"-class.
14
 *
15
 * @package   voku\db
16
 */
17
final class Result implements \Countable, \SeekableIterator, \ArrayAccess
18
{
19
20
  const MYSQL_TYPE_BIT         = 16;
21
  const MYSQL_TYPE_BLOB        = 252;
22
  const MYSQL_TYPE_DATE        = 10;
23
  const MYSQL_TYPE_DATETIME    = 12;
24
  const MYSQL_TYPE_DECIMAL     = 0;
25
  const MYSQL_TYPE_DOUBLE      = 5;
26
  const MYSQL_TYPE_ENUM        = 247;
27
  const MYSQL_TYPE_FLOAT       = 4;
28
  const MYSQL_TYPE_GEOMETRY    = 255;
29
  const MYSQL_TYPE_INT24       = 9;
30
  const MYSQL_TYPE_JSON        = 245;
31
  const MYSQL_TYPE_LONG        = 3;
32
  const MYSQL_TYPE_LONGLONG    = 8;
33
  const MYSQL_TYPE_LONG_BLOB   = 251;
34
  const MYSQL_TYPE_MEDIUM_BLOB = 250;
35
  const MYSQL_TYPE_NEWDATE     = 14;
36
  const MYSQL_TYPE_NEWDECIMAL  = 246;
37
  const MYSQL_TYPE_NULL        = 6;
38
  const MYSQL_TYPE_SET         = 248;
39
  const MYSQL_TYPE_SHORT       = 2;
40
  const MYSQL_TYPE_STRING      = 254;
41
  const MYSQL_TYPE_TIME        = 11;
42
  const MYSQL_TYPE_TIMESTAMP   = 7;
43
  const MYSQL_TYPE_TINY        = 1;
44
  const MYSQL_TYPE_TINY_BLOB   = 249;
45
  const MYSQL_TYPE_VARCHAR     = 15;
46
  const MYSQL_TYPE_VAR_STRING  = 253;
47
  const MYSQL_TYPE_YEAR        = 13;
48
49
  const RESULT_TYPE_ARRAY  = 'array';
50
  const RESULT_TYPE_ARRAYY = 'Arrayy';
51
  const RESULT_TYPE_OBJECT = 'object';
52
  const RESULT_TYPE_YIELD  = 'yield';
53
54
  /**
55
   * @var int
56
   */
57
  public $num_rows;
58
59
  /**
60
   * @var string
61
   */
62
  public $sql;
63
64
  /**
65
   * @var \mysqli_result|\Doctrine\DBAL\Statement
66
   */
67
  private $_result;
68
69
  /**
70
   * @var int
71
   */
72
  private $current_row;
73
74
  /**
75
   * @var \Closure|null
76
   */
77
  private $_mapper;
78
79
  /**
80
   * @var string
81
   */
82
  private $_default_result_type = self::RESULT_TYPE_OBJECT;
83
84
  /**
85
   * @var \mysqli_stmt|null
86
   */
87
  private $doctrineMySQLiStmt;
88
89
  /**
90
   * @var \Doctrine\DBAL\Driver\PDOStatement|null
91
   */
92
  private $doctrinePdoStmt;
93
94
  /**
95
   * Result constructor.
96
   *
97
   * @param string         $sql
98
   * @param \mysqli_result $result
99
   * @param \Closure       $mapper Optional callback mapper for the "fetchCallable()" method
100
   */
101 91
  public function __construct(string $sql, $result, \Closure $mapper = null)
102
  {
103 91
    $this->sql = $sql;
104
105
    if (
106 91
        !$result instanceof \mysqli_result
107
        &&
108 91
        !$result instanceof \Doctrine\DBAL\Statement
109
    ) {
110
      throw new \InvalidArgumentException('$result must be ' . \mysqli_result::class . ' or ' . \Doctrine\DBAL\Statement::class . ' !');
111
    }
112
113 91
    $this->_result = $result;
114
115 91
    if ($this->_result instanceof \Doctrine\DBAL\Statement) {
116
117 24
      $doctrineDriver = $this->_result->getWrappedStatement();
118
119 24
      if ($doctrineDriver instanceof \Doctrine\DBAL\Driver\PDOStatement) {
120
        $this->doctrinePdoStmt = $doctrineDriver;
121
      } // try to get the mysqli driver from doctrine
122 24
      else if ($doctrineDriver instanceof \Doctrine\DBAL\Driver\Mysqli\MysqliStatement) {
123 24
        $reflectionTmp = new \ReflectionClass($doctrineDriver);
124 24
        $propertyTmp = $reflectionTmp->getProperty('_stmt');
125 24
        $propertyTmp->setAccessible(true);
126 24
        $this->doctrineMySQLiStmt = $propertyTmp->getValue($doctrineDriver);
127
      }
128
129 24
      $this->num_rows = $this->_result->rowCount();
130
    } else {
131 67
      $this->num_rows = (int)$this->_result->num_rows;
132
    }
133
134 91
    $this->current_row = 0;
135
136
137 91
    $this->_mapper = $mapper;
138 91
  }
139
140
  /**
141
   * __destruct
142
   */
143 90
  public function __destruct()
144
  {
145 90
    $this->free();
146 90
  }
147
148
  /**
149
   * Runs a user-provided callback with the MySQLi_Result object given as
150
   * argument and returns the result, or returns the MySQLi_Result object if
151
   * called without an argument.
152
   *
153
   * @param callable $callback User-provided callback (optional)
154
   *
155
   * @return mixed|\Doctrine\DBAL\Statement|\mysqli_result
156
   */
157 2
  public function __invoke(callable $callback = null)
158
  {
159 2
    if (null !== $callback) {
160 2
      return $callback($this->_result);
161
    }
162
163 1
    return $this->_result;
164
  }
165
166
  /**
167
   * Get the current "num_rows" as string.
168
   *
169
   * @return string
170
   */
171
  public function __toString()
172
  {
173
    return (string)$this->num_rows;
174
  }
175
176
  /**
177
   * Cast data into int, float or string.
178
   *
179
   * <p>
180
   *   <br />
181
   *   INFO: install / use "mysqlnd"-driver for better performance
182
   * </p>
183
   *
184
   * @param array|object $data
185
   *
186
   * @return array|object|false <p><strong>false</strong> on error</p>
187
   */
188 64
  private function cast(&$data)
189
  {
190
    if (
191 64
        !$this->doctrinePdoStmt // pdo only have limited support for types, so we try to improve it
192
        &&
193 64
        Helper::isMysqlndIsUsed() === true
194
    ) {
195 64
      return $data;
196
    }
197
198
    // init
199
    static $FIELDS_CACHE = [];
200
    static $TYPES_CACHE = [];
201
202
    $result_hash = \spl_object_hash($this->_result);
203
204
    if (!isset($FIELDS_CACHE[$result_hash])) {
205
      $FIELDS_CACHE[$result_hash] = $this->fetch_fields();
206
    }
207
208
    if (
209
        !isset($FIELDS_CACHE[$result_hash])
210
        ||
211
        $FIELDS_CACHE[$result_hash] === false
212
    ) {
213
      return false;
214
    }
215
216
    if (!isset($TYPES_CACHE[$result_hash])) {
217
      foreach ($FIELDS_CACHE[$result_hash] as $field) {
218
        switch ($field->type) {
219
          case self::MYSQL_TYPE_BIT:
220
            $TYPES_CACHE[$result_hash][$field->name] = 'boolean';
221
            break;
222
          case self::MYSQL_TYPE_TINY:
223
          case self::MYSQL_TYPE_SHORT:
224
          case self::MYSQL_TYPE_LONG:
225
          case self::MYSQL_TYPE_LONGLONG:
226
          case self::MYSQL_TYPE_INT24:
227
            $TYPES_CACHE[$result_hash][$field->name] = 'integer';
228
            break;
229
          case self::MYSQL_TYPE_DOUBLE:
230
          case self::MYSQL_TYPE_DECIMAL:
231
          case self::MYSQL_TYPE_NEWDECIMAL:
232
          case self::MYSQL_TYPE_FLOAT:
233
            $TYPES_CACHE[$result_hash][$field->name] = 'float';
234
            break;
235
          default:
236
            $TYPES_CACHE[$result_hash][$field->name] = 'string';
237
            break;
238
        }
239
      }
240
    }
241
242
    if (\is_array($data) === true) {
243 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
244
        if (isset($data[$type_name])) {
245
          \settype($data[$type_name], $type);
246
        }
247
      }
248
    } elseif (\is_object($data)) {
249 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
250
        if (isset($data->{$type_name})) {
251
          \settype($data->{$type_name}, $type);
252
        }
253
      }
254
    }
255
256
    return $data;
257
  }
258
259
  /**
260
   * Countable interface implementation.
261
   *
262
   * @return int The number of rows in the result
263
   */
264 2
  public function count(): int
265
  {
266 2
    return $this->num_rows;
267
  }
268
269
  /**
270
   * Iterator interface implementation.
271
   *
272
   * @return mixed The current element
273
   */
274 7
  public function current()
275
  {
276 7
    return $this->fetchCallable($this->current_row);
277
  }
278
279
  /**
280
   * Iterator interface implementation.
281
   *
282
   * @return int The current element key (row index; zero-based)
283
   */
284 1
  public function key(): int
285
  {
286 1
    return $this->current_row;
287
  }
288
289
  /**
290
   * Iterator interface implementation.
291
   *
292
   * @return void
293
   */
294 7
  public function next()
295
  {
296 7
    $this->current_row++;
297 7
  }
298
299
  /**
300
   * Iterator interface implementation.
301
   *
302
   * @param int $row Row position to rewind to; defaults to 0
303
   *
304
   * @return void
305
   */
306 11
  public function rewind($row = 0)
307
  {
308 11
    if ($this->seek($row)) {
309 9
      $this->current_row = $row;
310
    }
311 11
  }
312
313
  /**
314
   * Moves the internal pointer to the specified row position.
315
   *
316
   * @param int $row <p>Row position; zero-based and set to 0 by default</p>
317
   *
318
   * @return bool <p>true on success, false otherwise</p>
319
   */
320 19
  public function seek($row = 0): bool
321
  {
322 19
    if (\is_int($row) && $row >= 0 && $row < $this->num_rows) {
323
324 15
      if ($this->doctrineMySQLiStmt) {
325 1
        $this->doctrineMySQLiStmt->data_seek($row);
326
327 1
        return true;
328
      }
329
330 14
      if ($this->doctrinePdoStmt) {
331
        return (bool)$this->doctrinePdoStmt->fetch(FetchMode::ASSOCIATIVE, \PDO::FETCH_ORI_NEXT, $row);
332
      }
333
334 14
      return \mysqli_data_seek($this->_result, $row);
335
    }
336
337 4
    return false;
338
  }
339
340
  /**
341
   * Iterator interface implementation.
342
   *
343
   * @return bool <p>true if the current index is valid, false otherwise</p>
344
   */
345 7
  public function valid(): bool
346
  {
347 7
    return $this->current_row < $this->num_rows;
348
  }
349
350
  /**
351
   * Fetch.
352
   *
353
   * <p>
354
   *   <br />
355
   *   INFO: this will return an object by default, not an array<br />
356
   *   and you can change the behaviour via "Result->setDefaultResultType()"
357
   * </p>
358
   *
359
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
360
   *
361
   * @return array|object|false <p><strong>false</strong> on error</p>
362
   */
363 4
  public function fetch(bool $reset = false)
364
  {
365 4
    $return = false;
366
367 4
    if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
368 4
      $return = $this->fetchObject('', null, $reset);
369 4
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
370 4
      $return = $this->fetchArray($reset);
371
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
372
      $return = $this->fetchArrayy($reset);
373
    } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
374
      $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...
375
    }
376
377 4
    return $return;
378
  }
379
380
  /**
381
   * Fetch all results.
382
   *
383
   * <p>
384
   *   <br />
385
   *   INFO: this will return an object by default, not an array<br />
386
   *   and you can change the behaviour via "Result->setDefaultResultType()"
387
   * </p>
388
   *
389
   * @return array
390
   */
391 4
  public function fetchAll(): array
392
  {
393 4
    $return = [];
394
395 4
    if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
396 4
      $return = $this->fetchAllObject();
397 2
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
398 2
      $return = $this->fetchAllArray();
399
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
400
      $return = $this->fetchAllArrayy();
401
    } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
402
      $return = $this->fetchAllYield();
403
    }
404
405 4
    return $return;
406
  }
407
408
  /**
409
   * Fetch all results as array.
410
   *
411
   * @return array
412
   */
413 23
  public function fetchAllArray(): array
414
  {
415 23
    if ($this->is_empty()) {
416
      return [];
417
    }
418
419 23
    $this->reset();
420
421 23
    $data = [];
422
    /** @noinspection PhpAssignmentInConditionInspection */
423 23
    while ($row = $this->fetch_assoc()) {
424 23
      $data[] = $this->cast($row);
425
    }
426
427 23
    return $data;
428
  }
429
430
  /**
431
   * Fetch all results as "Arrayy"-object.
432
   *
433
   * @return Arrayy
434
   */
435 7
  public function fetchAllArrayy(): Arrayy
436
  {
437 7
    if ($this->is_empty()) {
438
      return Arrayy::create([]);
439
    }
440
441 7
    $this->reset();
442
443 7
    $data = [];
444
    /** @noinspection PhpAssignmentInConditionInspection */
445 7
    while ($row = $this->fetch_assoc()) {
446 7
      $data[] = $this->cast($row);
447
    }
448
449 7
    return Arrayy::create($data);
450
  }
451
452
  /**
453
   * Fetch a single column as an 1-dimension array.
454
   *
455
   * @param string $column
456
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
457
   *
458
   * @return array <p>Return an empty array if the "$column" wasn't found</p>
459
   */
460 4
  public function fetchAllColumn(string $column, bool $skipNullValues = false): array
461
  {
462 4
    return $this->fetchColumn($column, $skipNullValues, true);
463
  }
464
465
  /**
466
   * Fetch all results as array with objects.
467
   *
468
   * @param object|string $class  <p>
469
   *                              <strong>string</strong>: create a new object (with optional constructor
470
   *                              parameter)<br>
471
   *                              <strong>object</strong>: use a object and fill the the data into
472
   *                              </p>
473
   * @param null|array    $params optional
474
   *                              <p>
475
   *                              An array of parameters to pass to the constructor, used if $class is a
476
   *                              string.
477
   *                              </p>
478
   *
479
   * @return array
480
   */
481 14
  public function fetchAllObject($class = '', array $params = null): array
482
  {
483 14
    if ($this->is_empty()) {
484 1
      return [];
485
    }
486
487
    // fallback
488 13
    if (!$class || $class === 'stdClass') {
489 6
      $class = '\stdClass';
490
    }
491
492
    // init
493 13
    $data = [];
494 13
    $this->reset();
495 13
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
496
497 13
    if (\is_object($class)) {
498
499
      /** @noinspection PhpAssignmentInConditionInspection */
500 7 View Code Duplication
      while ($row = $this->fetch_assoc()) {
501 7
        $classTmp = clone $class;
502 7
        $row = $this->cast($row);
503 7
        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...
504 7
          $propertyAccessor->setValue($classTmp, $key, $value);
505
        }
506 7
        $data[] = $classTmp;
507
      }
508
509 6
    } else if ($class && $params) {
510
511
      /** @noinspection PhpAssignmentInConditionInspection */
512 2
      while ($row = $this->fetch_assoc()) {
513 2
        $reflectorTmp = new \ReflectionClass($class);
514 2
        $classTmp = $reflectorTmp->newInstanceArgs($params);
515 2
        $row = $this->cast($row);
516 2
        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...
517 2
          if ($class === '\stdClass') {
518
            $classTmp->{$key} = $value;
519
          } else {
520 2
            $propertyAccessor->setValue($classTmp, $key, $value);
521
          }
522
        }
523 2
        $data[] = $classTmp;
524
      }
525
526
    } else {
527
528
      /** @noinspection PhpAssignmentInConditionInspection */
529 6 View Code Duplication
      while ($row = $this->fetch_assoc()) {
530 6
        $classTmp = new $class;
531 6
        $row = $this->cast($row);
532 6
        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...
533 6
          if ($class === '\stdClass') {
534 6
            $classTmp->{$key} = $value;
535
          } else {
536 6
            $propertyAccessor->setValue($classTmp, $key, $value);
537
          }
538
        }
539 6
        $data[] = $classTmp;
540
      }
541
    }
542
543 13
    return $data;
544
  }
545
546
  /**
547
   * Fetch all results as "\Generator" via yield.
548
   *
549
   * @param object|string $class  <p>
550
   *                              <strong>string</strong>: create a new object (with optional constructor
551
   *                              parameter)<br>
552
   *                              <strong>object</strong>: use a object and fill the the data into
553
   *                              </p>
554
   * @param null|array    $params optional
555
   *                              <p>
556
   *                              An array of parameters to pass to the constructor, used if $class is a
557
   *                              string.
558
   *                              </p>
559
   *
560
   * @return \Generator
561
   */
562 2
  public function fetchAllYield($class = '', array $params = null): \Generator
563
  {
564 2
    if ($this->is_empty()) {
565
      return;
566
    }
567
568
    // init
569 2
    $this->reset();
570
571
    // fallback
572 2
    if (!$class || $class === 'stdClass') {
573 2
      $class = '\stdClass';
574
    }
575
576 2
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
577
578 2
    if (\is_object($class)) {
579
      /** @noinspection PhpAssignmentInConditionInspection */
580 View Code Duplication
      while ($row = $this->fetch_assoc()) {
581
        $classTmp = clone $class;
582
        $row = $this->cast($row);
583
        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...
584
          $propertyAccessor->setValue($classTmp, $key, $value);
585
        }
586
        yield $classTmp;
587
      }
588
589
      return;
590
    }
591
592 2
    if ($class && $params) {
593
      /** @noinspection PhpAssignmentInConditionInspection */
594 View Code Duplication
      while ($row = $this->fetch_assoc()) {
595
        $reflectorTmp = new \ReflectionClass($class);
596
        $classTmp = $reflectorTmp->newInstanceArgs($params);
597
        $row = $this->cast($row);
598
        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...
599
          if ($class === '\stdClass') {
600
            $classTmp->{$key} = $value;
601
          } else {
602
            $propertyAccessor->setValue($classTmp, $key, $value);
603
          }
604
        }
605
        yield $classTmp;
606
      }
607
608
      return;
609
    }
610
611
    /** @noinspection PhpAssignmentInConditionInspection */
612 2 View Code Duplication
    while ($row = $this->fetch_assoc()) {
613 2
      $classTmp = new $class;
614 2
      $row = $this->cast($row);
615 2
      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...
616 2
        if ($class === '\stdClass') {
617 2
          $classTmp->{$key} = $value;
618
        } else {
619 2
          $propertyAccessor->setValue($classTmp, $key, $value);
620
        }
621
      }
622 2
      yield $classTmp;
623
    }
624 2
  }
625
626
  /**
627
   * Fetch as array.
628
   *
629
   * @param bool $reset
630
   *
631
   * @return array|false <p><strong>false</strong> on error</p>
632
   */
633 23 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...
634
  {
635 23
    if ($reset === true) {
636 2
      $this->reset();
637
    }
638
639 23
    $row = $this->fetch_assoc();
640 23
    if ($row) {
641 21
      return $this->cast($row);
642
    }
643
644 4
    if ($row === null || $row === false) {
645 4
      return [];
646
    }
647
648
    return false;
649
  }
650
651
  /**
652
   * Fetch data as a key/value pair array.
653
   *
654
   * <p>
655
   *   <br />
656
   *   INFO: both "key" and "value" must exists in the fetched data
657
   *   the key will be the new key of the result-array
658
   *   <br /><br />
659
   * </p>
660
   *
661
   * e.g.:
662
   * <code>
663
   *    fetchArrayPair('some_id', 'some_value');
664
   *    // array(127 => 'some value', 128 => 'some other value')
665
   * </code>
666
   *
667
   * @param string $key
668
   * @param string $value
669
   *
670
   * @return array
671
   */
672 2
  public function fetchArrayPair(string $key, string $value): array
673
  {
674 2
    $arrayPair = [];
675 2
    $data = $this->fetchAllArray();
676
677 2
    foreach ($data as &$_row) {
678
      if (
679 2
          \array_key_exists($key, $_row) === true
680
          &&
681 2
          \array_key_exists($value, $_row) === true
682
      ) {
683 2
        $_key = $_row[$key];
684 2
        $_value = $_row[$value];
685 2
        $arrayPair[$_key] = $_value;
686
      }
687
    }
688
689 2
    return $arrayPair;
690
  }
691
692
  /**
693
   * Fetch as "Arrayy"-object.
694
   *
695
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
696
   *
697
   * @return Arrayy|false <p><strong>false</strong> on error</p>
698
   */
699 4 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...
700
  {
701 4
    if ($reset === true) {
702
      $this->reset();
703
    }
704
705 4
    $row = $this->fetch_assoc();
706 4
    if ($row) {
707 2
      return Arrayy::create($this->cast($row));
0 ignored issues
show
Bug introduced by
It seems like $this->cast($row) targeting voku\db\Result::cast() can also be of type false or object; however, Arrayy\Arrayy::create() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
708
    }
709
710 2
    if ($row === null || $row === false) {
711 2
      return Arrayy::create();
712
    }
713
714
    return false;
715
  }
716
717
  /**
718
   * Fetches a row or a single column within a row. Returns null if there are
719
   * no more rows in the result.
720
   *
721
   * @param int    $row    The row number (optional)
722
   * @param string $column The column name (optional)
723
   *
724
   * @return mixed An associative array or a scalar value
725
   */
726 16
  public function fetchCallable(int $row = null, string $column = null)
727
  {
728 16
    if (!$this->num_rows) {
729 2
      return null;
730
    }
731
732 14
    if (null !== $row) {
733 13
      $this->seek($row);
734
    }
735
736 14
    $rows = $this->fetch_assoc();
737
738 14
    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...
739 5
      return \is_array($rows) && isset($rows[$column]) ? $rows[$column] : null;
740
    }
741
742 13
    return \is_callable($this->_mapper) ? \call_user_func($this->_mapper, $rows) : $rows;
743
  }
744
745
  /**
746
   * Fetch a single column as string (or as 1-dimension array).
747
   *
748
   * @param string $column
749
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
750
   * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
751
   *
752
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
753
   *                      "$asArray"</p>
754
   */
755 7
  public function fetchColumn(string $column = '', bool $skipNullValues = true, bool $asArray = false)
756
  {
757 7
    if ($asArray === false) {
758 5
      $columnData = '';
759
760 5
      $data = $this->fetchAllArrayy()->reverse();
761 5 View Code Duplication
      foreach ($data as $_row) {
762
763 5
        if ($skipNullValues === true) {
764 5
          if (isset($_row[$column]) === false) {
765 5
            continue;
766
          }
767
        } else {
768 2
          if (\array_key_exists($column, $_row) === false) {
769 2
            break;
770
          }
771
        }
772
773 5
        $columnData = $_row[$column];
774 5
        break;
775
      }
776
777 5
      return $columnData;
778
    }
779
780
    // -- return as array -->
781
782 4
    $columnData = [];
783
784 4
    $data = $this->fetchAllArray();
785
786 4 View Code Duplication
    foreach ($data as $_row) {
787
788 4
      if ($skipNullValues === true) {
789 2
        if (isset($_row[$column]) === false) {
790 2
          continue;
791
        }
792
      } else {
793 4
        if (\array_key_exists($column, $_row) === false) {
794 2
          break;
795
        }
796
      }
797
798 4
      $columnData[] = $_row[$column];
799
    }
800
801 4
    return $columnData;
802
  }
803
804
  /**
805
   * Return rows of field information in a result set.
806
   *
807
   * @param bool $as_array Return each field info as array; defaults to false
808
   *
809
   * @return array Array of field information each as an associative array
810
   */
811 1
  public function fetchFields(bool $as_array = false): array
812
  {
813 1
    if ($as_array) {
814 1
      return \array_map(
815 1
          function ($object) {
816 1
            return (array)$object;
817 1
          },
818 1
          $this->fetch_fields()
819
      );
820
    }
821
822
    return $this->fetch_fields();
823
  }
824
825
  /**
826
   * Returns all rows at once as a grouped array of scalar values or arrays.
827
   *
828
   * @param string $group  The column name to use for grouping
829
   * @param string $column The column name to use as values (optional)
830
   *
831
   * @return array A grouped array of scalar values or arrays
832
   */
833 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...
834
  {
835
    // init
836 1
    $groups = [];
837 1
    $pos = $this->current_row;
838
839 1
    foreach ($this as $row) {
840
841 1
      if (!\array_key_exists($group, $row)) {
842
        continue;
843
      }
844
845 1
      if (null !== $column) {
846
847 1
        if (!\array_key_exists($column, $row)) {
848
          continue;
849
        }
850
851 1
        $groups[$row[$group]][] = $row[$column];
852
      } else {
853 1
        $groups[$row[$group]][] = $row;
854
      }
855
    }
856
857 1
    $this->rewind($pos);
858
859 1
    return $groups;
860
  }
861
862
  /**
863
   * Fetch as object.
864
   *
865
   * @param object|string $class  <p>
866
   *                              <strong>string</strong>: create a new object (with optional constructor
867
   *                              parameter)<br>
868
   *                              <strong>object</strong>: use a object and fill the the data into
869
   *                              </p>
870
   * @param null|array    $params optional
871
   *                              <p>
872
   *                              An array of parameters to pass to the constructor, used if $class is a
873
   *                              string.
874
   *                              </p>
875
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
876
   *
877
   * @return object|false <p><strong>false</strong> on error</p>
878
   */
879 25 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...
880
  {
881 25
    if ($reset === true) {
882 17
      $this->reset();
883
    }
884
885
    // fallback
886 25
    if (!$class || $class === 'stdClass') {
887 14
      $class = '\stdClass';
888
    }
889
890 25
    $row = $this->fetch_assoc();
891 25
    $row = $row ? $this->cast($row) : false;
892
893 25
    if (!$row) {
894 5
      return false;
895
    }
896
897 23
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
898
899 23
    if (\is_object($class)) {
900 11
      $classTmp = $class;
901
902 14
    } else if ($class && $params) {
903
904 2
      $reflectorTmp = new \ReflectionClass($class);
905 2
      $classTmp = $reflectorTmp->newInstanceArgs($params);
906
907
    } else {
908
909 14
      $classTmp = new $class;
910
911
    }
912
913 23
    foreach ($row as $key => $value) {
914 23
      if ($class === '\stdClass') {
915 14
        $classTmp->{$key} = $value;
916
      } else {
917 23
        $propertyAccessor->setValue($classTmp, $key, $value);
918
      }
919
    }
920
921 23
    return $classTmp;
922
  }
923
924
  /**
925
   * Returns all rows at once as key-value pairs.
926
   *
927
   * @param string $key    The column name to use as keys
928
   * @param string $column The column name to use as values (optional)
929
   *
930
   * @return array An array of key-value pairs
931
   */
932 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...
933
  {
934
    // init
935 1
    $pairs = [];
936 1
    $pos = $this->current_row;
937
938 1
    foreach ($this as $row) {
939
940 1
      if (!\array_key_exists($key, $row)) {
941
        continue;
942
      }
943
944 1
      if (null !== $column) {
945
946 1
        if (!\array_key_exists($column, $row)) {
947
          continue;
948
        }
949
950 1
        $pairs[$row[$key]] = $row[$column];
951
      } else {
952 1
        $pairs[$row[$key]] = $row;
953
      }
954
    }
955
956 1
    $this->rewind($pos);
957
958 1
    return $pairs;
959
  }
960
961
  /**
962
   * Returns all rows at once, transposed as an array of arrays. Instead of
963
   * returning rows of columns, this method returns columns of rows.
964
   *
965
   * @param string $column The column name to use as keys (optional)
966
   *
967
   * @return mixed A transposed array of arrays
968
   */
969 1
  public function fetchTranspose(string $column = null)
970
  {
971
    // init
972 1
    $keys = null !== $column ? $this->fetchAllColumn($column) : [];
973 1
    $rows = [];
974 1
    $pos = $this->current_row;
975
976 1
    foreach ($this as $row) {
977 1
      foreach ($row as $key => $value) {
978 1
        $rows[$key][] = $value;
979
      }
980
    }
981
982 1
    $this->rewind($pos);
983
984 1
    if (empty($keys)) {
985 1
      return $rows;
986
    }
987
988 1
    return \array_map(
989 1
        function ($values) use ($keys) {
990 1
          return \array_combine($keys, $values);
991 1
        }, $rows
992
    );
993
  }
994
995
  /**
996
   * Fetch as "\Generator" via yield.
997
   *
998
   * @param object|string $class  <p>
999
   *                              <strong>string</strong>: create a new object (with optional constructor
1000
   *                              parameter)<br>
1001
   *                              <strong>object</strong>: use a object and fill the the data into
1002
   *                              </p>
1003
   * @param null|array    $params optional
1004
   *                              <p>
1005
   *                              An array of parameters to pass to the constructor, used if $class is a
1006
   *                              string.
1007
   *                              </p>
1008
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
1009
   *
1010
   * @return \Generator
1011
   */
1012 2 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...
1013
  {
1014 2
    if ($reset === true) {
1015
      $this->reset();
1016
    }
1017
1018
    // fallback
1019 2
    if (!$class || $class === 'stdClass') {
1020 2
      $class = '\stdClass';
1021
    }
1022
1023 2
    $propertyAccessor = PropertyAccess::createPropertyAccessor();
1024
1025 2
    if (\is_object($class)) {
1026
      $classTmp = $class;
1027
1028 2
    } else if ($class && $params) {
1029
1030
      $reflectorTmp = new \ReflectionClass($class);
1031
      $classTmp = $reflectorTmp->newInstanceArgs($params);
1032
1033
    } else {
1034
1035 2
      $classTmp = new $class;
1036
1037
    }
1038
1039 2
    if (\is_object($class)) {
1040
      $row = $this->fetch_assoc();
1041
      $row = $row ? $this->cast($row) : false;
1042
1043
      if (!$row) {
1044
        return;
1045
      }
1046
1047
      foreach ($row as $key => $value) {
1048
        if ($class === '\stdClass') {
1049
          $classTmp->{$key} = $value;
1050
        } else {
1051
          $propertyAccessor->setValue($classTmp, $key, $value);
1052
        }
1053
      }
1054
1055
      yield $classTmp;
1056
    }
1057 2
  }
1058
1059
  /**
1060
   * @return mixed
1061
   */
1062 76
  private function fetch_assoc()
1063
  {
1064 76
    if ($this->_result instanceof \Doctrine\DBAL\Statement) {
1065 15
      $this->_result->setFetchMode(FetchMode::ASSOCIATIVE);
1066 15
      $object = $this->_result->fetch();
1067
1068 15
      return $object;
1069
    }
1070
1071 61
    return mysqli_fetch_assoc($this->_result);
1072
  }
1073
1074
  /**
1075
   * @return array|bool
1076
   */
1077 1
  private function fetch_fields()
1078
  {
1079 1
    if ($this->_result instanceof \mysqli_result) {
1080 1
      return \mysqli_fetch_fields($this->_result);
1081
    }
1082
1083
    if ($this->doctrineMySQLiStmt) {
1084
      $metadataTmp = $this->doctrineMySQLiStmt->result_metadata();
1085
1086
      return $metadataTmp->fetch_fields();
1087
    }
1088
1089
    if ($this->doctrinePdoStmt) {
1090
      $fields = [];
1091
1092
      static $THIS_CLASS_TMP = null;
1093
      if ($THIS_CLASS_TMP === null) {
1094
        $THIS_CLASS_TMP = new \ReflectionClass(__CLASS__);
1095
      }
1096
1097
      $totalColumnsTmp = $this->doctrinePdoStmt->columnCount();
1098
      for ($counterTmp = 0; $counterTmp < $totalColumnsTmp; $counterTmp++) {
1099
        $metadataTmp = $this->doctrinePdoStmt->getColumnMeta($counterTmp);
1100
        $fieldTmp = new \stdClass();
1101
        foreach ($metadataTmp as $metadataTmpKey => $metadataTmpValue) {
1102
          $fieldTmp->{$metadataTmpKey} = $metadataTmpValue;
1103
        }
1104
1105
        $typeNativeTmp = 'MYSQL_TYPE_' . $metadataTmp['native_type'];
1106
        $typeTmp = $THIS_CLASS_TMP->getConstant($typeNativeTmp);
1107
        if ($typeTmp) {
1108
          $fieldTmp->type = $typeTmp;
1109
        } else {
1110
          $fieldTmp->type = '';
1111
        }
1112
1113
        $fields[] = $fieldTmp;
1114
      }
1115
1116
      return $fields;
1117
    }
1118
1119
    return false;
1120
  }
1121
1122
  /**
1123
   * Returns the first row element from the result.
1124
   *
1125
   * @param string $column The column name to use as value (optional)
1126
   *
1127
   * @return mixed A row array or a single scalar value
1128
   */
1129 3
  public function first(string $column = null)
1130
  {
1131 3
    $pos = $this->current_row;
1132 3
    $first = $this->fetchCallable(0, $column);
1133 3
    $this->rewind($pos);
1134
1135 3
    return $first;
1136
  }
1137
1138
  /**
1139
   * free the memory
1140
   */
1141 90
  public function free()
1142
  {
1143 90
    if ($this->_result instanceof \mysqli_result) {
1144
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
1145 66
      @\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...
1146 66
      $this->_result = null;
1147
1148 66
      return true;
1149
    }
1150
1151 25
    $this->_result = null;
1152
1153 25
    return false;
1154
  }
1155
1156
  /**
1157
   * alias for "Result->fetch()"
1158
   *
1159
   * @see Result::fetch()
1160
   *
1161
   * @return array|object|false <p><strong>false</strong> on error</p>
1162
   */
1163 2
  public function get()
1164
  {
1165 2
    return $this->fetch();
1166
  }
1167
1168
  /**
1169
   * alias for "Result->fetchAll()"
1170
   *
1171
   * @see Result::fetchAll()
1172
   *
1173
   * @return array
1174
   */
1175 2
  public function getAll(): array
1176
  {
1177 2
    return $this->fetchAll();
1178
  }
1179
1180
  /**
1181
   * alias for "Result->fetchAllColumn()"
1182
   *
1183
   * @see Result::fetchAllColumn()
1184
   *
1185
   * @param string $column
1186
   * @param bool   $skipNullValues
1187
   *
1188
   * @return array
1189
   */
1190
  public function getAllColumn(string $column, bool $skipNullValues = false): array
1191
  {
1192
    return $this->fetchAllColumn($column, $skipNullValues);
1193
  }
1194
1195
  /**
1196
   * alias for "Result->fetchAllArray()"
1197
   *
1198
   * @see Result::fetchAllArray()
1199
   *
1200
   * @return array
1201
   */
1202 2
  public function getArray(): array
1203
  {
1204 2
    return $this->fetchAllArray();
1205
  }
1206
1207
  /**
1208
   * alias for "Result->fetchAllArrayy()"
1209
   *
1210
   * @see Result::fetchAllArrayy()
1211
   *
1212
   * @return Arrayy
1213
   */
1214
  public function getArrayy(): Arrayy
1215
  {
1216
    return $this->fetchAllArrayy();
1217
  }
1218
1219
  /**
1220
   * alias for "Result->fetchColumn()"
1221
   *
1222
   * @see Result::fetchColumn()
1223
   *
1224
   * @param string $column
1225
   * @param bool   $asArray
1226
   * @param bool   $skipNullValues
1227
   *
1228
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
1229
   *                      "$asArray"</p>
1230
   */
1231 2
  public function getColumn(string $column, bool $skipNullValues = true, bool $asArray = false)
1232
  {
1233 2
    return $this->fetchColumn($column, $skipNullValues, $asArray);
1234
  }
1235
1236
  /**
1237
   * @return string
1238
   */
1239 2
  public function getDefaultResultType(): string
1240
  {
1241 2
    return $this->_default_result_type;
1242
  }
1243
1244
  /**
1245
   * alias for "Result->fetchAllObject()"
1246
   *
1247
   * @see Result::fetchAllObject()
1248
   *
1249
   * @return array of mysql-objects
1250
   */
1251 2
  public function getObject(): array
1252
  {
1253 2
    return $this->fetchAllObject();
1254
  }
1255
1256
  /**
1257
   * alias for "Result->fetchAllYield()"
1258
   *
1259
   * @see Result::fetchAllYield()
1260
   *
1261
   * @param bool $asArray
1262
   *
1263
   * @return \Generator
1264
   */
1265
  public function getYield($asArray = false): \Generator
1266
  {
1267
    yield $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...
1268
  }
1269
1270
  /**
1271
   * Check if the result is empty.
1272
   *
1273
   * @return bool
1274
   */
1275 46
  public function is_empty(): bool
1276
  {
1277 46
    return !($this->num_rows > 0);
1278
  }
1279
1280
  /**
1281
   * Fetch all results as "json"-string.
1282
   *
1283
   * @return string
1284
   */
1285 2
  public function json(): string
1286
  {
1287 2
    $data = $this->fetchAllArray();
1288
1289 2
    return UTF8::json_encode($data);
1290
  }
1291
1292
  /**
1293
   * Returns the last row element from the result.
1294
   *
1295
   * @param string $column The column name to use as value (optional)
1296
   *
1297
   * @return mixed A row array or a single scalar value
1298
   */
1299 3
  public function last(string $column = null)
1300
  {
1301 3
    $pos = $this->current_row;
1302 3
    $last = $this->fetchCallable($this->num_rows - 1, $column);
1303 3
    $this->rewind($pos);
1304
1305 3
    return $last;
1306
  }
1307
1308
  /**
1309
   * Set the mapper...
1310
   *
1311
   * @param \Closure $callable
1312
   *
1313
   * @return $this
1314
   */
1315 1
  public function map(\Closure $callable)
1316
  {
1317 1
    $this->_mapper = $callable;
1318
1319 1
    return $this;
1320
  }
1321
1322
  /**
1323
   * Alias of count(). Deprecated.
1324
   *
1325
   * @return int The number of rows in the result
1326
   */
1327 1
  public function num_rows(): int
1328
  {
1329 1
    return $this->count();
1330
  }
1331
1332
  /**
1333
   * ArrayAccess interface implementation.
1334
   *
1335
   * @param int $offset <p>Offset number</p>
1336
   *
1337
   * @return bool <p>true if offset exists, false otherwise</p>
1338
   */
1339 1
  public function offsetExists($offset): bool
1340
  {
1341 1
    return \is_int($offset) && $offset >= 0 && $offset < $this->num_rows;
1342
  }
1343
1344
  /**
1345
   * ArrayAccess interface implementation.
1346
   *
1347
   * @param int $offset Offset number
1348
   *
1349
   * @return mixed
1350
   */
1351 1
  public function offsetGet($offset)
1352
  {
1353 1
    if ($this->offsetExists($offset)) {
1354 1
      return $this->fetchCallable($offset);
1355
    }
1356
1357
    throw new \OutOfBoundsException("undefined offset ($offset)");
1358
  }
1359
1360
  /**
1361
   * ArrayAccess interface implementation. Not implemented by design.
1362
   *
1363
   * @param mixed $offset
1364
   * @param mixed $value
1365
   */
1366
  public function offsetSet($offset, $value)
1367
  {
1368
    /** @noinspection UselessReturnInspection */
1369
    return;
1370
  }
1371
1372
  /**
1373
   * ArrayAccess interface implementation. Not implemented by design.
1374
   *
1375
   * @param mixed $offset
1376
   */
1377
  public function offsetUnset($offset)
1378
  {
1379
    /** @noinspection UselessReturnInspection */
1380
    return;
1381
  }
1382
1383
  /**
1384
   * Reset the offset (data_seek) for the results.
1385
   *
1386
   * @return Result
1387
   */
1388 43
  public function reset(): self
1389
  {
1390 43
    if (!$this->is_empty()) {
1391
1392 41
      if ($this->doctrineMySQLiStmt) {
1393 9
        $this->doctrineMySQLiStmt->data_seek(0);
1394
      }
1395
1396 41
      if ($this->_result instanceof \mysqli_result) {
1397 32
        \mysqli_data_seek($this->_result, 0);
1398
      }
1399
    }
1400
1401 43
    return $this;
1402
  }
1403
1404
  /**
1405
   * You can set the default result-type to Result::RESULT_TYPE_*.
1406
   *
1407
   * INFO: used for "fetch()" and "fetchAll()"
1408
   *
1409
   * @param string $default_result_type
1410
   */
1411 4
  public function setDefaultResultType(string $default_result_type = self::RESULT_TYPE_OBJECT)
1412
  {
1413
    if (
1414 4
        $default_result_type === self::RESULT_TYPE_OBJECT
1415
        ||
1416 4
        $default_result_type === self::RESULT_TYPE_ARRAY
1417
        ||
1418
        $default_result_type === self::RESULT_TYPE_ARRAYY
1419
        ||
1420 4
        $default_result_type === self::RESULT_TYPE_YIELD
1421
    ) {
1422 4
      $this->_default_result_type = $default_result_type;
1423
    }
1424 4
  }
1425
1426
  /**
1427
   * @param int      $offset
1428
   * @param null|int $length
1429
   * @param bool     $preserve_keys
1430
   *
1431
   * @return array
1432
   */
1433 1
  public function slice(int $offset = 0, int $length = null, bool $preserve_keys = false): array
1434
  {
1435
    // init
1436 1
    $slice = [];
1437
1438 1
    if ($offset < 0) {
1439 1
      if (\abs($offset) > $this->num_rows) {
1440 1
        $offset = 0;
1441
      } else {
1442 1
        $offset = $this->num_rows - (int)\abs($offset);
1443
      }
1444
    }
1445
1446 1
    $length = null !== $length ? (int)$length : $this->num_rows;
1447 1
    $n = 0;
1448 1
    for ($i = $offset; $i < $this->num_rows && $n < $length; $i++) {
1449 1
      if ($preserve_keys) {
1450 1
        $slice[$i] = $this->fetchCallable($i);
1451
      } else {
1452 1
        $slice[] = $this->fetchCallable($i);
1453
      }
1454 1
      ++$n;
1455
    }
1456
1457 1
    return $slice;
1458
  }
1459
}
1460