diff --git a/.github/workflows/analysis.yaml b/.github/workflows/analysis.yaml index 0b7974b3..8a69118d 100644 --- a/.github/workflows/analysis.yaml +++ b/.github/workflows/analysis.yaml @@ -17,11 +17,11 @@ jobs: fail-fast: false matrix: php: - - 8.0 + - 8.1 - 8.2 symfony: - '5.4.*' - - '6.2.*' + - '6.3.*' env: APP_ENV: test steps: diff --git a/.github/workflows/sylius.yaml b/.github/workflows/sylius.yaml index 1bc4b0f0..8e9b2d43 100644 --- a/.github/workflows/sylius.yaml +++ b/.github/workflows/sylius.yaml @@ -17,23 +17,20 @@ jobs: fail-fast: false matrix: php: - - 8.0 + - 8.1 - 8.2 sylius: - 1.10.0 - 1.12.0 symfony: - 5.4 - - 6.2 + - 6.3 node: - 14.x exclude: - sylius: 1.10.0 - symfony: 6.2 - - - php: '8.0' - symfony: 6.2 + symfony: 6.3 env: APP_ENV: test package-name: synolia/sylius-akeneo-plugin diff --git a/src/Processor/ProductAttribute/ProductAttributeAkeneoAttributeProcessor.php b/src/Processor/ProductAttribute/ProductAttributeAkeneoAttributeProcessor.php index 011fb2d6..8668a0ef 100644 --- a/src/Processor/ProductAttribute/ProductAttributeAkeneoAttributeProcessor.php +++ b/src/Processor/ProductAttribute/ProductAttributeAkeneoAttributeProcessor.php @@ -12,6 +12,10 @@ use Sylius\Component\Resource\Repository\RepositoryInterface; use Synolia\SyliusAkeneoPlugin\Builder\Attribute\ProductAttributeValueValueBuilder; use Synolia\SyliusAkeneoPlugin\Component\Attribute\AttributeType\AssetAttributeType; +use Synolia\SyliusAkeneoPlugin\Exceptions\Attribute\MissingLocaleTranslationException; +use Synolia\SyliusAkeneoPlugin\Exceptions\Attribute\MissingLocaleTranslationOrScopeException; +use Synolia\SyliusAkeneoPlugin\Exceptions\Attribute\MissingScopeException; +use Synolia\SyliusAkeneoPlugin\Exceptions\Attribute\TranslationNotFoundException; use Synolia\SyliusAkeneoPlugin\Provider\AkeneoAttributeDataProviderInterface; use Synolia\SyliusAkeneoPlugin\Provider\SyliusAkeneoLocaleCodeProvider; use Synolia\SyliusAkeneoPlugin\Transformer\AkeneoAttributeToSyliusAttributeTransformerInterface; @@ -74,18 +78,34 @@ public function process(string $attributeCode, array $context = []): void /** @var AttributeInterface $attribute */ $attribute = $this->productAttributeRepository->findOneBy(['code' => $transformedAttributeCode]); - foreach ($this->syliusAkeneoLocaleCodeProvider->getUsedLocalesOnBothPlatforms() as $syliusAkeneo) { - $this->setAttributeTranslation( - $context['model'], - $attribute, - $context['data'], - $syliusAkeneo, - $attributeCode, - $context['scope'], - ); + foreach ($this->syliusAkeneoLocaleCodeProvider->getUsedLocalesOnBothPlatforms() as $syliusLocale) { + try { + $this->setAttributeTranslation( + $context['model'], + $attribute, + $context['data'], + $syliusLocale, + $attributeCode, + $context['scope'], + ); + } catch (MissingLocaleTranslationException | MissingLocaleTranslationOrScopeException|MissingScopeException|TranslationNotFoundException $error) { + $this->logger->warning('Attribute translation error', [ + 'attribute_code' => $attributeCode, + 'sylius_locale' => $syliusLocale, + 'context' => $context, + 'error' => $error->getMessage(), + 'trace' => $error->getTraceAsString(), + ]); + } } } + /** + * @throws MissingLocaleTranslationOrScopeException + * @throws MissingLocaleTranslationException + * @throws MissingScopeException + * @throws TranslationNotFoundException + */ private function setAttributeTranslation( ProductInterface $product, AttributeInterface $attribute, diff --git a/src/Processor/ProductGroup/ProductGroupProcessor.php b/src/Processor/ProductGroup/ProductGroupProcessor.php index a5f55ca0..eb7c6398 100644 --- a/src/Processor/ProductGroup/ProductGroupProcessor.php +++ b/src/Processor/ProductGroup/ProductGroupProcessor.php @@ -54,7 +54,6 @@ private function createGroupForCodeAndFamily( $productGroup->setModel($code); $productGroup->setFamily($family); $productGroup->setFamilyVariant($familyVariant); - $this->entityManager->persist($productGroup); return; } diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index f45ec21c..6b3ad69e 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -1,3 +1,7 @@ +parameters: + env(SYNOLIA_AKENEO_MAX_RETRY_COUNT): 30 + env(SYNOLIA_AKENEO_RETRY_WAIT_TIME): 5000 + services: _defaults: autowire: true @@ -5,6 +9,8 @@ services: public: false bind: $projectDir: '%kernel.project_dir%' + $maxRetryCount: '%env(int:SYNOLIA_AKENEO_MAX_RETRY_COUNT)%' + $retryWaitTime: '%env(int:SYNOLIA_AKENEO_RETRY_WAIT_TIME)%' Synolia\SyliusAkeneoPlugin\: resource: '../../*' diff --git a/src/Task/ProductModel/BatchProductModelTask.php b/src/Task/ProductModel/BatchProductModelTask.php index 49f8ca32..cb776fb8 100644 --- a/src/Task/ProductModel/BatchProductModelTask.php +++ b/src/Task/ProductModel/BatchProductModelTask.php @@ -5,7 +5,6 @@ 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; @@ -41,6 +40,9 @@ public function __construct( private IsProductProcessableCheckerInterface $isProductProcessableChecker, private ProductGroupProcessor $productGroupProcessor, private ManagerRegistry $managerRegistry, + private int $maxRetryCount, + private int $retryWaitTime, + private int $retryCount = 0, ) { parent::__construct($entityManager); } @@ -52,64 +54,90 @@ public function __construct( */ public function __invoke(PipelinePayloadInterface $payload): PipelinePayloadInterface { + if ($this->retryCount === $this->maxRetryCount) { + return $payload; + } + $this->logger->debug(self::class); $this->type = $payload->getType(); $this->logger->notice(Messages::createOrUpdate($this->type)); $query = $this->getSelectStatement($payload); - /** @var Result $queryResult */ $queryResult = $query->executeQuery(); while ($results = $queryResult->fetchAll()) { + /** @var array $result */ foreach ($results as $result) { + $isSuccess = false; + /** @var array $resource */ $resource = json_decode($result['values'], true); - $this->handleProductGroup($resource); - - try { - $this->dispatcher->dispatch(new BeforeProcessingProductEvent($resource)); - - $this->entityManager->beginTransaction(); - - if ($this->isProductProcessableChecker->check($resource)) { - $product = $this->process($resource); - $this->dispatcher->dispatch(new AfterProcessingProductEvent($resource, $product)); + do { + try { + $this->handleProductModel($resource); + $isSuccess = true; + } catch (ORMInvalidArgumentException $ormInvalidArgumentException) { + ++$this->retryCount; + usleep($this->retryWaitTime); + + $this->logger->error('Retrying import', [ + 'product' => $result, + 'retry_count' => $this->retryCount, + 'error' => $ormInvalidArgumentException->getMessage(), + ]); + + $this->entityManager = $this->getNewEntityManager(); + } catch (\Throwable $throwable) { + ++$this->retryCount; + usleep($this->retryWaitTime); + + $this->logger->error('Error importing product', [ + 'message' => $throwable->getMessage(), + 'trace' => $throwable->getTraceAsString(), + ]); + + $this->entityManager = $this->getNewEntityManager(); } + } while (false === $isSuccess && $this->retryCount !== $this->maxRetryCount); - $this->entityManager->flush(); - $this->entityManager->commit(); - - unset($resource, $product); - $this->removeEntry($payload, (int) $result['id']); - } catch (\Throwable $throwable) { - $this->entityManager->rollback(); - $this->logger->warning($throwable->getMessage()); - $this->removeEntry($payload, (int) $result['id']); - } + unset($resource); + $this->removeEntry($payload, (int) $result['id']); + $this->retryCount = 0; } } return $payload; } + private function handleProductModel(array $resource): void + { + $this->handleProductGroup($resource); + $this->dispatcher->dispatch(new BeforeProcessingProductEvent($resource)); + + if (!$this->isProductProcessableChecker->check($resource)) { + return; + } + + $product = $this->process($resource); + $this->dispatcher->dispatch(new AfterProcessingProductEvent($resource, $product)); + $this->entityManager->flush(); + } + 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(); - } - + } catch (ORMInvalidArgumentException $ormInvalidArgumentException) { if (!$this->entityManager->isOpen()) { + $this->logger->warning('Recreating entity manager'); $this->entityManager = $this->getNewEntityManager(); } + + ++$this->retryCount; + + throw $ormInvalidArgumentException; } }