-
-
Notifications
You must be signed in to change notification settings - Fork 598
Add a ResultPager object to support pagination of all Api's #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
383346b
07138eb
3683986
bf81101
09e76f3
52ac68b
5783325
76c1b46
0cc862b
09b147d
336f50a
c94daa8
496bd03
84b76eb
5541dec
3e0ffa2
74210a4
7fd1cb1
99ed0c7
c8f5738
470b8ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
## Result Pager | ||
[Back to the navigation](index.md) | ||
|
||
### Usage examples | ||
|
||
Get all repositories of a organization | ||
|
||
```php | ||
$client = new Github\Client(); | ||
|
||
$organizationApi = $client->api('organization'); | ||
|
||
$paginator = new Github\ResultPager( $client ); | ||
$result = $paginator->fetchAll( $organizationApi, 'repositories', 'github ); | ||
``` | ||
|
||
Get the first page | ||
```php | ||
$client = new Github\Client(); | ||
|
||
$organizationApi = $client->api('organization'); | ||
|
||
$paginator = new Github\ResultPager( $client ); | ||
$result = $paginator->fetch( $organizationApi, 'repositories', 'github ); | ||
``` | ||
Check for a next page: | ||
```php | ||
$paginator->hasNext(); | ||
``` | ||
|
||
Get next page: | ||
```php | ||
$paginator->fetchNext(); | ||
``` | ||
|
||
Check for pervious page: | ||
```php | ||
$paginator->getPrevious(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hasPrevious ? |
||
``` | ||
|
||
Get prevrious page: | ||
```php | ||
$paginator->fetchPrevious(); | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,13 @@ abstract class AbstractApi implements ApiInterface | |
*/ | ||
protected $client; | ||
|
||
/** | ||
* number of items per page (GitHub pagination) | ||
* | ||
* @var int | ||
*/ | ||
protected $perPage = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
/** | ||
* @param Client $client | ||
*/ | ||
|
@@ -30,11 +37,31 @@ public function configure() | |
{ | ||
} | ||
|
||
/** | ||
* @return int|null | ||
*/ | ||
public function getPerPage() | ||
{ | ||
return $this->perPage; | ||
} | ||
|
||
/** | ||
* @param Client $client | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this looks wrong |
||
*/ | ||
public function setPerPage($perPage) | ||
{ | ||
$this->perPage = (int) $perPage; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this casting forbids setting it back to |
||
return $this; | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
protected function get($path, array $parameters = array(), $requestHeaders = array()) | ||
{ | ||
if (null !== $this->perPage && !isset($parameters['per_page'])) { | ||
$parameters['per_page'] = $this->perPage; | ||
} | ||
$response = $this->client->getHttpClient()->get($path, $parameters, $requestHeaders); | ||
|
||
return $response->getContent(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -163,7 +163,9 @@ public function put($path, array $parameters = array(), array $headers = array() | |
*/ | ||
public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array()) | ||
{ | ||
$path = trim($this->options['base_url'].$path, '/'); | ||
if ( !empty($this->options['base_url']) AND 0 !== strpos( $path, $this->options['base_url'] )) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. && |
||
$path = trim($this->options['base_url'].$path, '/'); | ||
} | ||
|
||
$request = $this->createRequest($httpMethod, $path); | ||
$request->addHeaders($headers); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
<?php | ||
|
||
namespace Github; | ||
|
||
use Github\Api\ApiInterface; | ||
use Github\Exception\InvalidArgumentException; | ||
use Github\HttpClient\HttpClient; | ||
use Github\HttpClient\HttpClientInterface; | ||
|
||
/** | ||
* Pager class for supporting pagination in github classes | ||
* | ||
* @author Ramon de la Fuente <ramon@future500.nl> | ||
* @author Mitchel Verschoof <mitchel@future500.nl> | ||
*/ | ||
class ResultPager implements ResultPagerInterface | ||
{ | ||
/** | ||
* @var Github\Client client | ||
*/ | ||
protected $client; | ||
|
||
/** | ||
* @var array pagination | ||
* Comes from pagination headers in Github API results | ||
*/ | ||
protected $pagination = null; | ||
|
||
public function __construct( Client $client ) | ||
{ | ||
$this->client = $client; | ||
} | ||
|
||
/** | ||
* @return null|array pagination result of last request | ||
*/ | ||
public function getPagination() | ||
{ | ||
return $this->pagination; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function fetch( ApiInterface $api, $method ) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be cleaner to define There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stof I had that initially, but most Api calls only have one parameter so you'd end up having to put array('param') around everything. Also, it stays closer to the api documentation this way. |
||
{ | ||
$parameters = array_slice(func_get_args(),2); | ||
|
||
$result = call_user_func_array(array($api, $method), $parameters); | ||
$this->postFetch(); | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function fetchAll( ApiInterface $api, $method ) | ||
{ | ||
$parameters = array_slice(func_get_args(),2); | ||
|
||
// Set parameters per_page to GitHub max to minimize number of requests | ||
$api->setPerPage(100); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't you reset it to its current value at the end to avoid side effects ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stof Good point. I decided to raise the parameter because it did not make sense to do more api calls than needed. But we'd like to add a resultLimit() to allow fetchAll to be used even when you don't literally want "all". And in that case you might want to keep control over the per_page count as well. |
||
|
||
$result = array(); | ||
$result = call_user_func_array(array($api, $method), $parameters); | ||
$this->postFetch(); | ||
|
||
while ($this->hasNext()) { | ||
$result = array_merge($result, $this->fetchNext()); | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function postFetch() | ||
{ | ||
$this->pagination = $this->client->getHttpClient()->getLastResponse()->getPagination(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function hasNext() | ||
{ | ||
return $this->has('next'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function fetchNext() | ||
{ | ||
return $this->get('next'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function hasPrevious() | ||
{ | ||
return $this->has('prev'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function fetchPrevious() | ||
{ | ||
return $this->get('prev'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function fetchFirst() | ||
{ | ||
return $this->get('first'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function fetchLast() | ||
{ | ||
return $this->get('last'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function has($key) | ||
{ | ||
if (!empty($this->pagination) and isset($this->pagination[$key])) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and the method can be simplified: return !empty($this->pagination) && isset($this->pagination[$key]); |
||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
protected function get($key) | ||
{ | ||
if ( $this->has($key) ) { | ||
$result = $this->client->getHttpClient()->get($this->pagination[$key]); | ||
$this->postFetch(); | ||
|
||
return $result->getContent(); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
namespace Github; | ||
|
||
use Github\Api\ApiInterface; | ||
|
||
/** | ||
* Pager interface | ||
* | ||
* @author Ramon de la Fuente <ramon@future500.nl> | ||
* @author Mitchel Verschoof <mitchel@future500.nl> | ||
*/ | ||
interface ResultPagerInterface | ||
{ | ||
/** | ||
* Fetch a single result (page) from an api call | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should document the return value in the methods |
||
*/ | ||
public function fetch( ApiInterface $api, $method ); | ||
|
||
/** | ||
* Fetch all results (pages) from an api call | ||
* Use with care - there is no maximum | ||
*/ | ||
public function fetchAll( ApiInterface $api, $method ); | ||
|
||
/** | ||
* Method that performs the actual work to refresh the pagination property | ||
*/ | ||
public function postFetch(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should it really be a public method ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @stof It wasn't - but thinking about how people would interact with the resultPager across requests makes is necesary at this point. If you request the pagination information from the pager i.e. getPagination() you could use a "next" button on your own interface. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps a public setPagination() that gets called by protected postFetch()? |
||
|
||
/** | ||
* Check to determine the availability of a next page | ||
* @return bool | ||
*/ | ||
public function hasNext(); | ||
|
||
/** | ||
* Check to determine the availability of a previous page | ||
* @return bool | ||
*/ | ||
public function hasPrevious(); | ||
|
||
/** | ||
* Fetch the next page | ||
* @return array | ||
*/ | ||
public function fetchNext(); | ||
|
||
/** | ||
* Fetch the previous page | ||
* @return array | ||
*/ | ||
public function fetchPrevious(); | ||
|
||
/** | ||
* Fetch the first page | ||
* @return array | ||
*/ | ||
public function fetchFirst(); | ||
|
||
/** | ||
* Fetch the last page | ||
* @return array | ||
*/ | ||
public function fetchLast(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
namespace Github\Tests\Mock; | ||
|
||
class TestResponse | ||
{ | ||
protected $loopCount; | ||
|
||
protected $content; | ||
|
||
public function __construct( $loopCount, array $content = array() ) | ||
{ | ||
$this->loopCount = $loopCount; | ||
$this->content = $content; | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public function getContent() | ||
{ | ||
return $this->content; | ||
} | ||
|
||
/** | ||
* @return array|null | ||
*/ | ||
public function getPagination() | ||
{ | ||
if($this->loopCount){ | ||
$returnArray = array( | ||
'next' => 'http://github.com/' . $this->loopCount | ||
); | ||
} else { | ||
$returnArray = array( | ||
'prev' => 'http://github.com/prev' | ||
); | ||
} | ||
|
||
$this->loopCount--; | ||
|
||
return $returnArray; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing closing quote