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
30 changes: 5 additions & 25 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -756,12 +756,12 @@ parameters:
path: src/Resolver.php

-
message: "#^Class ipl\\\\Orm\\\\ResultSet implements generic interface Iterator but does not specify its types\\: TKey, TValue$#"
message: "#^Cannot assign offset mixed to ArrayIterator\\.$#"
count: 1
path: src/ResultSet.php

-
message: "#^Method ipl\\\\Orm\\\\ResultSet\\:\\:__construct\\(\\) has parameter \\$limit with no type specified\\.$#"
message: "#^Class ipl\\\\Orm\\\\ResultSet implements generic interface Iterator but does not specify its types\\: TKey, TValue$#"
count: 1
path: src/ResultSet.php

Expand All @@ -776,17 +776,7 @@ parameters:
path: src/ResultSet.php

-
message: "#^Method ipl\\\\Orm\\\\ResultSet\\:\\:hasMore\\(\\) has no return type specified\\.$#"
count: 1
path: src/ResultSet.php

-
message: "#^Method ipl\\\\Orm\\\\ResultSet\\:\\:hasResult\\(\\) has no return type specified\\.$#"
count: 1
path: src/ResultSet.php

-
message: "#^Method ipl\\\\Orm\\\\ResultSet\\:\\:yieldTraversable\\(\\) has no return type specified\\.$#"
message: "#^Method ipl\\\\Orm\\\\ResultSet\\:\\:key\\(\\) should return int\\|null but returns mixed\\.$#"
count: 1
path: src/ResultSet.php

Expand All @@ -796,22 +786,12 @@ parameters:
path: src/ResultSet.php

-
message: "#^Property ipl\\\\Orm\\\\ResultSet\\:\\:\\$cache has no type specified\\.$#"
count: 1
path: src/ResultSet.php

-
message: "#^Property ipl\\\\Orm\\\\ResultSet\\:\\:\\$generator has no type specified\\.$#"
count: 1
path: src/ResultSet.php

-
message: "#^Property ipl\\\\Orm\\\\ResultSet\\:\\:\\$limit has no type specified\\.$#"
message: "#^Parameter \\#1 \\$offset of method ArrayIterator\\<\\(int\\|string\\),mixed\\>\\:\\:seek\\(\\) expects int, mixed given\\.$#"
count: 1
path: src/ResultSet.php

-
message: "#^Property ipl\\\\Orm\\\\ResultSet\\:\\:\\$position has no type specified\\.$#"
message: "#^Property ipl\\\\Orm\\\\ResultSet\\:\\:\\$cache with generic class ArrayIterator does not specify its types\\: TKey, TValue$#"
count: 1
path: src/ResultSet.php

Expand Down
94 changes: 90 additions & 4 deletions src/ResultSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,102 @@
namespace ipl\Orm;

use ArrayIterator;
use BadMethodCallException;
use Generator;
use Iterator;
use Traversable;

