Completed
Push — master ( bf5e6a...380057 )
by Lars
01:59
created

Result   D

Complexity

Total Complexity 181

Size/Duplication

Total Lines 1239
Duplicated Lines 17.27 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 77.2%

Importance

Changes 5
Bugs 1 Features 1
Metric Value
wmc 181
c 5
b 1
f 1
lcom 1
cbo 5
dl 214
loc 1239
ccs 298
cts 386
cp 0.772
rs 4.4102

53 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A __destruct() 0 4 1
A __invoke() 0 8 2
A __toString() 0 4 1
C cast() 10 52 14
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
A seek() 0 8 4
A valid() 0 4 1
B fetch() 0 16 5
B fetchAll() 0 16 5
A fetchAllArray() 0 16 3
A fetchAllArrayy() 0 16 3
A fetchAllColumn() 0 4 1
C fetchAllObject() 22 50 12
C fetchAllYield() 22 47 12
A fetchArray() 17 17 4
A fetchArrayPair() 0 19 4
A fetchArrayy() 17 17 4
B fetchCallable() 0 18 7
C fetchColumn() 29 48 10
A fetchFields() 0 13 2
B fetchGroups() 28 28 5
C fetchObject() 10 38 13
B fetchPairs() 28 28 5
B fetchTranspose() 0 25 5
C fetchYield() 31 44 13
A first() 0 8 1
A free() 0 12 2
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 getYield() 0 4 1
A getColumn() 0 4 1
A getDefaultResultType() 0 4 1
A getObject() 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 5 1
A offsetUnset() 0 5 1
A reset() 0 8 2
B setDefaultResultType() 0 14 5
C 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
 * @package   voku\db
15
 */
16
final class Result implements \Countable, \SeekableIterator, \ArrayAccess
17
{
18
19
  const RESULT_TYPE_ARRAY  = 'array';
20
  const RESULT_TYPE_ARRAYY = 'Arrayy';
21
  const RESULT_TYPE_OBJECT = 'object';
22
  const RESULT_TYPE_YIELD  = 'yield';
23
24
  /**
25
   * @var int
26
   */
27
  public $num_rows;
28
29
  /**
30
   * @var string
31
   */
32
  public $sql;
33
34
  /**
35
   * @var \mysqli_result
36
   */
37
  private $_result;
38
39
  /**
40
   * @var int
41
   */
42
  private $current_row;
43
44
  /**
45
   * @var \Closure|null
46
   */
47
  private $_mapper;
48
49
  /**
50
   * @var string
51
   */
52
  private $_default_result_type = self::RESULT_TYPE_OBJECT;
53
54
  /**
55
   * Result constructor.
56
   *
57
   * @param string         $sql
58
   * @param \mysqli_result $result
59
   * @param \Closure       $mapper Optional callback mapper for the "fetchCallable()" method
60
   */
61 64
  public function __construct(string $sql = '', \mysqli_result $result, \Closure $mapper = null)
62
  {
63 64
    $this->sql = $sql;
64
65 64
    $this->_result = $result;
66
67 64
    $this->current_row = 0;
68 64
    $this->num_rows = (int)$this->_result->num_rows;
69
70 64
    $this->_mapper = $mapper;
71 64
  }
72
73
  /**
74
   * __destruct
75
   */
76 63
  public function __destruct()
77
  {
78 63
    $this->free();
79 63
  }
80
81
  /**
82
   * Runs a user-provided callback with the MySQLi_Result object given as
83
   * argument and returns the result, or returns the MySQLi_Result object if
84
   * called without an argument.
85
   *
86
   * @param callable $callback User-provided callback (optional)
87
   *
88
   * @return mixed|\mysqli_result
89
   */
90 2
  public function __invoke(callable $callback = null)
91
  {
92 2
    if (null !== $callback) {
93 2
      return $callback($this->_result);
94
    }
95
96 1
    return $this->_result;
97
  }
98
99
  /**
100
   * Get the current "num_rows" as string.
101
   *
102
   * @return string
103
   */
104
  public function __toString()
105
  {
106
    return (string)$this->num_rows;
107
  }
108
109
  /**
110
   * Cast data into int, float or string.
111
   *
112
   * <p>
113
   *   <br />
114
   *   INFO: install / use "mysqlnd"-driver for better performance
115
   * </p>
116
   *
117
   * @param array|object $data
118
   *
119
   * @return array|object|false <p><strong>false</strong> on error</p>
120
   */
121 46
  private function cast(&$data)
122
  {
123 46
    if (Helper::isMysqlndIsUsed() === true) {
124 46
      return $data;
125
    }
126
127
    // init
128
    static $FIELDS_CACHE = [];
129
    static $TYPES_CACHE = [];
130
131
    $result_hash = \spl_object_hash($this->_result);
132
133
    if (!isset($FIELDS_CACHE[$result_hash])) {
134
      $FIELDS_CACHE[$result_hash] = \mysqli_fetch_fields($this->_result);
135
    }
136
137
    if ($FIELDS_CACHE[$result_hash] === false) {
138
      return false;
139
    }
140
141
    if (!isset($TYPES_CACHE[$result_hash])) {
142
      foreach ($FIELDS_CACHE[$result_hash] as $field) {
143
        switch ($field->type) {
144
          case 3:
145
            $TYPES_CACHE[$result_hash][$field->name] = 'int';
146
            break;
147
          case 4:
148
            $TYPES_CACHE[$result_hash][$field->name] = 'float';
149
            break;
150
          default:
151
            $TYPES_CACHE[$result_hash][$field->name] = 'string';
152
            break;
153
        }
154
      }
155
    }
156
157
    if (\is_array($data) === true) {
158 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
159
        if (isset($data[$type_name])) {
160
          settype($data[$type_name], $type);
161
        }
162
      }
163
    } elseif (\is_object($data)) {
164 View Code Duplication
      foreach ($TYPES_CACHE[$result_hash] as $type_name => $type) {
165
        if (isset($data->{$type_name})) {
166
          \settype($data->{$type_name}, $type);
167
        }
168
      }
169
    }
170
171
    return $data;
172
  }
