Skip to content

Commit afda5ee

Browse files
committed
Safely count all possible types
1 parent a9db08b commit afda5ee

File tree

7 files changed

+101
-5
lines changed

7 files changed

+101
-5
lines changed

phpstan.neon.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ parameters:
66
reportUnmatchedIgnoredErrors: false
77
paths:
88
- src
9+
bootstrapFiles:
10+
- test/phpstan.php

psalm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
errorBaseline="psalm.baseline.xml"
99
>
1010
<projectFiles>
11+
<file name="test/phpstan.php" />
1112
<directory name="src" />
1213
<ignoreFiles>
1314
<directory name="vendor" />

src/Pagination/DoctrinePaginatorAdapter.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*/
2020
class DoctrinePaginatorAdapter implements PaginatorInterface
2121
{
22+
use TraversableCountTrait;
23+
2224
/**
2325
* The paginator instance.
2426
* @var Paginator
@@ -73,7 +75,7 @@ public function getTotal(): int
7375
*/
7476
public function getCount(): int
7577
{
76-
return $this->paginator->getIterator()->count();
78+
return $this->getTraversableCount($this->paginator->getIterator());
7779
}
7880

7981
/**
@@ -93,7 +95,7 @@ public function getUrl(int $page): string
9395
}
9496

9597
/**
96-
* Get the the route generator.
98+
* Get the route generator.
9799
*/
98100
private function getRouteGenerator(): callable
99101
{
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace League\Fractal\Pagination;
4+
5+
trait TraversableCountTrait
6+
{
7+
/**
8+
* Safely get the count from a traversable
9+
*/
10+
private function getTraversableCount(\Traversable $traversable): int
11+
{
12+
if ($traversable instanceof \Countable) {
13+
return count($traversable);
14+
}
15+
16+
// Call the "count" method if it exists
17+
if (method_exists($traversable, 'count')) {
18+
return $traversable->count();
19+
}
20+
21+
// If not, fall back to iterator_count and rewind if possible
22+
$count = iterator_count($traversable);
23+
if ($traversable instanceof \Iterator || $traversable instanceof \IteratorAggregate) {
24+
$traversable->rewind();
25+
}
26+
27+
return $count;
28+
}
29+
30+
}

test/Pagination/DoctrinePaginatorAdapterTest.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
namespace League\Fractal\Test\Pagination;
33

44
use Doctrine\ORM\Query;
5+
use Doctrine\ORM\Tools\Pagination\Paginator;
56
use League\Fractal\Pagination\DoctrinePaginatorAdapter;
7+
use League\Fractal\Test\Stub\SimpleTraversable;
68
use Mockery;
79
use PHPUnit\Framework\TestCase;
810

@@ -28,11 +30,9 @@ public function testPaginationAdapter()
2830
$paginator->shouldReceive('getQuery')->andReturn($query);
2931

3032
//Mock the iterator of the paginator
31-
$iterator = Mockery::mock('IteratorAggregate');
32-
$iterator->shouldReceive('count')->andReturn($count);
33+
$iterator = new \ArrayIterator(range(1, $count));
3334
$paginator->shouldReceive('getIterator')->andReturn($iterator);
3435

35-
3636
$adapter = new DoctrinePaginatorAdapter($paginator, function ($page) {
3737
return 'http://example.com/foo?page='.$page;
3838
});
@@ -57,6 +57,20 @@ public function testPaginationAdapter()
5757
);
5858
}
5959

60+
public function testCountingTraversables()
61+
{
62+
$traversable = new SimpleTraversable(range(1, 100));
63+
$adapter = Mockery::mock('Doctrine\ORM\Tools\Pagination\Paginator');
64+
$adapter->shouldReceive('getIterator')->andReturn($traversable);
65+
$adapter = new DoctrinePaginatorAdapter($adapter, function ($page) {
66+
return (string) $page;
67+
});
68+
69+
$this->assertEquals($traversable->key(), 0);
70+
$this->assertEquals($adapter->getCount(), 100);
71+
$this->assertEquals($traversable->key(), 0);
72+
}
73+
6074
public function tearDown(): void
6175
{
6276
Mockery::close();

test/Stub/SimpleTraversable.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace League\Fractal\Test\Stub;
4+
5+
class SimpleTraversable implements \Iterator
6+
{
7+
private $list;
8+
private $keys;
9+
private $cursor = 0;
10+
11+
public function __construct(array $list)
12+
{
13+
$this->list = array_values($list);
14+
$this->keys = array_keys($list);
15+
}
16+
17+
#[\ReturnTypeWillChange]
18+
public function current()
19+
{
20+
return $this->list[$this->cursor];
21+
}
22+
23+
public function next(): void
24+
{
25+
$this->cursor++;
26+
}
27+
28+
#[\ReturnTypeWillChange]
29+
public function key()
30+
{
31+
return $this->keys[$this->cursor];
32+
}
33+
34+
public function valid(): bool
35+
{
36+
return isset($this->list[$this->cursor]);
37+
}
38+
39+
public function rewind(): void
40+
{
41+
$this->cursor = 0;
42+
}
43+
}

test/phpstan.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
3+
class_alias(\Laminas\Paginator\Adapter\ArrayAdapter::class, \Zend\Paginator\Adapter\ArrayAdapter::class);
4+
class_alias(\Laminas\Paginator\Paginator::class, \Zend\Paginator\Paginator::class);

0 commit comments

Comments
 (0)