/**
* Dataset containing database rows
*/
class ResultSet implements Iterator
{
/** @var ArrayIterator */
protected $cache;

/** @var bool Whether cache is disabled */
protected $isCacheDisabled = false;

/** @var Generator */
protected $generator;

/** @var ?int */
protected $limit;

/** @var ?int */
protected $position;

public function __construct(Traversable $traversable, $limit = null)
/** @var ?int */
protected $offset;

/** @var ?int */
protected $pageSize;

/**
* Construct the ResultSet object
*
* @param Traversable $traversable
* @param ?int $limit
* @param ?int $offset
*/
public function __construct(Traversable $traversable, $limit = null, $offset = null)
{
$this->cache = new ArrayIterator();
$this->generator = $this->yieldTraversable($traversable);
$this->limit = $limit;
$this->offset = $offset;
}

/**
* Get the current page number
*
* Returns the current page, calculated by the {@see self::$position position}, {@see self::$offset offset}
* and the {@see self::$pageSize page size}
*
* @return int page
* @throws BadMethodCallException if no {@see self::$pageSize page size} has been provided
*/
public function getCurrentPage(): int
{
if ($this->pageSize) {
$offset = $this->offset ?: 0;
$position = ($this->position ?: 0) + 1;
if (($position + $offset) > $this->pageSize) {
// we are not on the first page anymore, calculating proper page
return intval(ceil(($position + $offset) / $this->pageSize));
}

// still on the first page
return 1;
}

throw new BadMethodCallException("The 'pageSize' property has not been set. Cannot calculate pages.");
}

/**
* Set the amount of entries a page should contain (needed for pagination)
*
* @param ?int $size entries per page
*
* @return $this
*/
public function setPageSize(?int $size)
{
$this->pageSize = $size;

return $this;
}

/**
* Create a new result set from the given query
* Create a new result set from the given {@see Query query}
*
* @param Query $query
*
* @return static
*/
public static function fromQuery(Query $query)
{
return new static($query->yieldResults(), $query->getLimit());
return new static($query->yieldResults(), $query->getLimit(), $query->getOffset());
}

/**
Expand All @@ -52,11 +115,21 @@ public function disableCache()
return $this;
}

/**
* Check if dataset has more entries
*
* @return bool
*/
public function hasMore()
{
return $this->generator->valid();
}

/**
* Check if dataset has a result
*
* @return bool
*/
public function hasResult()
{
return $this->generator->valid();
Expand Down Expand Up @@ -86,7 +159,13 @@ public function next(): void
}
}

public function key(): int
/**
* Return the current item's key
*
* @return ?int
*/
#[\ReturnTypeWillChange]
public function key()
{
if ($this->position === null) {
$this->advance();
Expand Down Expand Up @@ -137,6 +216,13 @@ protected function advance()
}
}

/**
* Yield entry from dataset
*
* @param Traversable $traversable
*
* @return Generator
*/
protected function yieldTraversable(Traversable $traversable)
{
foreach ($traversable as $key => $value) {
Expand Down
80 changes: 80 additions & 0 deletions tests/ResultSetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ipl\Tests\Orm;

use ArrayIterator;
use BadMethodCallException;
use ipl\Orm\ResultSet;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -81,4 +82,83 @@ public function testResultWithCacheEnabledWithLimit()
['a', 'b', 'a', 'b']
);
}

public function testResultPaging()
{
$set = (new ResultSet(new ArrayIterator(['a', 'b', 'c', 'd', 'e', 'f', 'g'])))
->setPageSize(2);

$count = 0;
foreach ($set as $item) {
++$count;

if ($count > 2) {
if ($count % 2 === 0) {
// a multiple of two, page should equal to count / 2
$this->assertEquals(
$set->getCurrentPage(),
$count / 2
);
} elseif ($count % 2 === 1) {
$this->assertEquals(
$set->getCurrentPage(),
intval(ceil($count / 2))
);
}
} else {
$this->assertEquals(
$set->getCurrentPage(),
1
);
}
}
}

public function testResultPagingWithoutPageSize()
{
$this->expectException(BadMethodCallException::class);

$set = (new ResultSet(new ArrayIterator(['a', 'b', 'c', 'd', 'e', 'f', 'g'])));

foreach ($set as $_) {
// this raises an exception as no page size has been set
$set->getCurrentPage();
}
}

public function testResultPagingWithOffset()
{
$set = (new ResultSet(new ArrayIterator(['d', 'e', 'f', 'g', 'h', 'i', 'j']), null, 3))
->setPageSize(2);

$count = 0;
foreach ($set as $_) {
++$count;

$offsetCount = $count + 3;
if ($offsetCount % 2 === 0) {
// a multiple of two, page should equal to offsetCount / 2
$this->assertEquals(
$set->getCurrentPage(),
$offsetCount / 2
);
} elseif ($offsetCount % 2 === 1) {
$this->assertEquals(
$set->getCurrentPage(),
intval(ceil($offsetCount / 2))
);
}
}
}

public function testResultPagingBeforeIteration()
{
$set = (new ResultSet(new ArrayIterator(['a', 'b', 'c', 'd', 'e', 'f', 'g'])))
->setPageSize(2);

$this->assertEquals(
$set->getCurrentPage(),
1
);
}
}