• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 26778039881

01 Jun 2026 07:48PM UTC coverage: 88.199%. First build
26778039881

Pull #10257

github

web-flow
Merge ef673ba46 into 3dafe0bd6
Pull Request #10257: fix(database): use currentRow instead of customResultObject in getRowObject and add null fallback

2 of 3 new or added lines in 1 file covered. (66.67%)

22131 of 25092 relevant lines covered (88.2%)

210.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.48
/system/Database/BaseResult.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Database;
15

16
use CodeIgniter\Entity\Entity;
17
use stdClass;
18

19
/**
20
 * @template TConnection
21
 * @template TResult
22
 *
23
 * @implements ResultInterface<TConnection, TResult>
24
 */
25
abstract class BaseResult implements ResultInterface
26
{
27
    /**
28
     * Connection ID
29
     *
30
     * @var TConnection
31
     */
32
    public $connID;
33

34
    /**
35
     * Result ID
36
     *
37
     * @var false|TResult
38
     */
39
    public $resultID;
40

41
    /**
42
     * Result Array
43
     *
44
     * @var list<array>
45
     */
46
    public $resultArray = [];
47

48
    /**
49
     * Result Object
50
     *
51
     * @var list<object>
52
     */
53
    public $resultObject = [];
54

55
    /**
56
     * Custom Result Object
57
     *
58
     * @var array
59
     */
60
    public $customResultObject = [];
61

62
    /**
63
     * Current Row index
64
     *
65
     * @var int
66
     */
67
    public $currentRow = 0;
68

69
    /**
70
     * The number of records in the query result
71
     *
72
     * @var int|null
73
     */
74
    protected $numRows;
75

76
    /**
77
     * Row data
78
     *
79
     * @var array|null
80
     */
81
    public $rowData;
82

83
    /**
84
     * Constructor
85
     *
86
     * @param TConnection $connID
87
     * @param TResult     $resultID
88
     */
89
    public function __construct(&$connID, &$resultID)
90
    {
91
        $this->connID   = $connID;
836✔
92
        $this->resultID = $resultID;
836✔
93
    }
94

95
    /**
96
     * Retrieve the results of the query. Typically an array of
97
     * individual data rows, which can be either an 'array', an
98
     * 'object', or a custom class name.
99
     *
100
     * @param string $type The row type. Either 'array', 'object', or a class name to use
101
     */
102
    public function getResult(string $type = 'object'): array
103
    {
104
        if ($type === 'array') {
214✔
105
            return $this->getResultArray();
43✔
106
        }
107

108
        if ($type === 'object') {
171✔
109
            return $this->getResultObject();
163✔
110
        }
111

112
        return $this->getCustomResultObject($type);
8✔
113
    }
114

115
    /**
116
     * Returns the results as an array of custom objects.
117
     *
118
     * @param class-string $className
119
     *
120
     * @return array
121
     */
122
    public function getCustomResultObject(string $className)
123
    {
124
        if (isset($this->customResultObject[$className])) {
9✔
125
            return $this->customResultObject[$className];
×
126
        }
127

128
        if (! $this->isValidResultId()) {
9✔
129
            return [];
×
130
        }
131

132
        // Don't fetch the result set again if we already have it
133
        $_data = null;
9✔
134
        if (($c = count($this->resultArray)) > 0) {
9✔
135
            $_data = 'resultArray';
×
136
        } elseif (($c = count($this->resultObject)) > 0) {
9✔
137
            $_data = 'resultObject';
×
138
        }
139

140
        if ($_data !== null) {
9✔
141
            for ($i = 0; $i < $c; $i++) {
×
142
                $this->customResultObject[$className][$i] = new $className();
×
143

144
                foreach ($this->{$_data}[$i] as $key => $value) {
×
145
                    $this->customResultObject[$className][$i]->{$key} = $value;
×
146
                }
147
            }
148

149
            return $this->customResultObject[$className];
×
150
        }
151

152
        if ($this->rowData !== null) {
9✔
153
            $this->dataSeek();
×
154
        }
155
        $this->customResultObject[$className] = [];
9✔
156

157
        while ($row = $this->fetchObject($className)) {
9✔
158
            if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) {
9✔
159
                $row->syncOriginal();
×
160
            }
161

162
            $this->customResultObject[$className][] = $row;
9✔
163
        }
164

165
        return $this->customResultObject[$className];
9✔
166
    }
167

168
    /**
169
     * Returns the results as an array of arrays.
170
     *
171
     * If no results, an empty array is returned.
172
     */
173
    public function getResultArray(): array
174
    {
175
        if ($this->resultArray !== []) {
814✔
176
            return $this->resultArray;
×
177
        }
178

179
        // In the event that query caching is on, the result_id variable
180
        // will not be a valid resource so we'll simply return an empty
181
        // array.
182
        if (! $this->isValidResultId()) {
814✔
183
            return [];
×
184
        }
185

186
        if ($this->resultObject !== []) {
814✔
187
            foreach ($this->resultObject as $row) {
×
188
                $this->resultArray[] = (array) $row;
×
189
            }
190

191
            return $this->resultArray;
×
192
        }
193

194
        if ($this->rowData !== null) {
814✔
195
            $this->dataSeek();
×
196
        }
197

198
        while ($row = $this->fetchAssoc()) {
814✔
199
            $this->resultArray[] = $row;
791✔
200
        }
201

202
        return $this->resultArray;
814✔
203
    }