173
174
  /**
175
   * Countable interface implementation.
176
   *
177
   * @return int The number of rows in the result
178
   */
179 2
  public function count(): int
180
  {
181 2
    return $this->num_rows;
182
  }
183
184
  /**
185
   * Iterator interface implementation.
186
   *
187
   * @return mixed The current element
188
   */
189 6
  public function current()
190
  {
191 6
    return $this->fetchCallable($this->current_row);
192
  }
193
194
  /**
195
   * Iterator interface implementation.
196
   *
197
   * @return int The current element key (row index; zero-based)
198
   */
199 1
  public function key(): int
200
  {
201 1
    return $this->current_row;
202
  }
203
204
  /**
205
   * Iterator interface implementation.
206
   *
207
   * @return void
208
   */
209 6
  public function next()
210
  {
211 6
    $this->current_row++;
212 6
  }
213
214
  /**
215
   * Iterator interface implementation.
216
   *
217
   * @param int $row Row position to rewind to; defaults to 0
218
   *
219
   * @return void
220
   */
221 10
  public function rewind($row = 0)
222
  {
223 10
    if ($this->seek($row)) {
224 8
      $this->current_row = $row;
225
    }
226 10
  }
227
228
  /**
229
   * Moves the internal pointer to the specified row position.
230
   *
231
   * @param int $row <p>Row position; zero-based and set to 0 by default</p>
232
   *
233
   * @return bool <p>true on success, false otherwise</p>
234
   */
235 18
  public function seek($row = 0): bool
236
  {
237 18
    if (\is_int($row) && $row >= 0 && $row < $this->num_rows) {
238 14
      return \mysqli_data_seek($this->_result, $row);
239
    }
240
241 4
    return false;
242
  }
243
244
  /**
245
   * Iterator interface implementation.
246
   *
247
   * @return bool <p>true if the current index is valid, false otherwise</p>
248
   */
249 6
  public function valid(): bool
250
  {
251 6
    return $this->current_row < $this->num_rows;
252
  }
253
254
  /**
255
   * Fetch.
256
   *
257
   * <p>
258
   *   <br />
259
   *   INFO: this will return an object by default, not an array<br />
260
   *   and you can change the behaviour via "Result->setDefaultResultType()"
261
   * </p>
262
   *
263
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
264
   *
265
   * @return array|object|false <p><strong>false</strong> on error</p>
266
   */
267 2
  public function fetch(bool $reset = false)
268
  {
269 2
    $return = false;
270
271 2
    if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
272 2
      $return = $this->fetchObject('', null, $reset);
273 2
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
274 2
      $return = $this->fetchArray($reset);
275
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
276
      $return = $this->fetchArrayy($reset);
277
    } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
278
      $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...
279
    }
280
281 2
    return $return;
282
  }
283
284
  /**
285
   * Fetch all results.
286
   *
287
   * <p>
288
   *   <br />
289
   *   INFO: this will return an object by default, not an array<br />
290
   *   and you can change the behaviour via "Result->setDefaultResultType()"
291
   * </p>
292
   *
293
   * @return array
294
   */
295 2
  public function fetchAll(): array
