From 88c5c030815540836a5931e1edcca6f0cf107a09 Mon Sep 17 00:00:00 2001 From: Nicolas MELONI Date: Tue, 31 Oct 2023 07:55:44 +0100 Subject: [PATCH] Product Group processing deported to product models import --- src/Component/Cache/CacheKey.php | 18 ++++ .../ProductGroup/ProductGroupProcessor.php | 88 +++++++++++++++++ src/Retriever/FamilyRetriever.php | 97 +++++++++++-------- src/Retriever/FamilyRetrieverInterface.php | 2 + src/Retriever/FamilyVariantRetriever.php | 33 ++++--- .../FamilyVariantRetrieverInterface.php | 10 ++ src/Task/Attribute/BatchAttributesTask.php | 19 ++-- .../ProcessProductGroupModelTask.php | 4 + .../ProductModel/BatchProductModelTask.php | 45 ++++++++- .../ProcessProductGroupModelTaskTest.php | 12 +++ .../family_variant_clothing_color_size.json | 2 +- 11 files changed, 257 insertions(+), 73 deletions(-) create mode 100644 src/Component/Cache/CacheKey.php create mode 100644 src/Processor/ProductGroup/ProductGroupProcessor.php create mode 100644 src/Retriever/FamilyVariantRetrieverInterface.php diff --git a/src/Component/Cache/CacheKey.php b/src/Component/Cache/CacheKey.php new file mode 100644 index 00000000..bec3f893 --- /dev/null +++ b/src/Component/Cache/CacheKey.php @@ -0,0 +1,18 @@ +createProductGroups($resource); + $this->familyVariationAxeProcessor->process($resource); + } + + private function createGroupForCodeAndFamily( + string $code, + string $family, + string $familyVariant, + ?string $parent = null, + ): ProductGroupInterface { + if (isset($this->productGroupsMapping[$code])) { + return $this->productGroupsMapping[$code]; + } + + $productGroup = $this->productGroupRepository->findOneBy(['model' => $code]); + + if ($productGroup instanceof ProductGroupInterface) { + $this->productGroupsMapping[$code] = $productGroup; + + $this->logger->info(sprintf( + 'Skipping ProductGroup "%s" for family "%s" as it already exists.', + $code, + $family, + )); + + $productGroup->setParent($this->productGroupsMapping[$parent] ?? null); + $productGroup->setModel($code); + $productGroup->setFamily($family); + $productGroup->setFamilyVariant($familyVariant); + $this->entityManager->persist($productGroup); + + return $productGroup; + } + + $this->logger->info(sprintf( + 'Creating ProductGroup "%s" for family "%s"', + $code, + $family, + )); + + /** @var ProductGroupInterface $productGroup */ + $productGroup = $this->productGroupFactory->createNew(); + $productGroup->setParent($this->productGroupsMapping[$parent] ?? null); + $productGroup->setModel($code); + $productGroup->setFamily($family); + $productGroup->setFamilyVariant($familyVariant); + $this->entityManager->persist($productGroup); + $this->productGroupsMapping[$code] = $productGroup; + + return $productGroup; + } + + private function createProductGroups(array $resource): void + { + if (null !== $resource['parent']) { + $this->createGroupForCodeAndFamily($resource['parent'], $resource['family'], $resource['family_variant']); + } + + if (null !== $resource['code']) { + $this->createGroupForCodeAndFamily($resource['code'], $resource['family'], $resource['family_variant'], $resource['parent']); + } + } +} diff --git a/src/Retriever/FamilyRetriever.php b/src/Retriever/FamilyRetriever.php index bd82ab2c..61607d55 100644 --- a/src/Retriever/FamilyRetriever.php +++ b/src/Retriever/FamilyRetriever.php @@ -6,77 +6,88 @@ use Akeneo\Pim\ApiClient\AkeneoPimClientInterface; use Psr\Log\LoggerInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Synolia\SyliusAkeneoPlugin\Component\Cache\CacheKey; use Synolia\SyliusAkeneoPlugin\Provider\Configuration\Api\ApiConnectionProviderInterface; final class FamilyRetriever implements FamilyRetrieverInterface { - /** @var array */ - private array $familiesByVariant = []; - private array $families = []; + private array $familiesByCode = []; + + private array $familiesByVariantCode = []; + public function __construct( private AkeneoPimClientInterface $akeneoPimClient, private LoggerInterface $logger, private ApiConnectionProviderInterface $apiConnectionProvider, + private CacheInterface $akeneoFamilies, + private CacheInterface $akeneoFamily, + private FamilyVariantRetriever $familyVariantRetriever, ) { } - public function getFamily(string $familyCode): array + public function getFamilies(): array { - if (\array_key_exists($familyCode, $this->families)) { - return $this->families[$familyCode]; + if ($this->families !== []) { + return $this->families; } - $paginationSize = $this->apiConnectionProvider->get()->getPaginationSize(); + /** @phpstan-ignore-next-line */ + return $this->families = $this->akeneoFamilies->get(CacheKey::FAMILIES, function (): array { + $families = []; - $families = $this->akeneoPimClient->getFamilyApi()->all($paginationSize); + $paginationSize = $this->apiConnectionProvider->get()->getPaginationSize(); - /** @var array{code: string} $family */ - foreach ($families as $family) { - $this->families[$family['code']] = $family; - } + $results = $this->akeneoPimClient->getFamilyApi()->all($paginationSize); - return $this->families[$familyCode]; + /** @var array{code: string} $result */ + foreach ($results as $result) { + $families[$result['code']] = $result; + } + + return $families; + }); } - public function getFamilyCodeByVariantCode(string $familyVariantCode): string + public function getFamily(string $familyCode): array { - if (\array_key_exists($familyVariantCode, $this->familiesByVariant)) { - return $this->familiesByVariant[$familyVariantCode]; + if (array_key_exists($familyCode, $this->familiesByCode)) { + return $this->familiesByCode[$familyCode]; } - $paginationSize = $this->apiConnectionProvider->get()->getPaginationSize(); - - try { - $families = $this->akeneoPimClient->getFamilyApi()->all($paginationSize); - - /** @var array{code: string} $family */ - foreach ($families as $family) { - if (!\array_key_exists($family['code'], $this->families)) { - $this->families[$family['code']] = $family; - } - - $familyVariants = $this->akeneoPimClient->getFamilyVariantApi()->all($family['code'], $paginationSize); - if (!$familyVariants->valid()) { - continue; - } + /** @phpstan-ignore-next-line */ + return $this->familiesByCode[$familyCode] = $this->akeneoFamily->get(\sprintf(CacheKey::FAMILY, $familyCode), function () use ($familyCode): array { + return $this->getFamilies()[$familyCode]; + }); + } - /** @var array{code: string} $familyVariant */ - foreach ($familyVariants as $familyVariant) { - $this->familiesByVariant[$familyVariant['code']] = $family['code']; - } + public function getFamilyCodeByVariantCode(string $familyVariantCode): string + { + if (array_key_exists($familyVariantCode, $this->familiesByVariantCode)) { + return $this->familiesByVariantCode[$familyVariantCode]; + } - if (isset($this->familiesByVariant[$familyVariantCode])) { - return $family['code']; + /** @phpstan-ignore-next-line */ + return $this->familiesByVariantCode[$familyVariantCode] = $this->akeneoFamily->get(\sprintf(CacheKey::FAMILY_BY_VARIANT_CODE, $familyVariantCode), function () use ($familyVariantCode): string { + try { + /** @var array{code: string} $family */ + foreach ($this->getFamilies() as $family) { + /** @var array{code: string} $familyVariant */ + foreach ($this->familyVariantRetriever->getVariants($family['code']) as $familyVariant) { + if ($familyVariant['code'] === $familyVariantCode) { + return $family['code']; + } + } } + } catch (\Throwable $exception) { + $this->logger->warning($exception->getMessage(), [ + 'exception' => $exception, + ]); } - } catch (\Throwable $exception) { - $this->logger->warning($exception->getMessage(), [ - 'exception' => $exception, - ]); - } - throw new \LogicException(sprintf('Unable to find family for variant "%s"', $familyVariantCode)); + throw new \LogicException(sprintf('Unable to find family for variant "%s"', $familyVariantCode)); + }); } } diff --git a/src/Retriever/FamilyRetrieverInterface.php b/src/Retriever/FamilyRetrieverInterface.php index 61649b88..44fe15a8 100644 --- a/src/Retriever/FamilyRetrieverInterface.php +++ b/src/Retriever/FamilyRetrieverInterface.php @@ -6,6 +6,8 @@ interface FamilyRetrieverInterface { + public function getFamilies(): array; + public function getFamilyCodeByVariantCode(string $familyVariantCode): string; public function getFamily(string $familyCode): array; diff --git a/src/Retriever/FamilyVariantRetriever.php b/src/Retriever/FamilyVariantRetriever.php index 176d91ff..867e54d5 100644 --- a/src/Retriever/FamilyVariantRetriever.php +++ b/src/Retriever/FamilyVariantRetriever.php @@ -6,39 +6,42 @@ use Akeneo\Pim\ApiClient\AkeneoPimClientInterface; use Psr\Log\LoggerInterface; +use Symfony\Contracts\Cache\CacheInterface; +use Synolia\SyliusAkeneoPlugin\Component\Cache\CacheKey; use Synolia\SyliusAkeneoPlugin\Provider\Configuration\Api\ApiConnectionProviderInterface; -final class FamilyVariantRetriever +final class FamilyVariantRetriever implements FamilyVariantRetrieverInterface { - private array $familyVariants = []; + private array $variantsByFamily = []; public function __construct( private AkeneoPimClientInterface $akeneoPimClient, private LoggerInterface $logger, private ApiConnectionProviderInterface $apiConnectionProvider, + private CacheInterface $akeneoFamilyVariants, ) { } public function getVariants(string $familyCode): array { - if (\array_key_exists($familyCode, $this->familyVariants)) { - return $this->familyVariants[$familyCode]; + if ($this->variantsByFamily !== []) { + return $this->variantsByFamily[$familyCode] ?? []; } - $paginationSize = $this->apiConnectionProvider->get()->getPaginationSize(); + /** @phpstan-ignore-next-line */ + return $this->variantsByFamily[$familyCode] = $this->akeneoFamilyVariants->get(\sprintf(CacheKey::FAMILY_VARIANTS, $familyCode), function () use ($familyCode): array { + $paginationSize = $this->apiConnectionProvider->get()->getPaginationSize(); - try { - $familyVariants = $this->akeneoPimClient->getFamilyVariantApi()->all($familyCode, $paginationSize); + try { + $results = $this->akeneoPimClient->getFamilyVariantApi()->all($familyCode, $paginationSize); + $familyVariants = iterator_to_array($results); + } catch (\Throwable $exception) { + $this->logger->warning($exception->getMessage()); - $this->familyVariants[$familyCode] = iterator_to_array($familyVariants); - - if (isset($this->familyVariants[$familyCode])) { - return $this->familyVariants[$familyCode]; + return []; } - } catch (\Throwable $exception) { - $this->logger->warning($exception->getMessage()); - } - throw new \LogicException(sprintf('Unable to find variants for family "%s"', $familyCode)); + return $familyVariants; + }); } } diff --git a/src/Retriever/FamilyVariantRetrieverInterface.php b/src/Retriever/FamilyVariantRetrieverInterface.php new file mode 100644 index 00000000..ee416db2 --- /dev/null +++ b/src/Retriever/FamilyVariantRetrieverInterface.php @@ -0,0 +1,10 @@ +getAkeneoPimClient(); - $pagination = $this->apiConnectionProvider->get()->getPaginationSize(); - - $families = $client->getFamilyApi()->all($pagination); + $families = $this->familyRetriever->getFamilies(); foreach ($families as $family) { - $familyVariants = $client->getFamilyVariantApi()->all( - $family['code'], - $pagination, - ); + $familyVariants = $this->familyVariantRetriever->getVariants($family['code']); $variationAxes = array_merge($variationAxes, $this->getVariationAxesForFamilies($familyVariants)); } @@ -148,7 +143,7 @@ private function getVariationAxes(PipelinePayloadInterface $payload): array return $variationAxes; } - private function getVariationAxesForFamilies(ResourceCursorInterface $familyVariants): array + private function getVariationAxesForFamilies(array $familyVariants): array { $variationAxes = []; diff --git a/src/Task/ProductGroup/ProcessProductGroupModelTask.php b/src/Task/ProductGroup/ProcessProductGroupModelTask.php index 8aeb70f9..acf9e38a 100644 --- a/src/Task/ProductGroup/ProcessProductGroupModelTask.php +++ b/src/Task/ProductGroup/ProcessProductGroupModelTask.php @@ -10,6 +10,7 @@ use Sylius\Component\Core\Repository\ProductRepositoryInterface; use Synolia\SyliusAkeneoPlugin\Entity\ProductGroupInterface; use Synolia\SyliusAkeneoPlugin\Payload\PipelinePayloadInterface; +use Synolia\SyliusAkeneoPlugin\Processor\ProductGroup\ProductGroupProcessor; use Synolia\SyliusAkeneoPlugin\Provider\Configuration\Api\ApiConnectionProviderInterface; use Synolia\SyliusAkeneoPlugin\Repository\ProductGroupRepository; use Synolia\SyliusAkeneoPlugin\Task\AkeneoTaskInterface; @@ -24,6 +25,7 @@ public function __construct( private ProductRepositoryInterface $productRepository, private LoggerInterface $logger, private EntityManagerInterface $entityManager, + private ProductGroupProcessor $productGroupProcessor, ) { $this->productGroups = []; } @@ -41,6 +43,8 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte * } $resource */ foreach ($resourceCursor as $resource) { + $this->productGroupProcessor->process($resource); + if (null === $resource['parent']) { continue; } diff --git a/src/Task/ProductModel/BatchProductModelTask.php b/src/Task/ProductModel/BatchProductModelTask.php index 4c58742a..49f8ca32 100644 --- a/src/Task/ProductModel/BatchProductModelTask.php +++ b/src/Task/ProductModel/BatchProductModelTask.php @@ -4,8 +4,11 @@ namespace Synolia\SyliusAkeneoPlugin\Task\ProductModel; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Result; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\ORMInvalidArgumentException; +use Doctrine\Persistence\ManagerRegistry; use Psr\Log\LoggerInterface; use Sylius\Component\Core\Model\ProductInterface; use Sylius\Component\Core\Repository\ProductRepositoryInterface; @@ -18,6 +21,7 @@ use Synolia\SyliusAkeneoPlugin\Payload\PipelinePayloadInterface; use Synolia\SyliusAkeneoPlugin\Payload\ProductModel\ProductModelPayload; use Synolia\SyliusAkeneoPlugin\Processor\Product\ProductProcessorChainInterface; +use Synolia\SyliusAkeneoPlugin\Processor\ProductGroup\ProductGroupProcessor; use Synolia\SyliusAkeneoPlugin\Task\AbstractBatchTask; final class BatchProductModelTask extends AbstractBatchTask @@ -35,12 +39,16 @@ public function __construct( private EventDispatcherInterface $dispatcher, private ProductProcessorChainInterface $productProcessorChain, private IsProductProcessableCheckerInterface $isProductProcessableChecker, + private ProductGroupProcessor $productGroupProcessor, + private ManagerRegistry $managerRegistry, ) { parent::__construct($entityManager); } /** * @param ProductModelPayload $payload + * + * @throws Exception */ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInterface { @@ -54,7 +62,10 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte while ($results = $queryResult->fetchAll()) { foreach ($results as $result) { - $resource = json_decode($result['values'], true, 512, \JSON_THROW_ON_ERROR); + /** @var array $resource */ + $resource = json_decode($result['values'], true); + + $this->handleProductGroup($resource); try { $this->dispatcher->dispatch(new BeforeProcessingProductEvent($resource)); @@ -68,7 +79,6 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte $this->entityManager->flush(); $this->entityManager->commit(); - $this->entityManager->clear(); unset($resource, $product); $this->removeEntry($payload, (int) $result['id']); @@ -83,6 +93,26 @@ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInte return $payload; } + private function handleProductGroup(array $resource): void + { + try { + $this->entityManager->beginTransaction(); + + $this->productGroupProcessor->process($resource); + + $this->entityManager->flush(); + $this->entityManager->commit(); + } catch (ORMInvalidArgumentException) { + if ($this->entityManager->getConnection()->isTransactionActive()) { + $this->entityManager->rollback(); + } + + if (!$this->entityManager->isOpen()) { + $this->entityManager = $this->getNewEntityManager(); + } + } + } + private function process(array &$resource): ProductInterface { $product = $this->productRepository->findOneByCode($resource['code']); @@ -105,4 +135,15 @@ private function process(array &$resource): ProductInterface return $product; } + + private function getNewEntityManager(): EntityManagerInterface + { + $objectManager = $this->managerRegistry->resetManager(); + + if (!$objectManager instanceof EntityManagerInterface) { + throw new \LogicException('Wrong ObjectManager'); + } + + return $objectManager; + } } diff --git a/tests/PHPUnit/Task/ProductGroup/ProcessProductGroupModelTaskTest.php b/tests/PHPUnit/Task/ProductGroup/ProcessProductGroupModelTaskTest.php index 447bda76..611e6ed4 100644 --- a/tests/PHPUnit/Task/ProductGroup/ProcessProductGroupModelTaskTest.php +++ b/tests/PHPUnit/Task/ProductGroup/ProcessProductGroupModelTaskTest.php @@ -4,10 +4,13 @@ namespace Tests\Synolia\SyliusAkeneoPlugin\PHPUnit\Task\ProductGroup; +use Akeneo\Pim\ApiClient\Api\FamilyVariantApi; +use donatj\MockWebServer\Response; use PHPUnit\Framework\Assert; use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository; use Sylius\Component\Core\Model\Product; use Sylius\Component\Core\Repository\ProductRepositoryInterface; +use Symfony\Component\HttpFoundation\Response as HttpResponse; use Synolia\SyliusAkeneoPlugin\Entity\ProductConfiguration; use Synolia\SyliusAkeneoPlugin\Entity\ProductGroup; use Synolia\SyliusAkeneoPlugin\Payload\ProductModel\ProductModelPayload; @@ -41,6 +44,15 @@ protected function setUp(): void $this->productGroupRepository = $this->getContainer()->get('akeneo.repository.product_group'); $this->processProductGroupModelTask = $this->getContainer()->get(ProcessProductGroupModelTask::class); self::assertInstanceOf(TaskProvider::class, $this->taskProvider); + + $this->server->setResponseOfPath( + '/' . sprintf(FamilyVariantApi::FAMILY_VARIANT_URI, 'clothing', 'clothing_color_size'), + new Response($this->getFileContent('family_variant_clothing_color_size.json'), [], HttpResponse::HTTP_OK), + ); +// $this->server->setResponseOfPath( +// '/' . sprintf(ProductModelApi::PRODUCT_MODELS_URI), +// new Response($this->getFileContent('product_models_caelus.json'), [], HttpResponse::HTTP_OK), +// ); } public function testCreateProductGroupAssociations(): void diff --git a/tests/PHPUnit/datas/sample/family_variant_clothing_color_size.json b/tests/PHPUnit/datas/sample/family_variant_clothing_color_size.json index d428a9cc..bdd745af 100644 --- a/tests/PHPUnit/datas/sample/family_variant_clothing_color_size.json +++ b/tests/PHPUnit/datas/sample/family_variant_clothing_color_size.json @@ -32,4 +32,4 @@ ] } ] -} \ No newline at end of file +}