Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import missing attribute option while importing products (#199) #200

Merged
merged 3 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 38 additions & 35 deletions spec/Converter/ValueConverterSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace spec\Webgriffe\SyliusAkeneoPlugin\Converter;

use InvalidArgumentException;
use LogicException;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Attribute\AttributeType\CheckboxAttributeType;
use Sylius\Component\Attribute\AttributeType\DatetimeAttributeType;
Expand All @@ -28,13 +30,14 @@ public function let(
AttributeInterface $integerAttribute,
AttributeInterface $selectAttribute,
AttributeInterface $datetimeAttribute
) {
): void {
$translator->trans('webgriffe_sylius_akeneo.ui.metric_amount_unit', ['unit' => 'INCH', 'amount' => '23'], null, 'it')->willReturn('23"');

$textAttribute->getType()->willReturn(TextAttributeType::TYPE);
$integerAttribute->getType()->willReturn(IntegerAttributeType::TYPE);
$textareaAttribute->getType()->willReturn(TextareaAttributeType::TYPE);
$checkboxAttribute->getType()->willReturn(CheckboxAttributeType::TYPE);
$selectAttribute->getCode()->willReturn('brand');
$selectAttribute->getType()->willReturn(SelectAttributeType::TYPE);
$datetimeAttribute->getType()->willReturn(DatetimeAttributeType::TYPE);

Expand All @@ -52,21 +55,21 @@ public function let(
$this->beConstructedWith($translator);
}

function it_is_initializable()
public function it_is_initializable(): void
{
$this->shouldHaveType(ValueConverter::class);
}

function it_implements_value_converter_interface()
public function it_implements_value_converter_interface(): void
{
$this->shouldHaveType(ValueConverterInterface::class);
}

function it_throws_exception_during_convert_when_value_not_contains_amount_key(
public function it_throws_exception_during_convert_when_value_not_contains_amount_key(
AttributeInterface $textAttribute
) {
): void {
$this->shouldThrow(
new \LogicException('Amount key not found')
new LogicException('Amount key not found')
)->during('convert', [
$textAttribute,
[
Expand All @@ -76,11 +79,11 @@ function it_throws_exception_during_convert_when_value_not_contains_amount_key(
]);
}

function it_throws_exception_during_convert_when_value_contains_amount_key_and_not_contains_unit_key(
public function it_throws_exception_during_convert_when_value_contains_amount_key_and_not_contains_unit_key(
AttributeInterface $textAttribute
) {
): void {
$this->shouldThrow(
new \LogicException('Unit key not found')
new LogicException('Unit key not found')
)->during('convert', [
$textAttribute,
[
Expand All @@ -91,9 +94,9 @@ function it_throws_exception_during_convert_when_value_contains_amount_key_and_n
]);
}

function it_converts_metric_value_from_akeneo_to_text_value_translated_when_translator_is_injected(
public function it_converts_metric_value_from_akeneo_to_text_value_translated_when_translator_is_injected(
AttributeInterface $textAttribute
) {
): void {
$this->convert(
$textAttribute,
[
Expand All @@ -104,89 +107,89 @@ function it_converts_metric_value_from_akeneo_to_text_value_translated_when_tran
)->shouldReturn('23"');
}

function it_converts_text_value_from_akeneo_to_text_value(
public function it_converts_text_value_from_akeneo_to_text_value(
AttributeInterface $textAttribute
) {
): void {
$value = 'Agape';
$this->convert($textAttribute, $value, self::IT_LOCALE_CODE)->shouldReturn($value);
}

function it_converts_integer_value_from_akeneo_to_integer_value(
public function it_converts_integer_value_from_akeneo_to_integer_value(
AttributeInterface $integerAttribute
) {
): void {
$value = 123;
$this->convert($integerAttribute, $value, self::IT_LOCALE_CODE)->shouldReturn($value);
}

function it_converts_textarea_value_from_akeneo_to_textarea_value(
public function it_converts_textarea_value_from_akeneo_to_textarea_value(
AttributeInterface $textareaAttribute
) {
): void {
$value = 'Lorem ipsum dolor sit amet';
$this->convert($textareaAttribute, $value, self::IT_LOCALE_CODE)->shouldReturn($value);
}

function it_converts_boolean_value_from_akeneo_to_checkbox_value(
public function it_converts_boolean_value_from_akeneo_to_checkbox_value(
AttributeInterface $checkboxAttribute
) {
): void {
$value = true;
$this->convert($checkboxAttribute, $value, self::IT_LOCALE_CODE)->shouldReturn($value);
}

function it_converts_select_value_from_akeneo_to_select_value(
public function it_converts_select_value_from_akeneo_to_select_value(
AttributeInterface $selectAttribute
) {
): void {
$value = ['brand_agape_IT'];
$this->convert($selectAttribute, $value, self::IT_LOCALE_CODE)->shouldReturn($value);
}

function it_throws_error_when_select_value_is_not_an_existing_option(
public function it_throws_error_when_select_value_is_not_an_existing_option(
AttributeInterface $selectAttribute
) {
): void {
$value = ['brand_not_existing'];

$this
->shouldThrow(
new \InvalidArgumentException(
new InvalidArgumentException(
'This select attribute can only save existing attribute options. ' .
'Attribute option codes [brand_not_existing] do not exist.',
'Attribute option codes [brand_not_existing] for attribute "brand" does not exist.',
)
)
->during('convert', [$selectAttribute, $value, self::IT_LOCALE_CODE]);
}

function it_throws_error_when_select_values_are_not_existing_options(
public function it_throws_error_when_select_values_are_not_existing_options(
AttributeInterface $selectAttribute
) {
): void {
$value = ['brand_not_existing', 'brand_not_existing_2'];

$this
->shouldThrow(
new \InvalidArgumentException(
new InvalidArgumentException(
'This select attribute can only save existing attribute options. ' .
'Attribute option codes [brand_not_existing, brand_not_existing_2] do not exist.',
'Attribute option codes [brand_not_existing, brand_not_existing_2] for attribute "brand" does not exist.',
)
)
->during('convert', [$selectAttribute, $value, self::IT_LOCALE_CODE]);
}

function it_throws_error_when_select_values_are_not_all_existing_options(
public function it_throws_error_when_select_values_are_not_all_existing_options(
AttributeInterface $selectAttribute
) {
): void {
$value = ['brand_agape', 'brand_not_existing'];

$this
->shouldThrow(
new \InvalidArgumentException(
new InvalidArgumentException(
'This select attribute can only save existing attribute options. ' .
'Attribute option codes [brand_not_existing] do not exist.',
'Attribute option codes [brand_agape, brand_not_existing] for attribute "brand" does not exist.',
)
)
->during('convert', [$selectAttribute, $value, self::IT_LOCALE_CODE]);
}

function it_converts_datetime_value_from_akeneo_to_datetime_value(
public function it_converts_datetime_value_from_akeneo_to_datetime_value(
AttributeInterface $datetimeAttribute
) {
): void {
$value = '2020-01-01 11:12:32';
$this->convert($datetimeAttribute, $value, self::IT_LOCALE_CODE)->shouldReturn($value);
}
Expand Down
71 changes: 15 additions & 56 deletions src/AttributeOptions/Importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Symfony\Contracts\Translation\TranslatorInterface;
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;
Expand All @@ -32,7 +33,7 @@
*/
final class Importer implements ImporterInterface
{
use ProductOptionHelperTrait, ProductOptionValueHelperTrait;
use ProductOptionHelperTrait, ProductOptionValueHelperTrait, ProductAttributeHelperTrait;

private const SIMPLESELECT_TYPE = 'pim_catalog_simpleselect';

Expand Down Expand Up @@ -214,37 +215,6 @@ private function filterSyliusOptionCodes(ResourceCursorInterface $akeneoAttribut
);
}

private function importAttributeConfiguration(string $attributeCode, ProductAttributeInterface $attribute): void
{
/** @var array{choices: array<string, array<string, string>>, multiple: bool, min: ?int, max: ?int} $configuration */
$configuration = $attribute->getConfiguration();
$configuration['choices'] = $this->convertAkeneoAttributeOptionsIntoSyliusChoices(
$this->getSortedAkeneoAttributeOptionsByAttributeCode($attributeCode),
);
$attribute->setConfiguration($configuration);

$this->attributeRepository->add($attribute);
}

/**
* @param array<array-key, AkeneoAttributeOption> $attributeOptions
*
* @return array<string, array<string, string>>
*/
private function convertAkeneoAttributeOptionsIntoSyliusChoices(array $attributeOptions): array
{
$choices = [];
foreach ($attributeOptions as $attributeOption) {
$attributeOptionLabelsNotNull = array_filter(
$attributeOption['labels'],
static fn (?string $label): bool => $label !== null,
);
$choices[$attributeOption['code']] = $attributeOptionLabelsNotNull;
}

return $choices;
}

/**
* @param AkeneoAttribute $akeneoAttribute
*/
Expand Down Expand Up @@ -282,30 +252,6 @@ private function importOptionValues(array $akeneoAttribute, ProductOptionInterfa
}
}

/**
* @return array<array-key, AkeneoAttributeOption>
*/
private function getSortedAkeneoAttributeOptionsByAttributeCode(string $attributeCode): array
{
$attributeOptionsOrdered = [];
/**
* @psalm-suppress TooManyTemplateParams
*
* @var ResourceCursorInterface<array-key, AkeneoAttributeOption> $attributeOptions
*/
$attributeOptions = $this->apiClient->getAttributeOptionApi()->all($attributeCode);
/** @var AkeneoAttributeOption $attributeOption */
foreach ($attributeOptions as $attributeOption) {
$attributeOptionsOrdered[] = $attributeOption;
}
usort(
$attributeOptionsOrdered,
static fn (array $option1, array $option2): int => $option1['sort_order'] <=> $option2['sort_order'],
);

return $attributeOptionsOrdered;
}

/**
* This method should be called only if the productOptionRepository is injected, so we can assume
* that this factory is injected too.
Expand Down Expand Up @@ -387,4 +333,17 @@ private function getProductOptionValueFromOption(

return $productOptionValue;
}

private function getAkeneoPimClient(): AkeneoPimClientInterface
{
return $this->apiClient;
}

/**
* @return RepositoryInterface<ProductAttributeInterface>
*/
private function getAttributeRepository(): RepositoryInterface
{
return $this->attributeRepository;
}
}
39 changes: 27 additions & 12 deletions src/Converter/ValueConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

namespace Webgriffe\SyliusAkeneoPlugin\Converter;

use InvalidArgumentException;
use Sylius\Component\Attribute\AttributeType\SelectAttributeType;
use Sylius\Component\Attribute\AttributeType\TextAttributeType;
use Sylius\Component\Attribute\Model\AttributeInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Webmozart\Assert\Assert;

final class ValueConverter implements ValueConverterInterface
{
Expand Down Expand Up @@ -35,26 +37,39 @@ public function convert(AttributeInterface $attribute, array|bool|int|string $va

return $value;
}
if ($attribute->getType() === SelectAttributeType::TYPE && !is_bool($value)) {
if (!is_bool($value) && $attribute->getType() === SelectAttributeType::TYPE) {
/** @var string[] $value */
$value = (array) $value;
$attributeConfiguration = $attribute->getConfiguration();
/** @var array $choices */
$choices = $attributeConfiguration['choices'];
$possibleOptionsCodes = array_map('strval', array_keys($choices));
/** @var string[] $invalid */
$invalid = array_diff($value, $possibleOptionsCodes);

if (count($invalid) > 0) {
throw new \InvalidArgumentException(
if (!$this->isAttributeValueBetweenProductAttributeChoices($attribute, $value)) {
$attributeCode = $attribute->getCode();
Assert::string($attributeCode);

throw new InvalidArgumentException(
sprintf(
'This select attribute can only save existing attribute options. ' .
'Attribute option codes [%s] do not exist.',
implode(', ', $invalid),
'Attribute option codes [%s] for attribute "%s" does not exist.',
implode(', ', $value),
$attributeCode,
),
);
}
}

return $value;
}

private function isAttributeValueBetweenProductAttributeChoices(AttributeInterface $attribute, array $value): bool
{
$attributeConfiguration = $attribute->getConfiguration();
if (!array_key_exists('choices', $attributeConfiguration)) {
return false;
}
/** @var array<string, array> $choices */
$choices = $attributeConfiguration['choices'];
$possibleAttributeOptionCodes = array_map('strval', array_keys($choices));
/** @var string[] $invalid */
$invalid = array_diff($value, $possibleAttributeOptionCodes);

return count($invalid) === 0;
}
}
1 change: 1 addition & 0 deletions src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ final class WebgriffeSyliusAkeneoExtension extends AbstractResourceExtension imp
'$factory' => 'sylius.factory.product_attribute_value',
'$localeProvider' => 'sylius.translation_locale_provider.admin',
'$valueConverter' => 'webgriffe_sylius_akeneo.converter.value',
'$akeneoPimClient' => 'webgriffe_sylius_akeneo.api_client',
],
],
'file_attribute' => [
Expand Down
Loading
Loading