296
  {
297 2
    $return = [];
298
299 2
    if ($this->_default_result_type === self::RESULT_TYPE_OBJECT) {
300 2
      $return = $this->fetchAllObject();
301 1
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAY) {
302 1
      $return = $this->fetchAllArray();
303
    } elseif ($this->_default_result_type === self::RESULT_TYPE_ARRAYY) {
304
      $return = $this->fetchAllArrayy();
305
    } elseif ($this->_default_result_type === self::RESULT_TYPE_YIELD) {
306
      $return = $this->fetchAllYield();
307
    }
308
309 2
    return $return;
310
  }
311
312
  /**
313
   * Fetch all results as array.
314
   *
315
   * @return array
316
   */
317 14
  public function fetchAllArray(): array
318
  {
319 14
    if ($this->is_empty()) {
320
      return [];
321
    }
322
323 14
    $this->reset();
324
325 14
    $data = [];
326
    /** @noinspection PhpAssignmentInConditionInspection */
327 14
    while ($row = \mysqli_fetch_assoc($this->_result)) {
328 14
      $data[] = $this->cast($row);
329
    }
330
331 14
    return $data;
332
  }
333
334
  /**
335
   * Fetch all results as "Arrayy"-object.
336
   *
337
   * @return Arrayy
338
   */
339 4
  public function fetchAllArrayy(): Arrayy
340
  {
341 4
    if ($this->is_empty()) {
342
      return Arrayy::create([]);
343
    }
344
345 4
    $this->reset();
346
347 4
    $data = [];
348
    /** @noinspection PhpAssignmentInConditionInspection */
349 4
    while ($row = \mysqli_fetch_assoc($this->_result)) {
350 4
      $data[] = $this->cast($row);
351
    }
352
353 4
    return Arrayy::create($data);
354
  }
355
356
  /**
357
   * Fetch a single column as an 1-dimension array.
358
   *
359
   * @param string $column
360
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: false</p>
361
   *
362
   * @return array <p>Return an empty array if the "$column" wasn't found</p>
363
   */
364 3
  public function fetchAllColumn(string $column, bool $skipNullValues = false): array
365
  {
366 3
    return $this->fetchColumn($column, $skipNullValues, true);
367
  }
368
369
  /**
370
   * Fetch all results as array with objects.
371
   *
372
   * @param object|string $class  <p>
373
   *                              <strong>string</strong>: create a new object (with optional constructor
374
   *                              parameter)<br>
375
   *                              <strong>object</strong>: use a object and fill the the data into
376
   *                              </p>
377
   * @param null|array    $params optional
378
   *                              <p>
379
   *                              An array of parameters to pass to the constructor, used if $class is a
380
   *                              string.
381
   *                              </p>
382
   *
383
   * @return array
384
   */
385 11
  public function fetchAllObject($class = '', array $params = null): array
386
  {
387 11
    if ($this->is_empty()) {
388 1
      return [];
389
    }
390
391
    // init
392 10
    $data = [];
393 10
    $this->reset();
394
395 10 View Code Duplication
    if ($class && \is_object($class)) {
396 7
      $propertyAccessor = PropertyAccess::createPropertyAccessor();
397
      /** @noinspection PhpAssignmentInConditionInspection */
398 7
      while ($row = \mysqli_fetch_assoc($this->_result)) {
399 7
        $classTmp = clone $class;
400 7
        $row = $this->cast($row);
401 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...
402 7
          $propertyAccessor->setValue($classTmp, $key, $value);
403
        }
404 7
        $data[] = $classTmp;
405
      }
406
407 7
      return $data;
408
    }
409
410 3
    if ($class && $params) {
411
      /** @noinspection PhpAssignmentInConditionInspection */
412 1
      while ($row = \mysqli_fetch_object($this->_result, $class, $params)) {
413 1
        $data[] = $this->cast($row);
414
      }
415
416 1
      return $data;
417
    }
418
419 3 View Code Duplication
    if ($class) {
420
      /** @noinspection PhpAssignmentInConditionInspection */
421 1
      while ($row = \mysqli_fetch_object($this->_result, $class)) {
422 1
        $data[] = $this->cast($row);
423
      }
424
425 1
      return $data;
426
    }
427
428
    /** @noinspection PhpAssignmentInConditionInspection */
429 3
    while ($row = \mysqli_fetch_object($this->_result)) {
430 3
      $data[] = $this->cast($row);
431
    }
432
433 3
    return $data;
434
  }
435
436
  /**
437
   * Fetch all results as "\Generator" via yield.
438
   *
439
   * @param object|string $class  <p>
440
   *                              <strong>string</strong>: create a new object (with optional constructor
441
   *                              parameter)<br>
442
   *                              <strong>object</strong>: use a object and fill the the data into
443
   *                              </p>
444
   * @param null|array    $params optional
445
   *                              <p>
446
   *                              An array of parameters to pass to the constructor, used if $class is a
447
   *                              string.
448
   *                              </p>
449
   *
450
   * @return \Generator
451
   */
