From 59dc7e2c56f884f9f674334903010cdd0264225d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 7 Dec 2023 15:17:38 +0100 Subject: [PATCH 01/24] Add logical cursor for long query parameters --- src/FilterGroup.php | 36 ++++++++++++++++++++ src/OrderExtractor.php | 76 ++++++++++++++++++++++++++++++++---------- 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/FilterGroup.php b/src/FilterGroup.php index 1634f83..a7ab44f 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -7,6 +7,9 @@ class FilterGroup { private array $filters = []; + private array $longFilters = []; + private int $offset = 0; + private int $lenght = 200; public function withFilter(Filter $filter): self { @@ -19,6 +22,20 @@ public function withFilter(Filter $filter): self return $this; } + public function withLongFilter(Filter $filter, int $offset = 0, int $lenght = 200): self + { + $this->longFilters[] = [ + 'field' => $filter->field, + 'value' => $filter->value, + 'condition_type' => $filter->conditionType, + ]; + + $this->offset = $offset; + $this->lenght = $lenght; + + return $this; + } + public function withFilters(Filter ...$filters): self { array_walk($filters, fn (Filter $filter) => $this->filters[] = [ @@ -30,6 +47,25 @@ public function withFilters(Filter ...$filters): self return $this; } + private function sliceLongFilter(string $value): iterable + { + $iterator = new \ArrayIterator(explode(',', $value)); + while($this->offset < iterator_count($iterator)) { + $filteredValue = array_slice(iterator_to_array($iterator), $this->offset, $this->lenght); + $this->offset += $this->lenght; + yield $filteredValue; + } + } + + public function compileLongFilters(int $groupIndex = 0) + { + return array_merge(...array_map(fn (array $item, int $key) => [ + sprintf('searchCriteria[filterGroups][%s][filters][%s][field]', $groupIndex, $key) => $item['field'], + sprintf('searchCriteria[filterGroups][%s][filters][%s][value]', $groupIndex, $key) => iterator_to_array($this->sliceLongFilter($item['value'])), + sprintf('searchCriteria[filterGroups][%s][filters][%s][conditionType]', $groupIndex, $key) => $item['condition_type'], + ], $this->longFilters, array_keys($this->longFilters))); + } + public function compileFilters(int $groupIndex = 0): array { return array_merge(...array_map(fn (array $item, int $key) => [ diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index e5cc5d6..e782f61 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -25,7 +25,7 @@ public function __construct( ) { } - private function compileQueryParameters(int $currentPage = 1): array + private function compileQueryParameters(int $currentPage = 1) { $parameters = $this->queryParameters; $parameters['searchCriteria[currentPage]'] = $currentPage; @@ -36,31 +36,71 @@ private function compileQueryParameters(int $currentPage = 1): array return array_merge($parameters, ...$filters); } - public function extract(): iterable + private function compileQueryLongParameters() { - try { - $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters(), - ); - - if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataOrderSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataOrderSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\SalesDataOrderSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\SalesDataOrderSearchResultInterface - ) { - return; + $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileLongFilters($key), $this->filters, array_keys($this->filters)); + + return array_merge(...$filters); + } + + private function generateFinalQueryParameters(array $queryParameters, array $queryLongParameters): array + { + $finalQueryParameters = []; + if (!empty($queryLongParameters)) { + foreach ($queryLongParameters as $key => $longParameter) { + if (str_contains($key, '[value]')) { + $queryParameterWithLongFilters = $queryParameters; + $searchString = str_replace('[value]', '', $key); + $queryParameterWithLongFilters = array_merge( + $queryParameterWithLongFilters, + [$searchString.'[field]' => $queryLongParameters[$searchString.'[field]']], + [$searchString.'[conditionType]' => $queryLongParameters[$searchString.'[conditionType]']] + ); + foreach ($longParameter as $parameterSlicedValue) { + $queryParameterWithLongFilters = array_merge( + $queryParameterWithLongFilters, + [$searchString.'[value]' => implode(',', $parameterSlicedValue)] + ); + $finalQueryParameters[] = $queryParameterWithLongFilters; + } + } } + } else { + $finalQueryParameters[] = $queryParameters; + } + return $finalQueryParameters; + } - yield $this->processResponse($response); + public function extract(): iterable + { + try { + $queryParameters = $this->compileQueryParameters(); + $queryLongParameters = $this->compileQueryLongParameters(); + $finalQueryParameters = $this->generateFinalQueryParameters($queryParameters, $queryLongParameters); - $currentPage = 1; - $pageCount = ceil($response->getTotalCount() / $this->pageSize); - while ($currentPage++ < $pageCount) { + foreach($finalQueryParameters as $finalQueryParameter) { $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters($currentPage), + queryParameters: $finalQueryParameter, ); + if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataOrderSearchResultInterface + && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataOrderSearchResultInterface + && !$response instanceof \Kiboko\Magento\V2_3\Model\SalesDataOrderSearchResultInterface + && !$response instanceof \Kiboko\Magento\V2_4\Model\SalesDataOrderSearchResultInterface + ) { + return; + } yield $this->processResponse($response); + + $currentPage = 1; + $pageCount = ceil($response->getTotalCount() / $this->pageSize); + while ($currentPage++ < $pageCount) { + $response = $this->client->salesOrderRepositoryV1GetListGet( + queryParameters: $this->compileQueryParameters($currentPage), + ); + + yield $this->processResponse($response); + } } } catch (\Exception $exception) { $this->logger->alert($exception->getMessage(), ['exception' => $exception]); From e7db160f116b21702afbe29a0ba0984cdb10df14 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 8 Dec 2023 15:37:39 +0100 Subject: [PATCH 02/24] fix type phpstan, manage api return's cursor with the array of parameters --- src/CategoryLookup.php | 3 ++- src/Lookup.php | 3 ++- src/OrderExtractor.php | 17 +++++++++++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index e331a75..a286ce0 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -5,6 +5,7 @@ namespace Kiboko\Component\Flow\Magento2; use Kiboko\Component\Bucket\AcceptanceResultBucket; +use Kiboko\Component\Bucket\EmptyResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; use Kiboko\Contract\Mapping\CompiledMapperInterface; use Kiboko\Contract\Pipeline\TransformerInterface; @@ -24,7 +25,7 @@ public function __construct( public function transform(): \Generator { - $line = yield; + $line = yield new EmptyResultBucket(); while (true) { if (null === $line[$this->mappingField]) { $line = yield new AcceptanceResultBucket($line); diff --git a/src/Lookup.php b/src/Lookup.php index 8b1347e..462f8e8 100644 --- a/src/Lookup.php +++ b/src/Lookup.php @@ -6,6 +6,7 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; +use Kiboko\Component\Bucket\EmptyResultBucket; use Kiboko\Contract\Mapping\CompiledMapperInterface; use Kiboko\Contract\Pipeline\TransformerInterface; use Psr\SimpleCache\CacheInterface; @@ -25,7 +26,7 @@ public function __construct( public function transform(): \Generator { - $line = yield; + $line = yield new EmptyResultBucket(); while (true) { if (null === $line[$this->mappingField]) { $line = yield new AcceptanceResultBucket($line); diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index e782f61..787d3dc 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -8,6 +8,7 @@ use Kiboko\Component\Bucket\RejectionResultBucket; use Kiboko\Contract\Bucket\ResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Psr\Http\Client\NetworkExceptionInterface; final class OrderExtractor implements ExtractorInterface { @@ -25,7 +26,7 @@ public function __construct( ) { } - private function compileQueryParameters(int $currentPage = 1) + private function compileQueryParameters(int $currentPage = 1): array { $parameters = $this->queryParameters; $parameters['searchCriteria[currentPage]'] = $currentPage; @@ -36,7 +37,7 @@ private function compileQueryParameters(int $currentPage = 1) return array_merge($parameters, ...$filters); } - private function compileQueryLongParameters() + private function compileQueryLongParameters(): array { $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileLongFilters($key), $this->filters, array_keys($this->filters)); @@ -95,15 +96,23 @@ public function extract(): iterable $currentPage = 1; $pageCount = ceil($response->getTotalCount() / $this->pageSize); while ($currentPage++ < $pageCount) { + $finalQueryParameter['searchCriteria[currentPage]'] = $currentPage; $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters($currentPage), + queryParameters: $finalQueryParameter, ); yield $this->processResponse($response); } } - } catch (\Exception $exception) { + } catch (NetworkExceptionInterface $exception) { $this->logger->alert($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket([ + 'path' => 'order', + 'method' => 'get', + 'queryParameters' => $this->generateFinalQueryParameters($this->compileQueryParameters(), $this->compileQueryLongParameters()), + ]); + } catch (\Exception $exception) { + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); } } From cb0cc3e1d824aa11980d97586269ee4aec3715cf Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 8 Dec 2023 15:38:54 +0100 Subject: [PATCH 03/24] remove IGNORE_ENV_TRUE parameter for cs-fixer --- .github/workflows/quality.yaml | 2 +- src/CategoryLookup.php | 3 +-- src/CustomerExtractor.php | 3 +-- src/Filter.php | 3 +-- src/FilterGroup.php | 4 ++-- src/InvoiceExtractor.php | 3 +-- src/Lookup.php | 5 ++--- src/OrderExtractor.php | 6 +++--- src/ProductExtractor.php | 3 +-- 9 files changed, 13 insertions(+), 19 deletions(-) diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 8e70ec6..2176323 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -12,7 +12,7 @@ jobs: run: | wget -q https://cs.symfony.com/download/php-cs-fixer-v3.phar -O php-cs-fixer chmod a+x php-cs-fixer - PHP_CS_FIXER_IGNORE_ENV=true ./php-cs-fixer fix src --dry-run + ./php-cs-fixer fix src --dry-run phpstan: runs-on: ubuntu-latest diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index a286ce0..1ca2316 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -20,8 +20,7 @@ public function __construct( private string $cacheKey, private CompiledMapperInterface $mapper, private string $mappingField, - ) { - } + ) {} public function transform(): \Generator { diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index c7e4324..8d126b9 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -22,8 +22,7 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) { - } + ) {} private function compileQueryParameters(int $currentPage = 1): array { diff --git a/src/Filter.php b/src/Filter.php index 6312fac..84b2bba 100644 --- a/src/Filter.php +++ b/src/Filter.php @@ -10,6 +10,5 @@ public function __construct( public string $field, public string $conditionType, public mixed $value, - ) { - } + ) {} } diff --git a/src/FilterGroup.php b/src/FilterGroup.php index a7ab44f..d342d4f 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -50,8 +50,8 @@ public function withFilters(Filter ...$filters): self private function sliceLongFilter(string $value): iterable { $iterator = new \ArrayIterator(explode(',', $value)); - while($this->offset < iterator_count($iterator)) { - $filteredValue = array_slice(iterator_to_array($iterator), $this->offset, $this->lenght); + while ($this->offset < iterator_count($iterator)) { + $filteredValue = \array_slice(iterator_to_array($iterator), $this->offset, $this->lenght); $this->offset += $this->lenght; yield $filteredValue; } diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index bbbe364..79f8389 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -22,8 +22,7 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) { - } + ) {} private function compileQueryParameters(int $currentPage = 1): array { diff --git a/src/Lookup.php b/src/Lookup.php index 462f8e8..1c90dc7 100644 --- a/src/Lookup.php +++ b/src/Lookup.php @@ -5,8 +5,8 @@ namespace Kiboko\Component\Flow\Magento2; use Kiboko\Component\Bucket\AcceptanceResultBucket; -use Kiboko\Component\Bucket\RejectionResultBucket; use Kiboko\Component\Bucket\EmptyResultBucket; +use Kiboko\Component\Bucket\RejectionResultBucket; use Kiboko\Contract\Mapping\CompiledMapperInterface; use Kiboko\Contract\Pipeline\TransformerInterface; use Psr\SimpleCache\CacheInterface; @@ -21,8 +21,7 @@ public function __construct( private CompiledMapperInterface $mapper, private string $mappingField, private string $attributeCode, - ) { - } + ) {} public function transform(): \Generator { diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 787d3dc..0254aca 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -23,8 +23,7 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) { - } + ) {} private function compileQueryParameters(int $currentPage = 1): array { @@ -69,6 +68,7 @@ private function generateFinalQueryParameters(array $queryParameters, array $que } else { $finalQueryParameters[] = $queryParameters; } + return $finalQueryParameters; } @@ -79,7 +79,7 @@ public function extract(): iterable $queryLongParameters = $this->compileQueryLongParameters(); $finalQueryParameters = $this->generateFinalQueryParameters($queryParameters, $queryLongParameters); - foreach($finalQueryParameters as $finalQueryParameter) { + foreach ($finalQueryParameters as $finalQueryParameter) { $response = $this->client->salesOrderRepositoryV1GetListGet( queryParameters: $finalQueryParameter, ); diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index e0b0665..3f2106d 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -22,8 +22,7 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) { - } + ) {} private function compileQueryParameters(int $currentPage = 1): array { From 2b2443f8cf97a9b24a2a8ea89c32d9c515a43c61 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 10:51:46 +0100 Subject: [PATCH 04/24] Ignore a rule from phpstan --- phpstan.neon | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 phpstan.neon diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..6d8dd44 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,3 @@ +parameters: + level: 4 + treatPhpDocTypesAsCertain: false From a3cae9711c4e7d25799b43dd0f8e1302ea479a63 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 11:06:04 +0100 Subject: [PATCH 05/24] fix from cs fixer --- src/CategoryLookup.php | 3 ++- src/CustomerExtractor.php | 3 ++- src/Filter.php | 3 ++- src/InvoiceExtractor.php | 3 ++- src/Lookup.php | 3 ++- src/OrderExtractor.php | 3 ++- src/ProductExtractor.php | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 1ca2316..a286ce0 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -20,7 +20,8 @@ public function __construct( private string $cacheKey, private CompiledMapperInterface $mapper, private string $mappingField, - ) {} + ) { + } public function transform(): \Generator { diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index 8d126b9..c7e4324 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -22,7 +22,8 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) {} + ) { + } private function compileQueryParameters(int $currentPage = 1): array { diff --git a/src/Filter.php b/src/Filter.php index 84b2bba..6312fac 100644 --- a/src/Filter.php +++ b/src/Filter.php @@ -10,5 +10,6 @@ public function __construct( public string $field, public string $conditionType, public mixed $value, - ) {} + ) { + } } diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index 79f8389..bbbe364 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -22,7 +22,8 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) {} + ) { + } private function compileQueryParameters(int $currentPage = 1): array { diff --git a/src/Lookup.php b/src/Lookup.php index 1c90dc7..991212f 100644 --- a/src/Lookup.php +++ b/src/Lookup.php @@ -21,7 +21,8 @@ public function __construct( private CompiledMapperInterface $mapper, private string $mappingField, private string $attributeCode, - ) {} + ) { + } public function transform(): \Generator { diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 0254aca..4fcb5aa 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -23,7 +23,8 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) {} + ) { + } private function compileQueryParameters(int $currentPage = 1): array { diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index 3f2106d..e0b0665 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -22,7 +22,8 @@ public function __construct( private readonly int $pageSize = 100, /** @var FilterGroup[] $filters */ private readonly array $filters = [], - ) {} + ) { + } private function compileQueryParameters(int $currentPage = 1): array { From 49487d40413b0d4245e67a20a4d321b612b73e09 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 11:32:11 +0100 Subject: [PATCH 06/24] change phpstan config for level 5 --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 6d8dd44..bf3085e 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,3 @@ parameters: - level: 4 + level: 5 treatPhpDocTypesAsCertain: false From 1deb04783516841080ca35a53fb29141b9c8bc7c Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 11:42:00 +0100 Subject: [PATCH 07/24] Change a method name --- src/FilterGroup.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FilterGroup.php b/src/FilterGroup.php index d342d4f..dc12525 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -47,7 +47,7 @@ public function withFilters(Filter ...$filters): self return $this; } - private function sliceLongFilter(string $value): iterable + private function sliceFilter(string $value): iterable { $iterator = new \ArrayIterator(explode(',', $value)); while ($this->offset < iterator_count($iterator)) { @@ -61,7 +61,7 @@ public function compileLongFilters(int $groupIndex = 0) { return array_merge(...array_map(fn (array $item, int $key) => [ sprintf('searchCriteria[filterGroups][%s][filters][%s][field]', $groupIndex, $key) => $item['field'], - sprintf('searchCriteria[filterGroups][%s][filters][%s][value]', $groupIndex, $key) => iterator_to_array($this->sliceLongFilter($item['value'])), + sprintf('searchCriteria[filterGroups][%s][filters][%s][value]', $groupIndex, $key) => iterator_to_array($this->sliceFilter($item['value'])), sprintf('searchCriteria[filterGroups][%s][filters][%s][conditionType]', $groupIndex, $key) => $item['condition_type'], ], $this->longFilters, array_keys($this->longFilters))); } From 7739f089b1d069454950a417cd9f8a7bb5d4412d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Thu, 18 Jan 2024 10:51:15 +0100 Subject: [PATCH 08/24] Refactored the way filters are handled --- src/CustomerExtractor.php | 81 +++++++++----- src/Filter.php | 15 --- src/Filter/ArrayFilter.php | 30 ++++++ src/Filter/FilterInterface.php | 12 +++ src/Filter/ScalarFilter.php | 23 ++++ src/FilterGroup.php | 89 +++++++--------- src/QueryParameters.php | 41 +++++++ tests/CustomerExtractorTest.php | 17 +-- tests/Filter/ArrayFilterTest.php | 46 ++++++++ tests/Filter/ScalarFilterTest.php | 24 +++++ tests/FilterGroupTest.php | 162 ++++++++++++++++++++++++++++ tests/QueryParametersTest.php | 172 ++++++++++++++++++++++++++++++ 12 files changed, 614 insertions(+), 98 deletions(-) delete mode 100644 src/Filter.php create mode 100644 src/Filter/ArrayFilter.php create mode 100644 src/Filter/FilterInterface.php create mode 100644 src/Filter/ScalarFilter.php create mode 100644 src/QueryParameters.php create mode 100644 tests/Filter/ArrayFilterTest.php create mode 100644 tests/Filter/ScalarFilterTest.php create mode 100644 tests/FilterGroupTest.php create mode 100644 tests/QueryParametersTest.php diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index c7e4324..ddfa164 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -6,64 +6,93 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; +use Kiboko\Component\Flow\Magento2\Filter\FilterInterface; use Kiboko\Contract\Bucket\ResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Psr\Http\Client\NetworkExceptionInterface; final class CustomerExtractor implements ExtractorInterface { - private array $queryParameters = [ - 'searchCriteria[currentPage]' => 1, - 'searchCriteria[pageSize]' => 100, - ]; - public function __construct( private readonly \Psr\Log\LoggerInterface $logger, private readonly \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private readonly QueryParameters $queryParameters, private readonly int $pageSize = 100, - /** @var FilterGroup[] $filters */ - private readonly array $filters = [], ) { } - private function compileQueryParameters(int $currentPage = 1): array + private function walkFilterVariants(int $currentPage = 1): \Traversable { - $parameters = $this->queryParameters; - $parameters['searchCriteria[currentPage]'] = $currentPage; - $parameters['searchCriteria[pageSize]'] = $this->pageSize; + $parameters = [ + ...$this->queryParameters, + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $this->pageSize, + ], + ]; $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileFilters($key), $this->filters, array_keys($this->filters)); return array_merge($parameters, ...$filters); } + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array + { + return [ + ...$parameters, + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $this->pageSize, + ], + ]; + } + public function extract(): iterable { try { - $response = $this->client->customerCustomerRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters(), - ); + foreach ($this->queryParameters->walkVariants([]) as $parameters) { + $currentPage = 1; + $response = $this->client->customerCustomerRepositoryV1GetListGet( + queryParameters: $parameters, + ); + + if (!$response instanceof \Kiboko\Magento\V2_1\Model\CustomerDataCustomerSearchResultsInterface + && !$response instanceof \Kiboko\Magento\V2_2\Model\CustomerDataCustomerSearchResultsInterface + && !$response instanceof \Kiboko\Magento\V2_3\Model\CustomerDataCustomerSearchResultsInterface + && !$response instanceof \Kiboko\Magento\V2_4\Model\CustomerDataCustomerSearchResultsInterface + ) { + return; + } - if (!$response instanceof \Kiboko\Magento\V2_1\Model\CustomerDataCustomerSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\CustomerDataCustomerSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\CustomerDataCustomerSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\CustomerDataCustomerSearchResultsInterface - ) { - return; + yield $this->processResponse($response); } - yield $this->processResponse($response); - $currentPage = 1; - $pageCount = ceil($response->getTotalCount() / $this->pageSize); while ($currentPage++ < $pageCount) { $response = $this->client->customerCustomerRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters($currentPage), + queryParameters: $this->walkFilterVariants($currentPage), ); yield $this->processResponse($response); } + } catch (NetworkExceptionInterface $exception) { + $this->logger->alert( + $exception->getMessage(), + [ + 'exception' => $exception, + 'context' => [ + 'path' => 'customer', + 'method' => 'get', + 'queryParameters' => $this->walkFilterVariants(), + ], + ], + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); } catch (\Exception $exception) { - $this->logger->alert($exception->getMessage(), ['exception' => $exception]); + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); } } @@ -74,7 +103,7 @@ private function processResponse($response): ResultBucketInterface || $response instanceof \Kiboko\Magento\V2_3\Model\ErrorResponse || $response instanceof \Kiboko\Magento\V2_4\Model\ErrorResponse ) { - return new RejectionResultBucket($response); + return new RejectionResultBucket($response->getMessage(), null, $response); } return new AcceptanceResultBucket(...$response->getItems()); diff --git a/src/Filter.php b/src/Filter.php deleted file mode 100644 index 6312fac..0000000 --- a/src/Filter.php +++ /dev/null @@ -1,15 +0,0 @@ - + */ + public function getIterator(): \Traversable + { + $length = count($this->value); + for ($offset = 0; $offset < $length; $offset += $this->threshold) { + yield [ + 'field' => $this->field, + 'value' => implode(',', array_slice($this->value, $offset, $this->threshold, false)), + 'conditionType' => $this->conditionType, + ]; + } + } +} diff --git a/src/Filter/FilterInterface.php b/src/Filter/FilterInterface.php new file mode 100644 index 0000000..e038ea8 --- /dev/null +++ b/src/Filter/FilterInterface.php @@ -0,0 +1,12 @@ + + */ +interface FilterInterface extends \Traversable +{ +} diff --git a/src/Filter/ScalarFilter.php b/src/Filter/ScalarFilter.php new file mode 100644 index 0000000..1cf5e5c --- /dev/null +++ b/src/Filter/ScalarFilter.php @@ -0,0 +1,23 @@ + $this->field, + 'value' => $this->value, + 'conditionType' => $this->conditionType, + ]; + } +} diff --git a/src/FilterGroup.php b/src/FilterGroup.php index dc12525..e2b92ce 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -4,84 +4,71 @@ namespace Kiboko\Component\Flow\Magento2; +use Kiboko\Component\Flow\Magento2\Filter\FilterInterface; +use Kiboko\Component\Flow\Magento2\Filter\ScalarFilter; + class FilterGroup { private array $filters = []; - private array $longFilters = []; - private int $offset = 0; - private int $lenght = 200; - public function withFilter(Filter $filter): self + public function withFilter(FilterInterface $filter): self { - $this->filters[] = [ - 'field' => $filter->field, - 'value' => $filter->value, - 'condition_type' => $filter->conditionType, - ]; + $this->filters[] = $filter; return $this; } - public function withLongFilter(Filter $filter, int $offset = 0, int $lenght = 200): self + /** + * @param array $parameters + * @param int $groupIndex + * @return \Traversable + */ + public function walkFilters(array $parameters, int $groupIndex = 0): \Traversable { - $this->longFilters[] = [ - 'field' => $filter->field, - 'value' => $filter->value, - 'condition_type' => $filter->conditionType, - ]; - - $this->offset = $offset; - $this->lenght = $lenght; + if (count($this->filters) < 1) { + return; + } - return $this; + yield from $this->buildFilters($parameters, $groupIndex, 1, ...$this->filters); } - public function withFilters(Filter ...$filters): self + private function buildFilters(array $parameters, int $groupIndex, int $filterIndex, FilterInterface $first, FilterInterface ...$next): \Traversable { - array_walk($filters, fn (Filter $filter) => $this->filters[] = [ - 'field' => $filter->field, - 'value' => $filter->value, - 'condition_type' => $filter->conditionType, - ]); + foreach ($first as $current) { + $childParameters = [ + ...$parameters, + ...[ + sprintf('searchCriteria[filterGroups][%s][filters][%s][field]', $groupIndex, $filterIndex) => $current['field'], + sprintf('searchCriteria[filterGroups][%s][filters][%s][value]', $groupIndex, $filterIndex) => $current['value'], + sprintf('searchCriteria[filterGroups][%s][filters][%s][conditionType]', $groupIndex, $filterIndex) => $current['conditionType'], + ] + ]; - return $this; - } - - private function sliceFilter(string $value): iterable - { - $iterator = new \ArrayIterator(explode(',', $value)); - while ($this->offset < iterator_count($iterator)) { - $filteredValue = \array_slice(iterator_to_array($iterator), $this->offset, $this->lenght); - $this->offset += $this->lenght; - yield $filteredValue; + if (count($next) >= 1) { + yield from $this->buildFilters($childParameters, $groupIndex, $filterIndex + 1, ...$next); + } else { + yield $childParameters; + } } } - public function compileLongFilters(int $groupIndex = 0) + public function greaterThan(string $field, int|float|string|\DateTimeInterface $value): self { - return array_merge(...array_map(fn (array $item, int $key) => [ - sprintf('searchCriteria[filterGroups][%s][filters][%s][field]', $groupIndex, $key) => $item['field'], - sprintf('searchCriteria[filterGroups][%s][filters][%s][value]', $groupIndex, $key) => iterator_to_array($this->sliceFilter($item['value'])), - sprintf('searchCriteria[filterGroups][%s][filters][%s][conditionType]', $groupIndex, $key) => $item['condition_type'], - ], $this->longFilters, array_keys($this->longFilters))); + return $this->withFilter(new ScalarFilter($field, 'gt', $value)); } - public function compileFilters(int $groupIndex = 0): array + public function lowerThan(string $field, int|float|string|\DateTimeInterface $value): self { - return array_merge(...array_map(fn (array $item, int $key) => [ - sprintf('searchCriteria[filterGroups][%s][filters][%s][field]', $groupIndex, $key) => $item['field'], - sprintf('searchCriteria[filterGroups][%s][filters][%s][value]', $groupIndex, $key) => $item['value'], - sprintf('searchCriteria[filterGroups][%s][filters][%s][conditionType]', $groupIndex, $key) => $item['condition_type'], - ], $this->filters, array_keys($this->filters))); + return $this->withFilter(new ScalarFilter($field, 'lt', $value)); } - public function greaterThan(string $field, mixed $value): self + public function greaterThanOrEqual(string $field, int|float|string|\DateTimeInterface $value): self { - return $this->withFilter(new Filter($field, 'gt', $value)); + return $this->withFilter(new ScalarFilter($field, 'gteq', $value)); } - public function greaterThanEqual(string $field, mixed $value): self + public function lowerThanOrEqual(string $field, int|float|string|\DateTimeInterface $value): self { - return $this->withFilter(new Filter($field, 'gteq', $value)); + return $this->withFilter(new ScalarFilter($field, 'lteq', $value)); } } diff --git a/src/QueryParameters.php b/src/QueryParameters.php new file mode 100644 index 0000000..4508485 --- /dev/null +++ b/src/QueryParameters.php @@ -0,0 +1,41 @@ + */ + private array $groups = []; + + public function withGroup(FilterGroup $group): self + { + $this->groups[] = $group; + + return $this; + } + + /** + * @return \Traversable + */ + public function walkVariants(array $parameters): \Traversable + { + if (count($this->groups) < 1) { + return; + } + + yield from $this->buildFilters($parameters, 0, ...$this->groups); + } + + private function buildFilters(array $parameters, int $groupIndex, FilterGroup $first, FilterGroup ...$next): \Traversable + { + foreach ($first->walkFilters($parameters, $groupIndex) as $current) { + if (count($next) >= 1) { + yield from $this->buildFilters($current, $groupIndex + 1, ...$next); + } else { + yield $current; + } + } + } +} diff --git a/tests/CustomerExtractorTest.php b/tests/CustomerExtractorTest.php index 8bc25c0..86c6f7c 100644 --- a/tests/CustomerExtractorTest.php +++ b/tests/CustomerExtractorTest.php @@ -5,8 +5,9 @@ namespace Tests\Kiboko\Magento\V2\Extractor; use Kiboko\Component\Flow\Magento2\CustomerExtractor; -use Kiboko\Component\Flow\Magento2\Filter; +use Kiboko\Component\Flow\Magento2\Filter\ScalarFilter; use Kiboko\Component\Flow\Magento2\FilterGroup; +use Kiboko\Component\Flow\Magento2\QueryParameters; use Kiboko\Component\PHPUnitExtension\Assert\ExtractorAssertTrait; use Kiboko\Component\PHPUnitExtension\PipelineRunner; use Kiboko\Contract\Pipeline\PipelineRunnerInterface; @@ -48,11 +49,15 @@ public function testIsSuccessful(): void $extractor = new CustomerExtractor( new NullLogger(), $client, - 1, - [ - (new FilterGroup())->withFilter(new Filter('updated_at', 'eq', '2022-09-05')), - (new FilterGroup())->withFilter(new Filter('active', 'eq', true)), - ] + (new QueryParameters()) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('updated_at', 'eq', '2022-09-05')), + ) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('active', 'eq', true)), + ) ); $this->assertExtractorExtractsExactly( diff --git a/tests/Filter/ArrayFilterTest.php b/tests/Filter/ArrayFilterTest.php new file mode 100644 index 0000000..40f8a04 --- /dev/null +++ b/tests/Filter/ArrayFilterTest.php @@ -0,0 +1,46 @@ +assertCount(1, iterator_to_array($filter->getIterator(), false)); + $this->assertContains([ + 'field' => 'foo', + 'value' => '1,2,3,4', + 'conditionType' => 'in', + ], $filter); + } + + #[Test] + public function shouldProduceSeveralVariants(): void + { + $filter = new ArrayFilter('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4); + $this->assertCount(3, iterator_to_array($filter->getIterator(), false)); + $this->assertContains([ + 'field' => 'foo', + 'value' => '1,2,3,4', + 'conditionType' => 'in', + ], $filter); + $this->assertContains([ + 'field' => 'foo', + 'value' => '5,6,7,8', + 'conditionType' => 'in', + ], $filter); + $this->assertContains([ + 'field' => 'foo', + 'value' => '9,10,11', + 'conditionType' => 'in', + ], $filter); + } +} diff --git a/tests/Filter/ScalarFilterTest.php b/tests/Filter/ScalarFilterTest.php new file mode 100644 index 0000000..8876cff --- /dev/null +++ b/tests/Filter/ScalarFilterTest.php @@ -0,0 +1,24 @@ +assertCount(1, iterator_to_array($filter->getIterator(), false)); + $this->assertContains([ + 'field' => 'foo', + 'value' => 4, + 'conditionType' => 'eq', + ], $filter); + } +} diff --git a/tests/FilterGroupTest.php b/tests/FilterGroupTest.php new file mode 100644 index 0000000..b6c9c24 --- /dev/null +++ b/tests/FilterGroupTest.php @@ -0,0 +1,162 @@ +withFilter( + new ArrayFilter('foo', 'in', [1, 2, 3, 4], 4), + ); + $this->assertCount(1, iterator_to_array($group->walkFilters([]), false)); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $group->walkFilters([])); + } + + #[Test] + public function shouldProduceSeveralVariants(): void + { + $group = new FilterGroup(); + $group->withFilter( + new ArrayFilter('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4), + ); + $this->assertCount(3, iterator_to_array($group->walkFilters([]), false)); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $group->walkFilters([])); + } + + #[Test] + public function shouldProduceDemultipliedVariants(): void + { + $group = new FilterGroup(); + $group->withFilter( + new ArrayFilter('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4), + ); + $group->withFilter( + new ArrayFilter('bar', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 4), + ); + $this->assertCount(12, iterator_to_array($group->walkFilters([]), false)); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '9,10,11,12', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '9,10,11,12', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '9,10,11,12', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '13,14,15', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '13,14,15', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][0][filters][2][field]' => 'bar', + 'searchCriteria[filterGroups][0][filters][2][value]' => '13,14,15', + 'searchCriteria[filterGroups][0][filters][2][conditionType]' => 'in', + ], $group->walkFilters([])); + } +} diff --git a/tests/QueryParametersTest.php b/tests/QueryParametersTest.php new file mode 100644 index 0000000..0cad45a --- /dev/null +++ b/tests/QueryParametersTest.php @@ -0,0 +1,172 @@ +withGroup((new FilterGroup()) + ->withFilter( + new ArrayFilter('foo', 'in', [1, 2, 3, 4], 4), + ) + ); + + $this->assertCount(1, iterator_to_array($queryParameters->walkVariants([]), false)); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + } + + #[Test] + public function shouldProduceSeveralVariants(): void + { + $queryParameters = (new QueryParameters()) + ->withGroup((new FilterGroup()) + ->withFilter( + new ArrayFilter('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4), + ) + ); + $this->assertCount(3, iterator_to_array($queryParameters->walkVariants([]), false)); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + } + + #[Test] + public function shouldProduceDemultipliedVariants(): void + { + $queryParameters = (new QueryParameters()) + ->withGroup((new FilterGroup()) + ->withFilter( + new ArrayFilter('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4), + ) + ) + ->withGroup((new FilterGroup()) + ->withFilter( + new ArrayFilter('bar', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 4), + ) + ); + $this->assertCount(12, iterator_to_array($queryParameters->walkVariants([]), false)); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '9,10,11,12', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '9,10,11,12', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '9,10,11,12', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '13,14,15', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '13,14,15', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + $this->assertContains([ + 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', + 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', + 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', + 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', + 'searchCriteria[filterGroups][1][filters][1][value]' => '13,14,15', + 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', + ], $queryParameters->walkVariants([])); + } +} From fa3110fe87a8b2e7b4001224ff630e32fdb1f0b2 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 18 Jan 2024 09:52:22 +0000 Subject: [PATCH 09/24] [rector] Rector fixes --- src/CustomerExtractor.php | 12 +++++++----- src/Filter/ArrayFilter.php | 2 +- src/FilterGroup.php | 2 -- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index ddfa164..223a80e 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -11,13 +11,13 @@ use Kiboko\Contract\Pipeline\ExtractorInterface; use Psr\Http\Client\NetworkExceptionInterface; -final class CustomerExtractor implements ExtractorInterface +final readonly class CustomerExtractor implements ExtractorInterface { public function __construct( - private readonly \Psr\Log\LoggerInterface $logger, - private readonly \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, - private readonly QueryParameters $queryParameters, - private readonly int $pageSize = 100, + private \Psr\Log\LoggerInterface $logger, + private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private QueryParameters $queryParameters, + private int $pageSize = 100, ) { } @@ -49,6 +49,8 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS public function extract(): iterable { + $currentPage = null; + $pageCount = null; try { foreach ($this->queryParameters->walkVariants([]) as $parameters) { $currentPage = 1; diff --git a/src/Filter/ArrayFilter.php b/src/Filter/ArrayFilter.php index 521019f..e623bfd 100644 --- a/src/Filter/ArrayFilter.php +++ b/src/Filter/ArrayFilter.php @@ -10,7 +10,7 @@ public function __construct( public string $field, public string $conditionType, public array $value, - private int $threshold = 200 + private readonly int $threshold = 200 ) {} /** diff --git a/src/FilterGroup.php b/src/FilterGroup.php index e2b92ce..920b208 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -19,8 +19,6 @@ public function withFilter(FilterInterface $filter): self } /** - * @param array $parameters - * @param int $groupIndex * @return \Traversable */ public function walkFilters(array $parameters, int $groupIndex = 0): \Traversable From 8557f04db627e9e2ac7c0a7720b994f57624d63c Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 19 Jan 2024 14:53:14 +0100 Subject: [PATCH 10/24] refacto extractors, add withGroups and withFilters methods --- src/CustomerExtractor.php | 14 ++--- src/FilterGroup.php | 9 +++ src/InvoiceExtractor.php | 88 ++++++++++++++++++---------- src/OrderExtractor.php | 120 +++++++++++++++----------------------- src/ProductExtractor.php | 88 ++++++++++++++++++---------- src/QueryParameters.php | 9 +++ 6 files changed, 185 insertions(+), 143 deletions(-) diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index 223a80e..48f1d3a 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -23,17 +23,13 @@ public function __construct( private function walkFilterVariants(int $currentPage = 1): \Traversable { - $parameters = [ - ...$this->queryParameters, + yield from [ + ...$this->queryParameters->walkVariants([]), ...[ 'searchCriteria[currentPage]' => $currentPage, 'searchCriteria[pageSize]' => $this->pageSize, ], ]; - - $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileFilters($key), $this->filters, array_keys($this->filters)); - - return array_merge($parameters, ...$filters); } private function applyPagination(array $parameters, int $currentPage, int $pageSize): array @@ -42,7 +38,7 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS ...$parameters, ...[ 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $this->pageSize, + 'searchCriteria[pageSize]' => $pageSize, ], ]; } @@ -55,7 +51,7 @@ public function extract(): iterable foreach ($this->queryParameters->walkVariants([]) as $parameters) { $currentPage = 1; $response = $this->client->customerCustomerRepositoryV1GetListGet( - queryParameters: $parameters, + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), ); if (!$response instanceof \Kiboko\Magento\V2_1\Model\CustomerDataCustomerSearchResultsInterface @@ -72,7 +68,7 @@ public function extract(): iterable while ($currentPage++ < $pageCount) { $response = $this->client->customerCustomerRepositoryV1GetListGet( - queryParameters: $this->walkFilterVariants($currentPage), + queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), ); yield $this->processResponse($response); diff --git a/src/FilterGroup.php b/src/FilterGroup.php index 920b208..b41c06f 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -18,6 +18,15 @@ public function withFilter(FilterInterface $filter): self return $this; } + public function withFilters(FilterInterface ...$filter): self + { + foreach ($filter as $item) { + $this->filters[] = $item; + } + + return $this; + } + /** * @return \Traversable */ diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index bbbe364..027da29 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -8,62 +8,88 @@ use Kiboko\Component\Bucket\RejectionResultBucket; use Kiboko\Contract\Bucket\ResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Psr\Http\Client\NetworkExceptionInterface; final class InvoiceExtractor implements ExtractorInterface { - private array $queryParameters = [ - 'searchCriteria[currentPage]' => 1, - 'searchCriteria[pageSize]' => 100, - ]; - public function __construct( - private readonly \Psr\Log\LoggerInterface $logger, - private readonly \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, - private readonly int $pageSize = 100, - /** @var FilterGroup[] $filters */ - private readonly array $filters = [], + private \Psr\Log\LoggerInterface $logger, + private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private QueryParameters $queryParameters, + private int $pageSize = 100, ) { } - private function compileQueryParameters(int $currentPage = 1): array + private function walkFilterVariants(int $currentPage = 1): \Traversable { - $parameters = $this->queryParameters; - $parameters['searchCriteria[currentPage]'] = $currentPage; - $parameters['searchCriteria[pageSize]'] = $this->pageSize; - - $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileFilters($key), $this->filters, array_keys($this->filters)); + yield from [ + ...$this->queryParameters->walkVariants([]), + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $this->pageSize, + ], + ]; + } - return array_merge($parameters, ...$filters); + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array + { + return [ + ...$parameters, + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $pageSize, + ], + ]; } public function extract(): iterable { + $currentPage = null; + $pageCount = null; try { - $response = $this->client->salesInvoiceRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters(), - ); + foreach ($this->queryParameters->walkVariants([]) as $parameters) { + $currentPage = 1; + $response = $this->client->salesInvoiceRepositoryV1GetListGet( + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + ); + + if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataInvoiceSearchResultInterface + && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataInvoiceSearchResultInterface + && !$response instanceof \Kiboko\Magento\V2_3\Model\SalesDataInvoiceSearchResultInterface + && !$response instanceof \Kiboko\Magento\V2_4\Model\SalesDataInvoiceSearchResultInterface + ) { + return; + } - if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataInvoiceSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataInvoiceSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\SalesDataInvoiceSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\SalesDataInvoiceSearchResultInterface - ) { - return; + yield $this->processResponse($response); } - yield $this->processResponse($response); - $currentPage = 1; - $pageCount = ceil($response->getTotalCount() / $this->pageSize); while ($currentPage++ < $pageCount) { $response = $this->client->salesInvoiceRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters($currentPage), + queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), ); yield $this->processResponse($response); } + } catch (NetworkExceptionInterface $exception) { + $this->logger->alert( + $exception->getMessage(), + [ + 'exception' => $exception, + 'context' => [ + 'path' => 'invoice', + 'method' => 'get', + 'queryParameters' => $this->walkFilterVariants(), + ], + ], + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); } catch (\Exception $exception) { - $this->logger->alert($exception->getMessage(), ['exception' => $exception]); + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); } } diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 4fcb5aa..613da3e 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -12,78 +12,47 @@ final class OrderExtractor implements ExtractorInterface { - private array $queryParameters = [ - 'searchCriteria[currentPage]' => 1, - 'searchCriteria[pageSize]' => 100, - ]; - public function __construct( - private readonly \Psr\Log\LoggerInterface $logger, - private readonly \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, - private readonly int $pageSize = 100, - /** @var FilterGroup[] $filters */ - private readonly array $filters = [], + private \Psr\Log\LoggerInterface $logger, + private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private QueryParameters $queryParameters, + private int $pageSize = 100, ) { } - private function compileQueryParameters(int $currentPage = 1): array + private function walkFilterVariants(int $currentPage = 1): \Traversable { - $parameters = $this->queryParameters; - $parameters['searchCriteria[currentPage]'] = $currentPage; - $parameters['searchCriteria[pageSize]'] = $this->pageSize; - - $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileFilters($key), $this->filters, array_keys($this->filters)); - - return array_merge($parameters, ...$filters); + yield from [ + ...$this->queryParameters->walkVariants([]), + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $this->pageSize, + ], + ]; } - private function compileQueryLongParameters(): array + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array { - $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileLongFilters($key), $this->filters, array_keys($this->filters)); - - return array_merge(...$filters); - } - - private function generateFinalQueryParameters(array $queryParameters, array $queryLongParameters): array - { - $finalQueryParameters = []; - if (!empty($queryLongParameters)) { - foreach ($queryLongParameters as $key => $longParameter) { - if (str_contains($key, '[value]')) { - $queryParameterWithLongFilters = $queryParameters; - $searchString = str_replace('[value]', '', $key); - $queryParameterWithLongFilters = array_merge( - $queryParameterWithLongFilters, - [$searchString.'[field]' => $queryLongParameters[$searchString.'[field]']], - [$searchString.'[conditionType]' => $queryLongParameters[$searchString.'[conditionType]']] - ); - foreach ($longParameter as $parameterSlicedValue) { - $queryParameterWithLongFilters = array_merge( - $queryParameterWithLongFilters, - [$searchString.'[value]' => implode(',', $parameterSlicedValue)] - ); - $finalQueryParameters[] = $queryParameterWithLongFilters; - } - } - } - } else { - $finalQueryParameters[] = $queryParameters; - } - - return $finalQueryParameters; + return [ + ...$parameters, + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $pageSize, + ], + ]; } public function extract(): iterable { + $currentPage = null; + $pageCount = null; try { - $queryParameters = $this->compileQueryParameters(); - $queryLongParameters = $this->compileQueryLongParameters(); - $finalQueryParameters = $this->generateFinalQueryParameters($queryParameters, $queryLongParameters); - - foreach ($finalQueryParameters as $finalQueryParameter) { + foreach ($this->queryParameters->walkVariants([]) as $parameters) { + $currentPage = 1; $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: $finalQueryParameter, + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), ); + if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataOrderSearchResultInterface && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataOrderSearchResultInterface && !$response instanceof \Kiboko\Magento\V2_3\Model\SalesDataOrderSearchResultInterface @@ -93,25 +62,32 @@ public function extract(): iterable } yield $this->processResponse($response); + } - $currentPage = 1; - $pageCount = ceil($response->getTotalCount() / $this->pageSize); - while ($currentPage++ < $pageCount) { - $finalQueryParameter['searchCriteria[currentPage]'] = $currentPage; - $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: $finalQueryParameter, - ); - yield $this->processResponse($response); - } + while ($currentPage++ < $pageCount) { + $response = $this->client->salesOrderRepositoryV1GetListGet( + queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), + ); + + yield $this->processResponse($response); } } catch (NetworkExceptionInterface $exception) { - $this->logger->alert($exception->getMessage(), ['exception' => $exception]); - yield new RejectionResultBucket([ - 'path' => 'order', - 'method' => 'get', - 'queryParameters' => $this->generateFinalQueryParameters($this->compileQueryParameters(), $this->compileQueryLongParameters()), - ]); + $this->logger->alert( + $exception->getMessage(), + [ + 'exception' => $exception, + 'context' => [ + 'path' => 'order', + 'method' => 'get', + 'queryParameters' => $this->walkFilterVariants(), + ], + ], + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); } catch (\Exception $exception) { $this->logger->critical($exception->getMessage(), ['exception' => $exception]); } diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index e0b0665..92235cc 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -8,62 +8,88 @@ use Kiboko\Component\Bucket\RejectionResultBucket; use Kiboko\Contract\Bucket\ResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Psr\Http\Client\NetworkExceptionInterface; final class ProductExtractor implements ExtractorInterface { - private array $queryParameters = [ - 'searchCriteria[currentPage]' => 1, - 'searchCriteria[pageSize]' => 100, - ]; - public function __construct( - private readonly \Psr\Log\LoggerInterface $logger, - private readonly \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, - private readonly int $pageSize = 100, - /** @var FilterGroup[] $filters */ - private readonly array $filters = [], + private \Psr\Log\LoggerInterface $logger, + private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private QueryParameters $queryParameters, + private int $pageSize = 100, ) { } - private function compileQueryParameters(int $currentPage = 1): array + private function walkFilterVariants(int $currentPage = 1): \Traversable { - $parameters = $this->queryParameters; - $parameters['searchCriteria[currentPage]'] = $currentPage; - $parameters['searchCriteria[pageSize]'] = $this->pageSize; - - $filters = array_map(fn (FilterGroup $item, int $key) => $item->compileFilters($key), $this->filters, array_keys($this->filters)); + yield from [ + ...$this->queryParameters->walkVariants([]), + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $this->pageSize, + ], + ]; + } - return array_merge($parameters, ...$filters); + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array + { + return [ + ...$parameters, + ...[ + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $pageSize, + ], + ]; } public function extract(): iterable { + $currentPage = null; + $pageCount = null; try { - $response = $this->client->catalogProductRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters(), - ); + foreach ($this->queryParameters->walkVariants([]) as $parameters) { + $currentPage = 1; + $response = $this->client->catalogProductRepositoryV1GetListGet( + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + ); + + if (!$response instanceof \Kiboko\Magento\V2_1\Model\CatalogDataProductSearchResultsInterface + && !$response instanceof \Kiboko\Magento\V2_2\Model\CatalogDataProductSearchResultsInterface + && !$response instanceof \Kiboko\Magento\V2_3\Model\CatalogDataProductSearchResultsInterface + && !$response instanceof \Kiboko\Magento\V2_4\Model\CatalogDataProductSearchResultsInterface + ) { + return; + } - if (!$response instanceof \Kiboko\Magento\V2_1\Model\CatalogDataProductSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\CatalogDataProductSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\CatalogDataProductSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\CatalogDataProductSearchResultsInterface - ) { - return; + yield $this->processResponse($response); } - yield $this->processResponse($response); - $currentPage = 1; - $pageCount = ceil($response->getTotalCount() / $this->pageSize); while ($currentPage++ < $pageCount) { $response = $this->client->catalogProductRepositoryV1GetListGet( - queryParameters: $this->compileQueryParameters($currentPage), + queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), ); yield $this->processResponse($response); } + } catch (NetworkExceptionInterface $exception) { + $this->logger->alert( + $exception->getMessage(), + [ + 'exception' => $exception, + 'context' => [ + 'path' => 'product', + 'method' => 'get', + 'queryParameters' => $this->walkFilterVariants(), + ], + ], + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); } catch (\Exception $exception) { - $this->logger->alert($exception->getMessage(), ['exception' => $exception]); + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); } } diff --git a/src/QueryParameters.php b/src/QueryParameters.php index 4508485..c1fd9a2 100644 --- a/src/QueryParameters.php +++ b/src/QueryParameters.php @@ -16,6 +16,15 @@ public function withGroup(FilterGroup $group): self return $this; } + public function withGroups(FilterGroup ...$group): self + { + foreach ($group as $item) { + $this->groups[] = $item; + } + + return $this; + } + /** * @return \Traversable */ From 76e3b156a21c844941a357390f0c68b086ddf01f Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 23 Jan 2024 16:22:42 +0100 Subject: [PATCH 11/24] manage page count, fix extractor to use api pagination --- src/CustomerExtractor.php | 14 +++++++------- src/InvoiceExtractor.php | 14 +++++++------- src/OrderExtractor.php | 14 +++++++------- src/ProductExtractor.php | 14 +++++++------- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index 48f1d3a..c90a673 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -53,6 +53,7 @@ public function extract(): iterable $response = $this->client->customerCustomerRepositoryV1GetListGet( queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), ); + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); if (!$response instanceof \Kiboko\Magento\V2_1\Model\CustomerDataCustomerSearchResultsInterface && !$response instanceof \Kiboko\Magento\V2_2\Model\CustomerDataCustomerSearchResultsInterface @@ -63,15 +64,14 @@ public function extract(): iterable } yield $this->processResponse($response); - } - - while ($currentPage++ < $pageCount) { - $response = $this->client->customerCustomerRepositoryV1GetListGet( - queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), - ); + while ($currentPage++ < $pageCount) { + $response = $this->client->customerCustomerRepositoryV1GetListGet( + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + ); - yield $this->processResponse($response); + yield $this->processResponse($response); + } } } catch (NetworkExceptionInterface $exception) { $this->logger->alert( diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index 027da29..7de2a22 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -52,6 +52,7 @@ public function extract(): iterable $response = $this->client->salesInvoiceRepositoryV1GetListGet( queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), ); + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataInvoiceSearchResultInterface && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataInvoiceSearchResultInterface @@ -62,15 +63,14 @@ public function extract(): iterable } yield $this->processResponse($response); - } - - while ($currentPage++ < $pageCount) { - $response = $this->client->salesInvoiceRepositoryV1GetListGet( - queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), - ); + while ($currentPage++ < $pageCount) { + $response = $this->client->salesInvoiceRepositoryV1GetListGet( + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + ); - yield $this->processResponse($response); + yield $this->processResponse($response); + } } } catch (NetworkExceptionInterface $exception) { $this->logger->alert( diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 613da3e..da41d46 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -52,6 +52,7 @@ public function extract(): iterable $response = $this->client->salesOrderRepositoryV1GetListGet( queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), ); + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataOrderSearchResultInterface && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataOrderSearchResultInterface @@ -62,15 +63,14 @@ public function extract(): iterable } yield $this->processResponse($response); - } - - while ($currentPage++ < $pageCount) { - $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), - ); + while ($currentPage++ < $pageCount) { + $response = $this->client->salesOrderRepositoryV1GetListGet( + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + ); - yield $this->processResponse($response); + yield $this->processResponse($response); + } } } catch (NetworkExceptionInterface $exception) { $this->logger->alert( diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index 92235cc..8f0728d 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -52,6 +52,7 @@ public function extract(): iterable $response = $this->client->catalogProductRepositoryV1GetListGet( queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), ); + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); if (!$response instanceof \Kiboko\Magento\V2_1\Model\CatalogDataProductSearchResultsInterface && !$response instanceof \Kiboko\Magento\V2_2\Model\CatalogDataProductSearchResultsInterface @@ -62,15 +63,14 @@ public function extract(): iterable } yield $this->processResponse($response); - } - - while ($currentPage++ < $pageCount) { - $response = $this->client->catalogProductRepositoryV1GetListGet( - queryParameters: iterator_to_array($this->walkFilterVariants($currentPage)), - ); + while ($currentPage++ < $pageCount) { + $response = $this->client->catalogProductRepositoryV1GetListGet( + queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + ); - yield $this->processResponse($response); + yield $this->processResponse($response); + } } } catch (NetworkExceptionInterface $exception) { $this->logger->alert( From 1bf121b070f421fa5e4f710dcdfc390eccedd722 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 24 Jan 2024 10:12:57 +0100 Subject: [PATCH 12/24] fix and add tests for extractors --- tests/InvoiceExtractorTest.php | 69 ++++++++++++++++++++++++++++++++++ tests/OrderExtractorTest.php | 12 ++++++ tests/ProductExtractorTest.php | 15 ++++++++ 3 files changed, 96 insertions(+) create mode 100644 tests/InvoiceExtractorTest.php diff --git a/tests/InvoiceExtractorTest.php b/tests/InvoiceExtractorTest.php new file mode 100644 index 0000000..74ddcb0 --- /dev/null +++ b/tests/InvoiceExtractorTest.php @@ -0,0 +1,69 @@ +setBaseCurrencyCode('EUR') + ->setTotalQty(1) + ->setBaseGrandTotal(59.90); + + $client = $this->createMock(Client::class); + $client + ->expects($this->once()) + ->method('salesInvoiceRepositoryV1GetListGet') + ->willReturn( + (new SalesDataInvoiceSearchResultInterface()) + ->setItems([ + $invoice, + ]) + ->setTotalCount(1) + ); + + $extractor = new InvoiceExtractor( + new NullLogger(), + $client, + (new QueryParameters()) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('updated_at', 'eq', '2022-09-05')), + ) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('active', 'eq', true)), + ) + ); + + $this->assertExtractorExtractsExactly( + [ + $invoice, + ], + $extractor + ); + } + + public function pipelineRunner(): PipelineRunnerInterface + { + return new PipelineRunner(); + } +} diff --git a/tests/OrderExtractorTest.php b/tests/OrderExtractorTest.php index bfc853f..fb545ab 100644 --- a/tests/OrderExtractorTest.php +++ b/tests/OrderExtractorTest.php @@ -4,7 +4,10 @@ namespace Tests\Kiboko\Magento\V2\Extractor; +use Kiboko\Component\Flow\Magento2\Filter\ScalarFilter; +use Kiboko\Component\Flow\Magento2\FilterGroup; use Kiboko\Component\Flow\Magento2\OrderExtractor; +use Kiboko\Component\Flow\Magento2\QueryParameters; use Kiboko\Component\PHPUnitExtension\Assert\ExtractorAssertTrait; use Kiboko\Component\PHPUnitExtension\PipelineRunner; use Kiboko\Contract\Pipeline\PipelineRunnerInterface; @@ -39,6 +42,15 @@ public function testIsSuccessful(): void $extractor = new OrderExtractor( new NullLogger(), $client, + (new QueryParameters()) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('updated_at', 'eq', '2022-09-05')), + ) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('status', 'eq', 'complete')), + ) ); $this->assertExtractorExtractsExactly( diff --git a/tests/ProductExtractorTest.php b/tests/ProductExtractorTest.php index 2b8cd25..479aa40 100644 --- a/tests/ProductExtractorTest.php +++ b/tests/ProductExtractorTest.php @@ -4,7 +4,10 @@ namespace Tests\Kiboko\Magento\V2\Extractor; +use Kiboko\Component\Flow\Magento2\Filter\ScalarFilter; +use Kiboko\Component\Flow\Magento2\FilterGroup; use Kiboko\Component\Flow\Magento2\ProductExtractor; +use Kiboko\Component\Flow\Magento2\QueryParameters; use Kiboko\Component\PHPUnitExtension\Assert\ExtractorAssertTrait; use Kiboko\Component\PHPUnitExtension\PipelineRunner; use Kiboko\Contract\Pipeline\PipelineRunnerInterface; @@ -40,6 +43,18 @@ public function testIsSuccessful(): void $extractor = new ProductExtractor( new NullLogger(), $client, + (new QueryParameters()) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('updated_at', 'eq', '2022-09-05')), + ) + ->withGroup( + (new FilterGroup()) + ->withFilter(new ScalarFilter('status', 'eq', 'complete')) + ->withFilter(new ScalarFilter('status', 'eq', 'canceled')) + ->withFilter(new ScalarFilter('status', 'eq', 'canceled')) + ->withFilter(new ScalarFilter('status', 'eq', 'in_preparation')) + ) ); $this->assertExtractorExtractsExactly( From 6d2204842fb1bde9ea25c870e59f5cbb05c4663e Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 24 Jan 2024 09:35:43 +0000 Subject: [PATCH 13/24] [rector] Rector fixes --- src/InvoiceExtractor.php | 2 +- src/OrderExtractor.php | 2 +- src/ProductExtractor.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index 7de2a22..4b02d6d 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -10,7 +10,7 @@ use Kiboko\Contract\Pipeline\ExtractorInterface; use Psr\Http\Client\NetworkExceptionInterface; -final class InvoiceExtractor implements ExtractorInterface +final readonly class InvoiceExtractor implements ExtractorInterface { public function __construct( private \Psr\Log\LoggerInterface $logger, diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index da41d46..f9fd850 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -10,7 +10,7 @@ use Kiboko\Contract\Pipeline\ExtractorInterface; use Psr\Http\Client\NetworkExceptionInterface; -final class OrderExtractor implements ExtractorInterface +final readonly class OrderExtractor implements ExtractorInterface { public function __construct( private \Psr\Log\LoggerInterface $logger, diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index 8f0728d..a8cb19e 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -10,7 +10,7 @@ use Kiboko\Contract\Pipeline\ExtractorInterface; use Psr\Http\Client\NetworkExceptionInterface; -final class ProductExtractor implements ExtractorInterface +final readonly class ProductExtractor implements ExtractorInterface { public function __construct( private \Psr\Log\LoggerInterface $logger, From 68b0dae6835acc6f2a76fd75b4dd5a4bccf3e927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Tue, 30 Jan 2024 12:37:41 +0100 Subject: [PATCH 14/24] Refactored the way filters are handled --- .github/workflows/phpstan-7.yaml | 23 --- .../{phpstan-6.yaml => phpstan-9.yaml} | 6 +- .github/workflows/quality.yaml | 35 ++-- phpstan.neon | 2 +- src/CategoryLookup.php | 152 +++++++++++--- src/CustomerExtractor.php | 190 ++++++++++++------ src/Filter/ArrayFilter.php | 20 +- src/Filter/FilterInterface.php | 2 +- src/Filter/ScalarFilter.php | 8 +- src/FilterGroup.php | 8 +- src/InvoiceExtractor.php | 182 +++++++++++------ src/Lookup.php | 69 ------- src/OrderExtractor.php | 182 +++++++++++------ src/ProductExtractor.php | 174 ++++++++++------ src/ProductOptionsLookup.php | 187 +++++++++++++++++ src/QueryParameters.php | 9 +- tests/QueryParametersTest.php | 38 ++-- 17 files changed, 862 insertions(+), 425 deletions(-) delete mode 100644 .github/workflows/phpstan-7.yaml rename .github/workflows/{phpstan-6.yaml => phpstan-9.yaml} (90%) delete mode 100644 src/Lookup.php create mode 100644 src/ProductOptionsLookup.php diff --git a/.github/workflows/phpstan-7.yaml b/.github/workflows/phpstan-7.yaml deleted file mode 100644 index e7d8d6e..0000000 --- a/.github/workflows/phpstan-7.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: PHPStan level 7 -on: push -jobs: - phpstan: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: '**/vendor' - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - uses: php-actions/composer@v6 - with: - args: --prefer-dist - php_version: '8.2' - - - name: PHPStan - uses: php-actions/phpstan@v3 - with: - path: src/ - level: 7 diff --git a/.github/workflows/phpstan-6.yaml b/.github/workflows/phpstan-9.yaml similarity index 90% rename from .github/workflows/phpstan-6.yaml rename to .github/workflows/phpstan-9.yaml index 8fe418d..c0fe8c0 100644 --- a/.github/workflows/phpstan-6.yaml +++ b/.github/workflows/phpstan-9.yaml @@ -1,7 +1,7 @@ -name: PHPStan level 6 +name: PHPStan level 8 on: push jobs: - phpstan: + phpstan-9: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -20,4 +20,4 @@ jobs: uses: php-actions/phpstan@v3 with: path: src/ - level: 6 + level: 9 diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 2176323..7e391e1 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -1,4 +1,4 @@ -name: Quality (PHPStan lvl 4) +name: Quality (PHPStan lvl 7) on: push jobs: cs-fixer: @@ -17,20 +17,19 @@ jobs: phpstan: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: '**/vendor' - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-composer- - - uses: php-actions/composer@v6 - with: - args: --prefer-dist - php_version: '8.2' - - - name: PHPStan - uses: php-actions/phpstan@v3 - with: - path: src/ - level: 4 + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: '**/vendor' + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + - uses: php-actions/composer@v6 + with: + args: --prefer-dist + php_version: '8.2' + - name: PHPStan + uses: php-actions/phpstan@v3 + with: + path: src/ + level: 7 diff --git a/phpstan.neon b/phpstan.neon index bf3085e..53b8e89 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,3 @@ parameters: - level: 5 + level: 7 treatPhpDocTypesAsCertain: false diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index a286ce0..02f6bee 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -7,55 +7,155 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\EmptyResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; +use Kiboko\Contract\Bucket\RejectionResultBucketInterface; use Kiboko\Contract\Mapping\CompiledMapperInterface; use Kiboko\Contract\Pipeline\TransformerInterface; -use Psr\SimpleCache\CacheInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Exception\GetV1CategoriesCategoryIdBadRequestException; +use Kiboko\Magento\Exception\UnexpectedStatusCodeException; +use Kiboko\Magento\Model\CatalogDataCategoryInterface; +use Kiboko\Magento\Model\ErrorResponse; +use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Log\LoggerInterface; +/** + * @template InputType of array + * @template OutputType of InputType|array + * @implements TransformerInterface + */ final readonly class CategoryLookup implements TransformerInterface { + /** + * @param CompiledMapperInterface $mapper + */ public function __construct( - private \Psr\Log\LoggerInterface $logger, - private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, - private CacheInterface $cache, - private string $cacheKey, + private LoggerInterface $logger, + private Client $client, private CompiledMapperInterface $mapper, private string $mappingField, ) { } + /** + * @param ErrorResponse $response + * @return RejectionResultBucketInterface + */ + private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface + { + $this->logger->error( + $response->getMessage(), + [ + 'resource' => 'getV1CategoriesCategoryId', + 'method' => 'get', + ], + ); + return new RejectionResultBucket($response->getMessage(), null); + } + + /** + * @return RejectionResultBucketInterface + */ + private function rejectInvalidResponse(): RejectionResultBucketInterface + { + $this->logger->error( + $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', + [ + 'resource' => 'getV1CategoriesCategoryId', + 'method' => 'get', + ], + ); + return new RejectionResultBucket($message, null); + } + + /** + * @param InputType $line + * @return OutputType + */ + public function passThrough(array $line): array + { + /** @var OutputType $line */ + return $line; + } + public function transform(): \Generator { $line = yield new EmptyResultBucket(); while (true) { + if ($line === null) { + $line = yield new EmptyResultBucket(); + continue; + } + if (null === $line[$this->mappingField]) { - $line = yield new AcceptanceResultBucket($line); + $line = yield new AcceptanceResultBucket($this->passThrough($line)); + continue; } try { - $lookup = $this->cache->get(sprintf($this->cacheKey, $line[$this->mappingField])); - - if (null === $lookup) { - $lookup = $this->client->catalogCategoryRepositoryV1GetGet( - categoryId: (int) $line[$this->mappingField], - ); + $lookup = $this->client->getV1CategoriesCategoryId( + categoryId: (int) $line[$this->mappingField], + ); - if (!$lookup instanceof \Kiboko\Magento\V2_1\Model\CatalogDataCategoryInterface - && !$lookup instanceof \Kiboko\Magento\V2_2\Model\CatalogDataCategoryInterface - && !$lookup instanceof \Kiboko\Magento\V2_3\Model\CatalogDataCategoryInterface - && !$lookup instanceof \Kiboko\Magento\V2_4\Model\CatalogDataCategoryInterface - ) { - return; - } + if ($lookup instanceof ErrorResponse) { + $line = yield $this->rejectErrorResponse($lookup); + continue; + } - $this->cache->set( - sprintf($this->cacheKey, $line[$this->mappingField]), - $lookup, - ); + if (!$lookup instanceof CatalogDataCategoryInterface) { + $line = yield $this->rejectInvalidResponse(); + continue; } - } catch (\RuntimeException $exception) { - $this->logger->warning($exception->getMessage(), ['exception' => $exception, 'item' => $line]); - $line = yield new RejectionResultBucket($line); + } catch (NetworkExceptionInterface $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1CategoriesCategoryId', + 'method' => 'get', + 'categoryId' => (int) $line[$this->mappingField], + 'mappingField' => $this->mappingField, + ], + ); + $line = yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + $this->passThrough($line), + ); + continue; + } catch (GetV1CategoriesCategoryIdBadRequestException $exception) { + $this->logger->error( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1CategoriesCategoryId', + 'method' => 'get', + 'categoryId' => (int) $line[$this->mappingField], + 'mappingField' => $this->mappingField, + ], + ); + $line = yield new RejectionResultBucket( + 'The source API rejected our request. Ignoring line. Maybe you are requesting on incompatible versions.', + $exception, + $this->passThrough($line), + ); continue; + } catch (UnexpectedStatusCodeException $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1CategoriesCategoryId', + 'method' => 'get', + 'categoryId' => (int) $line[$this->mappingField], + 'mappingField' => $this->mappingField, + ], + ); + $line = yield new RejectionResultBucket( + 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', + $exception, + $this->passThrough($line), + ); + return; } $output = ($this->mapper)($lookup, $line); diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index c90a673..10d8c7a 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -6,104 +6,166 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; -use Kiboko\Component\Flow\Magento2\Filter\FilterInterface; +use Kiboko\Contract\Bucket\RejectionResultBucketInterface; use Kiboko\Contract\Bucket\ResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Exception\GetV1CustomersSearchInternalServerErrorException; +use Kiboko\Magento\Exception\GetV1CustomersSearchUnauthorizedException; +use Kiboko\Magento\Exception\UnexpectedStatusCodeException; +use Kiboko\Magento\Model\CustomerDataCustomerInterface; +use Kiboko\Magento\Model\CustomerDataCustomerSearchResultsInterface; +use Kiboko\Magento\Model\ErrorResponse; use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Log\LoggerInterface; +/** + * @implements ExtractorInterface + */ final readonly class CustomerExtractor implements ExtractorInterface { public function __construct( - private \Psr\Log\LoggerInterface $logger, - private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private LoggerInterface $logger, + private Client $client, private QueryParameters $queryParameters, private int $pageSize = 100, ) { } - private function walkFilterVariants(int $currentPage = 1): \Traversable + /** + * @param array $parameters + * @return array + */ + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array { - yield from [ - ...$this->queryParameters->walkVariants([]), - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $this->pageSize, - ], + return [ + ...$parameters, + 'searchCriteria[currentPage]' => (string) $currentPage, + 'searchCriteria[pageSize]' => (string) $pageSize, ]; } - private function applyPagination(array $parameters, int $currentPage, int $pageSize): array + /** + * @param ErrorResponse $response + * @param array $parameters + * @return RejectionResultBucketInterface + */ + private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface { - return [ - ...$parameters, - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $pageSize, + $this->logger->error( + $response->getMessage(), + [ + 'resource' => 'getV1Orders', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ]; + ); + return new RejectionResultBucket($response->getMessage(), null); + } + + /** + * @param array $parameters + * @param int $currentPage + * @return RejectionResultBucketInterface + */ + private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface + { + $this->logger->error( + $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', + [ + 'resource' => 'getV1Orders', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, + ], + ); + return new RejectionResultBucket($message, null); } public function extract(): iterable { - $currentPage = null; - $pageCount = null; - try { - foreach ($this->queryParameters->walkVariants([]) as $parameters) { + foreach ($this->queryParameters->walkVariants() as $parameters) { + try { $currentPage = 1; - $response = $this->client->customerCustomerRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1CustomersSearch( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\V2_1\Model\CustomerDataCustomerSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\CustomerDataCustomerSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\CustomerDataCustomerSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\CustomerDataCustomerSearchResultsInterface - ) { + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; + } + if (!$response instanceof CustomerDataCustomerSearchResultsInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); return; } + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); while ($currentPage++ < $pageCount) { - $response = $this->client->customerCustomerRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1CustomersSearch( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; + } + if (!$response instanceof CustomerDataCustomerSearchResultsInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); + return; + } - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); } - } - } catch (NetworkExceptionInterface $exception) { - $this->logger->alert( - $exception->getMessage(), - [ - 'exception' => $exception, - 'context' => [ - 'path' => 'customer', + } catch (NetworkExceptionInterface $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1CustomersSearch', 'method' => 'get', - 'queryParameters' => $this->walkFilterVariants(), + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ], - ); - yield new RejectionResultBucket( - 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', - $exception, - ); - } catch (\Exception $exception) { - $this->logger->critical($exception->getMessage(), ['exception' => $exception]); - } - } - - private function processResponse($response): ResultBucketInterface - { - if ($response instanceof \Kiboko\Magento\V2_1\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_2\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_3\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_4\Model\ErrorResponse - ) { - return new RejectionResultBucket($response->getMessage(), null, $response); + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); + return; + } catch (GetV1CustomersSearchUnauthorizedException $exception) { + $this->logger->warning($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded we are not authorized to access this resource. Aborting. Please check the credentials you provided.', + $exception, + ); + return; + } catch (GetV1CustomersSearchInternalServerErrorException $exception) { + $this->logger->error($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded it is currently unavailable due to an internal error. Aborting. Please check the availability of the source API.', + $exception, + ); + return; + } catch (UnexpectedStatusCodeException $exception) { + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', + $exception, + ); + return; + } catch (\Throwable $exception) { + $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The client failed critically. Aborting. Please contact customer support or your system administrator.', + $exception, + ); + return; + } } - - return new AcceptanceResultBucket(...$response->getItems()); } } diff --git a/src/Filter/ArrayFilter.php b/src/Filter/ArrayFilter.php index e623bfd..5bf0fcd 100644 --- a/src/Filter/ArrayFilter.php +++ b/src/Filter/ArrayFilter.php @@ -4,25 +4,35 @@ namespace Kiboko\Component\Flow\Magento2\Filter; +/** + * @implements \IteratorAggregate + */ final class ArrayFilter implements FilterInterface, \IteratorAggregate { + /** + * @param list $values + */ public function __construct( public string $field, public string $conditionType, - public array $value, + public array $values, private readonly int $threshold = 200 - ) {} + ) { + } /** - * @return \Traversable + * @return \Traversable */ public function getIterator(): \Traversable { - $length = count($this->value); + $length = \count($this->values); for ($offset = 0; $offset < $length; $offset += $this->threshold) { yield [ 'field' => $this->field, - 'value' => implode(',', array_slice($this->value, $offset, $this->threshold, false)), + 'value' => implode(',', array_map( + fn (bool|\DateTimeInterface|float|int|string $value) => $value instanceof \DateTimeInterface ? $value->format(\DateTimeInterface::ATOM) : (string) $value, + \array_slice($this->values, $offset, $this->threshold, false) + )), 'conditionType' => $this->conditionType, ]; } diff --git a/src/Filter/FilterInterface.php b/src/Filter/FilterInterface.php index e038ea8..e753cfb 100644 --- a/src/Filter/FilterInterface.php +++ b/src/Filter/FilterInterface.php @@ -5,7 +5,7 @@ namespace Kiboko\Component\Flow\Magento2\Filter; /** - * @extends \Traversable + * @extends \Traversable */ interface FilterInterface extends \Traversable { diff --git a/src/Filter/ScalarFilter.php b/src/Filter/ScalarFilter.php index 1cf5e5c..cb7b780 100644 --- a/src/Filter/ScalarFilter.php +++ b/src/Filter/ScalarFilter.php @@ -4,6 +4,9 @@ namespace Kiboko\Component\Flow\Magento2\Filter; +/** + * @implements \IteratorAggregate + */ final class ScalarFilter implements FilterInterface, \IteratorAggregate { public function __construct( @@ -12,11 +15,14 @@ public function __construct( public bool|int|float|string|\DateTimeInterface $value, ) {} + /** + * @return \Traversable + */ public function getIterator(): \Traversable { yield [ 'field' => $this->field, - 'value' => $this->value, + 'value' => $this->value instanceof \DateTimeInterface ? $this->value->format(\DateTimeInterface::ATOM) : (string) $this->value, 'conditionType' => $this->conditionType, ]; } diff --git a/src/FilterGroup.php b/src/FilterGroup.php index b41c06f..4460cb9 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -9,6 +9,7 @@ class FilterGroup { + /** @var array */ private array $filters = []; public function withFilter(FilterInterface $filter): self @@ -28,7 +29,8 @@ public function withFilters(FilterInterface ...$filter): self } /** - * @return \Traversable + * @param array $parameters + * @return \Traversable> */ public function walkFilters(array $parameters, int $groupIndex = 0): \Traversable { @@ -39,6 +41,10 @@ public function walkFilters(array $parameters, int $groupIndex = 0): \Traversabl yield from $this->buildFilters($parameters, $groupIndex, 1, ...$this->filters); } + /** + * @param array $parameters + * @return \Traversable> + */ private function buildFilters(array $parameters, int $groupIndex, int $filterIndex, FilterInterface $first, FilterInterface ...$next): \Traversable { foreach ($first as $current) { diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index 4b02d6d..42ce879 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -6,103 +6,157 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; -use Kiboko\Contract\Bucket\ResultBucketInterface; +use Kiboko\Contract\Bucket\RejectionResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Exception\GetV1InvoicesUnauthorizedException; +use Kiboko\Magento\Exception\UnexpectedStatusCodeException; +use Kiboko\Magento\Model\ErrorResponse; +use Kiboko\Magento\Model\SalesDataInvoiceInterface; +use Kiboko\Magento\Model\SalesDataInvoiceSearchResultInterface; use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Log\LoggerInterface; +/** + * @implements ExtractorInterface + */ final readonly class InvoiceExtractor implements ExtractorInterface { public function __construct( - private \Psr\Log\LoggerInterface $logger, - private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private LoggerInterface $logger, + private Client $client, private QueryParameters $queryParameters, private int $pageSize = 100, ) { } - private function walkFilterVariants(int $currentPage = 1): \Traversable + /** + * @param array $parameters + * @return array + */ + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array { - yield from [ - ...$this->queryParameters->walkVariants([]), - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $this->pageSize, - ], + return [ + ...$parameters, + 'searchCriteria[currentPage]' => (string) $currentPage, + 'searchCriteria[pageSize]' => (string) $pageSize, ]; } - private function applyPagination(array $parameters, int $currentPage, int $pageSize): array + /** + * @param ErrorResponse $response + * @param array $parameters + * @return RejectionResultBucketInterface + */ + private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface { - return [ - ...$parameters, - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $pageSize, + $this->logger->error( + $response->getMessage(), + [ + 'resource' => 'getV1Orders', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ]; + ); + return new RejectionResultBucket($response->getMessage(), null); + } + + /** + * @param array $parameters + * @param int $currentPage + * @return RejectionResultBucketInterface + */ + private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface + { + $this->logger->error( + $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', + [ + 'resource' => 'getV1Orders', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, + ], + ); + return new RejectionResultBucket($message, null); } public function extract(): iterable { - $currentPage = null; - $pageCount = null; - try { - foreach ($this->queryParameters->walkVariants([]) as $parameters) { + foreach ($this->queryParameters->walkVariants() as $parameters) { + try { $currentPage = 1; - $response = $this->client->salesInvoiceRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1Invoices( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataInvoiceSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataInvoiceSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\SalesDataInvoiceSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\SalesDataInvoiceSearchResultInterface - ) { + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); return; } + if (!$response instanceof SalesDataInvoiceSearchResultInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); + return; + } + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); while ($currentPage++ < $pageCount) { - $response = $this->client->salesInvoiceRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1Invoices( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; + } + if (!$response instanceof SalesDataInvoiceSearchResultInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); + return; + } - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); } - } - } catch (NetworkExceptionInterface $exception) { - $this->logger->alert( - $exception->getMessage(), - [ - 'exception' => $exception, - 'context' => [ - 'path' => 'invoice', + } catch (NetworkExceptionInterface $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1Invoices', 'method' => 'get', - 'queryParameters' => $this->walkFilterVariants(), + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ], - ); - yield new RejectionResultBucket( - 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', - $exception, - ); - } catch (\Exception $exception) { - $this->logger->critical($exception->getMessage(), ['exception' => $exception]); - } - } - - private function processResponse($response): ResultBucketInterface - { - if ($response instanceof \Kiboko\Magento\V2_1\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_2\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_3\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_4\Model\ErrorResponse - ) { - return new RejectionResultBucket($response); + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); + return; + } catch (GetV1InvoicesUnauthorizedException $exception) { + $this->logger->warning($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded we are not authorized to access this resource. Aborting. Please check the credentials you provided.', + $exception, + ); + return; + } catch (UnexpectedStatusCodeException $exception) { + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', + $exception, + ); + return; + } catch (\Throwable $exception) { + $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The client failed critically. Aborting. Please contact customer support or your system administrator.', + $exception, + ); + return; + } } - - return new AcceptanceResultBucket(...$response->getItems()); } } diff --git a/src/Lookup.php b/src/Lookup.php deleted file mode 100644 index 991212f..0000000 --- a/src/Lookup.php +++ /dev/null @@ -1,69 +0,0 @@ -mappingField]) { - $line = yield new AcceptanceResultBucket($line); - } - - try { - $lookup = $this->cache->get(sprintf($this->cacheKey, $line[$this->mappingField])); - - if (null === $lookup) { - $results = $this->client->catalogProductAttributeOptionManagementV1GetItemsGet( - attributeCode: $this->attributeCode, - ); - - $lookup = array_values(array_filter($results, fn (object $item) => $item->getValue() === $line[$this->mappingField]))[0]; - - if (!$lookup instanceof \Kiboko\Magento\V2_1\Model\EavDataAttributeOptionInterface - && !$lookup instanceof \Kiboko\Magento\V2_2\Model\EavDataAttributeOptionInterface - && !$lookup instanceof \Kiboko\Magento\V2_3\Model\EavDataAttributeOptionInterface - && !$lookup instanceof \Kiboko\Magento\V2_4\Model\EavDataAttributeOptionInterface - ) { - return; - } - - $this->cache->set( - sprintf($this->cacheKey, $line[$this->mappingField]), - $lookup, - ); - } - } catch (\RuntimeException $exception) { - $this->logger->warning($exception->getMessage(), ['exception' => $exception, 'item' => $line]); - $line = yield new RejectionResultBucket($line); - continue; - } - - $output = ($this->mapper)($lookup, $line); - - $line = yield new AcceptanceResultBucket($output); - } - } -} diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index f9fd850..278acba 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -6,103 +6,157 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; -use Kiboko\Contract\Bucket\ResultBucketInterface; +use Kiboko\Contract\Bucket\RejectionResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Exception\GetV1OrdersUnauthorizedException; +use Kiboko\Magento\Exception\UnexpectedStatusCodeException; +use Kiboko\Magento\Model\ErrorResponse; +use Kiboko\Magento\Model\SalesDataOrderInterface; +use Kiboko\Magento\Model\SalesDataOrderSearchResultInterface; use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Log\LoggerInterface; +/** + * @implements ExtractorInterface + */ final readonly class OrderExtractor implements ExtractorInterface { public function __construct( - private \Psr\Log\LoggerInterface $logger, - private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private LoggerInterface $logger, + private Client $client, private QueryParameters $queryParameters, private int $pageSize = 100, ) { } - private function walkFilterVariants(int $currentPage = 1): \Traversable + /** + * @param array $parameters + * @return array + */ + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array { - yield from [ - ...$this->queryParameters->walkVariants([]), - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $this->pageSize, - ], + return [ + ...$parameters, + 'searchCriteria[currentPage]' => (string) $currentPage, + 'searchCriteria[pageSize]' => (string) $pageSize, ]; } - private function applyPagination(array $parameters, int $currentPage, int $pageSize): array + /** + * @param ErrorResponse $response + * @param array $parameters + * @return RejectionResultBucketInterface + */ + private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface { - return [ - ...$parameters, - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $pageSize, + $this->logger->error( + $response->getMessage(), + [ + 'resource' => 'getV1Orders', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ]; + ); + return new RejectionResultBucket($response->getMessage(), null); + } + + /** + * @param array $parameters + * @param int $currentPage + * @return RejectionResultBucketInterface + */ + private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface + { + $this->logger->error( + $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', + [ + 'resource' => 'getV1Orders', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, + ], + ); + return new RejectionResultBucket($message, null); } public function extract(): iterable { - $currentPage = null; - $pageCount = null; - try { - foreach ($this->queryParameters->walkVariants([]) as $parameters) { + foreach ($this->queryParameters->walkVariants() as $parameters) { + try { $currentPage = 1; - $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1Orders( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\V2_1\Model\SalesDataOrderSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\SalesDataOrderSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\SalesDataOrderSearchResultInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\SalesDataOrderSearchResultInterface - ) { + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); return; } + if (!$response instanceof SalesDataOrderSearchResultInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); + return; + } + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); while ($currentPage++ < $pageCount) { - $response = $this->client->salesOrderRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1Orders( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; + } + if (!$response instanceof SalesDataOrderSearchResultInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); + return; + } - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); } - } - } catch (NetworkExceptionInterface $exception) { - $this->logger->alert( - $exception->getMessage(), - [ - 'exception' => $exception, - 'context' => [ - 'path' => 'order', + } catch (NetworkExceptionInterface $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1Orders', 'method' => 'get', - 'queryParameters' => $this->walkFilterVariants(), + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ], - ); - yield new RejectionResultBucket( - 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', - $exception, - ); - } catch (\Exception $exception) { - $this->logger->critical($exception->getMessage(), ['exception' => $exception]); - } - } - - private function processResponse($response): ResultBucketInterface - { - if ($response instanceof \Kiboko\Magento\V2_1\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_2\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_3\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_4\Model\ErrorResponse - ) { - return new RejectionResultBucket($response); + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); + return; + } catch (GetV1OrdersUnauthorizedException $exception) { + $this->logger->warning($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded we are not authorized to access this resource. Aborting. Please check the credentials you provided.', + $exception, + ); + return; + } catch (UnexpectedStatusCodeException $exception) { + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', + $exception, + ); + return; + } catch (\Throwable $exception) { + $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The client failed critically. Aborting. Please contact customer support or your system administrator.', + $exception, + ); + return; + } } - - return new AcceptanceResultBucket(...$response->getItems()); } } diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index a8cb19e..a02f673 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -6,103 +6,149 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; -use Kiboko\Contract\Bucket\ResultBucketInterface; +use Kiboko\Contract\Bucket\RejectionResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Exception\UnexpectedStatusCodeException; +use Kiboko\Magento\Model\CatalogDataProductInterface; +use Kiboko\Magento\Model\CatalogDataProductSearchResultsInterface; +use Kiboko\Magento\Model\ErrorResponse; use Psr\Http\Client\NetworkExceptionInterface; +use Psr\Log\LoggerInterface; +/** + * @implements ExtractorInterface + */ final readonly class ProductExtractor implements ExtractorInterface { public function __construct( - private \Psr\Log\LoggerInterface $logger, - private \Kiboko\Magento\V2_1\Client|\Kiboko\Magento\V2_2\Client|\Kiboko\Magento\V2_3\Client|\Kiboko\Magento\V2_4\Client $client, + private LoggerInterface $logger, + private Client $client, private QueryParameters $queryParameters, private int $pageSize = 100, ) { } - private function walkFilterVariants(int $currentPage = 1): \Traversable + /** + * @param array $parameters + * @return array + */ + private function applyPagination(array $parameters, int $currentPage, int $pageSize): array { - yield from [ - ...$this->queryParameters->walkVariants([]), - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $this->pageSize, - ], + return [ + ...$parameters, + 'searchCriteria[currentPage]' => (string) $currentPage, + 'searchCriteria[pageSize]' => (string) $pageSize, ]; } - private function applyPagination(array $parameters, int $currentPage, int $pageSize): array + /** + * @param ErrorResponse $response + * @param array $parameters + * @return RejectionResultBucketInterface + */ + private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface { - return [ - ...$parameters, - ...[ - 'searchCriteria[currentPage]' => $currentPage, - 'searchCriteria[pageSize]' => $pageSize, + $this->logger->error( + $response->getMessage(), + [ + 'resource' => 'getV1Products', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ]; + ); + return new RejectionResultBucket($response->getMessage(), null); + } + + /** + * @param array $parameters + * @param int $currentPage + * @return RejectionResultBucketInterface + */ + private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface + { + $this->logger->error( + $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', + [ + 'resource' => 'getV1Products', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, + ], + ); + return new RejectionResultBucket($message, null); } public function extract(): iterable { - $currentPage = null; - $pageCount = null; - try { - foreach ($this->queryParameters->walkVariants([]) as $parameters) { + foreach ($this->queryParameters->walkVariants() as $parameters) { + try { $currentPage = 1; - $response = $this->client->catalogProductRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1Products( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\V2_1\Model\CatalogDataProductSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_2\Model\CatalogDataProductSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_3\Model\CatalogDataProductSearchResultsInterface - && !$response instanceof \Kiboko\Magento\V2_4\Model\CatalogDataProductSearchResultsInterface - ) { + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); return; } + if (!$response instanceof CatalogDataProductSearchResultsInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); + return; + } + $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); while ($currentPage++ < $pageCount) { - $response = $this->client->catalogProductRepositoryV1GetListGet( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + $response = $this->client->getV1Products( + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); + if ($response instanceof ErrorResponse) { + yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; + } + if (!$response instanceof CatalogDataProductSearchResultsInterface) { + yield $this->rejectInvalidResponse($parameters, $currentPage); + return; + } - yield $this->processResponse($response); + yield new AcceptanceResultBucket(...$response->getItems()); } - } - } catch (NetworkExceptionInterface $exception) { - $this->logger->alert( - $exception->getMessage(), - [ - 'exception' => $exception, - 'context' => [ - 'path' => 'product', + } catch (NetworkExceptionInterface $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1Products', 'method' => 'get', - 'queryParameters' => $this->walkFilterVariants(), + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, ], - ], - ); - yield new RejectionResultBucket( - 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', - $exception, - ); - } catch (\Exception $exception) { - $this->logger->critical($exception->getMessage(), ['exception' => $exception]); - } - } - - private function processResponse($response): ResultBucketInterface - { - if ($response instanceof \Kiboko\Magento\V2_1\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_2\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_3\Model\ErrorResponse - || $response instanceof \Kiboko\Magento\V2_4\Model\ErrorResponse - ) { - return new RejectionResultBucket($response); + ); + yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + ); + return; + } catch (UnexpectedStatusCodeException $exception) { + $this->logger->critical($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', + $exception, + ); + return; + } catch (\Throwable $exception) { + $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); + yield new RejectionResultBucket( + 'The client failed critically. Aborting. Please contact customer support or your system administrator.', + $exception, + ); + return; + } } - - return new AcceptanceResultBucket(...$response->getItems()); } } diff --git a/src/ProductOptionsLookup.php b/src/ProductOptionsLookup.php new file mode 100644 index 0000000..92b06dc --- /dev/null +++ b/src/ProductOptionsLookup.php @@ -0,0 +1,187 @@ + + */ +final readonly class ProductOptionsLookup implements TransformerInterface +{ + /** + * @param CompiledMapperInterface $mapper + */ + public function __construct( + private LoggerInterface $logger, + private Client $client, + private CompiledMapperInterface $mapper, + private string $mappingField, + private string $attributeCode, + ) { + } + + /** + * @param ErrorResponse $response + * @return RejectionResultBucketInterface + */ + private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface + { + $this->logger->error( + $response->getMessage(), + [ + 'resource' => 'getV1ProductsAttributesAttributeCodeOptions', + 'method' => 'get', + ], + ); + return new RejectionResultBucket($response->getMessage(), null); + } + + /** + * @return RejectionResultBucketInterface + */ + private function rejectInvalidResponse(): RejectionResultBucketInterface + { + $this->logger->error( + $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', + [ + 'resource' => 'getV1ProductsAttributesAttributeCodeOptions', + 'method' => 'get', + ], + ); + return new RejectionResultBucket($message, null); + } + + /** + * @param InputType $line + * @return OutputType + */ + public function passThrough(array $line): array + { + /** @var OutputType $line */ + return $line; + } + + public function transform(): \Generator + { + $line = yield new EmptyResultBucket(); + while (true) { + if ($line === null) { + $line = yield new EmptyResultBucket(); + continue; + } + + if (null === $line[$this->mappingField]) { + $line = yield new AcceptanceResultBucket($this->passThrough($line)); + continue; + } + + try { + $lookup = $this->client->getV1ProductsAttributesAttributeCodeOptions( + attributeCode: $this->attributeCode, + ); + + if ($lookup instanceof ErrorResponse) { + $line = yield $this->rejectErrorResponse($lookup); + continue; + } + + if (!is_array($lookup) || !array_is_list($lookup)) { + $line = yield $this->rejectInvalidResponse(); + continue; + } + + $lookup = array_filter( + $lookup, + fn (object $item) => $item->getValue() === $line[$this->mappingField], + ); + } catch (NetworkExceptionInterface $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1ProductsAttributesAttributeCodeOptions', + 'method' => 'get', + 'categoryId' => (int) $line[$this->mappingField], + 'mappingField' => $this->mappingField, + ], + ); + $line = yield new RejectionResultBucket( + 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', + $exception, + $this->passThrough($line), + ); + continue; + } catch (GetV1ProductsAttributesAttributeCodeOptionsBadRequestException $exception) { + $this->logger->error( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1ProductsAttributesAttributeCodeOptions', + 'method' => 'get', + 'categoryId' => (int) $line[$this->mappingField], + 'mappingField' => $this->mappingField, + ], + ); + $line = yield new RejectionResultBucket( + 'The source API rejected our request. Ignoring line. Maybe you are requesting on incompatible versions.', + $exception, + $this->passThrough($line), + ); + continue; + } catch (UnexpectedStatusCodeException $exception) { + $this->logger->critical( + $exception->getMessage(), + [ + 'exception' => $exception, + 'resource' => 'getV1ProductsAttributesAttributeCodeOptions', + 'method' => 'get', + 'categoryId' => (int) $line[$this->mappingField], + 'mappingField' => $this->mappingField, + ], + ); + $line = yield new RejectionResultBucket( + 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', + $exception, + $this->passThrough($line), + ); + return; + } + + reset($lookup); + $current = current($lookup); + if (count($lookup) <= 0 || $current === false) { + $this->logger->critical( + 'The lookup did not find any related resource. The lookup operation had no effect.', + [ + 'resource' => 'getV1ProductsAttributesAttributeCodeOptions', + 'method' => 'get', + 'categoryId' => (int) $line[$this->mappingField], + 'mappingField' => $this->mappingField, + ], + ); + $line = yield new AcceptanceResultBucket($this->passThrough($line)); + continue; + } + $output = ($this->mapper)($current, $line); + + $line = yield new AcceptanceResultBucket($output); + } + } +} diff --git a/src/QueryParameters.php b/src/QueryParameters.php index c1fd9a2..e14af9e 100644 --- a/src/QueryParameters.php +++ b/src/QueryParameters.php @@ -26,9 +26,10 @@ public function withGroups(FilterGroup ...$group): self } /** - * @return \Traversable + * @param array $parameters + * @return \Traversable> */ - public function walkVariants(array $parameters): \Traversable + public function walkVariants(array $parameters = []): \Traversable { if (count($this->groups) < 1) { return; @@ -37,6 +38,10 @@ public function walkVariants(array $parameters): \Traversable yield from $this->buildFilters($parameters, 0, ...$this->groups); } + /** + * @param array $parameters + * @return \Traversable> + */ private function buildFilters(array $parameters, int $groupIndex, FilterGroup $first, FilterGroup ...$next): \Traversable { foreach ($first->walkFilters($parameters, $groupIndex) as $current) { diff --git a/tests/QueryParametersTest.php b/tests/QueryParametersTest.php index 0cad45a..4e71e37 100644 --- a/tests/QueryParametersTest.php +++ b/tests/QueryParametersTest.php @@ -22,12 +22,12 @@ public function shouldProduceOneVariant(): void ) ); - $this->assertCount(1, iterator_to_array($queryParameters->walkVariants([]), false)); + $this->assertCount(1, iterator_to_array($queryParameters->walkVariants(), false)); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); } #[Test] @@ -39,22 +39,22 @@ public function shouldProduceSeveralVariants(): void new ArrayFilter('foo', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 4), ) ); - $this->assertCount(3, iterator_to_array($queryParameters->walkVariants([]), false)); + $this->assertCount(3, iterator_to_array($queryParameters->walkVariants(), false)); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', 'searchCriteria[filterGroups][0][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); } #[Test] @@ -71,7 +71,7 @@ public function shouldProduceDemultipliedVariants(): void new ArrayFilter('bar', 'in', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], 4), ) ); - $this->assertCount(12, iterator_to_array($queryParameters->walkVariants([]), false)); + $this->assertCount(12, iterator_to_array($queryParameters->walkVariants(), false)); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', @@ -79,7 +79,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '1,2,3,4', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', @@ -87,7 +87,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '1,2,3,4', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', @@ -95,7 +95,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '1,2,3,4', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', @@ -103,7 +103,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '5,6,7,8', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', @@ -111,7 +111,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '5,6,7,8', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', @@ -119,7 +119,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '5,6,7,8', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', @@ -127,7 +127,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '9,10,11,12', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', @@ -135,7 +135,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '9,10,11,12', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', @@ -143,7 +143,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '9,10,11,12', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '1,2,3,4', @@ -151,7 +151,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '13,14,15', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '5,6,7,8', @@ -159,7 +159,7 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '13,14,15', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); $this->assertContains([ 'searchCriteria[filterGroups][0][filters][1][field]' => 'foo', 'searchCriteria[filterGroups][0][filters][1][value]' => '9,10,11', @@ -167,6 +167,6 @@ public function shouldProduceDemultipliedVariants(): void 'searchCriteria[filterGroups][1][filters][1][field]' => 'bar', 'searchCriteria[filterGroups][1][filters][1][value]' => '13,14,15', 'searchCriteria[filterGroups][1][filters][1][conditionType]' => 'in', - ], $queryParameters->walkVariants([])); + ], $queryParameters->walkVariants()); } } From b9ba56b771fe7d0785db92abe730bb596ca52e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Tue, 30 Jan 2024 14:16:42 +0100 Subject: [PATCH 15/24] Fixed errors in the unit tests --- src/CustomerExtractor.php | 4 ++-- src/InvoiceExtractor.php | 4 ++-- tests/Filter/ScalarFilterTest.php | 2 +- tests/InvoiceExtractorTest.php | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index 10d8c7a..dae4dc4 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -55,7 +55,7 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, $this->logger->error( $response->getMessage(), [ - 'resource' => 'getV1Orders', + 'resource' => 'getV1CustomersSearch', 'method' => 'get', 'queryParameters' => $parameters, 'currentPage' => $currentPage, @@ -75,7 +75,7 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej $this->logger->error( $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', [ - 'resource' => 'getV1Orders', + 'resource' => 'getV1CustomersSearch', 'method' => 'get', 'queryParameters' => $parameters, 'currentPage' => $currentPage, diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index 42ce879..45aaffc 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -53,7 +53,7 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, $this->logger->error( $response->getMessage(), [ - 'resource' => 'getV1Orders', + 'resource' => 'getV1Invoices', 'method' => 'get', 'queryParameters' => $parameters, 'currentPage' => $currentPage, @@ -73,7 +73,7 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej $this->logger->error( $message = 'The result provided by the API client does not match the expected type. The connector compilation may have fetched incompatible versions.', [ - 'resource' => 'getV1Orders', + 'resource' => 'getV1Invoices', 'method' => 'get', 'queryParameters' => $parameters, 'currentPage' => $currentPage, diff --git a/tests/Filter/ScalarFilterTest.php b/tests/Filter/ScalarFilterTest.php index 8876cff..960c4ad 100644 --- a/tests/Filter/ScalarFilterTest.php +++ b/tests/Filter/ScalarFilterTest.php @@ -17,7 +17,7 @@ public function shouldProduceOneVariant(): void $this->assertCount(1, iterator_to_array($filter->getIterator(), false)); $this->assertContains([ 'field' => 'foo', - 'value' => 4, + 'value' => '4', 'conditionType' => 'eq', ], $filter); } diff --git a/tests/InvoiceExtractorTest.php b/tests/InvoiceExtractorTest.php index 74ddcb0..2f600aa 100644 --- a/tests/InvoiceExtractorTest.php +++ b/tests/InvoiceExtractorTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tests\Kiboko\Magento\V2\Extractor; +namespace Tests\Kiboko\Component\Flow\Magento2; use Kiboko\Component\Flow\Magento2\Filter\ScalarFilter; use Kiboko\Component\Flow\Magento2\FilterGroup; @@ -11,9 +11,9 @@ use Kiboko\Component\PHPUnitExtension\Assert\ExtractorAssertTrait; use Kiboko\Component\PHPUnitExtension\PipelineRunner; use Kiboko\Contract\Pipeline\PipelineRunnerInterface; -use Kiboko\Magento\V2_3\Client; -use Kiboko\Magento\V2_3\Model\SalesDataInvoiceInterface; -use Kiboko\Magento\V2_3\Model\SalesDataInvoiceSearchResultInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Model\SalesDataInvoiceInterface; +use Kiboko\Magento\Model\SalesDataInvoiceSearchResultInterface; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -31,7 +31,7 @@ public function testIsSuccessful(): void $client = $this->createMock(Client::class); $client ->expects($this->once()) - ->method('salesInvoiceRepositoryV1GetListGet') + ->method('getV1Invoices') ->willReturn( (new SalesDataInvoiceSearchResultInterface()) ->setItems([ From ef45ba63fd633df46eb669e4d39f3a38d0343c14 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 30 Jan 2024 17:23:24 +0100 Subject: [PATCH 16/24] Fix client api version to 2.4 --- .github/workflows/phpstan-9.yaml | 2 +- composer.json | 2 +- composer.lock | 29 ++++++++++++++--------------- src/CustomerExtractor.php | 1 - src/FilterGroup.php | 6 ++---- src/QueryParameters.php | 6 ++---- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/.github/workflows/phpstan-9.yaml b/.github/workflows/phpstan-9.yaml index c0fe8c0..68dbcae 100644 --- a/.github/workflows/phpstan-9.yaml +++ b/.github/workflows/phpstan-9.yaml @@ -1,4 +1,4 @@ -name: PHPStan level 8 +name: PHPStan level 9 on: push jobs: phpstan-9: diff --git a/composer.json b/composer.json index b3580f3..aceceed 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "psr/log": "^3.0", "nyholm/psr7": "^1.5", "php-etl/bucket": "*", - "php-etl/magento2-api-client": "^0.1.0", + "php-etl/magento2-api-client": "2.4.x-dev", "psr/simple-cache": "^3.0", "php-etl/mapping-contracts": "0.4.*" }, diff --git a/composer.lock b/composer.lock index d5e0fd3..9ee5222 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "032582e526fc85372024e11317f4a7be", + "content-hash": "971b575cae686edbfb95693562fcdba5", "packages": [ { "name": "clue/stream-filter", @@ -614,16 +614,16 @@ }, { "name": "php-etl/magento2-api-client", - "version": "dev-main", + "version": "2.4.x-dev", "source": { "type": "git", "url": "https://github.com/php-etl/magento2-api-client.git", - "reference": "8997167f00c6ea2f939aac95b9f4810ef43fb4a3" + "reference": "46a2c89a5275af40809f9c045577f22b0e53dce9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/8997167f00c6ea2f939aac95b9f4810ef43fb4a3", - "reference": "8997167f00c6ea2f939aac95b9f4810ef43fb4a3", + "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/46a2c89a5275af40809f9c045577f22b0e53dce9", + "reference": "46a2c89a5275af40809f9c045577f22b0e53dce9", "shasum": "" }, "require": { @@ -631,10 +631,10 @@ "php": "^8.0" }, "require-dev": { - "jane-php/open-api-3": "^7.3", - "phpunit/phpunit": "^9.5" + "jane-php/open-api-2": "^7.5", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.15" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -643,10 +643,7 @@ }, "autoload": { "psr-4": { - "Kiboko\\Magento\\V2_1\\": "src/v2_1", - "Kiboko\\Magento\\V2_2\\": "src/v2_2", - "Kiboko\\Magento\\V2_3\\": "src/v2_3", - "Kiboko\\Magento\\V2_4\\": "src/v2_4" + "Kiboko\\Magento\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -662,9 +659,9 @@ "description": "This package provides Jane-PHP generated API models and client based on the OpenAPI specification for Magento 2.3.x", "support": { "issues": "https://github.com/php-etl/magento2-api-client/issues", - "source": "https://github.com/php-etl/magento2-api-client/tree/main" + "source": "https://github.com/php-etl/magento2-api-client/tree/2.4" }, - "time": "2022-09-23T08:47:45+00:00" + "time": "2024-01-22T09:20:52+00:00" }, { "name": "php-etl/mapping-contracts", @@ -4641,7 +4638,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "php-etl/magento2-api-client": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index dae4dc4..fec6054 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -7,7 +7,6 @@ use Kiboko\Component\Bucket\AcceptanceResultBucket; use Kiboko\Component\Bucket\RejectionResultBucket; use Kiboko\Contract\Bucket\RejectionResultBucketInterface; -use Kiboko\Contract\Bucket\ResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; use Kiboko\Magento\Client; use Kiboko\Magento\Exception\GetV1CustomersSearchInternalServerErrorException; diff --git a/src/FilterGroup.php b/src/FilterGroup.php index 4460cb9..daa34cb 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -19,11 +19,9 @@ public function withFilter(FilterInterface $filter): self return $this; } - public function withFilters(FilterInterface ...$filter): self + public function withFilters(FilterInterface ...$filters): self { - foreach ($filter as $item) { - $this->filters[] = $item; - } + array_push($this->filters, ...$filters); return $this; } diff --git a/src/QueryParameters.php b/src/QueryParameters.php index e14af9e..3d6ced8 100644 --- a/src/QueryParameters.php +++ b/src/QueryParameters.php @@ -16,11 +16,9 @@ public function withGroup(FilterGroup $group): self return $this; } - public function withGroups(FilterGroup ...$group): self + public function withGroups(FilterGroup ...$groups): self { - foreach ($group as $item) { - $this->groups[] = $item; - } + array_push($this->groups, ...$groups); return $this; } From c67ad88ebc755e7b2bbb4f5af62efae69935eb79 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 31 Jan 2024 08:20:05 +0000 Subject: [PATCH 17/24] [rector] Rector fixes --- src/CategoryLookup.php | 1 - src/CustomerExtractor.php | 2 -- src/InvoiceExtractor.php | 2 -- src/OrderExtractor.php | 2 -- src/ProductExtractor.php | 2 -- src/ProductOptionsLookup.php | 1 - 6 files changed, 10 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 02f6bee..5a46db8 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -37,7 +37,6 @@ public function __construct( } /** - * @param ErrorResponse $response * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index fec6054..0d1cffa 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -45,7 +45,6 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS } /** - * @param ErrorResponse $response * @param array $parameters * @return RejectionResultBucketInterface */ @@ -66,7 +65,6 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, /** * @param array $parameters - * @param int $currentPage * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index 45aaffc..a352af7 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -44,7 +44,6 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS } /** - * @param ErrorResponse $response * @param array $parameters * @return RejectionResultBucketInterface */ @@ -65,7 +64,6 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, /** * @param array $parameters - * @param int $currentPage * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 278acba..35908ee 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -44,7 +44,6 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS } /** - * @param ErrorResponse $response * @param array $parameters * @return RejectionResultBucketInterface */ @@ -65,7 +64,6 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, /** * @param array $parameters - * @param int $currentPage * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index a02f673..6fc91f3 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -43,7 +43,6 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS } /** - * @param ErrorResponse $response * @param array $parameters * @return RejectionResultBucketInterface */ @@ -64,7 +63,6 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, /** * @param array $parameters - * @param int $currentPage * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface diff --git a/src/ProductOptionsLookup.php b/src/ProductOptionsLookup.php index 92b06dc..96fbcdd 100644 --- a/src/ProductOptionsLookup.php +++ b/src/ProductOptionsLookup.php @@ -38,7 +38,6 @@ public function __construct( } /** - * @param ErrorResponse $response * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface From b110c448d0713af98118515dc9f1217d8bcd548c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Planchat?= Date: Tue, 30 Jan 2024 15:22:18 +0100 Subject: [PATCH 18/24] Changed the variadic methods to use array_push instead of foreach --- src/FilterGroup.php | 16 ++++++++-------- src/QueryParameters.php | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/FilterGroup.php b/src/FilterGroup.php index daa34cb..39e79fd 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -21,7 +21,7 @@ public function withFilter(FilterInterface $filter): self public function withFilters(FilterInterface ...$filters): self { - array_push($this->filters, ...$filters); + array_push($this->filters, ...$filters); return $this; } @@ -32,7 +32,7 @@ public function withFilters(FilterInterface ...$filters): self */ public function walkFilters(array $parameters, int $groupIndex = 0): \Traversable { - if (count($this->filters) < 1) { + if (\count($this->filters) < 1) { return; } @@ -52,10 +52,10 @@ private function buildFilters(array $parameters, int $groupIndex, int $filterInd sprintf('searchCriteria[filterGroups][%s][filters][%s][field]', $groupIndex, $filterIndex) => $current['field'], sprintf('searchCriteria[filterGroups][%s][filters][%s][value]', $groupIndex, $filterIndex) => $current['value'], sprintf('searchCriteria[filterGroups][%s][filters][%s][conditionType]', $groupIndex, $filterIndex) => $current['conditionType'], - ] + ], ]; - if (count($next) >= 1) { + if (\count($next) >= 1) { yield from $this->buildFilters($childParameters, $groupIndex, $filterIndex + 1, ...$next); } else { yield $childParameters; @@ -63,22 +63,22 @@ private function buildFilters(array $parameters, int $groupIndex, int $filterInd } } - public function greaterThan(string $field, int|float|string|\DateTimeInterface $value): self + public function greaterThan(string $field, \DateTimeInterface|float|int|string $value): self { return $this->withFilter(new ScalarFilter($field, 'gt', $value)); } - public function lowerThan(string $field, int|float|string|\DateTimeInterface $value): self + public function lowerThan(string $field, \DateTimeInterface|float|int|string $value): self { return $this->withFilter(new ScalarFilter($field, 'lt', $value)); } - public function greaterThanOrEqual(string $field, int|float|string|\DateTimeInterface $value): self + public function greaterThanOrEqual(string $field, \DateTimeInterface|float|int|string $value): self { return $this->withFilter(new ScalarFilter($field, 'gteq', $value)); } - public function lowerThanOrEqual(string $field, int|float|string|\DateTimeInterface $value): self + public function lowerThanOrEqual(string $field, \DateTimeInterface|float|int|string $value): self { return $this->withFilter(new ScalarFilter($field, 'lteq', $value)); } diff --git a/src/QueryParameters.php b/src/QueryParameters.php index 3d6ced8..7240e55 100644 --- a/src/QueryParameters.php +++ b/src/QueryParameters.php @@ -18,7 +18,7 @@ public function withGroup(FilterGroup $group): self public function withGroups(FilterGroup ...$groups): self { - array_push($this->groups, ...$groups); + array_push($this->groups, ...$groups); return $this; } @@ -29,7 +29,7 @@ public function withGroups(FilterGroup ...$groups): self */ public function walkVariants(array $parameters = []): \Traversable { - if (count($this->groups) < 1) { + if (\count($this->groups) < 1) { return; } @@ -43,7 +43,7 @@ public function walkVariants(array $parameters = []): \Traversable private function buildFilters(array $parameters, int $groupIndex, FilterGroup $first, FilterGroup ...$next): \Traversable { foreach ($first->walkFilters($parameters, $groupIndex) as $current) { - if (count($next) >= 1) { + if (\count($next) >= 1) { yield from $this->buildFilters($current, $groupIndex + 1, ...$next); } else { yield $current; From 77b53da477be727de17fe885b2fd309a2f995bec Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 31 Jan 2024 09:44:28 +0100 Subject: [PATCH 19/24] Fix client api version to 2.4 --- src/CategoryLookup.php | 9 +++++++-- src/CustomerExtractor.php | 14 ++++++++++++++ src/Filter/ScalarFilter.php | 5 +++-- src/FilterGroup.php | 4 +++- src/InvoiceExtractor.php | 13 +++++++++++++ src/OrderExtractor.php | 13 +++++++++++++ src/ProductExtractor.php | 12 ++++++++++++ src/ProductOptionsLookup.php | 13 +++++++++---- src/QueryParameters.php | 4 +++- 9 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 5a46db8..7440e50 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -21,6 +21,7 @@ /** * @template InputType of array * @template OutputType of InputType|array + * * @implements TransformerInterface */ final readonly class CategoryLookup implements TransformerInterface @@ -48,6 +49,7 @@ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBu 'method' => 'get', ], ); + return new RejectionResultBucket($response->getMessage(), null); } @@ -63,16 +65,18 @@ private function rejectInvalidResponse(): RejectionResultBucketInterface 'method' => 'get', ], ); + return new RejectionResultBucket($message, null); } /** * @param InputType $line + * * @return OutputType */ public function passThrough(array $line): array { - /** @var OutputType $line */ + /* @var OutputType $line */ return $line; } @@ -80,7 +84,7 @@ public function transform(): \Generator { $line = yield new EmptyResultBucket(); while (true) { - if ($line === null) { + if (null === $line) { $line = yield new EmptyResultBucket(); continue; } @@ -154,6 +158,7 @@ public function transform(): \Generator $exception, $this->passThrough($line), ); + return; } diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index 0d1cffa..7c82dc1 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -33,6 +33,7 @@ public function __construct( /** * @param array $parameters + * * @return array */ private function applyPagination(array $parameters, int $currentPage, int $pageSize): array @@ -46,6 +47,7 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface @@ -60,11 +62,13 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($response->getMessage(), null); } /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface @@ -79,6 +83,7 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($message, null); } @@ -92,10 +97,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof CustomerDataCustomerSearchResultsInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); @@ -108,10 +115,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof CustomerDataCustomerSearchResultsInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } @@ -133,6 +142,7 @@ public function extract(): iterable 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', $exception, ); + return; } catch (GetV1CustomersSearchUnauthorizedException $exception) { $this->logger->warning($exception->getMessage(), ['exception' => $exception]); @@ -140,6 +150,7 @@ public function extract(): iterable 'The source API responded we are not authorized to access this resource. Aborting. Please check the credentials you provided.', $exception, ); + return; } catch (GetV1CustomersSearchInternalServerErrorException $exception) { $this->logger->error($exception->getMessage(), ['exception' => $exception]); @@ -147,6 +158,7 @@ public function extract(): iterable 'The source API responded it is currently unavailable due to an internal error. Aborting. Please check the availability of the source API.', $exception, ); + return; } catch (UnexpectedStatusCodeException $exception) { $this->logger->critical($exception->getMessage(), ['exception' => $exception]); @@ -154,6 +166,7 @@ public function extract(): iterable 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', $exception, ); + return; } catch (\Throwable $exception) { $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); @@ -161,6 +174,7 @@ public function extract(): iterable 'The client failed critically. Aborting. Please contact customer support or your system administrator.', $exception, ); + return; } } diff --git a/src/Filter/ScalarFilter.php b/src/Filter/ScalarFilter.php index cb7b780..7e8bc1d 100644 --- a/src/Filter/ScalarFilter.php +++ b/src/Filter/ScalarFilter.php @@ -12,8 +12,9 @@ final class ScalarFilter implements FilterInterface, \IteratorAggregate public function __construct( public string $field, public string $conditionType, - public bool|int|float|string|\DateTimeInterface $value, - ) {} + public bool|\DateTimeInterface|float|int|string $value, + ) { + } /** * @return \Traversable diff --git a/src/FilterGroup.php b/src/FilterGroup.php index 39e79fd..71a3aa1 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -21,13 +21,14 @@ public function withFilter(FilterInterface $filter): self public function withFilters(FilterInterface ...$filters): self { - array_push($this->filters, ...$filters); + array_push($this->filters, ...$filters); return $this; } /** * @param array $parameters + * * @return \Traversable> */ public function walkFilters(array $parameters, int $groupIndex = 0): \Traversable @@ -41,6 +42,7 @@ public function walkFilters(array $parameters, int $groupIndex = 0): \Traversabl /** * @param array $parameters + * * @return \Traversable> */ private function buildFilters(array $parameters, int $groupIndex, int $filterIndex, FilterInterface $first, FilterInterface ...$next): \Traversable diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index a352af7..bcda827 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -32,6 +32,7 @@ public function __construct( /** * @param array $parameters + * * @return array */ private function applyPagination(array $parameters, int $currentPage, int $pageSize): array @@ -45,6 +46,7 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface @@ -59,11 +61,13 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($response->getMessage(), null); } /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface @@ -78,6 +82,7 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($message, null); } @@ -91,10 +96,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof SalesDataInvoiceSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); @@ -107,10 +114,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof SalesDataInvoiceSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } @@ -132,6 +141,7 @@ public function extract(): iterable 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', $exception, ); + return; } catch (GetV1InvoicesUnauthorizedException $exception) { $this->logger->warning($exception->getMessage(), ['exception' => $exception]); @@ -139,6 +149,7 @@ public function extract(): iterable 'The source API responded we are not authorized to access this resource. Aborting. Please check the credentials you provided.', $exception, ); + return; } catch (UnexpectedStatusCodeException $exception) { $this->logger->critical($exception->getMessage(), ['exception' => $exception]); @@ -146,6 +157,7 @@ public function extract(): iterable 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', $exception, ); + return; } catch (\Throwable $exception) { $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); @@ -153,6 +165,7 @@ public function extract(): iterable 'The client failed critically. Aborting. Please contact customer support or your system administrator.', $exception, ); + return; } } diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 35908ee..be2a28c 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -32,6 +32,7 @@ public function __construct( /** * @param array $parameters + * * @return array */ private function applyPagination(array $parameters, int $currentPage, int $pageSize): array @@ -45,6 +46,7 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface @@ -59,11 +61,13 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($response->getMessage(), null); } /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface @@ -78,6 +82,7 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($message, null); } @@ -91,10 +96,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof SalesDataOrderSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); @@ -107,10 +114,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof SalesDataOrderSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } @@ -132,6 +141,7 @@ public function extract(): iterable 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', $exception, ); + return; } catch (GetV1OrdersUnauthorizedException $exception) { $this->logger->warning($exception->getMessage(), ['exception' => $exception]); @@ -139,6 +149,7 @@ public function extract(): iterable 'The source API responded we are not authorized to access this resource. Aborting. Please check the credentials you provided.', $exception, ); + return; } catch (UnexpectedStatusCodeException $exception) { $this->logger->critical($exception->getMessage(), ['exception' => $exception]); @@ -146,6 +157,7 @@ public function extract(): iterable 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', $exception, ); + return; } catch (\Throwable $exception) { $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); @@ -153,6 +165,7 @@ public function extract(): iterable 'The client failed critically. Aborting. Please contact customer support or your system administrator.', $exception, ); + return; } } diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index 6fc91f3..f6e7054 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -31,6 +31,7 @@ public function __construct( /** * @param array $parameters + * * @return array */ private function applyPagination(array $parameters, int $currentPage, int $pageSize): array @@ -44,6 +45,7 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface @@ -58,11 +60,13 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($response->getMessage(), null); } /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(array $parameters, int $currentPage): RejectionResultBucketInterface @@ -77,6 +81,7 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($message, null); } @@ -90,10 +95,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof CatalogDataProductSearchResultsInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); @@ -106,10 +113,12 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof CatalogDataProductSearchResultsInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } @@ -131,6 +140,7 @@ public function extract(): iterable 'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.', $exception, ); + return; } catch (UnexpectedStatusCodeException $exception) { $this->logger->critical($exception->getMessage(), ['exception' => $exception]); @@ -138,6 +148,7 @@ public function extract(): iterable 'The source API responded with a status we did not expect. Aborting. Please check the availability of the source API and if there are no rate limiting or redirections active.', $exception, ); + return; } catch (\Throwable $exception) { $this->logger->emergency($exception->getMessage(), ['exception' => $exception]); @@ -145,6 +156,7 @@ public function extract(): iterable 'The client failed critically. Aborting. Please contact customer support or your system administrator.', $exception, ); + return; } } diff --git a/src/ProductOptionsLookup.php b/src/ProductOptionsLookup.php index 96fbcdd..ff893fe 100644 --- a/src/ProductOptionsLookup.php +++ b/src/ProductOptionsLookup.php @@ -21,6 +21,7 @@ /** * @template InputType of array * @template OutputType of InputType|array + * * @implements TransformerInterface */ final readonly class ProductOptionsLookup implements TransformerInterface @@ -49,6 +50,7 @@ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBu 'method' => 'get', ], ); + return new RejectionResultBucket($response->getMessage(), null); } @@ -64,16 +66,18 @@ private function rejectInvalidResponse(): RejectionResultBucketInterface 'method' => 'get', ], ); + return new RejectionResultBucket($message, null); } /** * @param InputType $line + * * @return OutputType */ public function passThrough(array $line): array { - /** @var OutputType $line */ + /* @var OutputType $line */ return $line; } @@ -81,7 +85,7 @@ public function transform(): \Generator { $line = yield new EmptyResultBucket(); while (true) { - if ($line === null) { + if (null === $line) { $line = yield new EmptyResultBucket(); continue; } @@ -101,7 +105,7 @@ public function transform(): \Generator continue; } - if (!is_array($lookup) || !array_is_list($lookup)) { + if (!\is_array($lookup) || !array_is_list($lookup)) { $line = yield $this->rejectInvalidResponse(); continue; } @@ -160,12 +164,13 @@ public function transform(): \Generator $exception, $this->passThrough($line), ); + return; } reset($lookup); $current = current($lookup); - if (count($lookup) <= 0 || $current === false) { + if (\count($lookup) <= 0 || false === $current) { $this->logger->critical( 'The lookup did not find any related resource. The lookup operation had no effect.', [ diff --git a/src/QueryParameters.php b/src/QueryParameters.php index 7240e55..bf5ef79 100644 --- a/src/QueryParameters.php +++ b/src/QueryParameters.php @@ -18,13 +18,14 @@ public function withGroup(FilterGroup $group): self public function withGroups(FilterGroup ...$groups): self { - array_push($this->groups, ...$groups); + array_push($this->groups, ...$groups); return $this; } /** * @param array $parameters + * * @return \Traversable> */ public function walkVariants(array $parameters = []): \Traversable @@ -38,6 +39,7 @@ public function walkVariants(array $parameters = []): \Traversable /** * @param array $parameters + * * @return \Traversable> */ private function buildFilters(array $parameters, int $groupIndex, FilterGroup $first, FilterGroup ...$next): \Traversable From 1ebac38844bad0931f8ee9a5d912bdedcb330d5c Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 2 Feb 2024 15:04:17 +0100 Subject: [PATCH 20/24] fix annotations for phpstan, fix endpoints on tests --- composer.lock | 13 ++++++----- src/CategoryLookup.php | 12 +++++----- src/OrderExtractor.php | 39 ++++++++++++++++++++++++++++++--- tests/CustomerExtractorTest.php | 8 +++---- tests/OrderExtractorTest.php | 8 +++---- tests/ProductExtractorTest.php | 8 +++---- 6 files changed, 63 insertions(+), 25 deletions(-) diff --git a/composer.lock b/composer.lock index 9ee5222..78b4917 100644 --- a/composer.lock +++ b/composer.lock @@ -618,12 +618,12 @@ "source": { "type": "git", "url": "https://github.com/php-etl/magento2-api-client.git", - "reference": "46a2c89a5275af40809f9c045577f22b0e53dce9" + "reference": "a5d87f2570540e3dbaf19066a81b0cacf84717f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/46a2c89a5275af40809f9c045577f22b0e53dce9", - "reference": "46a2c89a5275af40809f9c045577f22b0e53dce9", + "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/a5d87f2570540e3dbaf19066a81b0cacf84717f8", + "reference": "a5d87f2570540e3dbaf19066a81b0cacf84717f8", "shasum": "" }, "require": { @@ -643,7 +643,10 @@ }, "autoload": { "psr-4": { - "Kiboko\\Magento\\": "src" + "Kiboko\\Magento\\": [ + "src/", + "generated/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -661,7 +664,7 @@ "issues": "https://github.com/php-etl/magento2-api-client/issues", "source": "https://github.com/php-etl/magento2-api-client/tree/2.4" }, - "time": "2024-01-22T09:20:52+00:00" + "time": "2024-02-01T15:52:31+00:00" }, { "name": "php-etl/mapping-contracts", diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 7440e50..c903c08 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -22,7 +22,7 @@ * @template InputType of array * @template OutputType of InputType|array * - * @implements TransformerInterface + * @implements TransformerInterface */ final readonly class CategoryLookup implements TransformerInterface { @@ -38,7 +38,9 @@ public function __construct( } /** - * @return RejectionResultBucketInterface + * @param ErrorResponse $response + * + * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface { @@ -54,7 +56,7 @@ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBu } /** - * @return RejectionResultBucketInterface + * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(): RejectionResultBucketInterface { @@ -70,7 +72,7 @@ private function rejectInvalidResponse(): RejectionResultBucketInterface } /** - * @param InputType $line + * @param array $line * * @return OutputType */ @@ -90,7 +92,7 @@ public function transform(): \Generator } if (null === $line[$this->mappingField]) { - $line = yield new AcceptanceResultBucket($this->passThrough($line)); + $line = yield new AcceptanceResultBucket($line); continue; } diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index be2a28c..6a36f9e 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -9,6 +9,7 @@ use Kiboko\Contract\Bucket\RejectionResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; use Kiboko\Magento\Client; +use Kiboko\Magento\Endpoint\GetV1Orders; use Kiboko\Magento\Exception\GetV1OrdersUnauthorizedException; use Kiboko\Magento\Exception\UnexpectedStatusCodeException; use Kiboko\Magento\Model\ErrorResponse; @@ -16,6 +17,7 @@ use Kiboko\Magento\Model\SalesDataOrderSearchResultInterface; use Psr\Http\Client\NetworkExceptionInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; /** * @implements ExtractorInterface @@ -33,14 +35,14 @@ public function __construct( /** * @param array $parameters * - * @return array + * @return array */ private function applyPagination(array $parameters, int $currentPage, int $pageSize): array { return [ ...$parameters, - 'searchCriteria[currentPage]' => (string) $currentPage, - 'searchCriteria[pageSize]' => (string) $pageSize, + 'searchCriteria[currentPage]' => $currentPage, + 'searchCriteria[pageSize]' => $pageSize, ]; } @@ -86,6 +88,27 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej return new RejectionResultBucket($message, null); } + /** + * @param array $parameters + * + * @return RejectionResultBucketInterface + */ + private function rejectUndefinedOptionsResponse(UndefinedOptionsException $response, array $parameters, int $currentPage): RejectionResultBucketInterface + { + $this->logger->error( + $message = 'The result provided by the API client does not match the expected query parameters. The connector compilation may have fetched incompatible versions.', + [ + 'resource' => 'getV1Orders', + 'method' => 'get', + 'queryParameters' => $parameters, + 'currentPage' => $currentPage, + 'pageSize' => $this->pageSize, + ], + ); + + return new RejectionResultBucket($message, null); + } + public function extract(): iterable { foreach ($this->queryParameters->walkVariants() as $parameters) { @@ -99,6 +122,11 @@ public function extract(): iterable return; } + if ($response instanceof UndefinedOptionsException) { + yield $this->rejectUndefinedOptionsResponse($response, $parameters, $currentPage); + + return; + } if (!$response instanceof SalesDataOrderSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); @@ -117,6 +145,11 @@ public function extract(): iterable return; } + if ($response instanceof UndefinedOptionsException) { + yield $this->rejectUndefinedOptionsResponse($response, $parameters, $currentPage); + + return; + } if (!$response instanceof SalesDataOrderSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); diff --git a/tests/CustomerExtractorTest.php b/tests/CustomerExtractorTest.php index 86c6f7c..8189cbe 100644 --- a/tests/CustomerExtractorTest.php +++ b/tests/CustomerExtractorTest.php @@ -11,9 +11,9 @@ use Kiboko\Component\PHPUnitExtension\Assert\ExtractorAssertTrait; use Kiboko\Component\PHPUnitExtension\PipelineRunner; use Kiboko\Contract\Pipeline\PipelineRunnerInterface; -use Kiboko\Magento\V2_3\Client; -use Kiboko\Magento\V2_3\Model\CustomerDataCustomerInterface; -use Kiboko\Magento\V2_3\Model\CustomerDataCustomerSearchResultsInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Model\CustomerDataCustomerInterface; +use Kiboko\Magento\Model\CustomerDataCustomerSearchResultsInterface; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -36,7 +36,7 @@ public function testIsSuccessful(): void $client = $this->createMock(Client::class); $client ->expects($this->once()) - ->method('customerCustomerRepositoryV1GetListGet') + ->method('getV1CustomersSearch') ->willReturn( (new CustomerDataCustomerSearchResultsInterface()) ->setItems([ diff --git a/tests/OrderExtractorTest.php b/tests/OrderExtractorTest.php index fb545ab..eba97d5 100644 --- a/tests/OrderExtractorTest.php +++ b/tests/OrderExtractorTest.php @@ -11,8 +11,8 @@ use Kiboko\Component\PHPUnitExtension\Assert\ExtractorAssertTrait; use Kiboko\Component\PHPUnitExtension\PipelineRunner; use Kiboko\Contract\Pipeline\PipelineRunnerInterface; -use Kiboko\Magento\V2_3\Model\SalesDataOrderInterface; -use Kiboko\Magento\V2_3\Model\SalesDataOrderSearchResultInterface; +use Kiboko\Magento\Model\SalesDataOrderInterface; +use Kiboko\Magento\Model\SalesDataOrderSearchResultInterface; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -27,10 +27,10 @@ public function testIsSuccessful(): void ->setCustomerId(10) ->setTotalQtyOrdered(3); - $client = $this->createMock(\Kiboko\Magento\V2_3\Client::class); + $client = $this->createMock(\Kiboko\Magento\Client::class); $client ->expects($this->once()) - ->method('salesOrderRepositoryV1GetListGet') + ->method('getV1Orders') ->willReturn( (new SalesDataOrderSearchResultInterface) ->setItems([ diff --git a/tests/ProductExtractorTest.php b/tests/ProductExtractorTest.php index 479aa40..7e4b3a7 100644 --- a/tests/ProductExtractorTest.php +++ b/tests/ProductExtractorTest.php @@ -11,9 +11,9 @@ use Kiboko\Component\PHPUnitExtension\Assert\ExtractorAssertTrait; use Kiboko\Component\PHPUnitExtension\PipelineRunner; use Kiboko\Contract\Pipeline\PipelineRunnerInterface; -use Kiboko\Magento\V2_3\Client; -use Kiboko\Magento\V2_3\Model\CatalogDataProductInterface; -use Kiboko\Magento\V2_3\Model\CatalogDataProductSearchResultsInterface; +use Kiboko\Magento\Client; +use Kiboko\Magento\Model\CatalogDataProductInterface; +use Kiboko\Magento\Model\CatalogDataProductSearchResultsInterface; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -31,7 +31,7 @@ public function testIsSuccessful(): void $client = $this->createMock(Client::class); $client ->expects($this->once()) - ->method('catalogProductRepositoryV1GetListGet') + ->method('getV1Products') ->willReturn( (new CatalogDataProductSearchResultsInterface()) ->setItems([ From bcd348062392dc5077b7410e305c47c5b1d06f43 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 2 Feb 2024 14:22:51 +0000 Subject: [PATCH 21/24] [rector] Rector fixes --- src/CategoryLookup.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index c903c08..9697fa5 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -38,8 +38,6 @@ public function __construct( } /** - * @param ErrorResponse $response - * * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface From 9dd01d15b2b8e33894c757705504c2a689a70c4b Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 8 Feb 2024 14:30:35 +0100 Subject: [PATCH 22/24] lock symfony serializer package, symfony 7 change an interface DenormalizerInterface that we used --- composer.lock | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/composer.lock b/composer.lock index 78b4917..2cc1f7f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "971b575cae686edbfb95693562fcdba5", + "content-hash": "de156730306f5951ab27d8facb6802cf", "packages": [ { "name": "clue/stream-filter", @@ -614,27 +614,28 @@ }, { "name": "php-etl/magento2-api-client", - "version": "2.4.x-dev", + "version": "dev-main", "source": { "type": "git", "url": "https://github.com/php-etl/magento2-api-client.git", - "reference": "a5d87f2570540e3dbaf19066a81b0cacf84717f8" + "reference": "0ecc3a7bd084e140351959557c336e19f7d0afe0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/a5d87f2570540e3dbaf19066a81b0cacf84717f8", - "reference": "a5d87f2570540e3dbaf19066a81b0cacf84717f8", + "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/0ecc3a7bd084e140351959557c336e19f7d0afe0", + "reference": "0ecc3a7bd084e140351959557c336e19f7d0afe0", "shasum": "" }, "require": { "jane-php/open-api-runtime": "^7.3", - "php": "^8.0" + "php": "^8.1", + "symfony/serializer": "^6.0" }, "require-dev": { - "jane-php/open-api-2": "^7.5", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.15" + "jane-php/open-api-3": "^7.3", + "phpunit/phpunit": "^9.5" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -643,10 +644,10 @@ }, "autoload": { "psr-4": { - "Kiboko\\Magento\\": [ - "src/", - "generated/" - ] + "Kiboko\\Magento\\V2_1\\": "src/v2_1", + "Kiboko\\Magento\\V2_2\\": "src/v2_2", + "Kiboko\\Magento\\V2_3\\": "src/v2_3", + "Kiboko\\Magento\\V2_4\\": "src/v2_4" } }, "notification-url": "https://packagist.org/downloads/", @@ -662,9 +663,9 @@ "description": "This package provides Jane-PHP generated API models and client based on the OpenAPI specification for Magento 2.3.x", "support": { "issues": "https://github.com/php-etl/magento2-api-client/issues", - "source": "https://github.com/php-etl/magento2-api-client/tree/2.4" + "source": "https://github.com/php-etl/magento2-api-client/tree/main" }, - "time": "2024-02-01T15:52:31+00:00" + "time": "2024-02-08T13:39:21+00:00" }, { "name": "php-etl/mapping-contracts", @@ -4641,9 +4642,7 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": { - "php-etl/magento2-api-client": 20 - }, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { From 602af15541c46883ffc599fcbc2faf09714e3062 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 9 Feb 2024 11:13:19 +0100 Subject: [PATCH 23/24] update api-client-magento to lock symfony/serializer --- composer.lock | 101 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/composer.lock b/composer.lock index 2cc1f7f..114226d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "de156730306f5951ab27d8facb6802cf", + "content-hash": "971b575cae686edbfb95693562fcdba5", "packages": [ { "name": "clue/stream-filter", @@ -614,28 +614,28 @@ }, { "name": "php-etl/magento2-api-client", - "version": "dev-main", + "version": "2.4.x-dev", "source": { "type": "git", "url": "https://github.com/php-etl/magento2-api-client.git", - "reference": "0ecc3a7bd084e140351959557c336e19f7d0afe0" + "reference": "8cd739e2b1da61abcf22ba150d53874854c8cc11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/0ecc3a7bd084e140351959557c336e19f7d0afe0", - "reference": "0ecc3a7bd084e140351959557c336e19f7d0afe0", + "url": "https://api.github.com/repos/php-etl/magento2-api-client/zipball/8cd739e2b1da61abcf22ba150d53874854c8cc11", + "reference": "8cd739e2b1da61abcf22ba150d53874854c8cc11", "shasum": "" }, "require": { "jane-php/open-api-runtime": "^7.3", - "php": "^8.1", + "php": "^8.0", "symfony/serializer": "^6.0" }, "require-dev": { - "jane-php/open-api-3": "^7.3", - "phpunit/phpunit": "^9.5" + "jane-php/open-api-2": "^7.5", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.15" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -644,10 +644,10 @@ }, "autoload": { "psr-4": { - "Kiboko\\Magento\\V2_1\\": "src/v2_1", - "Kiboko\\Magento\\V2_2\\": "src/v2_2", - "Kiboko\\Magento\\V2_3\\": "src/v2_3", - "Kiboko\\Magento\\V2_4\\": "src/v2_4" + "Kiboko\\Magento\\": [ + "src/", + "generated/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -663,9 +663,9 @@ "description": "This package provides Jane-PHP generated API models and client based on the OpenAPI specification for Magento 2.3.x", "support": { "issues": "https://github.com/php-etl/magento2-api-client/issues", - "source": "https://github.com/php-etl/magento2-api-client/tree/main" + "source": "https://github.com/php-etl/magento2-api-client/tree/2.4" }, - "time": "2024-02-08T13:39:21+00:00" + "time": "2024-02-09T10:10:21+00:00" }, { "name": "php-etl/mapping-contracts", @@ -778,21 +778,22 @@ }, { "name": "php-etl/pipeline-contracts", - "version": "v0.4.0", + "version": "v0.4.2", "source": { "type": "git", "url": "https://github.com/php-etl/pipeline-contracts.git", - "reference": "499da7f0d7340cf90dd47f76426e952eb23bf6c2" + "reference": "5a37ebe518d4d85aa964c74b9da068d29e2e9b92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-etl/pipeline-contracts/zipball/499da7f0d7340cf90dd47f76426e952eb23bf6c2", - "reference": "499da7f0d7340cf90dd47f76426e952eb23bf6c2", + "url": "https://api.github.com/repos/php-etl/pipeline-contracts/zipball/5a37ebe518d4d85aa964c74b9da068d29e2e9b92", + "reference": "5a37ebe518d4d85aa964c74b9da068d29e2e9b92", "shasum": "" }, "require": { "php": "^8.2", - "php-etl/bucket-contracts": "0.2.*" + "php-etl/bucket-contracts": "0.2.*", + "php-etl/satellite-contracts": "0.1.*" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.0", @@ -827,9 +828,61 @@ "description": "This library describes contracts for the Extract-Transform-Load pattern.", "support": { "issues": "https://github.com/php-etl/pipeline-contracts/issues", - "source": "https://github.com/php-etl/pipeline-contracts/tree/v0.4.0" + "source": "https://github.com/php-etl/pipeline-contracts/tree/v0.4.2" + }, + "time": "2023-06-12T09:38:03+00:00" + }, + { + "name": "php-etl/satellite-contracts", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-etl/satellite-contracts.git", + "reference": "d2be591800f42e460a59d864888aedf00f111dd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-etl/satellite-contracts/zipball/d2be591800f42e460a59d864888aedf00f111dd7", + "reference": "d2be591800f42e460a59d864888aedf00f111dd7", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "require-dev": { + "rector/rector": "^0.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Kiboko\\Contract\\Satellite\\": "src/" + } }, - "time": "2023-04-17T13:08:18+00:00" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kiboko SAS", + "homepage": "http://kiboko.fr" + }, + { + "name": "Grégory Planchat", + "email": "gregory@kiboko.fr" + } + ], + "description": "This library describes contracts for defining satellite formats", + "support": { + "issues": "https://github.com/php-etl/satellite-contracts/issues", + "source": "https://github.com/php-etl/satellite-contracts/tree/v0.1.1" + }, + "time": "2023-11-20T10:48:56+00:00" }, { "name": "php-http/client-common", @@ -4642,7 +4695,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "php-etl/magento2-api-client": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { From 9e44dc26769217f5b233ebbb8eddd77abdd15e09 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 9 Feb 2024 11:25:02 +0100 Subject: [PATCH 24/24] valid phpstan 7, use rector and cs-fixer --- src/CategoryLookup.php | 8 ++++---- src/OrderExtractor.php | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 9697fa5..e76cc88 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -22,7 +22,7 @@ * @template InputType of array * @template OutputType of InputType|array * - * @implements TransformerInterface + * @implements TransformerInterface */ final readonly class CategoryLookup implements TransformerInterface { @@ -38,7 +38,7 @@ public function __construct( } /** - * @return RejectionResultBucketInterface + * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface { @@ -54,7 +54,7 @@ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBu } /** - * @return RejectionResultBucketInterface + * @return RejectionResultBucketInterface */ private function rejectInvalidResponse(): RejectionResultBucketInterface { @@ -90,7 +90,7 @@ public function transform(): \Generator } if (null === $line[$this->mappingField]) { - $line = yield new AcceptanceResultBucket($line); + $line = yield new AcceptanceResultBucket($this->passThrough($line)); continue; } diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 6a36f9e..80d95c0 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -9,7 +9,6 @@ use Kiboko\Contract\Bucket\RejectionResultBucketInterface; use Kiboko\Contract\Pipeline\ExtractorInterface; use Kiboko\Magento\Client; -use Kiboko\Magento\Endpoint\GetV1Orders; use Kiboko\Magento\Exception\GetV1OrdersUnauthorizedException; use Kiboko\Magento\Exception\UnexpectedStatusCodeException; use Kiboko\Magento\Model\ErrorResponse;