Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
59dc7e2
Add logical cursor for long query parameters
JoMessina Dec 7, 2023
e7db160
fix type phpstan, manage api return's cursor with the array of parame…
JoMessina Dec 8, 2023
cb0cc3e
remove IGNORE_ENV_TRUE parameter for cs-fixer
JoMessina Dec 8, 2023
2b2443f
Ignore a rule from phpstan
JoMessina Jan 9, 2024
a3cae97
fix from cs fixer
JoMessina Jan 9, 2024
49487d4
change phpstan config for level 5
JoMessina Jan 9, 2024
1deb047
Change a method name
JoMessina Jan 9, 2024
7739f08
Refactored the way filters are handled
gplanchat Jan 18, 2024
fa3110f
[rector] Rector fixes
actions-user Jan 18, 2024
8557f04
refacto extractors, add withGroups and withFilters methods
JoMessina Jan 19, 2024
76e3b15
manage page count, fix extractor to use api pagination
JoMessina Jan 23, 2024
1bf121b
fix and add tests for extractors
JoMessina Jan 24, 2024
6d22048
[rector] Rector fixes
actions-user Jan 24, 2024
68b0dae
Refactored the way filters are handled
gplanchat Jan 30, 2024
b9ba56b
Fixed errors in the unit tests
gplanchat Jan 30, 2024
ef45ba6
Fix client api version to 2.4
JoMessina Jan 30, 2024
c67ad88
[rector] Rector fixes
actions-user Jan 31, 2024
b110c44
Changed the variadic methods to use array_push instead of foreach
gplanchat Jan 30, 2024
3c3a255
Merge remote-tracking branch 'origin/feature/add-cursor-for-long-requ…
JoMessina Jan 31, 2024
77b53da
Fix client api version to 2.4
JoMessina Jan 31, 2024
1ebac38
fix annotations for phpstan, fix endpoints on tests
JoMessina Feb 2, 2024
bcd3480
[rector] Rector fixes
actions-user Feb 2, 2024
9dd01d1
lock symfony serializer package, symfony 7 change an interface Denorm…
JoMessina Feb 8, 2024
602af15
update api-client-magento to lock symfony/serializer
JoMessina Feb 9, 2024
9e44dc2
valid phpstan 7, use rector and cs-fixer
JoMessina Feb 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
parameters:
level: 5
treatPhpDocTypesAsCertain: false
3 changes: 2 additions & 1 deletion src/CategoryLookup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
103 changes: 65 additions & 38 deletions src/CustomerExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,91 @@

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
final readonly 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 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->customerCustomerRepositoryV1GetListGet(
queryParameters: $this->compileQueryParameters(),
);

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);

$currentPage = 1;
$pageCount = ceil($response->getTotalCount() / $this->pageSize);
while ($currentPage++ < $pageCount) {
foreach ($this->queryParameters->walkVariants([]) as $parameters) {
$currentPage = 1;
$response = $this->client->customerCustomerRepositoryV1GetListGet(
queryParameters: $this->compileQueryParameters($currentPage),
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
&& !$response instanceof \Kiboko\Magento\V2_3\Model\CustomerDataCustomerSearchResultsInterface
&& !$response instanceof \Kiboko\Magento\V2_4\Model\CustomerDataCustomerSearchResultsInterface
) {
return;
}

yield $this->processResponse($response);

while ($currentPage++ < $pageCount) {
$response = $this->client->customerCustomerRepositoryV1GetListGet(
queryParameters: $this->applyPagination(iterator_to_array($parameters), $currentPage, $this->pageSize),
);

yield $this->processResponse($response);
}
}
} catch (NetworkExceptionInterface $exception) {
$this->logger->alert(
$exception->getMessage(),
[
'exception' => $exception,
'context' => [
'path' => 'customer',
'method' => 'get',
'queryParameters' => $this->walkFilterVariants(),
],
],
);
yield new RejectionResultBucket(
'There are some network difficulties. We could not properly connect to the Magento API. There is nothing we could no to fix this currently. Please contact the Magento administrator.',
$exception,
);
} catch (\Exception $exception) {
$this->logger->alert($exception->getMessage(), ['exception' => $exception]);
$this->logger->critical($exception->getMessage(), ['exception' => $exception]);
}
}

Expand All @@ -74,7 +101,7 @@ private function processResponse($response): ResultBucketInterface
|| $response instanceof \Kiboko\Magento\V2_3\Model\ErrorResponse
|| $response instanceof \Kiboko\Magento\V2_4\Model\ErrorResponse
) {
return new RejectionResultBucket($response);
return new RejectionResultBucket($response->getMessage(), null, $response);
}

return new AcceptanceResultBucket(...$response->getItems());
Expand Down
15 changes: 0 additions & 15 deletions src/Filter.php

This file was deleted.

30 changes: 30 additions & 0 deletions src/Filter/ArrayFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Flow\Magento2\Filter;

final class ArrayFilter implements FilterInterface, \IteratorAggregate
{
public function __construct(
public string $field,
public string $conditionType,
public array $value,
private readonly int $threshold = 200
) {}

/**
* @return \Traversable<int, {field: string, value: string, conditionType: string}>
*/
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,
];
}
}
}
12 changes: 12 additions & 0 deletions src/Filter/FilterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Flow\Magento2\Filter;

/**
* @extends \Traversable<int, {field: string, value: string, conditionType: string}>
*/
interface FilterInterface extends \Traversable
{
}
23 changes: 23 additions & 0 deletions src/Filter/ScalarFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Kiboko\Component\Flow\Magento2\Filter;

final class ScalarFilter implements FilterInterface, \IteratorAggregate
{
public function __construct(
public string $field,
public string $conditionType,
public bool|int|float|string|\DateTimeInterface $value,
) {}

public function getIterator(): \Traversable
{
yield [
'field' => $this->field,
'value' => $this->value,
'conditionType' => $this->conditionType,
];
}
}
74 changes: 52 additions & 22 deletions src/FilterGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,78 @@

namespace Kiboko\Component\Flow\Magento2;

use Kiboko\Component\Flow\Magento2\Filter\FilterInterface;
use Kiboko\Component\Flow\Magento2\Filter\ScalarFilter;

class FilterGroup
{
private array $filters = [];

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 withFilters(Filter ...$filters): self
public function withFilters(FilterInterface ...$filter): self
{
array_walk($filters, fn (Filter $filter) => $this->filters[] = [
'field' => $filter->field,
'value' => $filter->value,
'condition_type' => $filter->conditionType,
]);
foreach ($filter as $item) {
$this->filters[] = $item;
}

return $this;
}

public function compileFilters(int $groupIndex = 0): array
/**
* @return \Traversable<int, array>
*/
public function walkFilters(array $parameters, int $groupIndex = 0): \Traversable
{
if (count($this->filters) < 1) {
return;
}

yield from $this->buildFilters($parameters, $groupIndex, 1, ...$this->filters);
}

private function buildFilters(array $parameters, int $groupIndex, int $filterIndex, FilterInterface $first, FilterInterface ...$next): \Traversable
{
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'],
]
];

if (count($next) >= 1) {
yield from $this->buildFilters($childParameters, $groupIndex, $filterIndex + 1, ...$next);
} else {
yield $childParameters;
}
}
}

public function greaterThan(string $field, int|float|string|\DateTimeInterface $value): self
{
return $this->withFilter(new ScalarFilter($field, 'gt', $value));
}

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));
}
}
Loading