452 1
  public function fetchAllYield($class = '', array $params = null): \Generator
453
  {
454 1
    if ($this->is_empty()) {
455
      return;
456
    }
457
458
    // init
459 1
    $this->reset();
460
461 1 View Code Duplication
    if ($class && \is_object($class)) {
462
      $propertyAccessor = PropertyAccess::createPropertyAccessor();
463
      /** @noinspection PhpAssignmentInConditionInspection */
464
      while ($row = \mysqli_fetch_assoc($this->_result)) {
465
        $classTmp = clone $class;
466
        $row = $this->cast($row);
467
        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...
468
          $propertyAccessor->setValue($classTmp, $key, $value);
469
        }
470
        yield $classTmp;
471
      }
472
473
      return;
474
    }
475
476 1 View Code Duplication
    if ($class && $params) {
477
      /** @noinspection PhpAssignmentInConditionInspection */
478
      while ($row = \mysqli_fetch_object($this->_result, $class, $params)) {
479
        yield $this->cast($row);
480
      }
481
482
      return;
483
    }
484
485 1
    if ($class) {
486
      /** @noinspection PhpAssignmentInConditionInspection */
487 1
      while ($row = \mysqli_fetch_object($this->_result, $class)) {
488 1
        yield $this->cast($row);
489
      }
490
491 1
      return;
492
    }
493
494
    /** @noinspection PhpAssignmentInConditionInspection */
495 1
    while ($row = \mysqli_fetch_object($this->_result)) {
496 1
      yield $this->cast($row);
497
    }
498 1
  }
499
500
  /**
501
   * Fetch as array.
502
   *
503
   * @param bool $reset
504
   *
505
   * @return array|false <p><strong>false</strong> on error</p>
506
   */
507 16 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...
508
  {
509 16
    if ($reset === true) {
510 1
      $this->reset();
511
    }
512
513 16
    $row = \mysqli_fetch_assoc($this->_result);
514 16
    if ($row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
515 15
      return $this->cast($row);
516
    }
517
518 3
    if ($row === null) {
519 3
      return [];
520
    }
521
522
    return false;
523
  }
524
525
  /**
526
   * Fetch data as a key/value pair array.
527
   *
528
   * <p>
529
   *   <br />
530
   *   INFO: both "key" and "value" must exists in the fetched data
531
   *   the key will be the new key of the result-array
532
   *   <br /><br />
533
   * </p>
534
   *
535
   * e.g.:
536
   * <code>
537
   *    fetchArrayPair('some_id', 'some_value');
538
   *    // array(127 => 'some value', 128 => 'some other value')
539
   * </code>
540
   *
541
   * @param string $key
542
   * @param string $value
543
   *
544
   * @return array
545
   */
546 1
  public function fetchArrayPair(string $key, string $value): array
547
  {
548 1
    $arrayPair = [];
549 1
    $data = $this->fetchAllArray();
550
551 1
    foreach ($data as $_row) {
552
      if (
553 1
          \array_key_exists($key, $_row) === true
554
          &&
555 1
          \array_key_exists($value, $_row) === true
556
      ) {
557 1
        $_key = $_row[$key];
558 1
        $_value = $_row[$value];
559 1
        $arrayPair[$_key] = $_value;
560
      }
561
    }
562
563 1
    return $arrayPair;
564
  }
565
566
  /**
567
   * Fetch as "Arrayy"-object.
568
   *
569
   * @param bool $reset optional <p>Reset the \mysqli_result counter.</p>
570
   *
571
   * @return Arrayy|false <p><strong>false</strong> on error</p>
572
   */
573 2 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...
574
  {
575 2
    if ($reset === true) {
576
      $this->reset();
577
    }
578
579 2
    $row = \mysqli_fetch_assoc($this->_result);
580 2
    if ($row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
581 1
      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...
582
    }
583
584 1
    if ($row === null) {
585 1
      return Arrayy::create();
586
    }
587
588
    return false;
589
  }
590
591
  /**
592
   * Fetches a row or a single column within a row. Returns null if there are
593
   * no more rows in the result.
594
   *
595
   * @param int    $row    The row number (optional)
596
   * @param string $column The column name (optional)
597
   *
598
   * @return mixed An associative array or a scalar value
599
   */
600 15
  public function fetchCallable(int $row = null, string $column = null)
601
  {
602 15
    if (!$this->num_rows) {
603 2
      return null;
604
    }
605
606 13
    if (null !== $row) {
607 12
      $this->seek($row);
608
    }
609
610 13
    $rows = \mysqli_fetch_assoc($this->_result);
611
612 13
    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...
613 5
      return \is_array($rows) && isset($rows[$column]) ? $rows[$column] : null;
614
    }
