magento2: Saving product with non-default store scope causes untouched attributes to become store scoped if loaded using ProductRepository

This is related to #7720 and #7879 and #9486

Preconditions

  1. Magento 2.4-develop

Steps to reproduce

  1. Create a simple product that belongs to multiple websites.
  2. In a script, change the current store to non-default store
  3. Load the product using the ProductRepository class
  4. Change an attribute value for the loaded product
  5. Save the product either using the ProductRepository class or calling $product->save() (deprecated)
$this->storeManager->setCurrentStore(2);
$product = $this->productRepository->getById(1, true, 2);
$product->setShortDescription('Setting short desc');
$this->productRepository->save($product); // or $product->save();

Expected result

  1. The value for short_description should be overridden, all other attributes should remain as Use default value

Actual result

  1. All store scoped eav attributes are overridden. Values other than short_description are copied over from the default store scope values.

You can see the results of this by checking the catalog_product_entity_text table and seeing that new values for not just short_description, but description and meta_keyword as well. Checking the catalog_product_entity_{varchar,int,decimal,etc} tables will also show new entries for store scope 2 even though none of that was modified.

Checking the how the admin backend handles this, it seems the Save controller requires passing in values for whether to use default values in a param use_default. (https://github.com/magento/magento2/blob/develop/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php#L171) This makes it tightly coupled to the admin ui, and so it is hard to replicate proper save behaviour in code.

The use case is that products are being pushed externally via the Magento api, but this makes it so that doing store scoped save operations means anytime something is changed in default scope, it needs to also update in all store scopes independently rather than inheriting from default scope like it should.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 31
  • Comments: 54 (21 by maintainers)

Commits related to this issue

Most upvoted comments

The product attributes are solved by using the emulation to set your script to admin store id 0

use Magento\Store\Model\App\Emulation;
use Magento\Framework\App\Area;

    public function __construct(
        FilterFactory $filter,
        Emulation $emulation
)
    {
        $this->filter = $filter;
        $this->emulation = $emulation;
    }

.....
  $this->emulation->startEnvironmentEmulation(0,Area::AREA_ADMINHTML);
                $productrep->save($product);
                $this->emulation->stopEnvironmentEmulation();
....

This issue was marked as “Not a bug”, here is why: For REST API or for the case described here, the \Magento\Catalog\Api\ProductRepositoryInterface::save will take the action.

Implementation of \Magento\Catalog\Api\ProductRepositoryInterface::save loads Product Entity by ID according to the provided store, populates product attributes with new values, and saves the whole Product Entity in the scope of the current store.

The whole product is saved in the current scope, not regarding if some attributes had values in this scope before or not.

The solution is to set NULL values for attributes for which you want to preserve inheritance. I’ve tested that it helps in 2.4-develop.

It’s 2022, and we are still having this issue with both categories and products especially when updating using the REST APi.

We also have the issue, bloated up our whole database 3x its size as we have 3 stores and cronjobs that touched a lot of products and used the save() function. Very neat. As I understand right, a workaround until this is fixed, is to either set all attribute values you dont want to change to null or, if you’re not using the REST-Api, to use the addAttributeUpdate() function from the Product Model, which saves a specific attribute value for a specific store.

But the problem I see here is that the save() function triggers SaveHandlers, a cache clean for the saved product and reindexing for the product (just a problem if the indexes are set to update on save and not update on schedule, as update on schedule works with database triggers). Am I missing something if I say the addAttributeUpdate() function, which directly saves the value to the database, doesn’t do anything of the above?

And please someone reopen this issue or show us the related commit.

Hey @engcom-Hotel

Please take a look at your second screenshot. As you can see every single “Use Default Value” option is deselected. This means that for example Product Name, Tax Class and so on now use a custom scope value instead of the default scope.

Which proofs that the issue is still reproducible in the latest 2.4-develop branch.

I’m pretty confident that just describes the bug as it was raised years ago.

Using the API to update a single attribute of a product in a store view, it is expected that you have to list every attribute for the product and mark them as null so they fall back? That’s what is being said now?

Please assign that to me, I am going to fix the issue.

Other entities

I tried to reproduce the issue on other Entities to make sure if it’s global issue of EAV mechanism or just related to products.

Categories

  • Admin GUI ✔️ (Issue does not occur)
  • API Rest ❌ (Problem occurs)

We are also having this problem in 2.2.2

Pretty crazy that this big bug is not fixed yet.

Hey @engcom-Hotel

Please take a look at your second screenshot. As you can see every single “Use Default Value” option is deselected. This means that for example Product Name, Tax Class and so on now use a custom scope value instead of the default scope.

I guess you need to know which attributes are store view level as well, because if you use attributes that are default, or website, these will get overwritten on these levels as well.

So in the meantime should I just send two API requests to save product data using the Rest API? One to all and one to default? (Admin and Default values get out of sync)

Can someone please explain why are there two store views/copies of same data all over the show in the EAV/DB? As I do not understand why there is an Admin and a Frontend store id when website has the single store/site enabled.

Same in 2.2.5.

we are running in single store mode and creating products from the configurable products and several seemingly random attribute values are getting filled.

Still an issue on last Magento 2 version.