From 5adecfa9fc8bbc8826d6d0fedd3c6fc67c8cd10c Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 7 Dec 2023 15:17:38 +0100 Subject: [PATCH 01/23] 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 70744b3..d241df5 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -26,7 +26,7 @@ public function __construct( ) { } - private function compileQueryParameters(int $currentPage = 1): array + private function compileQueryParameters(int $currentPage = 1) { $parameters = $this->queryParameters; $parameters['searchCriteria[currentPage]'] = $currentPage; @@ -37,31 +37,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 (NetworkExceptionInterface $exception) { $this->logger->alert($exception->getMessage(), ['exception' => $exception]); From d32cc336242c5484992a3ff885747c168f45dda5 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 8 Dec 2023 15:37:39 +0100 Subject: [PATCH 02/23] fix type phpstan, manage api return's cursor with the array of parameters --- src/CategoryLookup.php | 3 ++- src/Lookup.php | 3 ++- src/OrderExtractor.php | 9 +++++---- 3 files changed, 9 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 d241df5..787d3dc 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -26,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; @@ -37,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)); @@ -96,8 +96,9 @@ 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); @@ -108,7 +109,7 @@ public function extract(): iterable yield new RejectionResultBucket([ 'path' => 'order', 'method' => 'get', - 'queryParameters' => $this->compileQueryParameters(), + 'queryParameters' => $this->generateFinalQueryParameters($this->compileQueryParameters(), $this->compileQueryLongParameters()), ]); } catch (\Exception $exception) { $this->logger->critical($exception->getMessage(), ['exception' => $exception]); From 1cdb190fc91c1e6412954e6af8672fdad96ceb88 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 8 Dec 2023 15:38:54 +0100 Subject: [PATCH 03/23] 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 8b0c919..31653aa 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.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 { 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 bd44c8d..5d627f3 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.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 { 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 2c4121e..bc849ca 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.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 { From c03dc1d35a073f45ffaf01b250f9841d9efdcbec Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 10:51:46 +0100 Subject: [PATCH 04/23] 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 3c8721ed467eb287d82acc6b1ffbf61efecfe5a7 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 11:06:04 +0100 Subject: [PATCH 05/23] 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 31653aa..8b0c919 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.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/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 5d627f3..bd44c8d 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.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/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 bc849ca..2c4121e 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.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 { From b0ca2a18a649d7c3653b470e86485a926905d585 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 11:14:41 +0100 Subject: [PATCH 06/23] 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/ProductExtractor.php | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 607e882..8bb4856 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 16dbc6c..a4f6671 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.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/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 2b14536..b00565d 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.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/Lookup.php b/src/Lookup.php index 600da40..ba24019 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/ProductExtractor.php b/src/ProductExtractor.php index 5791296..c85b1d7 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.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 { From 3a5066c69dc2042302d47517d5ebd3349cb24c4d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 11:32:11 +0100 Subject: [PATCH 07/23] 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 1442b14e6ad6fe64ebb36e5615bd8e2e1eb32b0b Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 9 Jan 2024 11:42:00 +0100 Subject: [PATCH 08/23] 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 e973d999404d67a03dd15e77637d01c3e61b7104 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 09/23] Refactored the way filters are handled --- src/CustomerExtractor.php | 62 ++++++----- 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, 596 insertions(+), 97 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 a4f6671..ddfa164 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -6,59 +6,71 @@ 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); @@ -71,7 +83,7 @@ public function extract(): iterable 'context' => [ 'path' => 'customer', 'method' => 'get', - 'queryParameters' => $this->compileQueryParameters(), + 'queryParameters' => $this->walkFilterVariants(), ], ], ); 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 730e90b08fca8d7d02fdfdac8cf4daf90b401765 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 18 Jan 2024 09:52:22 +0000 Subject: [PATCH 10/23] [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 c0a80eef4b5dc8776d01a1854890b7a602171d0f Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 19 Jan 2024 14:53:14 +0100 Subject: [PATCH 11/23] refacto extractors, add withGroups and withFilters methods --- src/CustomerExtractor.php | 14 ++---- src/FilterGroup.php | 9 ++++ src/InvoiceExtractor.php | 73 +++++++++++++++------------ src/OrderExtractor.php | 103 +++++++++++++------------------------- src/ProductExtractor.php | 73 +++++++++++++++------------ src/QueryParameters.php | 9 ++++ 6 files changed, 140 insertions(+), 141 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 b00565d..e391831 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -12,53 +12,62 @@ 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); @@ -71,9 +80,9 @@ public function extract(): iterable 'context' => [ 'path' => 'invoice', 'method' => 'get', - 'queryParameters' => $this->compileQueryParameters(), + '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.', diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 54b8ec2..1af5f48 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,17 +62,15 @@ 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( @@ -113,9 +80,9 @@ public function extract(): iterable 'context' => [ 'path' => 'order', 'method' => 'get', - 'queryParameters' => $this->generateFinalQueryParameters($this->compileQueryParameters(), $this->compileQueryLongParameters()), + '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.', diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index c85b1d7..b8c99de 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -12,53 +12,62 @@ 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); @@ -71,9 +80,9 @@ public function extract(): iterable 'context' => [ 'path' => 'product', 'method' => 'get', - 'queryParameters' => $this->compileQueryParameters(), + '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.', 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 afc78d585c00c9cb279429eb733ecbdcbcdbecde Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 23 Jan 2024 16:22:42 +0100 Subject: [PATCH 12/23] manage page count, fix extractor to use api pagination --- src/CustomerExtractor.php | 15 +++++++-------- src/Filter/ArrayFilter.php | 7 ++++--- src/Filter/ScalarFilter.php | 5 +++-- src/FilterGroup.php | 14 +++++++------- src/InvoiceExtractor.php | 16 ++++++++-------- src/OrderExtractor.php | 14 +++++++------- src/ProductExtractor.php | 14 +++++++------- src/QueryParameters.php | 4 ++-- 8 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index 48f1d3a..b15a0da 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -6,7 +6,6 @@ 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; @@ -53,6 +52,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 +63,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/Filter/ArrayFilter.php b/src/Filter/ArrayFilter.php index e623bfd..a23dda1 100644 --- a/src/Filter/ArrayFilter.php +++ b/src/Filter/ArrayFilter.php @@ -11,18 +11,19 @@ public function __construct( public string $conditionType, public array $value, private readonly int $threshold = 200 - ) {} + ) { + } /** * @return \Traversable */ public function getIterator(): \Traversable { - $length = count($this->value); + $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)), + 'value' => implode(',', \array_slice($this->value, $offset, $this->threshold, false)), 'conditionType' => $this->conditionType, ]; } diff --git a/src/Filter/ScalarFilter.php b/src/Filter/ScalarFilter.php index 1cf5e5c..ab73b4e 100644 --- a/src/Filter/ScalarFilter.php +++ b/src/Filter/ScalarFilter.php @@ -9,8 +9,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, + ) { + } public function getIterator(): \Traversable { diff --git a/src/FilterGroup.php b/src/FilterGroup.php index b41c06f..1d2d015 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -32,7 +32,7 @@ public function withFilters(FilterInterface ...$filter): self */ public function walkFilters(array $parameters, int $groupIndex = 0): \Traversable { - if (count($this->filters) < 1) { + if (\count($this->filters) < 1) { return; } @@ -48,10 +48,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; @@ -59,22 +59,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/InvoiceExtractor.php b/src/InvoiceExtractor.php index e391831..afc3e4d 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -22,7 +22,7 @@ public function __construct( private function walkFilterVariants(int $currentPage = 1): \Traversable { - yield from [ + yield from [ ...$this->queryParameters->walkVariants([]), ...[ 'searchCriteria[currentPage]' => $currentPage, @@ -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 1af5f48..d91d243 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 b8c99de..4a29391 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( diff --git a/src/QueryParameters.php b/src/QueryParameters.php index c1fd9a2..3fb6045 100644 --- a/src/QueryParameters.php +++ b/src/QueryParameters.php @@ -30,7 +30,7 @@ public function withGroups(FilterGroup ...$group): self */ public function walkVariants(array $parameters): \Traversable { - if (count($this->groups) < 1) { + if (\count($this->groups) < 1) { return; } @@ -40,7 +40,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 047ae6ef4018de18556bc4d2c0ee4f6e1fd5dc28 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 24 Jan 2024 10:12:57 +0100 Subject: [PATCH 13/23] 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 79303890f0d9a2898b3aa3ae4b1755bcf9fcd659 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 24 Jan 2024 09:33:50 +0000 Subject: [PATCH 14/23] [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 2d31025..384f9ea 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 4e43193..03db481 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 cec2941..2931a46 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 d9edd2499304abd1d5d441216de282a67599c7e8 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 15/23] Refactored the way filters are handled --- .github/workflows/phpstan-7.yaml | 23 --- .../{phpstan-6.yaml => phpstan-9.yaml} | 6 +- .github/workflows/quality.yaml | 4 +- phpstan.neon | 2 +- src/CategoryLookup.php | 146 +++++++++++--- src/CustomerExtractor.php | 177 ++++++++++++----- src/Filter.php | 0 src/Filter/ArrayFilter.php | 17 +- src/Filter/FilterInterface.php | 2 +- src/Filter/ScalarFilter.php | 8 +- src/FilterGroup.php | 8 +- src/InvoiceExtractor.php | 170 +++++++++++----- src/Lookup.php | 69 ------- src/OrderExtractor.php | 170 +++++++++++----- src/ProductExtractor.php | 162 ++++++++++----- src/ProductOptionsLookup.php | 187 ++++++++++++++++++ src/QueryParameters.php | 9 +- tests/QueryParametersTest.php | 38 ++-- 18 files changed, 834 insertions(+), 364 deletions(-) delete mode 100644 .github/workflows/phpstan-7.yaml rename .github/workflows/{phpstan-6.yaml => phpstan-9.yaml} (90%) delete mode 100644 src/Filter.php 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 4505ba2..0000000 --- a/.github/workflows/phpstan-7.yaml +++ /dev/null @@ -1,23 +0,0 @@ -name: PHPStan level 7 -on: push -jobs: - phpstan-7: - 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 b6bd269..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-6: + 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 c3b759e..7e391e1 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -1,4 +1,4 @@ -name: Quality (PHPStan lvl 5) +name: Quality (PHPStan lvl 7) on: push jobs: cs-fixer: @@ -32,4 +32,4 @@ jobs: uses: php-actions/phpstan@v3 with: path: src/ - level: 5 + 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 6601498..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\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->getV1CategoriesCategoryId( - categoryId: (int) $line[$this->mappingField], - ); + $lookup = $this->client->getV1CategoriesCategoryId( + categoryId: (int) $line[$this->mappingField], + ); - if (!$lookup instanceof \Kiboko\Magento\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]); + } 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( - sprintf('Something went wrong in the attempt to recover the category with id %d', (int) $line[$this->mappingField]), + '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, - $line + $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 91105c6..10d8c7a 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -6,95 +6,166 @@ 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; +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\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->getV1CustomersSearch( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\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->getV1CustomersSearch( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + 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\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.php b/src/Filter.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/Filter/ArrayFilter.php b/src/Filter/ArrayFilter.php index a23dda1..5bf0fcd 100644 --- a/src/Filter/ArrayFilter.php +++ b/src/Filter/ArrayFilter.php @@ -4,26 +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 ab73b4e..7e8bc1d 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( @@ -13,11 +16,14 @@ public function __construct( ) { } + /** + * @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 1d2d015..ada469b 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 384f9ea..42ce879 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -6,95 +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\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->getV1Invoices( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\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->getV1Invoices( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + 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\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 (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 6812bb6..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->getV1ProductsAttributesAttributeCodeOptions( - attributeCode: $this->attributeCode, - ); - - $lookup = array_values(array_filter($results, fn (object $item) => $item->getValue() === $line[$this->mappingField]))[0]; - - if (!$lookup instanceof \Kiboko\Magento\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( - sprintf('Something went wrong in the attempt to recover the attribute option for attribute %s', $this->attributeCode), - $exception, - $line - ); - continue; - } - - $output = ($this->mapper)($lookup, $line); - - $line = yield new AcceptanceResultBucket($output); - } - } -} diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 03db481..278acba 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -6,95 +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\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->getV1Orders( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\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->getV1Orders( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + 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\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 (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 2931a46..a02f673 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -6,95 +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\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->getV1Products( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + queryParameters: $this->applyPagination($parameters, $currentPage, $this->pageSize), ); - $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); - - if (!$response instanceof \Kiboko\Magento\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->getV1Products( - queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize), + 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\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 (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 3fb6045..ba7093f 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 784d8a4a326f8021576cbf3a567cb455953f5071 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 30 Jan 2024 11:38:17 +0000 Subject: [PATCH 16/23] [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 10d8c7a..b567bd2 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -46,7 +46,6 @@ private function applyPagination(array $parameters, int $currentPage, int $pageS } /** - * @param ErrorResponse $response * @param array $parameters * @return RejectionResultBucketInterface */ @@ -67,7 +66,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 42ce879..1175fa3 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 51dbb92c67a6daa581ac132c7d44359abb4edd2a 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 17/23] 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 b567bd2..f3fe916 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -54,7 +54,7 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, $this->logger->error( $response->getMessage(), [ - 'resource' => 'getV1Orders', + 'resource' => 'getV1CustomersSearch', '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' => 'getV1CustomersSearch', 'method' => 'get', 'queryParameters' => $parameters, 'currentPage' => $currentPage, diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index 1175fa3..a352af7 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -52,7 +52,7 @@ private function rejectErrorResponse(ErrorResponse $response, array $parameters, $this->logger->error( $response->getMessage(), [ - 'resource' => 'getV1Orders', + 'resource' => 'getV1Invoices', 'method' => 'get', 'queryParameters' => $parameters, 'currentPage' => $currentPage, @@ -71,7 +71,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 0ce5f65cbc5848d5df70bdfa9c35a2a0c3650d1b 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/23] Changed the variadic methods to use array_push instead of foreach --- src/FilterGroup.php | 4 +--- src/QueryParameters.php | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/FilterGroup.php b/src/FilterGroup.php index ada469b..6daaf6d 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -21,9 +21,7 @@ public function withFilter(FilterInterface $filter): self public function withFilters(FilterInterface ...$filter): self { - foreach ($filter as $item) { - $this->filters[] = $item; - } + array_push($this->filters, ...$filter); return $this; } diff --git a/src/QueryParameters.php b/src/QueryParameters.php index ba7093f..7240e55 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 912c39ca62cd9cc215875aa3b6ec34b1d31fd637 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 2 Feb 2024 15:04:17 +0100 Subject: [PATCH 19/23] remove a cast on string, add a catch for UndefinedOptionsException --- composer.lock | 13 ++++++----- src/CategoryLookup.php | 13 ++++++++--- src/OrderExtractor.php | 49 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/composer.lock b/composer.lock index e91539c..9008579 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 5a46db8..261a04c 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 @@ -37,6 +38,8 @@ public function __construct( } /** + * @param ErrorResponse $response + * * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBucketInterface @@ -48,6 +51,7 @@ private function rejectErrorResponse(ErrorResponse $response): RejectionResultBu 'method' => 'get', ], ); + return new RejectionResultBucket($response->getMessage(), null); } @@ -63,16 +67,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,13 +86,13 @@ public function transform(): \Generator { $line = yield new EmptyResultBucket(); while (true) { - if ($line === null) { + if (null === $line) { $line = yield new EmptyResultBucket(); continue; } if (null === $line[$this->mappingField]) { - $line = yield new AcceptanceResultBucket($this->passThrough($line)); + $line = yield new AcceptanceResultBucket($line); continue; } @@ -154,6 +160,7 @@ public function transform(): \Generator $exception, $this->passThrough($line), ); + return; } diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 35908ee..70c2277 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -16,6 +16,7 @@ use Kiboko\Magento\Model\SalesDataOrderSearchResultInterface; use Psr\Http\Client\NetworkExceptionInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException; /** * @implements ExtractorInterface @@ -32,19 +33,21 @@ public function __construct( /** * @param array $parameters + * * @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, ]; } /** * @param array $parameters + * * @return RejectionResultBucketInterface */ private function rejectErrorResponse(ErrorResponse $response, array $parameters, int $currentPage): RejectionResultBucketInterface @@ -59,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 @@ -78,6 +83,28 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej 'pageSize' => $this->pageSize, ], ); + + 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); } @@ -91,10 +118,17 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + + return; + } + if ($response instanceof UndefinedOptionsException) { + yield $this->rejectUndefinedOptionsResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof SalesDataOrderSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } $pageCount = (int) ceil($response->getTotalCount() / $this->pageSize); @@ -107,10 +141,17 @@ public function extract(): iterable ); if ($response instanceof ErrorResponse) { yield $this->rejectErrorResponse($response, $parameters, $currentPage); + + return; + } + if ($response instanceof UndefinedOptionsException) { + yield $this->rejectUndefinedOptionsResponse($response, $parameters, $currentPage); + return; } if (!$response instanceof SalesDataOrderSearchResultInterface) { yield $this->rejectInvalidResponse($parameters, $currentPage); + return; } @@ -132,6 +173,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 +181,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 +189,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 +197,7 @@ public function extract(): iterable 'The client failed critically. Aborting. Please contact customer support or your system administrator.', $exception, ); + return; } } From 8f501aa9f1ecae2773131650c83a05695ad86bda Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 2 Feb 2024 15:15:35 +0000 Subject: [PATCH 20/23] [rector] Rector fixes --- src/CategoryLookup.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 261a04c..1e57336 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 dca47a4d31ff6a8b77e9345c6f08e0b66806bc4d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 31 Jan 2024 09:44:28 +0100 Subject: [PATCH 21/23] Fix client api version to 2.4 --- src/CustomerExtractor.php | 14 ++++++++++++++ src/FilterGroup.php | 6 ++++-- src/InvoiceExtractor.php | 13 +++++++++++++ src/ProductExtractor.php | 12 ++++++++++++ src/ProductOptionsLookup.php | 13 +++++++++---- src/QueryParameters.php | 4 +++- 6 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/CustomerExtractor.php b/src/CustomerExtractor.php index f3fe916..7173c5c 100644 --- a/src/CustomerExtractor.php +++ b/src/CustomerExtractor.php @@ -34,6 +34,7 @@ public function __construct( /** * @param array $parameters + * * @return array */ private function applyPagination(array $parameters, int $currentPage, int $pageSize): array @@ -47,6 +48,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 @@ -61,11 +63,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 @@ -80,6 +84,7 @@ private function rejectInvalidResponse(array $parameters, int $currentPage): Rej 'pageSize' => $this->pageSize, ], ); + return new RejectionResultBucket($message, null); } @@ -93,10 +98,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); @@ -109,10 +116,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; } @@ -134,6 +143,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]); @@ -141,6 +151,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]); @@ -148,6 +159,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]); @@ -155,6 +167,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]); @@ -162,6 +175,7 @@ public function extract(): iterable 'The client failed critically. Aborting. Please contact customer support or your system administrator.', $exception, ); + return; } } diff --git a/src/FilterGroup.php b/src/FilterGroup.php index 6daaf6d..71a3aa1 100644 --- a/src/FilterGroup.php +++ b/src/FilterGroup.php @@ -19,15 +19,16 @@ public function withFilter(FilterInterface $filter): self return $this; } - public function withFilters(FilterInterface ...$filter): self + public function withFilters(FilterInterface ...$filters): self { - array_push($this->filters, ...$filter); + 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/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 90832555a55b426c85f00d83d5438058cd4f023c Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 9 Feb 2024 11:14:05 +0100 Subject: [PATCH 22/23] update api-client-magento to lock symfony/serializer --- composer.lock | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 9008579..611145c 100644 --- a/composer.lock +++ b/composer.lock @@ -618,17 +618,18 @@ "source": { "type": "git", "url": "https://github.com/php-etl/magento2-api-client.git", - "reference": "a5d87f2570540e3dbaf19066a81b0cacf84717f8" + "reference": "8cd739e2b1da61abcf22ba150d53874854c8cc11" }, "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/8cd739e2b1da61abcf22ba150d53874854c8cc11", + "reference": "8cd739e2b1da61abcf22ba150d53874854c8cc11", "shasum": "" }, "require": { "jane-php/open-api-runtime": "^7.3", - "php": "^8.0" + "php": "^8.0", + "symfony/serializer": "^6.0" }, "require-dev": { "jane-php/open-api-2": "^7.5", @@ -664,7 +665,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-02-01T15:52:31+00:00" + "time": "2024-02-09T10:10:21+00:00" }, { "name": "php-etl/mapping-contracts", @@ -4704,5 +4705,5 @@ "php": "^8.2" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From edfe5299e165f6ea72ed00fc105a0cc4dd1dad16 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 9 Feb 2024 11:21:14 +0100 Subject: [PATCH 23/23] valid phpstan 7, use rector and cs-fixer --- src/CategoryLookup.php | 2 +- src/CustomerExtractor.php | 7 +++---- src/InvoiceExtractor.php | 6 +++--- src/OrderExtractor.php | 2 +- src/ProductExtractor.php | 6 +++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/CategoryLookup.php b/src/CategoryLookup.php index 1e57336..7440e50 100644 --- a/src/CategoryLookup.php +++ b/src/CategoryLookup.php @@ -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/CustomerExtractor.php b/src/CustomerExtractor.php index 7173c5c..946c8e9 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; @@ -35,14 +34,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, ]; } diff --git a/src/InvoiceExtractor.php b/src/InvoiceExtractor.php index bcda827..7a7cbc7 100644 --- a/src/InvoiceExtractor.php +++ b/src/InvoiceExtractor.php @@ -33,14 +33,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, ]; } diff --git a/src/OrderExtractor.php b/src/OrderExtractor.php index 70c2277..211b40c 100644 --- a/src/OrderExtractor.php +++ b/src/OrderExtractor.php @@ -34,7 +34,7 @@ public function __construct( /** * @param array $parameters * - * @return array + * @return array */ private function applyPagination(array $parameters, int $currentPage, int $pageSize): array { diff --git a/src/ProductExtractor.php b/src/ProductExtractor.php index f6e7054..7ea4dab 100644 --- a/src/ProductExtractor.php +++ b/src/ProductExtractor.php @@ -32,14 +32,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, ]; }