615
616 12
    return \is_callable($this->_mapper) ? \call_user_func($this->_mapper, $rows) : $rows;
617
  }
618
619
  /**
620
   * Fetch a single column as string (or as 1-dimension array).
621
   *
622
   * @param string $column
623
   * @param bool   $skipNullValues <p>Skip "NULL"-values. | default: true</p>
624
   * @param bool   $asArray        <p>Get all values and not only the last one. | default: false</p>
625
   *
626
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
627
   *                      "$asArray"</p>
628
   */
629 5
  public function fetchColumn(string $column = '', bool $skipNullValues = true, bool $asArray = false)
630
  {
631 5
    if ($asArray === false) {
632 3
      $columnData = '';
633
634 3
      $data = $this->fetchAllArrayy()->reverse();
635 3 View Code Duplication
      foreach ($data as $_row) {
636
637 3
        if ($skipNullValues === true) {
638 3
          if (isset($_row[$column]) === false) {
639 3
            continue;
640
          }
641
        } else {
642 1
          if (\array_key_exists($column, $_row) === false) {
643 1
            break;
644
          }
645
        }
646
647 3
        $columnData = $_row[$column];
648 3
        break;
649
      }
650
651 3
      return $columnData;
652
    }
653
654
    // -- return as array -->
655
656 3
    $columnData = [];
657
658 3
    $data = $this->fetchAllArray();
659
660 3 View Code Duplication
    foreach ($data as $_row) {
661
662 3
      if ($skipNullValues === true) {
663 1
        if (isset($_row[$column]) === false) {
664 1
          continue;
665
        }
666
      } else {
667 3
        if (\array_key_exists($column, $_row) === false) {
668 1
          break;
669
        }
670
      }
671
672 3
      $columnData[] = $_row[$column];
673
    }
674
675 3
    return $columnData;
676
  }
677
678
  /**
679
   * Return rows of field information in a result set. This function is a
680
   * basically a wrapper on the native mysqli_fetch_fields function.
681
   *
682
   * @param bool $as_array Return each field info as array; defaults to false
683
   *
684
   * @return array Array of field information each as an associative array
685
   */
686 1
  public function fetchFields(bool $as_array = false): array
687
  {
688 1
    if ($as_array) {
689 1
      return \array_map(
690 1
          function ($object) {
691 1
            return (array)$object;
692 1
          },
693 1
          \mysqli_fetch_fields($this->_result)
694
      );
695
    }
696
697
    return \mysqli_fetch_fields($this->_result);
698
  }
699
700
  /**
701
   * Returns all rows at once as a grouped array of scalar values or arrays.
702
   *
703
   * @param string $group  The column name to use for grouping
704
   * @param string $column The column name to use as values (optional)
705
   *
706
   * @return array A grouped array of scalar values or arrays
707
   */
708 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...
709
  {
710
    // init
711 1
    $groups = [];
712 1
    $pos = $this->current_row;
713
714 1
    foreach ($this as $row) {
715
716 1
      if (!\array_key_exists($group, $row)) {
717
        continue;
718
      }
719
720 1
      if (null !== $column) {
721
722 1
        if (!\array_key_exists($column, $row)) {
723
          continue;
724
        }
725
726 1
        $groups[$row[$group]][] = $row[$column];
727
      } else {
728 1
        $groups[$row[$group]][] = $row;
729
      }
730
    }
731
732 1
    $this->rewind($pos);
733
734 1
    return $groups;
735
  }
736
737
  /**
738
   * Fetch as object.
739
   *
740
   * @param object|string $class  <p>
741
   *                              <strong>string</strong>: create a new object (with optional constructor
742
   *                              parameter)<br>
743
   *                              <strong>object</strong>: use a object and fill the the data into
744
   *                              </p>
745
   * @param null|array    $params optional
746
   *                              <p>
747
   *                              An array of parameters to pass to the constructor, used if $class is a
748
   *                              string.
749
   *                              </p>
750
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
751
   *
752
   * @return object|false <p><strong>false</strong> on error</p>
753
   */
754 18
  public function fetchObject($class = '', array $params = null, bool $reset = false)