204

205
    /**
206
     * Returns the results as an array of objects.
207
     *
208
     * If no results, an empty array is returned.
209
     *
210
     * @return list<stdClass>
211
     */
212
    public function getResultObject(): array
213
    {
214
        if ($this->resultObject !== []) {
812✔
215
            return $this->resultObject;
7✔
216
        }
217

218
        // In the event that query caching is on, the result_id variable
219
        // will not be a valid resource so we'll simply return an empty
220
        // array.
221
        if (! $this->isValidResultId()) {
812✔
222
            return [];
×
223
        }
224

225
        if ($this->resultArray !== []) {
812✔
226
            foreach ($this->resultArray as $row) {
1✔
227
                $this->resultObject[] = (object) $row;
1✔
228
            }
229

230
            return $this->resultObject;
1✔
231
        }
232

233
        if ($this->rowData !== null) {
812✔
234
            $this->dataSeek();
×
235
        }
236

237
        while ($row = $this->fetchObject()) {
812✔
238
            if (! is_subclass_of($row, Entity::class) && method_exists($row, 'syncOriginal')) {
811✔
239
                $row->syncOriginal();
×
240
            }
241

242
            $this->resultObject[] = $row;
811✔
243
        }
244

245
        return $this->resultObject;
812✔
246
    }
247

248
    /**
249
     * Wrapper object to return a row as either an array, an object, or
250
     * a custom class.
251
     *
252
     * If the row doesn't exist, returns null.
253
     *
254
     * @template T of object
255
     *
256
     * @param int|string                       $n    The index of the results to return, or column name.
257
     * @param 'array'|'object'|class-string<T> $type The type of result object. 'array', 'object' or class name.
258
     *
259
     * @return ($n is string ? float|int|string|null : ($type is 'object' ? stdClass|null : ($type is 'array' ? array|null : T|null)))
260
     */
261
    public function getRow($n = 0, string $type = 'object')
262
    {
263
        // $n is a column name.
264
        if (! is_numeric($n)) {
356✔
265
            // We cache the row data for subsequent uses
266
            if (! is_array($this->rowData)) {
1✔
267
                $this->rowData = $this->getRowArray();
1✔
268
            }
269

270
            // array_key_exists() instead of isset() to allow for NULL values
271
            if (empty($this->rowData) || ! array_key_exists($n, $this->rowData)) {
1✔
272
                return null;
×
273
            }
274

275
            return $this->rowData[$n];
1✔
276
        }
277

278
        if ($type === 'object') {
355✔
279
            return $this->getRowObject($n);
353✔
280
        }
281

282
        if ($type === 'array') {
2✔
283
            return $this->getRowArray($n);
1✔
284
        }
285

286
        return $this->getCustomRowObject($n, $type);
1✔
287
    }
288

289
    /**
290
     * Returns a row as a custom class instance.
291
     *
292
     * If the row doesn't exist, returns null.
293
     *
294
     * @template T of object
295
     *
296
     * @param int             $n         The index of the results to return.
297
     * @param class-string<T> $className
298
     *
299
     * @return T|null
300
     */
301
    public function getCustomRowObject(int $n, string $className)
302
    {
303
        if (! isset($this->customResultObject[$className])) {
1✔
304
            $this->getCustomResultObject($className);
1✔
305
        }
306

307
        if (empty($this->customResultObject[$className])) {
1✔
308
            return null;
×
309
        }
310

311
        if ($n !== $this->currentRow && isset($this->customResultObject[$className][$n])) {
1✔
NEW
312
            $this->currentRow = $n;
×
313
        }
314

315
        return $this->customResultObject[$className][$this->currentRow];
1✔
316
    }
317

318
    /**
319
     * Returns a single row from the results as an array.
320
     *
321
     * If row doesn't exist, returns null.
322
     *
323
     * @return array|null
324
     */
325
    public function getRowArray(int $n = 0)
326
    {
327
        $result = $this->getResultArray();
8✔
328
        if ($result === []) {
8✔
329
            return null;
×
330
        }
331

332
        if ($n !== $this->currentRow && isset($result[$n])) {
8✔
333
            $this->currentRow = $n;
×
334
        }
335

336
        return $result[$this->currentRow] ?? null;
8✔
337
    }
338

339
    /**
340
     * Returns a single row from the results as an object.
341
     *
342
     * If row doesn't exist, returns null.
343
     *
344
     * @return object|stdClass|null
345
     */
346
    public function getRowObject(int $n = 0)
347
    {
348
        $result = $this->getResultObject();
353✔
349
        if ($result === []) {
353✔
350
            return null;
17✔
351
        }
352

353
        if ($n !== $this->currentRow && isset($result[$n])) {
343✔
354
            $this->currentRow = $n;
×
355
        }
356

357
        return $result[$this->currentRow] ?? null;
343✔
358
    }
