Skip to content

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

Merged
merged 21 commits into from
Jul 30, 2013
Merged
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
1 change: 1 addition & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ APIs:

Additional features:

* [Pagination support](result_pager.md)
* [Authentication & Security](security.md)
* [Request any Route](request_any_route.md)
* [Customize `php-github-api` and testing](customize.md)
52 changes: 52 additions & 0 deletions doc/result_pager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## 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);
$parameters = array('github');
$result = $paginator->fetchAll($organizationApi, 'repositories', $parameters);
```

Get the first page
```php
$client = new Github\Client();

$organizationApi = $client->api('organization');

$paginator = new Github\ResultPager( $client );
$parameters = array('github');
$result = $paginator->fetch($organizationApi, 'repositories', $parameters);
```

Check for a next page:
```php
$paginator->hasNext();
```

Get next page:
```php
$paginator->fetchNext();
```

Check for pervious page:
```php
$paginator->hasPrevious();
```

Get prevrious page:
```php
$paginator->fetchPrevious();
```

If you want to retrieve the pagination links (available after the call to fetch):
```php
$paginator->getPagination();
```
28 changes: 28 additions & 0 deletions lib/Github/Api/AbstractApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ abstract class AbstractApi implements ApiInterface
*/
protected $client;

/**
* number of items per page (GitHub pagination)
*
* @var null|int
*/
protected $perPage;

/**
* @param Client $client
*/
Expand All @@ -30,11 +37,32 @@ public function configure()
{
}

/**
* @return null|int
*/
public function getPerPage()
{
return $this->perPage;
}

/**
* @param null|int $perPage
*/
public function setPerPage($perPage)
{
$this->perPage = (null === $perPage ? $perPage : (int) $perPage);

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();
Expand Down
4 changes: 3 additions & 1 deletion lib/Github/HttpClient/HttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']) && 0 !== strpos($path, $this->options['base_url'])) {
$path = trim($this->options['base_url'].$path, '/');
}

$request = $this->createRequest($httpMethod, $path);
$request->addHeaders($headers);
Expand Down
166 changes: 166 additions & 0 deletions lib/Github/ResultPager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?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;


/**
* The Github client to use for pagination. This must be the same
* instance that you got the Api instance from, i.e.:
*
* $client = new \Github\Client();
* $api = $client->api('someApi');
* $pager = new \Github\ResultPager($client);
*
* @param \Github\Client $client
*
*/
public function __construct(Client $client)
{
$this->client = $client;
}

/**
* {@inheritdoc}
*/
public function getPagination()
{
return $this->pagination;
}

/**
* {@inheritdoc}
*/
public function fetch(ApiInterface $api, $method, array $parameters = array())
{
$result = call_user_func_array(array($api, $method), $parameters);
$this->postFetch();

return $result;
}

/**
* {@inheritdoc}
*/
public function fetchAll(ApiInterface $api, $method, array $parameters = array())
{
// get the perPage from the api
$perPage = $api->getPerPage();

// Set parameters per_page to GitHub max to minimize number of requests
$api->setPerPage(100);
Copy link
Contributor

Choose a reason for hiding this comment

The 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 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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());
}

// restore the perPage
$api->setPerPage($perPage);

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)
{
return !empty($this->pagination) && isset($this->pagination[$key]);
}

/**
* {@inheritdoc}
*/
protected function get($key)
{
if ($this->has($key)) {
$result = $this->client->getHttpClient()->get($this->pagination[$key]);
$this->postFetch();

return $result->getContent();
}
}
}
84 changes: 84 additions & 0 deletions lib/Github/ResultPagerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?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
{

/**
* @return null|array pagination result of last request
*/
public function getPagination();

/**
* Fetch a single result (page) from an api call
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should document the return value in the methods

*
* @param ApiInterface $api the Api instance
* @param string $method the method name to call on the Api instance
* @param array $parameters the method parameters in an array
*
* @return array returns the result of the Api::$method() call
*/
public function fetch(ApiInterface $api, $method, array $parameters = array());

/**
* Fetch all results (pages) from an api call
* Use with care - there is no maximum
*
* @param ApiInterface $api the Api instance
* @param string $method the method name to call on the Api instance
* @param array $parameters the method parameters in an array
*
* @return array returns a merge of the results of the Api::$method() call
*/
public function fetchAll(ApiInterface $api, $method, array $parameters = array());

/**
* Method that performs the actual work to refresh the pagination property
*/
public function postFetch();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it really be a public method ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
But Github does not advise constructing your own urls, just use the full url provided in the link headers. So you'd have to perform a httpclient->get('nextUrl') request, after which you inject the client into the pager and call postFetch once from the outside.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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();
}
Loading