feat(result): Update result wrapper with new doctrine methods

- fetch is replaced with fetchAssociative/fetchNumeric/fetchOne with
  better type hinting
- Same with fetchAll
- And add iterateAssociative/iterateNumeric which are nicer to use than
  a `while ($row = $result->fetch()) {}`

Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
This commit is contained in:
Carl Schwan 2025-11-17 11:45:06 +01:00
parent d843e03a34
commit 3682dbbf5e
3 changed files with 193 additions and 25 deletions

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace OC\DB;
use OCP\DB\IResult;
use Override;
use PDO;
/**
@ -18,16 +19,19 @@ class ArrayResult implements IResult {
protected int $count;
public function __construct(
/** @var array<string, mixed> $rows */
protected array $rows,
) {
$this->count = count($this->rows);
}
#[Override]
public function closeCursor(): bool {
// noop
return true;
}
#[Override]
public function fetch(int $fetchMode = PDO::FETCH_ASSOC) {
$row = array_shift($this->rows);
if (!$row) {
@ -42,23 +46,22 @@ class ArrayResult implements IResult {
}
#[Override]
public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array {
return match ($fetchMode) {
PDO::FETCH_ASSOC => $this->rows,
PDO::FETCH_NUM => array_map(function ($row) {
return array_values($row);
}, $this->rows),
PDO::FETCH_COLUMN => array_map(function ($row) {
return current($row);
}, $this->rows),
PDO::FETCH_NUM => array_map(static fn (array $row): array => array_values($row), $this->rows),
PDO::FETCH_COLUMN => array_map(static fn (array $row): mixed => current($row), $this->rows),
default => throw new \InvalidArgumentException('Fetch mode not supported for array result'),
};
}
#[Override]
public function fetchColumn() {
return $this->fetchOne();
}
#[Override]
public function fetchOne() {
$row = $this->fetch();
if ($row) {
@ -68,7 +71,65 @@ class ArrayResult implements IResult {
}
}
#[Override]
public function fetchAssociative(): array|false {
$row = $this->fetch();
if ($row) {
/** @var array<string, mixed> $row */
return $row;
} else {
return false;
}
}
#[Override]
public function fetchNumeric(): array|false {
$row = $this->fetch(PDO::FETCH_NUM);
if ($row) {
/** @var list<mixed> $row */
return $row;
} else {
return false;
}
}
#[Override]
public function fetchAllNumeric(): array {
/** @var list<list<mixed>> $result */
$result = $this->fetchAll(PDO::FETCH_NUM);
return $result;
}
#[Override]
public function fetchAllAssociative(): array {
/** @var list<array<string,mixed>> $result */
$result = $this->fetchAll();
return $result;
}
#[Override]
public function fetchFirstColumn(): array {
/** @var list<mixed> $result */
$result = $this->fetchAll(PDO::FETCH_COLUMN);
return $result;
}
#[Override]
public function rowCount(): int {
return $this->count;
}
#[Override]
public function iterateNumeric(): \Traversable {
while (($row = $this->fetchNumeric()) !== false) {
yield $row;
}
}
#[Override]
public function iterateAssociative(): \Traversable {
while (($row = $this->fetchAssociative()) !== false) {
yield $row;
}
}
}

View file

@ -10,25 +10,26 @@ namespace OC\DB;
use Doctrine\DBAL\Result;
use OCP\DB\IResult;
use Override;
use PDO;
/**
* Adapts DBAL 2.6 API for DBAL 3.x for backwards compatibility of a leaked type
*/
class ResultAdapter implements IResult {
/** @var Result */
private $inner;
public function __construct(Result $inner) {
$this->inner = $inner;
public function __construct(
private readonly Result $inner,
) {
}
#[Override]
public function closeCursor(): bool {
$this->inner->free();
return true;
}
#[Override]
public function fetch(int $fetchMode = PDO::FETCH_ASSOC) {
return match ($fetchMode) {
PDO::FETCH_ASSOC => $this->inner->fetchAssociative(),
@ -38,6 +39,22 @@ class ResultAdapter implements IResult {
};
}
#[Override]
public function fetchAssociative(): array|false {
return $this->inner->fetchAssociative();
}
#[Override]
public function fetchNumeric(): array|false {
return $this->inner->fetchNumeric();
}
#[Override]
public function fetchOne(): mixed {
return $this->inner->fetchOne();
}
#[Override]
public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array {
return match ($fetchMode) {
PDO::FETCH_ASSOC => $this->inner->fetchAllAssociative(),
@ -47,15 +64,38 @@ class ResultAdapter implements IResult {
};
}
#[Override]
public function fetchColumn($columnIndex = 0) {
return $this->inner->fetchOne();
}
public function fetchOne() {
return $this->inner->fetchOne();
}
#[Override]
public function rowCount(): int {
return $this->inner->rowCount();
}
#[Override]
public function fetchAllAssociative(): array {
return $this->inner->fetchAllAssociative();
}
#[Override]
public function fetchAllNumeric(): array {
return $this->inner->fetchAllNumeric();
}
#[Override]
public function fetchFirstColumn(): array {
return $this->inner->fetchFirstColumn();
}
#[Override]
public function iterateNumeric(): \Traversable {
yield from $this->inner->iterateNumeric();
}
#[Override]
public function iterateAssociative(): \Traversable {
yield from $this->inner->iterateAssociative();
}
}

View file

@ -8,7 +8,9 @@ declare(strict_types=1);
*/
namespace OCP\DB;
use OCP\AppFramework\Attribute\Consumable;
use PDO;
use Traversable;
/**
* This interface represents the result of a database query.
@ -19,12 +21,15 @@ use PDO;
* $qb = $this->db->getQueryBuilder();
* $qb->select(...);
* $result = $query->executeQuery();
* ```
*
* This interface must not be implemented in your application.
* foreach ($result->iterateAssociative() as $row) {
* $id = $row['id'];
* }
* ```
*
* @since 21.0.0
*/
#[Consumable(since: '21.0.0')]
interface IResult {
/**
* @return true
@ -39,25 +44,27 @@ interface IResult {
* @return mixed
*
* @since 21.0.0
* @deprecated Since 33.0.0, use fetchAssociative/fetchNumeric/fetchOne or iterateAssociate/iterateNumeric instead.
*/
public function fetch(int $fetchMode = PDO::FETCH_ASSOC);
/**
* @param int $fetchMode (one of PDO::FETCH_ASSOC, PDO::FETCH_NUM or PDO::FETCH_COLUMN (2, 3 or 7)
* Returns the next row of the result as an associative array or FALSE if there are no more rows.
*
* @return mixed[]
* @return array<string, mixed>|false
*
* @since 21.0.0
* @since 33.0.0
*/
public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array;
public function fetchAssociative(): array|false;
/**
* @return mixed
* Returns the next row of the result as a numeric array or FALSE if there are no more rows.
*
* @since 21.0.0
* @deprecated 21.0.0 use \OCP\DB\IResult::fetchOne
* @return list<mixed>|false
*
* @since 33.0.0
*/
public function fetchColumn();
public function fetchNumeric(): array|false;
/**
* Returns the first value of the next row of the result or FALSE if there are no more rows.
@ -68,10 +75,70 @@ interface IResult {
*/
public function fetchOne();
/**
* @param int $fetchMode (one of PDO::FETCH_ASSOC, PDO::FETCH_NUM or PDO::FETCH_COLUMN (2, 3 or 7)
*
* @return mixed[]
*
* @since 21.0.0
* @deprecated Since 33.0.0, use fetchAllAssociative/fetchAllNumeric/fetchFirstColumn or iterateAssociate/iterateNumeric instead.
*/
public function fetchAll(int $fetchMode = PDO::FETCH_ASSOC): array;
/**
* Returns an array containing all the result rows represented as associative arrays.
*
* @return list<array<string,mixed>>
* @since 33.0.0
*/
public function fetchAllAssociative(): array;
/**
* Returns an array containing all the result rows represented as numeric arrays.
*
* @return list<list<mixed>>
* @since 33.0.0
*/
public function fetchAllNumeric(): array;
/**
* Returns the value of the first column of all rows.
*
* @return list<mixed>
* @since 33.0.0
*/
public function fetchFirstColumn(): array;
/**
* @return mixed
*
* @since 21.0.0
* @deprecated 21.0.0 use \OCP\DB\IResult::fetchOne
*/
public function fetchColumn();
/**
* @return int
*
* @since 21.0.0
*/
public function rowCount(): int;
/**
* Returns the result as an iterator over rows represented as numeric arrays.
*
* @return Traversable<list<mixed>>
*
* @since 33.0.0
*/
public function iterateNumeric(): Traversable;
/**
* Returns the result as an iterator over rows represented as associative arrays.
*
* @return Traversable<array<string,mixed>>
*
* @since 33.0.0
*/
public function iterateAssociative(): Traversable;
}