Skip to content
This repository has been archived by the owner on Oct 13, 2023. It is now read-only.

Commit

Permalink
Merge pull request #3 from nihilsen/pagination
Browse files Browse the repository at this point in the history
Add support for paginated API results.
  • Loading branch information
Persaeus authored Oct 16, 2022
2 parents fd805b8 + 7da26b8 commit 377756f
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 7 deletions.
36 changes: 32 additions & 4 deletions src/API/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@

use GuzzleHttp\Cookie\CookieJar;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Nihilsen\BoxBilling\Collection;
use Nihilsen\BoxBilling\Exceptions\APIErrorException;
use Nihilsen\BoxBilling\Facades\BoxBilling;
use Nihilsen\BoxBilling\Role;

abstract class API
{
/**
* The keys of parameters that should always be submitted as GET parameters.
*/
public const GET_PARAMETERS = ['page', 'per_page'];

/**
* The cookies for the request.
*
Expand Down Expand Up @@ -86,24 +93,45 @@ protected function cookieTtl(): \DateTimeInterface|\DateInterval|int
* Query the API with given $method and $parameters.
*
* @param string $method
* @param array|null $parameters
* @param array $parameters
* @return mixed|\Nihilsen\BoxBilling\Collection
*/
protected function query(string $method, ?array $parameters)
public function query(string $method, array $parameters)
{
// Partition parameters into GET and POST parameters.
[$get, $post] = collect($parameters)
->partition(fn ($_, $key) => in_array($key, static::GET_PARAMETERS))
->toArray();

$endpoint = str($method)
->replaceFirst('_', '/')
->prepend(class_basename($this), '/')
->append('?', Arr::query($get))
->lower();

$response = $this->request()->post($endpoint, $parameters ?? []);
$response = $this->request()->post(
$endpoint,
$post
);

$this->cookies(set: $response->cookies());

if ($error = $response->json('error')) {
throw new APIErrorException($error);
}

return $response->json('result');
$result = $response->json('result');

if (Collection::canCollect($result)) {
return Collection::collectResultFromAPI(
$result,
$this,
$method,
$parameters
);
}

return $result;
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/API/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

/**
* @method mixed client_login(int $id)
* @method \Nihilsen\BoxBilling\Collection<array> client_get_list(string|null $status = null, int|null $page = null, int|null $per_page = null)
*/
class Admin extends AuthenticatedAPI
{
Expand Down
2 changes: 1 addition & 1 deletion src/API/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected function logIn()
/**
* {@inheritDoc}
*/
protected function query(string $method, ?array $parameters)
public function query(string $method, ?array $parameters)
{
try {
return parent::query($method, $parameters);
Expand Down
107 changes: 107 additions & 0 deletions src/Collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

namespace Nihilsen\BoxBilling;

use Illuminate\Support\Arr;
use Illuminate\Support\LazyCollection;
use Nihilsen\BoxBilling\API\API;
use Nihilsen\BoxBilling\Exceptions\UnexpectedAPIResultException;

class Collection extends LazyCollection
{
/**
* Create a new BoxBiling lazy collection instance.
*
* @return void
*/
protected function __construct(
/**
* The collection result items.
*
* @var array
*/
protected array $list,
public readonly int $page,
public readonly int $pages,
public readonly int $per_page,
public readonly int $total,
public readonly API $api,
public readonly string $method,
public readonly array $parameters
) {
return parent::__construct(function () {
foreach ($this->list as $result) {
yield $result;
}

if ($this->pages > $this->page) {
/** @var static */
$nextPageCollection = $this->api->query(
$this->method,
array_merge(
$this->parameters,
['page' => $this->page + 1]
)
);

if (! $nextPageCollection instanceof static) {
throw new UnexpectedAPIResultException('API did not return iterable results for pagination.');
}

foreach ($nextPageCollection as $result) {
yield $result;
}
}
});
}

public static function canCollect(mixed $result): bool
{
return
is_array($result) &&
Arr::has(
$result,
[
'list',
'page',
'pages',
'per_page',
'total',
]
);
}

/**
* Collect the given API $result, and indicate that the next chunk
* may be retrievable via the given callable $next.
*
* @param array{pages: int, page: int, per_page: int, total: int, list: array} $result
* @return static
*/
public static function collectResultFromAPI(
array $result,
API $api,
string $method,
array $parameters
): static {
/**
* @var int $pages
* @var int $page
* @var int $per_page
* @var int $total
* @var array $list
*/
extract($result);

return new static(
$list,
$page,
$pages,
$per_page,
$total,
$api,
$method,
$parameters
);
}
}
8 changes: 8 additions & 0 deletions src/Exceptions/UnexpectedAPIResultException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Nihilsen\BoxBilling\Exceptions;

class UnexpectedAPIResultException extends \UnexpectedValueException
{
//
}
60 changes: 58 additions & 2 deletions tests/HttpTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use Nihilsen\BoxBilling\Collection;
use Nihilsen\BoxBilling\Facades\BoxBilling;

it('can query guest api endpoint', function () {
Expand All @@ -23,8 +24,7 @@

it('can query authenticated api endpoint', function () {
Http::fake(function (Request $request) {
$isValidAuthenticatedRequest = (
$request->url() == 'https://boxbilling.invalid/api/admin/system/messages' &&
$isValidAuthenticatedRequest = ($request->url() == 'https://boxbilling.invalid/api/admin/system/messages' &&
$request->hasHeader(
'Authorization',
'Basic '.base64_encode('admin:token')
Expand Down Expand Up @@ -59,3 +59,59 @@

expect(BoxBilling::client(id: 1)->profile_get())->toBe('logged in');
});

it('handles paginated results', function () {
Http::fake(function (Request $request) {
if (! str_contains($request->url(), 'boxbilling.invalid/api/admin/client/get_list')) {
dd($request->url());

return;
}

parse_str(parse_url($request->url(), PHP_URL_QUERY), $result);

/** @var int|null $page */
extract($result);

if (empty($page) || $page == 1) {
return Http::response('{
"result": {
"pages": 2,
"page": 1,
"per_page": 2,
"total": "3",
"list": [
{
"id": "1"
},
{
"id": "2"
}
]
},
"error": null
}');
}

if ($page == 2) {
return Http::response('{
"result": {
"pages": 2,
"page": 2,
"per_page": 2,
"total": "3",
"list": [
{
"id": "3"
}
]
},
"error": null
}');
}
});

expect(BoxBilling::admin()->client_get_list())
->toBeInstanceOf(Collection::class)
->toHaveCount(3);
});

0 comments on commit 377756f

Please sign in to comment.