diff --git a/lib/Collection.php b/lib/Collection.php index f1e9266b6..dbe5c02bd 100644 --- a/lib/Collection.php +++ b/lib/Collection.php @@ -18,6 +18,7 @@ class Collection extends StripeObject implements \IteratorAggregate use ApiOperations\Request; + /** @var array */ protected $filters = []; /** @@ -46,8 +47,6 @@ public function getFilters() public function setFilters($filters) { $this->filters = $filters; - unset($this->filters['starting_after']); - unset($this->filters['ending_before']); } public function offsetGet($k) @@ -113,6 +112,15 @@ public function getIterator() return new \ArrayIterator($this->data); } + /** + * @return \ArrayIterator An iterator that can be used to iterate + * backwards across objects in the current page. + */ + public function getReverseIterator() + { + return new \ArrayIterator(array_reverse($this->data)); + } + /** * @return \Generator|StripeObject[] A generator that can be used to * iterate across all objects across all pages. As page boundaries are @@ -124,12 +132,19 @@ public function autoPagingIterator() $page = $this; while (true) { - foreach ($page as $item) { - yield $item; + if (array_key_exists('ending_before', $this->filters) && + !array_key_exists('starting_after', $this->filters)) { + foreach ($page->getReverseIterator() as $item) { + yield $item; + } + $page = $page->previousPage(); + } else { + foreach ($page as $item) { + yield $item; + } + $page = $page->nextPage(); } - $page = $page->nextPage(); - if ($page->isEmpty()) { break; } @@ -178,7 +193,7 @@ public function nextPage($params = null, $opts = null) $lastId = end($this->data)->id; $params = array_merge( - $this->filters, + $this->filters ?: [], ['starting_after' => $lastId], $params ?: [] ); @@ -198,10 +213,14 @@ public function nextPage($params = null, $opts = null) */ public function previousPage($params = null, $opts = null) { + if (!$this->has_more) { + return static::emptyCollection($opts); + } + $firstId = $this->data[0]->id; $params = array_merge( - $this->filters, + $this->filters ?: [], ['ending_before' => $firstId], $params ?: [] ); diff --git a/tests/Stripe/CollectionTest.php b/tests/Stripe/CollectionTest.php index 5715412d9..edd8b11f5 100644 --- a/tests/Stripe/CollectionTest.php +++ b/tests/Stripe/CollectionTest.php @@ -86,12 +86,34 @@ public function testCanCreate() public function testCanIterate() { + $collection = Collection::constructFrom([ + 'data' => [['id' => 1], ['id' => 2], ['id' => 3]], + 'has_more' => true, + 'url' => '/things', + ]); + $seen = []; - foreach ($this->fixture as $item) { + foreach ($collection as $item) { array_push($seen, $item['id']); } - $this->assertSame([1], $seen); + $this->assertSame([1, 2, 3], $seen); + } + + public function testCanIterateBackwards() + { + $collection = Collection::constructFrom([ + 'data' => [['id' => 1], ['id' => 2], ['id' => 3]], + 'has_more' => true, + 'url' => '/things', + ]); + + $seen = []; + foreach ($collection->getReverseIterator() as $item) { + array_push($seen, $item['id']); + } + + $this->assertSame([3, 2, 1], $seen); } public function testSupportsIteratorToArray() @@ -154,6 +176,38 @@ public function testAutoPagingIteratorSupportsIteratorToArray() $this->assertSame([1, 2, 3], $seen); } + public function testProvidesAutoPagingIteratorThatSupportsBackwardsPagination() + { + $this->stubRequest( + 'GET', + '/things', + [ + 'ending_before' => 3, + ], + null, + false, + [ + 'object' => 'list', + 'data' => [['id' => 1], ['id' => 2]], + 'has_more' => false, + ] + ); + + $collection = Collection::constructFrom([ + 'data' => [['id' => 3]], + 'has_more' => true, + 'url' => '/things', + ]); + $collection->setFilters(['ending_before' => 4]); + + $seen = []; + foreach ($collection->autoPagingIterator() as $item) { + array_push($seen, $item['id']); + } + + $this->assertSame([3, 2, 1], $seen); + } + public function testHeaders() { $this->stubRequest(