359

360
    /**
361
     * Assigns an item into a particular column slot.
362
     *
363
     * @param array|string               $key
364
     * @param array|object|stdClass|null $value
365
     *
366
     * @return void
367
     */
368
    public function setRow($key, $value = null)
369
    {
370
        // We cache the row data for subsequent uses
371
        if (! is_array($this->rowData)) {
×
372
            $this->rowData = $this->getRowArray();
×
373
        }
374

375
        if (is_array($key)) {
×
376
            foreach ($key as $k => $v) {
×
377
                $this->rowData[$k] = $v;
×
378
            }
379

380
            return;
×
381
        }
382

383
        if ($key !== '' && $value !== null) {
×
384
            $this->rowData[$key] = $value;
×
385
        }
386
    }
387

388
    /**
389
     * Returns the "first" row of the current results.
390
     *
391
     * @return array|object|null
392
     */
393
    public function getFirstRow(string $type = 'object')
394
    {
395
        $result = $this->getResult($type);
73✔
396

397
        return ($result === []) ? null : $result[0];
73✔
398
    }
399

400
    /**
401
     * Returns the "last" row of the current results.
402
     *
403
     * @return array|object|null
404
     */
405
    public function getLastRow(string $type = 'object')
406
    {
407
        $result = $this->getResult($type);
1✔
408

409
        return ($result === []) ? null : $result[count($result) - 1];
1✔
410
    }
411

412
    /**
413
     * Returns the "next" row of the current results.
414
     *
415
     * @return array|object|null
416
     */
417
    public function getNextRow(string $type = 'object')
418
    {
419
        $result = $this->getResult($type);
1✔
420
        if ($result === []) {
1✔
421
            return null;
×
422
        }
423

424
        return isset($result[$this->currentRow + 1]) ? $result[++$this->currentRow] : null;
1✔
425
    }
426

427
    /**
428
     * Returns the "previous" row of the current results.
429
     *
430
     * @return array|object|null
431
     */
432
    public function getPreviousRow(string $type = 'object')
433
    {
434
        $result = $this->getResult($type);
1✔
435
        if ($result === []) {
1✔
436
            return null;
×
437
        }
438

439
        if (isset($result[$this->currentRow - 1])) {
1✔
440
            $this->currentRow--;
1✔
441
        }
442

443
        return $result[$this->currentRow];
1✔
444
    }
445

446
    /**
447
     * Returns an unbuffered row and move the pointer to the next row.
448
     *
449
     * @return array|object|null
450
     */
451
    public function getUnbufferedRow(string $type = 'object')
452
    {
453
        if ($type === 'array') {
2✔
454
            return $this->fetchAssoc();
1✔
455
        }
456

457
        if ($type === 'object') {
1✔
458
            return $this->fetchObject();
1✔
459
        }
460

461
        return $this->fetchObject($type);
×
462
    }
463

464
    /**
465
     * Number of rows in the result set; checks for previous count, falls
466
     * back on counting resultArray or resultObject, finally fetching resultArray
467
     * if nothing was previously fetched
468
     */
469
    public function getNumRows(): int
470
    {
471
        if (is_int($this->numRows)) {
5✔
472
            return $this->numRows;
×
473
        }
474
        if ($this->resultArray !== []) {
5✔
475
            return $this->numRows = count($this->resultArray);
×
476
        }
477
        if ($this->resultObject !== []) {
5✔
478
            return $this->numRows = count($this->resultObject);
×
479
        }
480

481
        return $this->numRows = count($this->getResultArray());
5✔
482
    }
483

484
    private function isValidResultId(): bool
485
    {
486
        return is_resource($this->resultID) || is_object($this->resultID);
830✔
487
    }
488

489
    /**
490
     * Gets the number of fields in the result set.
491
     */
492
    abstract public function getFieldCount(): int;
493

494
    /**
495
     * Generates an array of column names in the result set.
496
     */
497
    abstract public function getFieldNames(): array;
498

499
    /**
500
     * Generates an array of objects representing field meta-data.
501
     */
502
    abstract public function getFieldData(): array;
503

504
    /**
505
     * Frees the current result.
506
     *
507
     * @return void
508
     */
509
    abstract public function freeResult();
510

511
    /**
512
     * Moves the internal pointer to the desired offset. This is called
513
     * internally before fetching results to make sure the result set
514
     * starts at zero.
515
     *
516
     * @return bool
517
     */
518
    abstract public function dataSeek(int $n = 0);
519

520
    /**
521
     * Returns the result set as an array.
522
     *
523
     * Overridden by driver classes.
524
     *
525
     * @return array|false|null
526
     */
527
    abstract protected function fetchAssoc();
528

529
    /**
530
     * Returns the result set as an object.
531
     *
532
     * @param class-string $className
533
     *
534
     * @return false|object
535
     */
536
    abstract protected function fetchObject(string $className = stdClass::class);
537
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc