Magento2: Product repository save complains about required attributes not included in defined attribute set

Created on 28 Oct 2016  路  13Comments  路  Source: magento/magento2

Preconditions

  1. Magento CE 2.1.2
  2. PHP 5.6.27
  3. MySQL 5.7.15

Steps to reproduce

  1. Clean install via composer
  2. Create attribute, with attribute code test_attribute, set is_required to 1
  3. Create a new attribute set Test Set (e.g. clone from Default)
  4. Add test_attribute attribute to the Test Set, but _not_ to Default attribute set
  5. Programmatically try to create product with attribute set Default with code like
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
$productRepository = $this->_productRepositoryFactory->create();

/** @var $product \Magento\Catalog\Api\Data\ProductInterface $product */
$product = $this->_productFactory->create();

$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
    ->setAttributeSetId(4) // 4 is id of Default attribute set here
    ->setWebsiteIds([1])
    ->setName('Some name')
    ->setSku('some-sku')
    ->setPrice(10.0)
    ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE)
    ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
    ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]);

$product = $productRepository->save($product);

Expected result

The product saves without problems.

Actual result

Upon save an Exception is thrown:
[Magento\Eav\Model\Entity\Attribute\Exception] The value of attribute "test_attribute" must be set

I expected the validation that occurs to take the attribute set into account. Of course the attribute is marked as required, but this should not be the case, when the attribute set (in this case Default) doesn't even include the required attribute.

Is there a flaw in my logic?

Catalog Cannot Reproduce Clear Description Format is valid Ready for Work bug report

Most helpful comment

I have this same problem in 2.1.7

All 13 comments

Nice, thank you for your report. Internal issue is created MAGETWO-60360

Same for me.

Same here on 2.1.1

As a workaround I removed the "required" flag from all attributes not in default set. But it is obviously suboptimal.

same here on 2.1.0

Having this problem in 2.1.6. Looks like there is a check for whether the attribute is in the current set, but the relevant data is not loaded. From magento/module-eav/Model/Entity/Attribute/AbstractAttribute.php line 610:

    public function isInSet($setId)
    {
        if (!$this->hasAttributeSetInfo()) {
            return true;
        }

hasAttributeSetInfo() always evaluates to false when saving the product from the admin panel, so it assumes all attributes are in set.

Trying to work out where this set data should be loaded per attribute. Looking at vendor/magento/module-eav/Model/Entity/AbstractEntity.php line 832:

public function validate($object)
{
    $this->loadAllAttributes($object);
    $result = $this->walkAttributes('backend/validate', [$object], $object->getCollectExceptionMessages());

The walkAttributes call is where the attributes are iterated over and validated. I notice that loadAllAttributes($object) does not have anything to do with sets, but there _is_ a call in this file that could set attribute set data:

$this->_attrSetEntity->addSetInfo($this->getEntityType(), $attributes, $setId);

_And_... if we add this line between the load and walk above, then the problem disappears! Here's what would need to be added:

$this->_attrSetEntity->addSetInfo($this->getEntityType(), $this->getAttributesByCode(), $object->getAttributeSetId());

There is a problem though, which is that this abstract class should not be assuming that the $object has a method called getAttributeSetId() as $object is only restricted to be \Magento\Framework\DataObject. But as this is an EAV resource class, perhaps this is safe to assume?

As _attrSetEntity is protected my workaround is going to be a preference for the entire Magento\Catalog\Model\ResourceModel\Product class so that I can override the validate function and add in the addSetInfo call. I'll post the code once I've tested it.

This is the code that I'm using as preference for \Magento\Catalog\Model\ResourceModel\Product:

<?php

namespace SadlyNeeded\MagentoFixes\Model\ResourceModel;

/**
 * Temp fix for https://github.com/magento/magento2/issues/7232
 *   - Force setting attribute data before validation
 */
class Product extends \Magento\Catalog\Model\ResourceModel\Product
{
    /**
     * {@inheritdoc}
     */
    public function validate($object)
    {
        //validate attribute set entity type
        $entityType = $this->typeFactory->create()->loadByCode(\Magento\Catalog\Model\Product::ENTITY);
        $attributeSet = $this->setFactory->create()->load($object->getAttributeSetId());
        if ($attributeSet->getEntityTypeId() != $entityType->getId()) {
            return ['attribute_set' => 'Invalid attribute set entity type'];
        }

        /*
         * @mod Copy and paste of abstract parent validation so that attribute-set data
         * can be set against each attribute after loading. Without this step,
         * the walk over attributes cannot skip those that are not in the set
         */
        $this->loadAllAttributes($object);
        // This is the added line to set attribute-set info on the attributes
        $this->_attrSetEntity->addSetInfo($this->getEntityType(), $this->getAttributesByCode(), $object->getAttributeSetId());
        // That was it
        $result = $this->walkAttributes('backend/validate', [$object], $object->getCollectExceptionMessages());
        $errors = [];
        foreach ($result as $attributeCode => $error) {
            if ($error === false) {
                $errors[$attributeCode] = true;
            } elseif (is_string($error)) {
                $errors[$attributeCode] = $error;
            }
        }
        if (!$errors) {
            return true;
        }

        return $errors;
        // End mod
    }
}

I should note that I get the error intermittently, so this is a tricky one to test. The above hack does seem to resolve the issue, but we'll have to wait for a while and see if it reoccurs.

I have this same problem in 2.1.7

@jBOKA, thank you for your report.
We were not able to reproduce this issue by following the steps you provided. If you'd like to update it, please reopen the issue.
We tested the issue on 2.3.0-dev, 2.2.0, 2.1.9

Will re-test on the installation that was causing the issue now that we have upgraded to 2.1.9

Was this page helpful?
0 / 5 - 0 ratings