Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions system/Database/BaseResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ public function getRowArray(int $n = 0)
$this->currentRow = $n;
}

return $result[$this->currentRow];
return $result[$this->currentRow] ?? null;
}

/**
Expand All @@ -350,11 +350,11 @@ public function getRowObject(int $n = 0)
return null;
}

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

return $result[$this->currentRow];
return $result[$this->currentRow] ?? null;
}

/**
Expand Down
311 changes: 311 additions & 0 deletions tests/system/Database/BaseResultTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
<?php

declare(strict_types=1);

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

namespace CodeIgniter\Database;

use CodeIgniter\Test\CIUnitTestCase;
use PHPUnit\Framework\Attributes\Group;
use stdClass;

/**
* @internal
*/
#[Group('Database')]
final class BaseResultTest extends CIUnitTestCase
{
/**
* Create a minimal concrete implementation of BaseResult for testing.
*/
private function createResultDouble(array $resultArray, array $resultObject): BaseResult
{
return new class ($resultArray, $resultObject) extends BaseResult {
public function __construct(array $resultArray, array $resultObject)
{
$this->resultArray = $resultArray;
$this->resultObject = $resultObject;
$this->currentRow = 0;

$connId = null;
$resultId = null;
parent::__construct($connId, $resultId);
}

public function getFieldCount(): int
{
return 0;
}

public function getFieldNames(): array
{
return [];
}

public function getFieldData(): array
{
return [];
}

public function freeResult(): void
{
}

public function dataSeek(int $n = 0): bool
{
return true;
}

protected function fetchAssoc()
{
return false;
}

protected function fetchObject(string $className = stdClass::class)
{
return false;
}
};
}

// --------------------------------------------------------------------
// getRowArray()
// --------------------------------------------------------------------

public function testGetRowArrayReturnsRow(): void
{
$result = $this->createResultDouble(
[
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Jane'],
],
[],
);

$this->assertSame(['id' => 1, 'name' => 'John'], $result->getRowArray(0));
$this->assertSame(['id' => 2, 'name' => 'Jane'], $result->getRowArray(1));
}

public function testGetRowArrayReturnsNullForEmptyResult(): void
{
$result = $this->createResultDouble([], []);

$this->assertNull($result->getRowArray(0));
}

public function testGetRowArrayReturnsFirstRowByDefault(): void
{
$result = $this->createResultDouble(
[
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Jane'],
],
[],
);

$this->assertSame(['id' => 1, 'name' => 'John'], $result->getRowArray());
}

// --------------------------------------------------------------------
// getRowObject()
// --------------------------------------------------------------------

public function testGetRowObjectReturnsObject(): void
{
$row1 = new stdClass();
$row1->id = 1;
$row1->name = 'John';
$row2 = new stdClass();
$row2->id = 2;
$row2->name = 'Jane';

$result = $this->createResultDouble([], [$row1, $row2]);

$this->assertEquals($row1, $result->getRowObject(0));
$this->assertEquals($row2, $result->getRowObject(1));
}

public function testGetRowObjectReturnsNullForEmptyResult(): void
{
$result = $this->createResultDouble([], []);

$this->assertNull($result->getRowObject(0));
}

public function testGetRowObjectReturnsFirstRowByDefault(): void
{
$row1 = new stdClass();
$row1->id = 1;
$row1->name = 'John';

$result = $this->createResultDouble([], [$row1]);

$this->assertEquals($row1, $result->getRowObject());
}

public function testGetRowObjectAndGetRowArrayShareCurrentRow(): void
{
$row1 = new stdClass();
$row1->id = 1;
$row1->name = 'John';
$row2 = new stdClass();
$row2->id = 2;
$row2->name = 'Jane';

$result = $this->createResultDouble(
[
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Jane'],
],
[$row1, $row2],
);

// getRowObject(1) should advance currentRow to 1 (same as getRowArray would)
$result->getRowObject(1);
$this->assertSame(['id' => 2, 'name' => 'Jane'], $result->getRowArray(1));
}

public function testGetRowObjectUsesCurrentRowLikeGetRowArray(): void
{
$row1 = new stdClass();
$row1->id = 1;
$row1->name = 'John';
$row2 = new stdClass();
$row2->id = 2;
$row2->name = 'Jane';

$result = $this->createResultDouble(
[
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Jane'],
],
[$row1, $row2],
);

// Both methods should advance currentRow consistently
$result->getRowObject(1);
$result->getRowArray();
$this->assertEquals($row1, $result->getRowObject());
}

// --------------------------------------------------------------------
// getRow() — convenience wrapper
// --------------------------------------------------------------------

public function testGetRowWithInvalidIndexReturnsFirstRow(): void
{
$result = $this->createResultDouble(
[['id' => 1, 'name' => 'John']],
[],
);

$this->assertSame(['id' => 1, 'name' => 'John'], $result->getRow(999, 'array'));
}

public function testGetRowObjectWithInvalidIndexReturnsFirstRow(): void
{
$row1 = new stdClass();
$row1->id = 1;
$row1->name = 'John';

$result = $this->createResultDouble([], [$row1]);

$this->assertEquals($row1, $result->getRow(999, 'object'));
}

public function testGetRowNullForColumnNameNotFound(): void
{
$result = $this->createResultDouble(
[['id' => 1, 'name' => 'John']],
[],
);

$this->assertNull($result->getRow('nonexistent', 'array'));
}

// --------------------------------------------------------------------
// Custom Result Object
// --------------------------------------------------------------------

public function testGetCustomRowObjectReturnsNullForOutOfBounds(): void
{
$row = new stdClass();
$row->id = 1;
$row->name = 'John';

$result = $this->createResultDouble([], [$row]);
$result->getCustomResultObject(stdClass::class);

$this->assertEquals($row, $result->getCustomRowObject(999, stdClass::class));
}

// --------------------------------------------------------------------
// Fallback Tests (Null return on invalid currentRow)
// --------------------------------------------------------------------

public function testGetRowArrayReturnsNullWhenCurrentRowIsInvalid(): void
{
$result = $this->createResultDouble(
[['id' => 1, 'name' => 'John']],
[]
);

$result->currentRow = 999;

$this->assertNull($result->getRowArray());
}

public function testGetRowObjectReturnsNullWhenCurrentRowIsInvalid(): void
{
$row1 = new stdClass();
$row1->id = 1;
$row1->name = 'John';

$result = $this->createResultDouble(
[],
[$row1]
);

$result->currentRow = 999;

$this->assertNull($result->getRowObject());
}

public function testGetCustomRowObjectReturnsNullWhenCurrentRowIsInvalid(): void
{
$row1 = new stdClass();
$row1->id = 1;
$row1->name = 'John';

$result = $this->createResultDouble([], [$row1]);

$result->getCustomResultObject(stdClass::class);

$result->currentRow = 999;

$this->assertNull($result->getCustomRowObject(0, stdClass::class));
}

public function testGetPreviousRowReturnsNullWhenCurrentRowIsInvalid(): void
{
$result = $this->createResultDouble(
[
['id' => 1],
['id' => 2],
],
[]
);

$result->currentRow = -1;

$this->assertNull($result->getPreviousRow());
}
}