From f56e18520c77203dfbfa2a1e6dea0db2e5d0d8a8 Mon Sep 17 00:00:00 2001 From: Timothy Date: Mon, 21 Oct 2024 17:33:13 +0200 Subject: [PATCH] Feature/upgrade pagination (#34) * Upgrade pagination to v3 for `getAll()` * Fix styling --- .php-cs-fixer.cache | 2 +- CHANGELOG.md | 7 ++- src/Services/ApiClient.php | 112 +++++++++++++++++------------------- src/Traits/QueryBuilder.php | 6 +- 4 files changed, 62 insertions(+), 65 deletions(-) diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache index 257b4ba..28656e6 100644 --- a/.php-cs-fixer.cache +++ b/.php-cs-fixer.cache @@ -1 +1 @@ -{"php":"8.3.12","version":"3.64.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"braces_position":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline","keep_multiple_spaces_after_comma":true},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","do","else","elseif","final","for","foreach","function","if","interface","namespace","private","protected","public","static","switch","trait","try","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"ordered_imports":{"sort_algorithm":"alpha"},"no_unused_imports":true,"not_operator_with_successor_space":true,"trailing_comma_in_multiline":true,"phpdoc_scalar":true,"unary_operator_spaces":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["break","continue","declare","return","throw","try"]},"phpdoc_single_line_var_spacing":true,"phpdoc_var_without_name":true},"hashes":{"src\/Repositories\/TokenRepository.php":"4c7e3cae90c4b82a4af71eb79379f13c","src\/Jobs\/RemoveResourceFromLightspeedRetail.php":"41ced6695106572e195bdcceb437affb","src\/Jobs\/Middleware\/RateLimited.php":"70d1a708795723a8057fe003dd412b36","src\/Jobs\/SendResourceToLightspeedRetail.php":"06a6665dcb710064051010be0d64a747","src\/Interfaces\/AutomaticSynchronisationInterface.php":"6cb6f34adfdc0f3e493f78aa7d94c39f","src\/Interfaces\/TokenInterface.php":"5eec333b8948a0d4600ab72f0d6b267e","src\/Models\/LightspeedRetailResource.php":"db317c4a52377a0b866ec89b38a9d9da","src\/Models\/ApiToken.php":"c887efa1d1ad1c326cb9347ebbf6eff1","src\/LightspeedRetailApiServiceProvider.php":"a574b45dffc99069e530e6f67f396082","src\/LightspeedRetailApi.php":"838334c91e74a59599635f7d9d4ae4c4","src\/Facades\/LightspeedRetailApi.php":"f41d2efd10fb66074f2f44de7c8e7b01","src\/Traits\/HasLightspeedRetailResources.php":"6ea826caae2eda6d63a63ecd25d8d188","src\/Traits\/QueryBuilder.php":"e72a6c086b2c5a61356a1ec8fcd6e537","src\/Traits\/RetailResources.php":"fbf6b52348377cb0cce4f2984110029f","src\/Console\/Commands\/VerifyApiConnectionCommand.php":"64a49fe8835fe34cd9c03202293925d6","src\/Console\/Commands\/GenerateAuthenticationUrlCommand.php":"8d352858337963a7ece67a91fdd023cb","src\/Exceptions\/MissingLightspeedResourceException.php":"108edcafb4771e6f40821797cc260720","src\/Exceptions\/LightspeedRetailException.php":"a3cb67cc8fd5dfae2a7225f9df909b8c","src\/Exceptions\/IncorrectModelConfigurationException.php":"b4de6f62f6e26945c2469624cae159e4","src\/Exceptions\/DuplicateResourceException.php":"8076d0af87afde30f566ee40c77e1002","src\/Exceptions\/WaitingForSynchronisationException.php":"b14988adbf4b339d5c2bba0e45cc6d76","src\/Exceptions\/AuthenticationException.php":"9f33b80f486f0430c2246f3cdcd470fe","src\/Http\/Controllers\/SaveAccessTokenController.php":"d43a70b5e4246e5081b50fc084b32ad9","src\/Resource.php":"ff289351a1a289dbbc8de113c0f21a2a","src\/Actions\/GenerateRetailPayloadAction.php":"93eaaa027b7a586c63e10b5cda82668b","src\/Actions\/SaveLightspeedRetailResourceAction.php":"884115c83dedcc5fc6f27de0cbaea264","src\/Actions\/DispatchLightspeedRetailResourceAction.php":"2cf053306a01ac64fd20fde07cf724ca","src\/Actions\/RemoveResourceFromLightspeedRetailAction.php":"161b8081b7bcca676d534258c6832b8e","src\/Services\/Lightspeed\/ResourceManufacturer.php":"a5ec65218d0162dd4e7a78e071d37040","src\/Services\/Lightspeed\/ResourceImage.php":"8abf66fc267f9023bba1bc2af12cdeec","src\/Services\/Lightspeed\/ResourceAccount.php":"ab6ea568d93dcf4454210a33158ca552","src\/Services\/Lightspeed\/ResourceSale.php":"4b218338038f3f287cd11226e272f726","src\/Services\/Lightspeed\/ResourceCustomer.php":"be314a66626c215ad3e059518a81d1d0","src\/Services\/Lightspeed\/ResourceVendor.php":"f9998cabd45498adbf4c8047431a32c3","src\/Services\/Lightspeed\/ResourceCategory.php":"410ce3d3405e45d4c33a1a1d58216da0","src\/Services\/Lightspeed\/ResourceItem.php":"8f9c0c0707461ad9d40f5484612991e9","src\/Services\/ApiClient.php":"09e9c9fec6ce1bb37c937c0d837fe86a","src\/Events\/ResourceSendEvent.php":"c381cdc55bb013a5861669015aac691b","src\/Scope.php":"935d170d40bf7c19761e7561fa18b52e","tests\/TestCase.php":"4b54880a083676440aaeb071629a9601"}} \ No newline at end of file +{"php":"8.3.12","version":"3.64.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"braces_position":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline","keep_multiple_spaces_after_comma":true},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","do","else","elseif","final","for","foreach","function","if","interface","namespace","private","protected","public","static","switch","trait","try","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"ordered_imports":{"sort_algorithm":"alpha"},"no_unused_imports":true,"not_operator_with_successor_space":true,"trailing_comma_in_multiline":true,"phpdoc_scalar":true,"unary_operator_spaces":true,"binary_operator_spaces":true,"blank_line_before_statement":{"statements":["break","continue","declare","return","throw","try"]},"phpdoc_single_line_var_spacing":true,"phpdoc_var_without_name":true},"hashes":{"src\/Repositories\/TokenRepository.php":"4c7e3cae90c4b82a4af71eb79379f13c","src\/Jobs\/RemoveResourceFromLightspeedRetail.php":"41ced6695106572e195bdcceb437affb","src\/Jobs\/Middleware\/RateLimited.php":"70d1a708795723a8057fe003dd412b36","src\/Jobs\/SendResourceToLightspeedRetail.php":"06a6665dcb710064051010be0d64a747","src\/Interfaces\/AutomaticSynchronisationInterface.php":"6cb6f34adfdc0f3e493f78aa7d94c39f","src\/Interfaces\/TokenInterface.php":"5eec333b8948a0d4600ab72f0d6b267e","src\/Models\/LightspeedRetailResource.php":"db317c4a52377a0b866ec89b38a9d9da","src\/Models\/ApiToken.php":"c887efa1d1ad1c326cb9347ebbf6eff1","src\/LightspeedRetailApiServiceProvider.php":"a574b45dffc99069e530e6f67f396082","src\/LightspeedRetailApi.php":"838334c91e74a59599635f7d9d4ae4c4","src\/Facades\/LightspeedRetailApi.php":"f41d2efd10fb66074f2f44de7c8e7b01","src\/Traits\/HasLightspeedRetailResources.php":"6ea826caae2eda6d63a63ecd25d8d188","src\/Traits\/QueryBuilder.php":"274c265ee9054f7536f7ba27139884d2","src\/Traits\/RetailResources.php":"fbf6b52348377cb0cce4f2984110029f","src\/Console\/Commands\/VerifyApiConnectionCommand.php":"64a49fe8835fe34cd9c03202293925d6","src\/Console\/Commands\/GenerateAuthenticationUrlCommand.php":"8d352858337963a7ece67a91fdd023cb","src\/Exceptions\/MissingLightspeedResourceException.php":"108edcafb4771e6f40821797cc260720","src\/Exceptions\/LightspeedRetailException.php":"a3cb67cc8fd5dfae2a7225f9df909b8c","src\/Exceptions\/IncorrectModelConfigurationException.php":"b4de6f62f6e26945c2469624cae159e4","src\/Exceptions\/DuplicateResourceException.php":"8076d0af87afde30f566ee40c77e1002","src\/Exceptions\/WaitingForSynchronisationException.php":"b14988adbf4b339d5c2bba0e45cc6d76","src\/Exceptions\/AuthenticationException.php":"9f33b80f486f0430c2246f3cdcd470fe","src\/Http\/Controllers\/SaveAccessTokenController.php":"d43a70b5e4246e5081b50fc084b32ad9","src\/Resource.php":"ff289351a1a289dbbc8de113c0f21a2a","src\/Actions\/GenerateRetailPayloadAction.php":"93eaaa027b7a586c63e10b5cda82668b","src\/Actions\/SaveLightspeedRetailResourceAction.php":"884115c83dedcc5fc6f27de0cbaea264","src\/Actions\/DispatchLightspeedRetailResourceAction.php":"2cf053306a01ac64fd20fde07cf724ca","src\/Actions\/RemoveResourceFromLightspeedRetailAction.php":"161b8081b7bcca676d534258c6832b8e","src\/Services\/Lightspeed\/ResourceManufacturer.php":"a5ec65218d0162dd4e7a78e071d37040","src\/Services\/Lightspeed\/ResourceImage.php":"8abf66fc267f9023bba1bc2af12cdeec","src\/Services\/Lightspeed\/ResourceAccount.php":"ab6ea568d93dcf4454210a33158ca552","src\/Services\/Lightspeed\/ResourceSale.php":"4b218338038f3f287cd11226e272f726","src\/Services\/Lightspeed\/ResourceCustomer.php":"be314a66626c215ad3e059518a81d1d0","src\/Services\/Lightspeed\/ResourceVendor.php":"f9998cabd45498adbf4c8047431a32c3","src\/Services\/Lightspeed\/ResourceCategory.php":"410ce3d3405e45d4c33a1a1d58216da0","src\/Services\/Lightspeed\/ResourceItem.php":"8f9c0c0707461ad9d40f5484612991e9","src\/Services\/ApiClient.php":"84cc8e0b56737a28e7580d027f82f8da","src\/Events\/ResourceSendEvent.php":"c381cdc55bb013a5861669015aac691b","src\/Scope.php":"935d170d40bf7c19761e7561fa18b52e","tests\/TestCase.php":"4b54880a083676440aaeb071629a9601"}} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ac16ee1..8cb41d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v1.1.0] - 2024-10-21 +### Changed +- Upgrade pagination to v3 for `getAll()` + ## [v1.0.0] - 2024-10-20 ### Fixed - Load multiple relations @@ -26,7 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Everything. Initial alpha release -[Unreleased]: https://github.com/timothydc/laravel-lightspeed-retail-api/compare/v1.0.0...HEAD +[Unreleased]: https://github.com/timothydc/laravel-lightspeed-retail-api/compare/v1.1.0...HEAD +[v1.1.0]: https://github.com/timothydc/laravel-lightspeed-retail-api/compare/v1.0.0...v1.1.0 [v1.0.0]: https://github.com/timothydc/laravel-lightspeed-retail-api/compare/v0.21-alpha...v1.0.0 [v0.21-alpha]: https://github.com/timothydc/laravel-lightspeed-retail-api/compare/v0.20-alpha...v0.21-alpha [v0.20-alpha]: https://github.com/timothydc/laravel-lightspeed-retail-api/compare/v0.19-alpha...v0.20-alpha diff --git a/src/Services/ApiClient.php b/src/Services/ApiClient.php index e7d8303..b7f943c 100644 --- a/src/Services/ApiClient.php +++ b/src/Services/ApiClient.php @@ -69,32 +69,26 @@ public function isConfigured(): bool public function getAll(string $resource = null, int $id = null, array $query = []): Collection { - if (! array_key_exists('offset', $query)) { - $query['offset'] = 0; - } - - if (! array_key_exists('limit', $query)) { - $query['limit'] = self::API_RESULT_LIMIT; - } - - $results = []; - - while (($result = $this->get($resource, $id, $query))->count() > 0) { + $results = collect(); + while (($result = $this->getWithPagination($resource, $id, $query))->count() > 0) { $result = Collection::unwrap($result); - if ($query['limit'] === 1) { - $result = [$result]; - } - - $results = array_merge($result, $results); + // merge resource results + $results = $results->merge($result[$resource]); - $query['offset'] += $query['limit']; + // prepare next page + if (isset($result['@attributes']['after']) && $result['@attributes']['after'] !== '') { + $query['after'] = $result['@attributes']['after']; + } else { + // exit while loop when there is no "next" page + break; + } } - return collect($results); + return $results; } - public function get(string $resource = null, int $id = null, array $query = []): Collection + public function get(string $resource = null, int $id = null, array $query = [], bool $withPagination = false): Collection { $responseObject = Http::withHeaders(['Accept' => 'application/json']) ->withOptions(['handler' => $this->createHandlerStack()]) @@ -109,56 +103,25 @@ public function get(string $resource = null, int $id = null, array $query = []): return collect($response['Account']); } - // fix Lightspeed unstructured way of returning an array when a multi dimensional array is expected + // fix Lightspeed unstructured way of returning an array when a multidimensional array is expected if (isset($response['@attributes']['count']) && $response['@attributes']['count'] === 1) { $response[$resource] = [$response[$resource]]; } + + // for pagination, add @attributes meta data to result + if ($withPagination) { + $response['@attributes'] = $this->extractAttributes($response); + $response[$resource] = collect($response[$resource] ?? []); + + return collect($response); + } return collect($response[$resource] ?? []); } public function getWithPagination(string $resource = null, int $id = null, array $query = []): Collection { - $responseObject = Http::withHeaders(['Accept' => 'application/json']) - ->withOptions(['handler' => $this->createHandlerStack()]) - ->get($this->getUrl($resource, $id) . $this->buildQueryString($query)); - - $this->logAction('GET ' . $this->getUrl($resource, $id), ['params' => func_get_args(), 'status' => $responseObject->status()]); - - $response = $responseObject->json(); - - // unstructured way of requesting the "Account" resource - if (! $resource) { - return collect($response['Account']); - } - - // fix Lightspeed unstructured way of returning an array when a multi dimensional array is expected - if (isset($response['@attributes']['count']) && $response['@attributes']['count'] === 1) { - $response[$resource] = [$response[$resource]]; - } - - $attributes = $response['@attributes']; - $after = ''; - $before = ''; - - if ($attributes['next'] !== '') { - $matches = []; - preg_match('/after=([^&]+)/', $attributes['next'], $matches); - $after = array_key_exists(1, $matches) ? $matches[1] : ''; - } - - if ($attributes['previous'] !== '') { - $matches = []; - preg_match('/before=([^&]+)/', $attributes['previous'], $matches); - $before = array_key_exists(1, $matches) ? $matches[1] : ''; - } - - $attributes['after'] = $after; - $attributes['before'] = $before; - $response['@attributes'] = collect($attributes); - $response[$resource] = collect($response[$resource] ?? []); - - return collect($response); + return $this->get($resource, $id, $query, true); } /** @@ -505,4 +468,33 @@ protected function refreshToken(): void Log::emergency(self::class . ' Unable to refresh token.', [$response]); } } + + protected function extractAttributes(array $data): Collection + { + if (! array_key_exists('@attributes', $data)) { + return collect(); + } + + $attributes = $data['@attributes'] ?? []; + + // extract "next" key from URL + if (array_key_exists('next', $attributes) && $attributes['next'] !== '') { + // parse URL and extract query parameters + parse_str(parse_url($attributes['next'])['query'], $queryParameters); + $after = $queryParameters['after'] ?? ''; + } + + // extract "previous" key from URL + if (array_key_exists('previous', $attributes) && $attributes['previous'] !== '') { + // parse URL and extract query parameters + parse_str(parse_url($attributes['previous'])['query'], $queryParameters); + $before = $queryParameters['before'] ?? ''; + } + + // add new keys to $attributes + $attributes['after'] = $after ?? ''; + $attributes['before'] = $before ?? ''; + + return collect($attributes); + } } diff --git a/src/Traits/QueryBuilder.php b/src/Traits/QueryBuilder.php index be4daa3..33af3f8 100644 --- a/src/Traits/QueryBuilder.php +++ b/src/Traits/QueryBuilder.php @@ -36,9 +36,9 @@ private function operatorMapping(): array private function buildQueryString(array $parameters = []) : string { - $parameters = $this->buildQueryParameters($parameters); + $queryParameters = $this->buildQueryParameters($parameters); - return $parameters ? '?' . $parameters : ''; + return $queryParameters ? '?' . $queryParameters : ''; } private function buildQueryParameters(array $parameters = []): string @@ -84,7 +84,7 @@ private function _getOperator($operator): string if (array_key_exists($operator, $this->operatorMapping())) { $parsedOperator = $this->operatorMapping()[$operator]; - if ($operator != $this->operator_equal) { + if ($operator !== $this->operator_equal) { $parsedOperator = '=' . $parsedOperator . ','; }