diff --git a/.styleci.yml b/.styleci.yml index f186f21..a77c58a 100755 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,9 +1,5 @@ preset: recommended -enabled: - - strict - - strict_param - disabled: - - align_double_arrow - - simplified_null_return + - align_double_arrow + diff --git a/.travis.yml b/.travis.yml index be67488..0dc5077 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,21 @@ language: php -php: - - 7.1 - - 7.2 - - 7.3 +services: + - docker cache: directories: - vendor - $HOME/.composer/cache +before_install: + - docker pull php:7.1-cli + - docker pull php:7.2-cli + - docker pull php:7.3-cli + - docker pull php:7.4-cli + install: - composer install + +script: + - ./run-tests.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index ed13133..692d152 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,14 @@ All notable changes to `exonet-api-php` will be documented in this file. Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. ## Unreleased -[Compare v2.2.0 - Unreleased](https://github.com/exonet/exonet-api-php/compare/v2.2.0...master) +[Compare v2.3.0 - Unreleased](https://github.com/exonet/exonet-api-php/compare/v2.3.0...master) + +## [v2.3.0](https://github.com/exonet/exonet-api-php/releases/tag/v2.3.0) - 2020-08-07 +[Compare v2.2.0 - v2.3.0](https://github.com/exonet/exonet-api-php/compare/v2.2.0...v2.3.0) +### Added +- Add the `total()` method to resource sets to get the total number of resources (and not only the number of resources in the current resource set). +- Add `nextPage`, `previousPage`, `firstPage` and `lastPage` methods to the `ApiResourceSet` for easy loading of paginated resource sets. +- Add a `getRecursive` method to the `Request` to get the resource set including recursively the resource sets from the following pages. ## [v2.2.0](https://github.com/exonet/exonet-api-php/releases/tag/v2.2.0) - 2019-11-19 [Compare v2.1.1 - v2.2.0](https://github.com/exonet/exonet-api-php/compare/v2.1.1...v2.2.0) diff --git a/docs/api_responses.md b/docs/api_responses.md index bd18c10..ac44f01 100644 --- a/docs/api_responses.md +++ b/docs/api_responses.md @@ -1,5 +1,5 @@ # Using API Responses -There are two types of API responses upon a successful request. If a single resource is requested then an [`ApiResource`](api_resources.md) is +There are two types of API responses upon a successful request. If a single resource is requested then an [`ApiResource`](api_resources.md) is returned, if multiple resources are requested then an `ApiResourceSet` is returned. ## The `ApiResourceSet` class @@ -16,6 +16,29 @@ foreach ($certificates as $certificate) { } ``` +The get the number of items in a resource set, you can use one of the following methods: +```php +$certificates->count(); // Returns the number of resources in the current resource set. +$certificates->total(); // Returns the total number of resources in the resource set, ignoring pagination. +``` + +If `count != total` you can get the next/previous/first/last page by calling one of the pagination methods: +```php +// Get the next resource set: +$certificates->nextPage(); + +// Get the previous resource set: +$certificates->previousPage(); + +// Get the first resource set: +$certificates->firstPage(); + +// Get the last resource set: +$certificates->lastPage(); +``` + +Each of this methods will return `null` if not available. + ## The [`ApiResource`](api_resources.md) class Each resource returned by the API is transformed to an [`ApiResource`](api_resources.md) instance. This makes it possible to have easy access to the attributes, resourceType and ID of the resource. Each of these fields can be accessed as if it is a property on the class: diff --git a/docs/calls.md b/docs/calls.md index 8bda17a..5b962e2 100644 --- a/docs/calls.md +++ b/docs/calls.md @@ -1,5 +1,5 @@ # Making API calls -After the client has been initialised, use the `resource` method to define which type of resource you want to get from +After the client has been initialised, use the `resource` method to define which type of resource you want to get from the API: ```php @@ -24,6 +24,19 @@ After setting the parameters you can call the `->get()` method to retrieve the r $certificates = $certificatesRequest->get(); ``` +It is also possible to get all resource sets recursively. The package will check the URL defined in `links.next` and as +long as the value is not `null` it will make an additional request and merge the results: + +```php +$certificates = $certificatesRequest->getRecursive(); +``` + +Please note that the `getRecursive` method respects pagination and filters. So the following example will get all +non-expired certificates, starting from page two in batches of ten: +```php +$certificates = $certificatesRequest->filter('expired', false)->page(2)->size(10)->getRecursive(); +``` + ## Getting a single resource by ID If you want to get a specific resource by its ID, use the `id()` method to define specify the ID of the resource: ```php diff --git a/examples/dns_record_post.php b/examples/dns_record_post.php index ce663ac..9c06e38 100644 --- a/examples/dns_record_post.php +++ b/examples/dns_record_post.php @@ -21,7 +21,7 @@ // Show this message when there are no zones. if (empty($zones)) { echo 'There are no zones available.'; - die(); + exit(); } $zone = $zones[0]; diff --git a/examples/dns_zone_details.php b/examples/dns_zone_details.php index cca19a1..c813761 100644 --- a/examples/dns_zone_details.php +++ b/examples/dns_zone_details.php @@ -19,7 +19,7 @@ // Show this message when there are no zones. if (empty($zones)) { echo 'There are no zones available.'; - die(); + exit(); } $zone = $zones[0]; diff --git a/examples/ticket_details.php b/examples/ticket_details.php index 97a4aa3..b3e8720 100644 --- a/examples/ticket_details.php +++ b/examples/ticket_details.php @@ -20,7 +20,7 @@ // Show this message when there are no tickets available. if (empty($tickets)) { echo 'There are no tickets available'; - die(); + exit(); } $ticket = $tickets[0]; diff --git a/examples/tickets.php b/examples/tickets.php index ce9eb6e..ae70317 100644 --- a/examples/tickets.php +++ b/examples/tickets.php @@ -25,7 +25,7 @@ * @param string $description The description of the list. * @param ApiResourceSet $ticketList The resource set containing ticket resources. */ -function renderTickets(string $description, ApiResourceSet $ticketList) : void +function renderTickets(string $description, ApiResourceSet $ticketList): void { echo sprintf("\n%s (%d)\n%s\n", $description, count($ticketList), str_repeat('-', strlen($description))); diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..6188bc8 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,55 @@ +bold=$(tput bold) +red=$(tput setaf 1) +green=$(tput setaf 2) +normal=$(tput sgr0) +RESULTS="" +HAS_FAILED_TESTS=0 + +function run() { + PHP_VERSION=$1 + + echo "" + echo "--------------------------------------------" + echo "Testing with PHP ${bold}$PHP_VERSION${normal}" + echo "--------------------------------------------" + + docker run \ + -it \ + --rm \ + --name php"$PHP_VERSION" \ + -v "$PWD":/usr/src \ + -w /usr/src/ \ + php:"$PHP_VERSION"-cli \ + php ./vendor/bin/phpunit + + if [ $? -eq 0 ]; then + RESULTS="$RESULTS\n${green}✓ PHP $PHP_VERSION${normal}" + else + HAS_FAILED_TESTS=1 + RESULTS="$RESULTS\n${red}𐄂 PHP $PHP_VERSION${normal}" + fi + + echo "(PHP $PHP_VERSION)" +} + +# Get the arguments from the call (i.e. ./run-tests.sh 7.4) +SET_PHP_VERSION=$1 + +# If an arguments is given, only run that version. +if [ "$#" -eq 1 ]; then + run "$SET_PHP_VERSION" +else + # Run tests for different PHP 7 versions. + for phpversion in {1..4}; do + run "7.$phpversion" + RESULTS="$RESULTS\n" + done +fi + +echo "" +echo "RESULTS" +echo "----------------------" +printf "$RESULTS" + +exit $HAS_FAILED_TESTS + diff --git a/src/Auth/AbstractAuth.php b/src/Auth/AbstractAuth.php index b13deff..0ed2ce8 100644 --- a/src/Auth/AbstractAuth.php +++ b/src/Auth/AbstractAuth.php @@ -19,7 +19,7 @@ abstract class AbstractAuth * * @return string The access token. */ - public function getToken() : string + public function getToken(): string { return $this->token; } @@ -29,7 +29,7 @@ public function getToken() : string * * @param string $token The access token. */ - public function setToken(string $token) : void + public function setToken(string $token): void { $this->token = $token; } diff --git a/src/Client.php b/src/Client.php index 8d6c569..9a08a2a 100755 --- a/src/Client.php +++ b/src/Client.php @@ -19,7 +19,7 @@ class Client implements LoggerAwareInterface /** * The version of this package. Used in the user-agent header. */ - public const CLIENT_VERSION = 'v1.0.0'; + public const CLIENT_VERSION = 'v2.3.0'; /** * The API base URL. @@ -75,7 +75,7 @@ public function __construct(?AbstractAuth $auth = null, ?string $apiUrl = null) * * @return Client The cache instance. */ - public static function getInstance() : self + public static function getInstance(): self { if (!isset(self::$_instance)) { self::$_instance = new self(); @@ -91,7 +91,7 @@ public static function getInstance() : self * * @return AbstractAuth The auth instance. */ - public function getAuth() : AbstractAuth + public function getAuth(): AbstractAuth { if ($this->auth === null) { $this->log()->error('No authentication method set.'); @@ -109,7 +109,7 @@ public function getAuth() : AbstractAuth * * @return self The current Client instance. */ - public function setAuth(AbstractAuth $auth) : self + public function setAuth(AbstractAuth $auth): self { $this->auth = $auth; @@ -121,7 +121,7 @@ public function setAuth(AbstractAuth $auth) : self * * @return string The API URL to connect to. */ - public function getApiUrl() : string + public function getApiUrl(): string { return $this->apiUrl; } @@ -133,7 +133,7 @@ public function getApiUrl() : string * * @return self The current Client instance. */ - public function setApiUrl(string $apiUrl) : self + public function setApiUrl(string $apiUrl): self { if (substr($apiUrl, -1) !== '/') { $apiUrl .= '/'; @@ -149,7 +149,7 @@ public function setApiUrl(string $apiUrl) : self * * @return LoggerInterface The log instance. */ - public function log() : LoggerInterface + public function log(): LoggerInterface { if ($this->logger === null) { // If there's no logger set, use the NullLogger. @@ -166,7 +166,7 @@ public function log() : LoggerInterface * * @return self The current Client instance. */ - public function setLogger(LoggerInterface $log) : self + public function setLogger(LoggerInterface $log): self { $this->logger = $log; diff --git a/src/Connector.php b/src/Connector.php index 89e50db..32a2f00 100644 --- a/src/Connector.php +++ b/src/Connector.php @@ -66,6 +66,20 @@ public function get(string $urlPath) return $this->parseResponse($response); } + /** + * Get the given URL recursively, based on the value of the 'links.next' value. + * + * @param string $urlPath The URL path to GET. + */ + public function getRecursive(string $urlPath): ApiResourceSet + { + $apiUrl = $this->apiClient()->getApiUrl().$urlPath; + + $data = $this->runRecursiveGet($apiUrl); + + return new ApiResourceSet(['data' => $data]); + } + /** * Convert the data to JSON and post it to a URL. * @@ -100,7 +114,7 @@ public function post(string $urlPath, array $data) * * @return true When the patch succeeded. */ - public function patch(string $urlPath, array $data) : bool + public function patch(string $urlPath, array $data): bool { $apiUrl = $this->apiClient()->getApiUrl().$urlPath; $this->apiClient()->log()->debug('Sending [PATCH] request', ['url' => $apiUrl]); @@ -125,7 +139,7 @@ public function patch(string $urlPath, array $data) : bool * * @return true When the delete was successful. */ - public function delete(string $urlPath, array $data = []) : bool + public function delete(string $urlPath, array $data = []): bool { $apiUrl = $this->apiClient()->getApiUrl().$urlPath; $this->apiClient()->log()->debug('Sending [DELETE] request', ['url' => $apiUrl]); @@ -183,7 +197,7 @@ private function parseResponse(PsrResponse $response) * * @return GuzzleClient The HTTP client instance. */ - private static function httpClient() : GuzzleClient + private static function httpClient(): GuzzleClient { $stackHash = spl_object_hash(self::$guzzleHandlerStack ?? new \stdClass()); if (!isset(self::$httpClient[$stackHash])) { @@ -199,17 +213,51 @@ private static function httpClient() : GuzzleClient * * @return Client The API client. */ - private function apiClient() : Client + private function apiClient(): Client { return $this->apiClientInstance ?? Client::getInstance(); } + /** + * Get the given URL recursively, based on the value of the 'links.next' value. + * + * @param string $apiUrl The URL to get. + * @param array $data The existing data. + * + * @return array The merged results. + */ + private function runRecursiveGet(string $apiUrl, $data = []): array + { + $this->apiClient()->log()->debug('Sending recursive [GET] request', ['url' => $apiUrl]); + + $request = new Request('GET', $apiUrl, $this->getDefaultHeaders()); + + $response = self::httpClient()->send($request); + + $this->apiClient()->log()->debug('Recursive request completed', ['statusCode' => $response->getStatusCode()]); + + if ($response->getStatusCode() >= 300) { + (new ResponseExceptionHandler($response))->handle(); + } + + $contents = $response->getBody()->getContents(); + + $decodedContent = json_decode($contents, true); + $mergedData = array_merge($data, $decodedContent['data']); + + if ($decodedContent['links']['next'] !== null) { + return $this->runRecursiveGet($decodedContent['links']['next'], $mergedData); + } + + return $mergedData; + } + /** * Get the headers that are default for each request. * * @return string[] The headers. */ - private function getDefaultHeaders() : array + private function getDefaultHeaders(): array { return [ 'Authorization' => sprintf('Bearer %s', $this->apiClient()->getAuth()->getToken()), diff --git a/src/Exceptions/ExonetApiException.php b/src/Exceptions/ExonetApiException.php index 91738bf..19620be 100644 --- a/src/Exceptions/ExonetApiException.php +++ b/src/Exceptions/ExonetApiException.php @@ -40,7 +40,7 @@ public function __construct($message = '', $code = 0, Throwable $previous = null * * @return string|null The detailed error code. */ - public function getDetailCode() : ?string + public function getDetailCode(): ?string { return $this->detailCode; } @@ -50,7 +50,7 @@ public function getDetailCode() : ?string * * @return array Array with detailed information if provided by the API. */ - public function getVariables() : array + public function getVariables(): array { return $this->variables; } diff --git a/src/Exceptions/ResponseExceptionHandler.php b/src/Exceptions/ResponseExceptionHandler.php index 09c0761..0120143 100644 --- a/src/Exceptions/ResponseExceptionHandler.php +++ b/src/Exceptions/ResponseExceptionHandler.php @@ -53,7 +53,7 @@ public function __construct(PsrResponse $response, ?Client $client = null) * * @throws ExonetApiException An (extended) instance of ExonetApiException. */ - public function handle() : void + public function handle(): void { $this->client->log()->error( 'Request failed', @@ -80,7 +80,7 @@ public function handle() : void * * @return ExonetApiException|null The error class or null if no class can be determined. */ - private function parseResponse() : ?ExonetApiException + private function parseResponse(): ?ExonetApiException { // If there are validation errors, parse them separately to include all failed validations. if ($this->response->getStatusCode() === 422) { @@ -112,7 +112,7 @@ private function parseResponse() : ?ExonetApiException * * @return ExonetApiException|null The validation exception with details. */ - private function parseValidationErrors() : ?ExonetApiException + private function parseValidationErrors(): ?ExonetApiException { $errorList = json_decode($this->responseBody, true)['errors'] ?? null; $errorCount = count($errorList); diff --git a/src/Exceptions/ValidationException.php b/src/Exceptions/ValidationException.php index 6b41bff..f977ee2 100644 --- a/src/Exceptions/ValidationException.php +++ b/src/Exceptions/ValidationException.php @@ -12,7 +12,7 @@ class ValidationException extends ExonetApiException * @param string|null $field The name of the field. * @param string $description The returned error description. */ - public function setFailedValidation(?string $field, string $description) : void + public function setFailedValidation(?string $field, string $description): void { $this->variables[$field ?? 'generic'][] = $description; } @@ -22,7 +22,7 @@ public function setFailedValidation(?string $field, string $description) : void * * @return array The failed validation details. */ - public function getFailedValidations() : array + public function getFailedValidations(): array { return $this->variables; } diff --git a/src/Request.php b/src/Request.php index 54ffa40..697fb8d 100644 --- a/src/Request.php +++ b/src/Request.php @@ -53,7 +53,7 @@ public function __construct(string $resource, ?Connector $connector = null) * * @return ApiResourceIdentifier The resource identifier. */ - public function id(string $id) : ApiResourceIdentifier + public function id(string $id): ApiResourceIdentifier { return new ApiResourceIdentifier($this->resource, $id); } @@ -70,6 +70,16 @@ public function get(?string $id = null) return $this->connector->get($this->prepareUrl($id)); } + /** + * Get the resource including all next resources. + * + * @return ApiResourceSet The requested data. + */ + public function getRecursive(): ApiResourceSet + { + return $this->connector->getRecursive($this->prepareUrl()); + } + /** * Post new data to the API. * @@ -95,7 +105,7 @@ public function post(array $payload, string $appendUrl = null) * * @return true When the patch succeeded. */ - public function patch(string $id, array $payload) : bool + public function patch(string $id, array $payload): bool { return $this->connector->patch( trim($this->resource, '/').'/'.$id, @@ -111,7 +121,7 @@ public function patch(string $id, array $payload) : bool * * @return true When the delete was successful. */ - public function delete(string $id, array $data = []) : bool + public function delete(string $id, array $data = []): bool { return $this->connector->delete( trim($this->resource, '/').'/'.$id, @@ -126,7 +136,7 @@ public function delete(string $id, array $data = []) : bool * * @return self This current Request instance. */ - public function size(int $pageSize) : self + public function size(int $pageSize): self { $this->queryStringParameters['page']['size'] = $pageSize; @@ -140,7 +150,7 @@ public function size(int $pageSize) : self * * @return self This current Request instance. */ - public function page(int $pageNumber) : self + public function page(int $pageNumber): self { $this->queryStringParameters['page']['number'] = $pageNumber; @@ -155,7 +165,7 @@ public function page(int $pageNumber) : self * * @return self This current Request instance. */ - public function filter(string $name, $value = true) : self + public function filter(string $name, $value = true): self { if (is_array($value)) { $value = implode(',', $value); @@ -174,7 +184,7 @@ public function filter(string $name, $value = true) : self * * @return string The fully prepared URL. */ - private function prepareUrl(?string $id = null) : string + private function prepareUrl(?string $id = null): string { $url = trim($this->resource, '/'); if ($id) { diff --git a/src/Structures/ApiResource.php b/src/Structures/ApiResource.php index 14af3e2..eef54dc 100644 --- a/src/Structures/ApiResource.php +++ b/src/Structures/ApiResource.php @@ -102,7 +102,7 @@ public function post() * * @return true When the patch succeeded. */ - public function patch() : bool + public function patch(): bool { // Patch the attributes. if (!empty($this->changedAttributes)) { @@ -127,7 +127,7 @@ public function patch() : bool * * @return Relation[] The parsed relationships. */ - private function parseRelations(array $relationships) : array + private function parseRelations(array $relationships): array { $parsedRelations = []; @@ -158,7 +158,7 @@ private function parseRelations(array $relationships) : array * * @return array Array that can be used as json. */ - protected function toJson(bool $onlyChangedAttributes = false, $onlyChangedRelations = false) : array + protected function toJson(bool $onlyChangedAttributes = false, $onlyChangedRelations = false): array { $json = [ 'data' => [ diff --git a/src/Structures/ApiResourceIdentifier.php b/src/Structures/ApiResourceIdentifier.php index 7c19e2a..fdd942a 100644 --- a/src/Structures/ApiResourceIdentifier.php +++ b/src/Structures/ApiResourceIdentifier.php @@ -56,7 +56,7 @@ public function __construct(string $resourceType, ?string $id = null, ?Request $ * * @return string The resource type. */ - public function type() : string + public function type(): string { return $this->resourceType; } @@ -66,7 +66,7 @@ public function type() : string * * @return string|null The resource Id. */ - public function id() : ?string + public function id(): ?string { return $this->id; } @@ -86,7 +86,7 @@ public function get() * * @return true When the delete was successful. */ - public function delete() : bool + public function delete(): bool { // If there are no changed relationships, perform a 'normal' delete. if (empty($this->changedRelationships)) { @@ -159,7 +159,7 @@ public function relationship(string $name, $resource = null) /** * Transform the set identifiers for this resource to an array that can be used for JSON. */ - protected function toJson() : array + protected function toJson(): array { return [ 'type' => $this->type(), diff --git a/src/Structures/ApiResourceSet.php b/src/Structures/ApiResourceSet.php index 4ba453f..0a6a37c 100644 --- a/src/Structures/ApiResourceSet.php +++ b/src/Structures/ApiResourceSet.php @@ -7,6 +7,8 @@ use ArrayAccess; use ArrayIterator; use Countable; +use Exonet\Api\Client; +use Exonet\Api\Connector; use Exonet\Api\Exceptions\ValidationException; use IteratorAggregate; @@ -16,7 +18,7 @@ class ApiResourceSet implements IteratorAggregate, ArrayAccess, Countable { /** - * @var \Exonet\Api\Structures\ApiResource[] The returned resources. + * @var ApiResource[] The returned resources. */ private $resources; @@ -30,13 +32,22 @@ class ApiResourceSet implements IteratorAggregate, ArrayAccess, Countable */ private $links; + /** + * @var Connector Class to use for making pagination requests. + */ + private $connector; + /** * ApiResourceSet constructor. * - * @param string|array $resources The resources from the API as encoded JSON string or a similar array. + * @param string|array $resources The resources from the API as encoded JSON string or a similar array. + * @param Connector|null $connector The connector used for pagination requests. If null, the default connect will be + * used. */ - public function __construct($resources) + public function __construct($resources, Connector $connector = null) { + $this->connector = $connector ?? new Connector(); + if (is_string($resources)) { $resources = json_decode($resources, true); } @@ -64,7 +75,7 @@ public function __construct($resources) * * @return ArrayIterator The array iterator. */ - public function getIterator() : ArrayIterator + public function getIterator(): ArrayIterator { return new ArrayIterator($this->resources ?? []); } @@ -76,7 +87,7 @@ public function getIterator() : ArrayIterator * * @return bool true on success or false on failure. */ - public function offsetExists($offset) : bool + public function offsetExists($offset): bool { return isset($this->resources[$offset]); } @@ -101,7 +112,7 @@ public function offsetGet($offset) * * @throws ValidationException if the provided $value is not an ApiResource. */ - public function offsetSet($offset, $value) : void + public function offsetSet($offset, $value): void { if (!$value instanceof ApiResource) { throw new ValidationException('Only ApiResources can be set.'); @@ -115,7 +126,7 @@ public function offsetSet($offset, $value) : void * * @param mixed $offset The offset to unset. */ - public function offsetUnset($offset) : void + public function offsetUnset($offset): void { unset($this->resources[$offset]); } @@ -133,4 +144,84 @@ public function count() return count($this->resources); } + + /** + * Get the total number of resources. + * + * @return int|null The total number of resources. + */ + public function total(): ?int + { + return $this->meta['resources']['total'] ?? null; + } + + /** + * Get the meta data. + * + * @return mixed[]|null The meta data. + */ + public function meta(): ?array + { + return $this->meta; + } + + /** + * Get the next page with resources. + * + * @return ApiResource|ApiResourceSet|null The next page with resources. + */ + public function nextPage() + { + return $this->navigateToLink('next'); + } + + /** + * Get the previous page with resources. + * + * @return ApiResource|ApiResourceSet|null The previous page with resources. + */ + public function previousPage() + { + return $this->navigateToLink('prev'); + } + + /** + * Get the first page with resources. + * + * @return ApiResource|ApiResourceSet|null The first page with resources. + */ + public function firstPage() + { + return $this->navigateToLink('first'); + } + + /** + * Get the last page with resources. + * + * @return ApiResource|ApiResourceSet|null The last page with resources. + */ + public function lastPage() + { + return $this->navigateToLink('last'); + } + + /** + * Get the resource for the given link name. + * + * @param string $linkName The name of the element in the 'links' array. + * + * @return ApiResource|ApiResourceSet|null The requested page with resources. + */ + private function navigateToLink(string $linkName) + { + $linkValue = $this->links[$linkName]; + + if ($linkValue === null) { + return null; + } + + $link = substr($linkValue, strlen(Client::getInstance()->getApiUrl())); + + return $this->connector->get($link); + } } diff --git a/tests/Auth/PersonalAccessTokenTest.php b/tests/Auth/PersonalAccessTokenTest.php index 1eeff7c..a19ca82 100644 --- a/tests/Auth/PersonalAccessTokenTest.php +++ b/tests/Auth/PersonalAccessTokenTest.php @@ -6,7 +6,7 @@ class PersonalAccessTokenTest extends TestCase { - public function testSetTokenViaConstructor() : void + public function testSetTokenViaConstructor(): void { $token = 'test_token'; @@ -15,7 +15,7 @@ public function testSetTokenViaConstructor() : void $this->assertSame($token, $authClass->getToken()); } - public function testSetTokenViaSetter() : void + public function testSetTokenViaSetter(): void { $token = 'test_token'; diff --git a/tests/ConnectorTest.php b/tests/ConnectorTest.php index 19f3327..8134aa4 100644 --- a/tests/ConnectorTest.php +++ b/tests/ConnectorTest.php @@ -56,7 +56,9 @@ public function setUp() ], ], 'meta' => [], - 'links' => [], + 'links' => [ + 'next' => null, + ], ]; } @@ -96,6 +98,26 @@ public function testGetMultipleResourcesWithValidResponse() $this->assertInstanceOf(ApiResourceSet::class, $connectorClass->get('test')); } + public function testGetRecursive() + { + $responseWithNext = $this->multiResource; + $responseWithNext['links']['next'] = 'next.url'; + + $mock = new MockHandler([ + new Response(200, [], json_encode($responseWithNext)), + new Response(200, [], json_encode($this->multiResource)), + ]); + + $handler = HandlerStack::create($mock); + + new Client(new PersonalAccessToken('test-token')); + $connectorClass = new Connector($handler); + + $apiResourceSet = $connectorClass->getRecursive('test'); + $this->assertInstanceOf(ApiResourceSet::class, $apiResourceSet); + $this->assertCount(2, $apiResourceSet); + } + public function testGetInvalidResponse() { $mock = new MockHandler([new Response(401)]); diff --git a/tests/Structures/ApiResourceSetTest.php b/tests/Structures/ApiResourceSetTest.php index 6ffbcc2..f8107be 100644 --- a/tests/Structures/ApiResourceSetTest.php +++ b/tests/Structures/ApiResourceSetTest.php @@ -2,6 +2,13 @@ namespace Exonet\Api\Structures; +use Exonet\Api\Auth\PersonalAccessToken; +use Exonet\Api\Client; +use Exonet\Api\Connector; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Middleware; +use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase; class ApiResourceSetTest extends TestCase @@ -76,7 +83,7 @@ public function testArrayAccessMethods() $this->assertSame( 'world1', - reset($resources)->attribute('hello') + $resources[0]->attribute('hello') ); // Test isset with an offset that should not exist. @@ -110,4 +117,54 @@ public function testOffsetSetValidation() // Try to set something other than an ApiResource. $resourceSetClass[55] = 'some string'; } + + public function testPaginationLinks() + { + Client::getInstance()->setAuth(new PersonalAccessToken('test-token')); + $apiUrl = Client::getInstance()->getApiUrl(); + $data = json_encode( + [ + 'data' => [], + 'meta' => ['count' => 0], + 'links' => [ + 'next' => $apiUrl.'next.url', + 'prev' => $apiUrl.'previous.url', + 'first' => $apiUrl.'first.url', + 'last' => $apiUrl.'last.url', + ], + ] + ); + + $apiCalls = []; + $mock = new MockHandler( + [ + new Response(200, [], $data), + new Response(200, [], $data), + new Response(200, [], $data), + new Response(200, [], $data), + ] + ); + + $history = Middleware::history($apiCalls); + $handler = HandlerStack::create($mock); + $handler->push($history); + + $resourceSet = new ApiResourceSet($data, new Connector($handler)); + $this->assertInstanceOf(ApiResourceSet::class, $resourceSet->nextPage()); + + $resourceSet = new ApiResourceSet($data, new Connector($handler)); + $this->assertInstanceOf(ApiResourceSet::class, $resourceSet->previousPage()); + + $resourceSet = new ApiResourceSet($data, new Connector($handler)); + $this->assertInstanceOf(ApiResourceSet::class, $resourceSet->firstPage()); + + $resourceSet = new ApiResourceSet($data, new Connector($handler)); + $this->assertInstanceOf(ApiResourceSet::class, $resourceSet->lastPage()); + + // Test the called URLs. + $this->assertSame('/next.url', $apiCalls[0]['request']->getRequestTarget()); + $this->assertSame('/previous.url', $apiCalls[1]['request']->getRequestTarget()); + $this->assertSame('/first.url', $apiCalls[2]['request']->getRequestTarget()); + $this->assertSame('/last.url', $apiCalls[3]['request']->getRequestTarget()); + } }