Last active
August 29, 2017 21:06
-
-
Save bluefirex/cdfff8e76cc3c90898e62db1d86a5770 to your computer and use it in GitHub Desktop.
Paginated Results in a JSON API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Adepto\Asgard\API; | |
use Adepto\Asgard\Common\PaginatedResults; | |
use Purl\Url; | |
use Psr\Http\Message\ServerRequestInterface; | |
/** | |
* Represents a request on a paginated result set. | |
* | |
* @author bluefirex | |
* @version 1.0 | |
*/ | |
class PaginatedRequest { | |
protected $request; | |
protected $currentPage; | |
protected $perPage; | |
public function __construct(ServerRequestInterface $request) { | |
$this->request = $request; | |
$this->currentPage = max(1, $request->getQueryParams()['page'] ?? 1); | |
$this->perPage = $request->getQueryParams()['per_page'] ?? null; | |
} | |
/** | |
* Get the original request. | |
* | |
* @return ServerRequestInterface | |
*/ | |
public function getRequest(): ServerRequestInterface { | |
return $this->request; | |
} | |
/** | |
* Get the currently requested page number | |
* | |
* @return int | |
*/ | |
public function getCurrentPage(): int { | |
return $this->currentPage; | |
} | |
/** | |
* How many items per page were requested? | |
* | |
* @param int|integer $default Default value to fall back to, if no specific number was requested | |
* | |
* @return int | |
*/ | |
public function getPerPage(int $default = 25): int { | |
return $this->perPage ?? $default; | |
} | |
/** | |
* Format results for a response. This will include meta information (page count, | |
* total count, etc.) as well as links to the previous and next pages. | |
* | |
* @param PaginatedResults $results Original Result Set | |
* @param array $formattedResults Formatted Result Set as array | |
* @param string $resultsKey Key under which the formatted result set should appear. | |
* | |
* @return array | |
*/ | |
public function formatResultsForResponse(PaginatedResults $results, array $formattedResults, string $resultsKey = 'results'): array { | |
// Prepare URLs | |
list($previousPage, $nextPage) = $this->getURLsForResults($this->getRequest(), $results, $this->getCurrentPage()); | |
return [ | |
$resultsKey => $formattedResults, | |
'pages' => $results->getPages(), | |
'total' => $results->getTotal(), | |
'next_page' => $nextPage, | |
'previous_page' => $previousPage, | |
'current_page' => $this->getCurrentPage(), | |
]; | |
} | |
/** | |
* Get the correct URLs for getting page-links in a paginated result set. | |
* | |
* @param ServerRequestInterface $request Original Request | |
* @param PaginatedResults $results Results to be applied on | |
* @param int|integer $currentPage Current Page, defaults to 1 | |
* | |
* @return array previous_page_link, next_page_link | |
*/ | |
protected function getURLsForResults(ServerRequestInterface $request, PaginatedResults $results, int $currentPage = 1) { | |
$thisURL = new Url($request->getUri()); | |
if ($currentPage < $results->getPages()) { | |
$nextPage = clone $thisURL; | |
$nextPage->query->page = $currentPage + 1; | |
$nextPage = self::urlToPathWithQuery($nextPage); | |
} else { | |
$nextPage = null; | |
} | |
if ($currentPage > 1) { | |
$previousPage = clone $thisURL; | |
$previousPage->query->page = $currentPage - 1; | |
$previousPage = self::urlToPathWithQuery($previousPage); | |
} else { | |
$previousPage = null; | |
} | |
return [ $previousPage, $nextPage ]; | |
} | |
protected function urlToPathWithQuery($url) { | |
if (!$url instanceof Url) { | |
$url = new Url($url); | |
} | |
$url->set('scheme', null) | |
->set('port', null) | |
->set('host', null); | |
$url = str_replace('://', '', $url); | |
return $url; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Adepto\Asgard\Common; | |
/** | |
* PaginatedResults | |
* Represents a collection of results in a paginated fashion. | |
* | |
* @author bluefirex | |
* @version 1.0 | |
*/ | |
class PaginatedResults { | |
protected $results; | |
protected $perPage; | |
protected $pages; | |
protected $total; | |
/** | |
* Read carefully on how to use this: | |
* Always supply $results, which is an array of your actual results. | |
* Always supply $perPage, which tells the result how many results per page are shown or fetched. | |
* Only supply $pages if you do NOT have all results in $results but only fetched a subset and only | |
* want to pass that subset. If you do NOT supply $pages, it will automatically calculate the number based | |
* on the count of $results and items displayed per page. | |
* | |
* @param array $results Actual Results, either all or a subset | |
* @param int|integer $perPage Number of results per page | |
* @param int|null $pages How many pages there are. Read method description for this! | |
* @param int|null $total How many items there are in total if this wasn't paginated | |
*/ | |
public function __construct(array $results = [], int $perPage = 1, int $pages = null, int $total = null) { | |
$this->results = $results; | |
$this->perPage = $perPage; | |
$this->pages = $pages ?? ceil(count($results) / $perPage); | |
$this->total = $total; | |
} | |
/** | |
* Get the results. This will be an array containing objects or arrays defined | |
* by the function using this. | |
* | |
* @return array | |
*/ | |
public function getResults(): array { | |
return $this->results; | |
} | |
/** | |
* @chainable | |
* Transform the results in-place using $callback. This will MODIFY the originals. | |
* | |
* @param callable $callback Callback, receives one item at a time | |
* | |
* @return PaginatedResults | |
*/ | |
public function transformResults(callable $callback): PaginatedResults { | |
$this->results = $this->getTransformedResults($callback); | |
return $this; | |
} | |
/** | |
* Get transformed results using $callback without modifying the originals | |
* | |
* @param callable $callback Callback, receives one item at a time | |
* | |
* @return array | |
*/ | |
public function getTransformedResults(callable $callback): array { | |
return array_map($callback, $this->results); | |
} | |
/** | |
* Get the subset of results for page number $page. | |
* This will only return logical results if ALL results have been passed to the constructor. | |
* | |
* @param int $page Page Number to get the results for. | |
* | |
* @return array | |
*/ | |
public function getPaginatedResults(int $page): array { | |
if ($page * $this->perPage > count($this->results)) { | |
return []; | |
} | |
return array_slice($this->results, $page * $this->perPage - $this->perPage, $this->perPage); | |
} | |
/** | |
* Get the number of results per page. | |
* | |
* @return int | |
*/ | |
public function getResultsPerPage(): int { | |
return $this->perPage; | |
} | |
/** | |
* Get the number of pages. | |
* | |
* @return int | |
*/ | |
public function getPages() { | |
return $this->pages; | |
} | |
/** | |
* Get the total number of results spanned across all pages | |
* | |
* @return int | |
*/ | |
public function getTotal() { | |
return $this->total; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment