How to create attribute-options in Magento2

How to create attribute-options in Magento2

Recently we had to create Configurable Products in our Product Import.
To create those products we had to make sure that all simple products are generated before the configurable products and that all the attribute options for our configurable attribute are available.
So this post will focus on how to create those attribute options and a problem where we had to spend some time figuring out.

Preface

We need to create those attribute options before we call
$this->magentoImporter->validateSource($dataSource);
otherwise the validation will fail because the options are validated as well.

So let’s assume we have a collection of attribute-options that we want to ensure for an attribute.

$attributes = [
    'dev98_diameter' => [
        '20mm' => '20mm'
        '30mm' => '30mm'
    ]
];

Creating the Options

We will iterate this array and generate an Option Data-Object for each of the attribute-options.
This will be done using the Service Contract from Magento_Eav with the following classes:

  • Magento\Eav\Api\AttributeOptionManagementInterface (used to create the option)
  • Magento\Eav\Api\Data\AttributeOptionInterface  (one option)
  • Magento\Eav\Api\Data\AttributeOptionInterfaceFactory  (used as $this->optionFactory, to create an Option Data-Object)

The resulting code might look something like this.

foreach (attributes as $attributeCode => $options) {
    foreach ($options as $optionValue => $optionKey) {
        /** @var AttributeOptionInterface $option */
        $option = $this->optionFactory->create();

        $option->setLabel($optionValue);

        // Add Option
        $this->optionManagement->add(Product::ENTITY, $attributeCode, $option);
    }
}

After running this peace of code you will get attribute-options for your attribute.
Note: This code does not check whether an attribute-option is already in the database.

Running the Import

Now when you start you product import using the Magento Core functionality your configurable products will be created and the simple items will be assigned properly.

What I left out until now, was the trouble we had finding out how to create the option correctly.
So the next chapter will be about the issue we face, how we solved it and based on that what not to do when creating an option.

Problem with generating the Options

At first we tried generating our options like this:

$option = $this->optionFactory->create();
$option->setValue($optionValue);
$option->setLabel($optionValue);

// Add Option
$this->optionManagement->add(Product::ENTITY, $attributeCode, $option);

As it turns this will fail with an Foreign-Key Constraint Violation while inserting the Option,
basically saying the referenced option_id cannot be found.

After some intense debugging I found out what was going on.
Inside the OptionManagement the option->getValue()  is used to define the option_id if it is set.
\Magento\Eav\Model\Entity\Attribute\OptionManagement::getOptionId

private function getOptionId($option)
{
     return $option->getValue() ?: 'new_option';
}

Debugging the Generation of an Option further we end up in the following method
\Magento\Eav\Model\ResourceModel\Entity\Attribute::_updateAttributeOption

protected function _updateAttributeOption($object, $optionId, $option)
{
    $connection = $this->getConnection();
    $table = $this->getTable('eav_attribute_option');
    $intOptionId = (int)$optionId;
    
    // …

    if (!$intOptionId) {
        $data = ['attribute_id' => $object->getId(), 'sort_order' => $sortOrder];
        $connection->insert($table, $data);
        $intOptionId = $connection->lastInsertId($table);
    } else {
        $data = ['sort_order' => $sortOrder];
        $where = ['option_id = ?' => $intOptionId];
        $connection->update($table, $data, $where);
    }
}

This method gets the before mentioned option_id aka option->getValue()  injected, does an int-cast and based on that decides whether it is an update or insert.

So if we come through this method with our sample having $optionId set to “20mm”
the int-cast will return 20 and assume that the option is available and an update is to be made.
That is the main reason for the mySQL Foreign-Key Exception.

Why does it work with ‘new_option’?

In case we leave out the $option->setValue()  in our implementation
the optionId will be set to ‘new_option’ as shown above.

The int-cast of the string ‘new_option’ will be 0 and therefore it will be detected as an insert.

Conclusion

It would have been great if Magento had some kind of validation as a first instance within the OptionManagement implementation, like throwing an Exception when the option->getValue()  is not an int. Or validating that the provided option_id is valid.

Finally I want to point out, that you should NOT call $option->setValue(…)unless you have the exact option_id for this Option available.

I hope this post is helpful and will save you some trouble when generating custom options programmatically.

If you had similar experience or have something to add, feel free to leave a comment. Your feedback is always welcome.

Leave a Reply

Your email address will not be published. Required fields are marked *