Skip to content

Commit 90e715c

Browse files
authored
Merge branch 'codeigniter4:develop' into fix/escape-identifiers-bypass
2 parents 00ef2a6 + 60ca0a0 commit 90e715c

3 files changed

Lines changed: 338 additions & 6 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"psr/log": "^3.0"
1818
},
1919
"require-dev": {
20-
"boundwize/structarmed": "0.12.0",
20+
"boundwize/structarmed": "0.12.5",
2121
"codeigniter/phpstan-codeigniter": "^1.5",
2222
"fakerphp/faker": "^1.24",
2323
"kint-php/kint": "^6.1",

system/Database/BaseResult.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ public function getCustomRowObject(int $n, string $className)
312312
$this->currentRow = $n;
313313
}
314314

315-
return $this->customResultObject[$className][$this->currentRow];
315+
return $this->customResultObject[$className][$this->currentRow] ?? null;
316316
}
317317

318318
/**
@@ -333,7 +333,7 @@ public function getRowArray(int $n = 0)
333333
$this->currentRow = $n;
334334
}
335335

336-
return $result[$this->currentRow];
336+
return $result[$this->currentRow] ?? null;
337337
}
338338

339339
/**
@@ -350,11 +350,11 @@ public function getRowObject(int $n = 0)
350350
return null;
351351
}
352352

353-
if ($n !== $this->customResultObject && isset($result[$n])) {
353+
if ($n !== $this->currentRow && isset($result[$n])) {
354354
$this->currentRow = $n;
355355
}
356356

357-
return $result[$this->currentRow];
357+
return $result[$this->currentRow] ?? null;
358358
}
359359

360360
/**
@@ -440,7 +440,7 @@ public function getPreviousRow(string $type = 'object')
440440
$this->currentRow--;
441441
}
442442

443-
return $result[$this->currentRow];
443+
return $result[$this->currentRow] ?? null;
444444
}
445445

446446
/**
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
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\Test\CIUnitTestCase;
17+
use PHPUnit\Framework\Attributes\Group;
18+
use stdClass;
19+
20+
/**
21+
* @internal
22+
*/
23+
#[Group('Others')]
24+
final class BaseResultTest extends CIUnitTestCase
25+
{
26+
/**
27+
* Create a minimal concrete implementation of BaseResult for testing.
28+
*
29+
* @param list<array<string,mixed>> $resultArray Result set as arrays.
30+
* @param list<object> $resultObject Result set as objects.
31+
*
32+
* @return BaseResult<mixed, mixed>
33+
*/
34+
private function createResultDouble(array $resultArray, array $resultObject): BaseResult
35+
{
36+
return new
37+
/**
38+
* @extends BaseResult<mixed, mixed>
39+
*/
40+
class ($resultArray, $resultObject) extends BaseResult {
41+
/**
42+
* @param list<array<string,mixed>> $resultArray Result set as arrays.
43+
* @param list<object> $resultObject Result set as objects.
44+
*/
45+
public function __construct(array $resultArray, array $resultObject)
46+
{
47+
$this->resultArray = $resultArray;
48+
$this->resultObject = $resultObject;
49+
$this->currentRow = 0;
50+
51+
$connId = null;
52+
$resultId = null;
53+
parent::__construct($connId, $resultId);
54+
}
55+
56+
public function getFieldCount(): int
57+
{
58+
return 0;
59+
}
60+
61+
/**
62+
* @return list<string>
63+
*/
64+
public function getFieldNames(): array
65+
{
66+
return [];
67+
}
68+
69+
/**
70+
* @return list<object>
71+
*/
72+
public function getFieldData(): array
73+
{
74+
return [];
75+
}
76+
77+
public function freeResult(): void
78+
{
79+
}
80+
81+
public function dataSeek(int $n = 0): bool
82+
{
83+
return true;
84+
}
85+
86+
/**
87+
* @return false|list<array<string,mixed>>|null
88+
*/
89+
protected function fetchAssoc(): array|bool|null
90+
{
91+
return false;
92+
}
93+
94+
protected function fetchObject(string $className = stdClass::class)
95+
{
96+
return false;
97+
}
98+
};
99+
}
100+
101+
// --------------------------------------------------------------------
102+
// getRowArray()
103+
// --------------------------------------------------------------------
104+
105+
public function testGetRowArrayReturnsRow(): void
106+
{
107+
$result = $this->createResultDouble(
108+
[
109+
['id' => 1, 'name' => 'John'],
110+
['id' => 2, 'name' => 'Jane'],
111+
],
112+
[],
113+
);
114+
115+
$this->assertSame(['id' => 1, 'name' => 'John'], $result->getRowArray(0));
116+
$this->assertSame(['id' => 2, 'name' => 'Jane'], $result->getRowArray(1));
117+
}
118+
119+
public function testGetRowArrayReturnsNullForEmptyResult(): void
120+
{
121+
$result = $this->createResultDouble([], []);
122+
123+
$this->assertNull($result->getRowArray(0));
124+
}
125+
126+
public function testGetRowArrayReturnsFirstRowByDefault(): void
127+
{
128+
$result = $this->createResultDouble(
129+
[
130+
['id' => 1, 'name' => 'John'],
131+
['id' => 2, 'name' => 'Jane'],
132+
],
133+
[],
134+
);
135+
136+
$this->assertSame(['id' => 1, 'name' => 'John'], $result->getRowArray());
137+
}
138+
139+
// --------------------------------------------------------------------
140+
// getRowObject()
141+
// --------------------------------------------------------------------
142+
143+
public function testGetRowObjectReturnsObject(): void
144+
{
145+
$row1 = new stdClass();
146+
$row1->id = 1;
147+
$row1->name = 'John';
148+
$row2 = new stdClass();
149+
$row2->id = 2;
150+
$row2->name = 'Jane';
151+
152+
$result = $this->createResultDouble([], [$row1, $row2]);
153+
154+
$this->assertSame($row1, $result->getRowObject(0));
155+
$this->assertSame($row2, $result->getRowObject(1));
156+
}
157+
158+
public function testGetRowObjectReturnsNullForEmptyResult(): void
159+
{
160+
$result = $this->createResultDouble([], []);
161+
162+
$this->assertNull($result->getRowObject(0));
163+
}
164+
165+
public function testGetRowObjectReturnsFirstRowByDefault(): void
166+
{
167+
$row1 = new stdClass();
168+
$row1->id = 1;
169+
$row1->name = 'John';
170+
171+
$result = $this->createResultDouble([], [$row1]);
172+
173+
$this->assertSame($row1, $result->getRowObject());
174+
}
175+
176+
public function testGetRowObjectAndGetRowArrayShareCurrentRow(): void
177+
{
178+
$row1 = new stdClass();
179+
$row1->id = 1;
180+
$row1->name = 'John';
181+
$row2 = new stdClass();
182+
$row2->id = 2;
183+
$row2->name = 'Jane';
184+
185+
$result = $this->createResultDouble(
186+
[
187+
['id' => 1, 'name' => 'John'],
188+
['id' => 2, 'name' => 'Jane'],
189+
],
190+
[$row1, $row2],
191+
);
192+
193+
// getRowObject(1) should advance currentRow to 1 (same as getRowArray would)
194+
$result->getRowObject(1);
195+
$this->assertSame(['id' => 2, 'name' => 'Jane'], $result->getRowArray(1));
196+
}
197+
198+
public function testGetRowObjectUsesCurrentRowLikeGetRowArray(): void
199+
{
200+
$row1 = new stdClass();
201+
$row1->id = 1;
202+
$row1->name = 'John';
203+
$row2 = new stdClass();
204+
$row2->id = 2;
205+
$row2->name = 'Jane';
206+
207+
$result = $this->createResultDouble(
208+
[
209+
['id' => 1, 'name' => 'John'],
210+
['id' => 2, 'name' => 'Jane'],
211+
],
212+
[$row1, $row2],
213+
);
214+
215+
// Both methods should advance currentRow consistently
216+
$result->getRowObject(1);
217+
$result->getRowArray();
218+
$this->assertSame($row1, $result->getRowObject());
219+
}
220+
221+
// --------------------------------------------------------------------
222+
// getRow() — convenience wrapper
223+
// --------------------------------------------------------------------
224+
225+
public function testGetRowWithInvalidIndexReturnsFirstRow(): void
226+
{
227+
$result = $this->createResultDouble(
228+
[['id' => 1, 'name' => 'John']],
229+
[],
230+
);
231+
232+
$this->assertSame(['id' => 1, 'name' => 'John'], $result->getRow(999, 'array'));
233+
}
234+
235+
public function testGetRowObjectWithInvalidIndexReturnsFirstRow(): void
236+
{
237+
$row1 = new stdClass();
238+
$row1->id = 1;
239+
$row1->name = 'John';
240+
241+
$result = $this->createResultDouble([], [$row1]);
242+
243+
$this->assertSame($row1, $result->getRow(999, 'object'));
244+
}
245+
246+
public function testGetRowNullForColumnNameNotFound(): void
247+
{
248+
$result = $this->createResultDouble(
249+
[['id' => 1, 'name' => 'John']],
250+
[],
251+
);
252+
253+
$this->assertNull($result->getRow('nonexistent', 'array'));
254+
}
255+
256+
// --------------------------------------------------------------------
257+
// Custom Result Object
258+
// --------------------------------------------------------------------
259+
260+
public function testGetCustomRowObjectWithInvalidIndexReturnsFirstRow(): void
261+
{
262+
$row = new stdClass();
263+
$row->id = 1;
264+
$row->name = 'John';
265+
266+
$result = $this->createResultDouble([], []);
267+
$result->customResultObject[stdClass::class] = [$row];
268+
269+
$this->assertSame($row, $result->getCustomRowObject(999, stdClass::class));
270+
}
271+
272+
// --------------------------------------------------------------------
273+
// Fallback Tests (Null return on invalid currentRow)
274+
// --------------------------------------------------------------------
275+
276+
public function testGetRowArrayReturnsNullWhenCurrentRowIsInvalid(): void
277+
{
278+
$result = $this->createResultDouble(
279+
[['id' => 1, 'name' => 'John']],
280+
[],
281+
);
282+
283+
$result->currentRow = 999;
284+
285+
$this->assertNull($result->getRowArray(999));
286+
}
287+
288+
public function testGetRowObjectReturnsNullWhenCurrentRowIsInvalid(): void
289+
{
290+
$row1 = new stdClass();
291+
$row1->id = 1;
292+
$row1->name = 'John';
293+
294+
$result = $this->createResultDouble(
295+
[],
296+
[$row1],
297+
);
298+
299+
$result->currentRow = 999;
300+
301+
$this->assertNull($result->getRowObject(999));
302+
}
303+
304+
public function testGetCustomRowObjectReturnsNullWhenCurrentRowIsInvalid(): void
305+
{
306+
$row1 = new stdClass();
307+
$row1->id = 1;
308+
$row1->name = 'John';
309+
310+
$result = $this->createResultDouble([], []);
311+
$result->customResultObject[stdClass::class] = [$row1];
312+
313+
$result->currentRow = 999;
314+
315+
$this->assertNotInstanceOf(stdClass::class, $result->getCustomRowObject(999, stdClass::class));
316+
}
317+
318+
public function testGetPreviousRowReturnsNullWhenCurrentRowIsInvalid(): void
319+
{
320+
$result = $this->createResultDouble(
321+
[
322+
['id' => 1],
323+
['id' => 2],
324+
],
325+
[],
326+
);
327+
328+
$result->currentRow = -1;
329+
330+
$this->assertNull($result->getPreviousRow('array'));
331+
}
332+
}

0 commit comments

Comments
 (0)