755
  {
756 18
    if ($reset === true) {
757 14
      $this->reset();
758
    }
759
760 18
    if ($class && \is_object($class)) {
761 12
      $row = \mysqli_fetch_assoc($this->_result);
762 12
      $row = $row ? $this->cast($row) : false;
763
764 12
      if (!$row) {
765 3
        return false;
766
      }
767
768 10
      $propertyAccessor = PropertyAccess::createPropertyAccessor();
769 10
      foreach ($row as $key => $value) {
770 10
        $propertyAccessor->setValue($class, $key, $value);
771
      }
772
773 10
      return $class;
774
    }
775
776 7 View Code Duplication
    if ($class && $params) {
777 1
      $row = \mysqli_fetch_object($this->_result, $class, $params);
778
779 1
      return $row ? $this->cast($row) : false;
780
    }
781
782 7 View Code Duplication
    if ($class) {
783 1
      $row = \mysqli_fetch_object($this->_result, $class);
784
785 1
      return $row ? $this->cast($row) : false;
786
    }
787
788 7
    $row = \mysqli_fetch_object($this->_result);
789
790 7
    return $row ? $this->cast($row) : false;
791
  }
792
793
  /**
794
   * Returns all rows at once as key-value pairs.
795
   *
796
   * @param string $key    The column name to use as keys
797
   * @param string $column The column name to use as values (optional)
798
   *
799
   * @return array An array of key-value pairs
800
   */
801 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...
802
  {
803
    // init
804 1
    $pairs = [];
805 1
    $pos = $this->current_row;
806
807 1
    foreach ($this as $row) {
808
809 1
      if (!\array_key_exists($key, $row)) {
810
        continue;
811
      }
812
813 1
      if (null !== $column) {
814
815 1
        if (!\array_key_exists($column, $row)) {
816
          continue;
817
        }
818
819 1
        $pairs[$row[$key]] = $row[$column];
820
      } else {
821 1
        $pairs[$row[$key]] = $row;
822
      }
823
    }
824
825 1
    $this->rewind($pos);
826
827 1
    return $pairs;
828
  }
829
830
  /**
831
   * Returns all rows at once, transposed as an array of arrays. Instead of
832
   * returning rows of columns, this method returns columns of rows.
833
   *
834
   * @param string $column The column name to use as keys (optional)
835
   *
836
   * @return mixed A transposed array of arrays
837
   */
838 1
  public function fetchTranspose(string $column = null)
839
  {
840
    // init
841 1
    $keys = null !== $column ? $this->fetchAllColumn($column) : [];
842 1
    $rows = [];
843 1
    $pos = $this->current_row;
844
845 1
    foreach ($this as $row) {
846 1
      foreach ($row as $key => $value) {
847 1
        $rows[$key][] = $value;
848
      }
849
    }
850
851 1
    $this->rewind($pos);
852
853 1
    if (empty($keys)) {
854 1
      return $rows;
855
    }
856
857 1
    return \array_map(
858 1
        function ($values) use ($keys) {
859 1
          return \array_combine($keys, $values);
860 1
        }, $rows
861
    );
862
  }
863
864
  /**
865
   * Fetch as "\Generator" via yield.
866
   *
867
   * @param object|string $class  <p>
868
   *                              <strong>string</strong>: create a new object (with optional constructor
869
   *                              parameter)<br>
870
   *                              <strong>object</strong>: use a object and fill the the data into
871
   *                              </p>
872
   * @param null|array    $params optional
873
   *                              <p>
874
   *                              An array of parameters to pass to the constructor, used if $class is a
875
   *                              string.
876
   *                              </p>
877
   * @param bool          $reset  optional <p>Reset the \mysqli_result counter.</p>
878
   *
879
   * @return \Generator
880
   */
881 1
  public function fetchYield($class = '', array $params = null, bool $reset = false): \Generator
882
  {
883 1
    if ($reset === true) {
884
      $this->reset();
885
    }
886
887 1 View Code Duplication
    if ($class && \is_object($class)) {
888
      $row = \mysqli_fetch_assoc($this->_result);
889
      $row = $row ? $this->cast($row) : false;
890
891
      if (!$row) {
892
        return;
893
      }
894
895
      $propertyAccessor = PropertyAccess::createPropertyAccessor();
896
      foreach ($row as $key => $value) {
897
        $propertyAccessor->setValue($class, $key, $value);
898
      }
899
900
      yield $class;
901
902
      return;
903
    }
904
905 1 View Code Duplication
    if ($class && $params) {
906
      $row = \mysqli_fetch_object($this->_result, $class, $params);
907
908
      yield $row ? $this->cast($row) : false;
909
910
      return;
911
    }
912
913 1 View Code Duplication
    if ($class) {
914
      $row = \mysqli_fetch_object($this->_result, $class);
915
916
      yield $row ? $this->cast($row) : false;
917
918
      return;
919
    }
920
921 1
    $row = \mysqli_fetch_object($this->_result);
922
923 1
    yield $row ? $this->cast($row) : false;
924 1
  }
