diff --git a/src/Attribute/Importer.php b/src/Attribute/Importer.php new file mode 100644 index 00000000..e15a1e3e --- /dev/null +++ b/src/Attribute/Importer.php @@ -0,0 +1,154 @@ +} + */ +final class Importer implements ImporterInterface +{ + public const SIMPLESELECT_TYPE = 'pim_catalog_simpleselect'; + + public const MULTISELECT_TYPE = 'pim_catalog_multiselect'; + + public const BOOLEAN_TYPE = 'pim_catalog_boolean'; + + public const METRIC_TYPE = 'pim_catalog_metric'; + + use ProductOptionHelperTrait, ProductAttributeHelperTrait; + + public const AKENEO_ENTITY = 'Attribute'; + + /** + * @param FactoryInterface $productOptionTranslationFactory + * @param RepositoryInterface $productAttributeRepository + * @param FactoryInterface $productAttributeTranslationFactory + */ + public function __construct( + private EventDispatcherInterface $eventDispatcher, + private AkeneoPimClientInterface $apiClient, + private ProductOptionRepositoryInterface $productOptionRepository, + private FactoryInterface $productOptionTranslationFactory, + private RepositoryInterface $productAttributeRepository, + private FactoryInterface $productAttributeTranslationFactory, + ) { + } + + public function getAkeneoEntity(): string + { + return self::AKENEO_ENTITY; + } + + public function getIdentifiersModifiedSince(DateTime $sinceDate): array + { + $searchBuilder = new SearchBuilder(); + $this->eventDispatcher->dispatch( + new IdentifiersModifiedSinceSearchBuilderBuiltEvent($this, $searchBuilder, $sinceDate), + ); + /** + * @psalm-suppress TooManyTemplateParams + * + * @var ResourceCursorInterface $akeneoAttributes + */ + $akeneoAttributes = $this->apiClient->getAttributeApi()->all(50, ['search' => $searchBuilder->getFilters()]); + + return array_merge( + $this->filterBySyliusAttributeCodes($akeneoAttributes), + $this->filterSyliusOptionCodes($akeneoAttributes), + ); + } + + public function import(string $identifier): void + { + /** @var AkeneoAttribute $akeneoAttribute */ + $akeneoAttribute = $this->apiClient->getAttributeApi()->get($identifier); + + $syliusProductAttribute = $this->productAttributeRepository->findOneBy(['code' => $identifier]); + if ($syliusProductAttribute instanceof ProductAttributeInterface) { + $this->importAttributeData($akeneoAttribute, $syliusProductAttribute); + } + + $syliusProductOption = $this->productOptionRepository->findOneBy(['code' => $identifier]); + if ($syliusProductOption instanceof ProductOptionInterface) { + $this->importOptionData($akeneoAttribute, $syliusProductOption); + } + } + + /** + * @return FactoryInterface + */ + private function getProductOptionTranslationFactory(): FactoryInterface + { + return $this->productOptionTranslationFactory; + } + + private function getProductOptionRepository(): ProductOptionRepositoryInterface + { + return $this->productOptionRepository; + } + + /** + * @return RepositoryInterface + */ + private function getProductAttributeRepository(): RepositoryInterface + { + return $this->productAttributeRepository; + } + + /** + * @param AkeneoAttribute $akeneoAttribute + */ + private function importAttributeData(array $akeneoAttribute, ProductAttributeInterface $syliusProductAttribute): void + { + $this->importProductAttributeTranslations($akeneoAttribute, $syliusProductAttribute); + $this->productAttributeRepository->add($syliusProductAttribute); + } + + /** + * @param AkeneoAttribute $akeneoAttribute + */ + private function importOptionData(array $akeneoAttribute, ProductOptionInterface $syliusProductOption): void + { + $this->importProductOptionTranslations($akeneoAttribute, $syliusProductOption); + $this->productOptionRepository->add($syliusProductOption); + // TODO: Update also the position of the option? The problem is that this position is on family variant entity! + } + + /** + * @param AkeneoAttribute $akeneoAttribute + */ + private function importProductAttributeTranslations(array $akeneoAttribute, ProductAttributeInterface $syliusProductAttribute): void + { + foreach ($akeneoAttribute['labels'] as $locale => $label) { + $productAttributeTranslation = $syliusProductAttribute->getTranslation($locale); + if ($productAttributeTranslation->getLocale() === $locale) { + $productAttributeTranslation->setName($label); + + continue; + } + $newProductAttributeTranslation = $this->productAttributeTranslationFactory->createNew(); + $newProductAttributeTranslation->setLocale($locale); + $newProductAttributeTranslation->setName($label); + $syliusProductAttribute->addTranslation($newProductAttributeTranslation); + } + } +} diff --git a/src/AttributeOptions/Importer.php b/src/AttributeOptions/Importer.php index ddac7f27..7bd494f7 100644 --- a/src/AttributeOptions/Importer.php +++ b/src/AttributeOptions/Importer.php @@ -20,8 +20,10 @@ use Sylius\Component\Resource\Translation\Provider\TranslationLocaleProviderInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\Translation\TranslatorInterface; +use Webgriffe\SyliusAkeneoPlugin\Attribute\Importer as AttributeImporter; use Webgriffe\SyliusAkeneoPlugin\Event\IdentifiersModifiedSinceSearchBuilderBuiltEvent; use Webgriffe\SyliusAkeneoPlugin\ImporterInterface; +use Webgriffe\SyliusAkeneoPlugin\ProductAttributeHelperTrait; use Webgriffe\SyliusAkeneoPlugin\ProductOptionHelperTrait; use Webgriffe\SyliusAkeneoPlugin\ProductOptionValueHelperTrait; use Webmozart\Assert\Assert; @@ -32,15 +34,9 @@ */ final class Importer implements ImporterInterface { - use ProductOptionHelperTrait, ProductOptionValueHelperTrait; - - private const SIMPLESELECT_TYPE = 'pim_catalog_simpleselect'; - - private const MULTISELECT_TYPE = 'pim_catalog_multiselect'; - - private const BOOLEAN_TYPE = 'pim_catalog_boolean'; - - private const METRIC_TYPE = 'pim_catalog_metric'; + use ProductOptionHelperTrait, + ProductOptionValueHelperTrait, + ProductAttributeHelperTrait; /** * @param RepositoryInterface $attributeRepository @@ -102,28 +98,6 @@ public function getAkeneoEntity(): string return 'AttributeOptions'; } - public function import(string $identifier): void - { - $attribute = $this->attributeRepository->findOneBy(['code' => $identifier]); - if (null !== $attribute && $attribute->getType() === SelectAttributeType::TYPE) { - $this->importAttributeConfiguration($identifier, $attribute); - } - $optionRepository = $this->optionRepository; - if (!$optionRepository instanceof ProductOptionRepositoryInterface) { - return; - } - $option = $optionRepository->findOneBy(['code' => $identifier]); - if (!$option instanceof ProductOptionInterface) { - return; - } - /** @var AkeneoAttribute $attributeResponse */ - $attributeResponse = $this->apiClient->getAttributeApi()->get($identifier); - - // TODO: Update also the position of the option? The problem is that this position is on family variant entity! - $this->importProductOptionTranslations($attributeResponse, $option); - $this->importOptionValues($attributeResponse, $option); - } - /** * As stated at https://api.akeneo.com/documentation/filter.html#by-update-date-3: * @@ -145,73 +119,29 @@ public function getIdentifiersModifiedSince(DateTime $sinceDate): array $akeneoAttributes = $this->apiClient->getAttributeApi()->all(50, ['search' => $searchBuilder->getFilters()]); return array_merge( - $this->filterBySyliusAttributeCodes($akeneoAttributes), + $this->filterBySyliusSelectAttributeCodes($akeneoAttributes), $this->filterSyliusOptionCodes($akeneoAttributes), ); } - /** - * Return the list of Akeneo attribute codes whose code is used as a code for a Sylius attribute - * - * @psalm-suppress TooManyTemplateParams - * - * @param ResourceCursorInterface $akeneoAttributes - * - * @return string[] - */ - private function filterBySyliusAttributeCodes(ResourceCursorInterface $akeneoAttributes): array + public function import(string $identifier): void { - $syliusSelectAttributes = $this->attributeRepository->findBy(['type' => SelectAttributeType::TYPE]); - $syliusSelectAttributes = array_filter( - array_map( - static fn (ProductAttributeInterface $attribute): ?string => $attribute->getCode(), - $syliusSelectAttributes, - ), - ); - $attributeCodes = []; - /** @var AkeneoAttribute $akeneoAttribute */ - foreach ($akeneoAttributes as $akeneoAttribute) { - if (!in_array($akeneoAttribute['code'], $syliusSelectAttributes, true)) { - continue; - } - if ($akeneoAttribute['type'] !== self::SIMPLESELECT_TYPE && $akeneoAttribute['type'] !== self::MULTISELECT_TYPE) { - continue; - } - $attributeCodes[] = $akeneoAttribute['code']; + $attribute = $this->attributeRepository->findOneBy(['code' => $identifier]); + if (null !== $attribute && $attribute->getType() === SelectAttributeType::TYPE) { + $this->importAttributeConfiguration($identifier, $attribute); } - - return $attributeCodes; - } - - /** - * Return the list of Akeneo attribute codes whose code is used as a code for a Sylius attribute - * - * @psalm-suppress TooManyTemplateParams - * - * @param ResourceCursorInterface $akeneoAttributes - * - * @return string[] - */ - private function filterSyliusOptionCodes(ResourceCursorInterface $akeneoAttributes): array - { - $productOptionRepository = $this->optionRepository; - if (!$productOptionRepository instanceof ProductOptionRepositoryInterface) { - return []; + $optionRepository = $this->optionRepository; + if (!$optionRepository instanceof ProductOptionRepositoryInterface) { + return; } - $akeneoAttributeCodes = []; - /** @var AkeneoAttribute $akeneoAttribute */ - foreach ($akeneoAttributes as $akeneoAttribute) { - if (!in_array($akeneoAttribute['type'], [self::SIMPLESELECT_TYPE, self::MULTISELECT_TYPE, self::BOOLEAN_TYPE, self::METRIC_TYPE], true)) { - continue; - } - $akeneoAttributeCodes[] = $akeneoAttribute['code']; + $option = $optionRepository->findOneBy(['code' => $identifier]); + if (!$option instanceof ProductOptionInterface) { + return; } - $syliusOptions = $productOptionRepository->findByCodes($akeneoAttributeCodes); + /** @var AkeneoAttribute $attributeResponse */ + $attributeResponse = $this->apiClient->getAttributeApi()->get($identifier); - return array_map( - static fn (ProductOptionInterface $option): string => (string) $option->getCode(), - $syliusOptions, - ); + $this->importOptionValues($attributeResponse, $option); } private function importAttributeConfiguration(string $attributeCode, ProductAttributeInterface $attribute): void @@ -250,15 +180,15 @@ private function convertAkeneoAttributeOptionsIntoSyliusChoices(array $attribute */ private function importOptionValues(array $akeneoAttribute, ProductOptionInterface $option): void { - if ($akeneoAttribute['type'] !== self::SIMPLESELECT_TYPE && - $akeneoAttribute['type'] !== self::MULTISELECT_TYPE && - $akeneoAttribute['type'] !== self::BOOLEAN_TYPE + if ($akeneoAttribute['type'] !== AttributeImporter::SIMPLESELECT_TYPE && + $akeneoAttribute['type'] !== AttributeImporter::MULTISELECT_TYPE && + $akeneoAttribute['type'] !== AttributeImporter::BOOLEAN_TYPE ) { return; } $attributeCode = $akeneoAttribute['code']; - if ($akeneoAttribute['type'] === self::BOOLEAN_TYPE) { + if ($akeneoAttribute['type'] === AttributeImporter::BOOLEAN_TYPE) { foreach ([true, false] as $booleanValue) { $optionValueCode = $this->getSyliusProductOptionValueCode($attributeCode, (string) $booleanValue); $productOptionValue = $this->getProductOptionValueFromOption($option, $optionValueCode); @@ -387,4 +317,17 @@ private function getProductOptionValueFromOption( return $productOptionValue; } + + private function getProductOptionRepository(): ?ProductOptionRepositoryInterface + { + return $this->optionRepository; + } + + /** + * @return RepositoryInterface + */ + private function getProductAttributeRepository(): RepositoryInterface + { + return $this->attributeRepository; + } } diff --git a/src/Product/ProductOptionsResolver.php b/src/Product/ProductOptionsResolver.php index 7bc8860b..e8520288 100644 --- a/src/Product/ProductOptionsResolver.php +++ b/src/Product/ProductOptionsResolver.php @@ -122,4 +122,9 @@ private function getProductOptionTranslationFactory(): FactoryInterface { return $this->productOptionTranslationFactory; } + + private function getProductOptionRepository(): ProductOptionRepositoryInterface + { + return $this->productOptionRepository; + } } diff --git a/src/ProductAttributeHelperTrait.php b/src/ProductAttributeHelperTrait.php new file mode 100644 index 00000000..aad413d1 --- /dev/null +++ b/src/ProductAttributeHelperTrait.php @@ -0,0 +1,81 @@ +} + */ +trait ProductAttributeHelperTrait +{ + /** + * @return RepositoryInterface + */ + abstract private function getProductAttributeRepository(): RepositoryInterface; + + /** + * Return the list of Akeneo attribute codes whose code is used as a code for a Sylius SELECT attribute + * + * @psalm-suppress TooManyTemplateParams + * + * @param ResourceCursorInterface $akeneoAttributes + * + * @return string[] + */ + private function filterBySyliusSelectAttributeCodes(ResourceCursorInterface $akeneoAttributes): array + { + $syliusSelectAttributes = $this->getProductAttributeRepository()->findBy(['type' => SelectAttributeType::TYPE]); + + return $this->filterBySyliusAttributes($syliusSelectAttributes, $akeneoAttributes); + } + + /** + * Return the list of Akeneo attribute codes whose code is used as a code for a Sylius attribute + * + * @psalm-suppress TooManyTemplateParams + * + * @param ResourceCursorInterface $akeneoAttributes + * + * @return string[] + */ + private function filterBySyliusAttributeCodes(ResourceCursorInterface $akeneoAttributes): array + { + $syliusAttributes = $this->getProductAttributeRepository()->findAll(); + + return $this->filterBySyliusAttributes($syliusAttributes, $akeneoAttributes); + } + + /** + * @psalm-suppress TooManyTemplateParams + * + * @param ProductAttributeInterface[] $syliusAttributes + * @param ResourceCursorInterface $akeneoAttributes + * + * @return string[] + */ + private function filterBySyliusAttributes(array $syliusAttributes, ResourceCursorInterface $akeneoAttributes): array + { + $syliusAttributes = array_filter( + array_map( + static fn (ProductAttributeInterface $attribute): ?string => $attribute->getCode(), + $syliusAttributes, + ), + ); + $attributeCodes = []; + /** @var AkeneoAttribute $akeneoAttribute */ + foreach ($akeneoAttributes as $akeneoAttribute) { + if (!in_array($akeneoAttribute['code'], $syliusAttributes, true)) { + continue; + } + $attributeCodes[] = $akeneoAttribute['code']; + } + + return $attributeCodes; + } +} diff --git a/src/ProductOptionHelperTrait.php b/src/ProductOptionHelperTrait.php index fce54f2e..4742ba79 100644 --- a/src/ProductOptionHelperTrait.php +++ b/src/ProductOptionHelperTrait.php @@ -4,9 +4,12 @@ namespace Webgriffe\SyliusAkeneoPlugin; +use Akeneo\Pim\ApiClient\Pagination\ResourceCursorInterface; use Sylius\Component\Product\Model\ProductOptionInterface; use Sylius\Component\Product\Model\ProductOptionTranslationInterface; +use Sylius\Component\Product\Repository\ProductOptionRepositoryInterface; use Sylius\Component\Resource\Factory\FactoryInterface; +use Webgriffe\SyliusAkeneoPlugin\Attribute\Importer as AttributeImporter; /** * @psalm-type AkeneoAttribute array{code: string, type: string, labels: array} @@ -19,6 +22,50 @@ trait ProductOptionHelperTrait */ abstract private function getProductOptionTranslationFactory(): FactoryInterface; + abstract private function getProductOptionRepository(): ?ProductOptionRepositoryInterface; + + /** + * Return the list of Akeneo attribute codes whose code is used as a code for a Sylius attribute + * + * @psalm-suppress TooManyTemplateParams + * + * @param ResourceCursorInterface $akeneoAttributes + * + * @return string[] + */ + private function filterSyliusOptionCodes(ResourceCursorInterface $akeneoAttributes): array + { + /** + * @psalm-suppress UnnecessaryVarAnnotation Necessary for PHPStan + * + * @var ?ProductOptionRepositoryInterface $productOptionRepository + */ + $productOptionRepository = $this->getProductOptionRepository(); + if (!$productOptionRepository instanceof ProductOptionRepositoryInterface) { + return []; + } + $akeneoAttributeCodes = []; + /** @var AkeneoAttribute $akeneoAttribute */ + foreach ($akeneoAttributes as $akeneoAttribute) { + if (!in_array($akeneoAttribute['type'], [ + AttributeImporter::SIMPLESELECT_TYPE, + AttributeImporter::MULTISELECT_TYPE, + AttributeImporter::BOOLEAN_TYPE, + AttributeImporter::METRIC_TYPE, + ], true) + ) { + continue; + } + $akeneoAttributeCodes[] = $akeneoAttribute['code']; + } + $syliusOptions = $productOptionRepository->findByCodes($akeneoAttributeCodes); + + return array_map( + static fn (ProductOptionInterface $option): string => (string) $option->getCode(), + $syliusOptions, + ); + } + /** * @param AkeneoAttribute $akeneoAttribute */