925
926
  /**
927
   * Returns the first row element from the result.
928
   *
929
   * @param string $column The column name to use as value (optional)
930
   *
931
   * @return mixed A row array or a single scalar value
932
   */
933 3
  public function first(string $column = null)
934
  {
935 3
    $pos = $this->current_row;
936 3
    $first = $this->fetchCallable(0, $column);
937 3
    $this->rewind($pos);
938
939 3
    return $first;
940
  }
941
942
  /**
943
   * free the memory
944
   */
945 63
  public function free()
946
  {
947 63
    if (!empty($this->_result)) {
948
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
949 63
      @\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...
950 63
      $this->_result = null;
951
952 63
      return true;
953
    }
954
955 1
    return false;
956
  }
957
958
  /**
959
   * alias for "Result->fetch()"
960
   *
961
   * @see Result::fetch()
962
   *
963
   * @return array|object|false <p><strong>false</strong> on error</p>
964
   */
965 1
  public function get()
966
  {
967 1
    return $this->fetch();
968
  }
969
970
  /**
971
   * alias for "Result->fetchAll()"
972
   *
973
   * @see Result::fetchAll()
974
   *
975
   * @return array
976
   */
977 1
  public function getAll(): array
978
  {
979 1
    return $this->fetchAll();
980
  }
981
982
  /**
983
   * alias for "Result->fetchAllColumn()"
984
   *
985
   * @see Result::fetchAllColumn()
986
   *
987
   * @param string $column
988
   * @param bool   $skipNullValues
989
   *
990
   * @return array
991
   */
992
  public function getAllColumn(string $column, bool $skipNullValues = false): array
993
  {
994
    return $this->fetchAllColumn($column, $skipNullValues);
995
  }
996
997
  /**
998
   * alias for "Result->fetchAllArray()"
999
   *
1000
   * @see Result::fetchAllArray()
1001
   *
1002
   * @return array
1003
   */
1004 1
  public function getArray(): array
1005
  {
1006 1
    return $this->fetchAllArray();
1007
  }
1008
1009
  /**
1010
   * alias for "Result->fetchAllArrayy()"
1011
   *
1012
   * @see Result::fetchAllArrayy()
1013
   *
1014
   * @return Arrayy
1015
   */
1016
  public function getArrayy(): Arrayy
1017
  {
1018
    return $this->fetchAllArrayy();
1019
  }
1020
1021
  /**
1022
   * alias for "Result->fetchAllYield()"
1023
   *
1024
   * @see Result::fetchAllYield()
1025
   *
1026
   * @param bool $asArray
1027
   *
1028
   * @return \Generator
1029
   */
1030
  public function getYield($asArray = false): \Generator
1031
  {
1032
    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...
1033
  }
1034
1035
  /**
1036
   * alias for "Result->fetchColumn()"
1037
   *
1038
   * @see Result::fetchColumn()
1039
   *
1040
   * @param string $column
1041
   * @param bool   $asArray
1042
   * @param bool   $skipNullValues
1043
   *
1044
   * @return string|array <p>Return a empty string or an empty array if the "$column" wasn't found, depend on
1045
   *                      "$asArray"</p>
1046
   */
1047 1
  public function getColumn(string $column, bool $skipNullValues = true, bool $asArray = false)
1048
  {
1049 1
    return $this->fetchColumn($column, $skipNullValues, $asArray);
1050
  }
1051
1052
  /**
1053
   * @return string
1054
   */
1055 1
  public function getDefaultResultType(): string
1056
  {
1057 1
    return $this->_default_result_type;
1058
  }
1059
1060
  /**
1061
   * alias for "Result->fetchAllObject()"
1062
   *
1063
   * @see Result::fetchAllObject()
1064
   *
1065
   * @return array of mysql-objects
1066
   */
1067 1
  public function getObject(): array
1068
  {
1069 1
    return $this->fetchAllObject();
1070
  }
1071
1072
  /**
1073
   * Check if the result is empty.
1074
   *
1075
   * @return bool
1076
   */
1077 34
  public function is_empty(): bool
1078
  {
1079 34
    return !($this->num_rows > 0);
1080
  }
1081
1082
  /**
1083
   * Fetch all results as "json"-string.
1084
   *
1085
   * @return string
1086
   */
1087 1
  public function json(): string
1088
  {
1089 1
    $data = $this->fetchAllArray();
1090
1091 1
    return UTF8::json_encode($data);
1092
  }
1093
1094
  /**
1095
   * Returns the last row element from the result.
1096
   *
1097
   * @param string $column The column name to use as value (optional)
1098
   *
1099
   * @return mixed A row array or a single scalar value
1100
   */
1101 3
  public function last(string $column = null)
1102
  {
1103 3
    $pos = $this->current_row;
1104 3
    $last = $this->fetchCallable($this->num_rows - 1, $column);
1105 3
    $this->rewind($pos);
1106
1107 3
    return $last;
1108
  }
1109
1110
  /**
1111
   * Set the mapper...
1112
   *
1113
   * @param \Closure $callable
1114
   *
1115
   * @return $this
1116
   */
1117 1
  public function map(\Closure $callable)
1118
  {
1119 1
    $this->_mapper = $callable;
1120
1121 1
    return $this;
1122
  }
1123
1124
  /**
1125
   * Alias of count(). Deprecated.
1126
   *
1127
   * @return int The number of rows in the result
1128
   */
1129 1
  public function num_rows(): int
1130
  {
1131 1
    return $this->count();
1132
  }
1133
1134
  /**
1135
   * ArrayAccess interface implementation.
1136
   *
1137
   * @param int $offset <p>Offset number</p>
1138
   *
1139
   * @return bool <p>true if offset exists, false otherwise</p>
1140
   */
1141 1
  public function offsetExists($offset): bool
1142
  {
1143 1
    return \is_int($offset) && $offset >= 0 && $offset < $this->num_rows;
1144
  }
1145
1146
  /**
1147
   * ArrayAccess interface implementation.
1148
   *
1149
   * @param int $offset Offset number
1150
   *
1151
   * @return mixed
1152
   */
1153 1
  public function offsetGet($offset)
1154
  {
1155 1
    if ($this->offsetExists($offset)) {
1156 1
      return $this->fetchCallable($offset);
1157
    }
1158
1159
    throw new \OutOfBoundsException("undefined offset ($offset)");
1160
  }
1161
1162
  /**
1163
   * ArrayAccess interface implementation. Not implemented by design.
1164
   *
1165
   * @param mixed $offset
1166
   * @param mixed $value
1167
   */
1168
  public function offsetSet($offset, $value)
1169
  {
1170
    /** @noinspection UselessReturnInspection */
1171
    return;
1172
  }
1173
1174
  /**
1175
   * ArrayAccess interface implementation. Not implemented by design.
1176
   *
1177
   * @param mixed $offset
1178
   */
1179
  public function offsetUnset($offset)
1180
  {
1181
    /** @noinspection UselessReturnInspection */
1182
    return;
1183
  }
1184
1185
  /**
1186
   * Reset the offset (data_seek) for the results.
1187
   *
1188
   * @return Result
1189
   */
1190 32
  public function reset(): self
1191
  {
1192 32
    if (!$this->is_empty()) {
1193 30
      \mysqli_data_seek($this->_result, 0);
1194
    }
1195
1196 32
    return $this;
1197
  }
1198
1199
  /**
1200
   * You can set the default result-type to Result::RESULT_TYPE_*.
1201
   *
1202
   * INFO: used for "fetch()" and "fetchAll()"
1203
   *
1204
   * @param string $default_result_type
1205
   */
1206 2
  public function setDefaultResultType(string $default_result_type = self::RESULT_TYPE_OBJECT)
1207
  {
1208
    if (
1209 2
        $default_result_type === self::RESULT_TYPE_OBJECT
1210
        ||
1211 2
        $default_result_type === self::RESULT_TYPE_ARRAY
1212
        ||
1213
        $default_result_type === self::RESULT_TYPE_ARRAYY
1214
        ||
1215 2
        $default_result_type === self::RESULT_TYPE_YIELD
1216
    ) {
1217 2
      $this->_default_result_type = $default_result_type;
1218
    }
1219 2
  }
1220
1221
  /**
1222
   * @param int      $offset
1223
   * @param null|int $length
1224
   * @param bool     $preserve_keys
1225
   *
1226
   * @return array
1227
   */
1228 1
  public function slice(int $offset = 0, int $length = null, bool $preserve_keys = false): array
1229
  {
1230
    // init
1231 1
    $slice = [];
1232
1233 1
    if ($offset < 0) {
1234 1
      if (\abs($offset) > $this->num_rows) {
1235 1
        $offset = 0;
1236
      } else {
1237 1
        $offset = $this->num_rows - (int)\abs($offset);
1238
      }
1239
    }
1240
1241 1
    $length = null !== $length ? (int)$length : $this->num_rows;
1242 1
    $n = 0;
1243 1
    for ($i = $offset; $i < $this->num_rows && $n < $length; $i++) {
1244 1
      if ($preserve_keys) {
1245 1
        $slice[$i] = $this->fetchCallable($i);
1246
      } else {
1247 1
        $slice[] = $this->fetchCallable($i);
1248
      }
1249 1
      ++$n;
1250
    }
1251
1252 1
    return $slice;
1253
  }
1254
}
1255