2

I am trying to create and update products in the global scope while making sure any attributes are set to "use default" values set in global scope. The script is triggered by CRON so there is no store/website value set like there is when accessing via web browser in index.php.

The product gets created, but the product attributes "use default" are unchecked in store view scope.

The following code snippets are how I create a new product, how I get and update an existing product, and how I save the products data.

Create Product Code

$product = $this->productFactory->create();
$product->setStoreId(0)
        ->setSku("my-sku")
        ->setName("Product name")
        ->setStatus(1)
        ->setVisibility(4)
        ->setTypeId("simple")
        ->setAttributeSetId(4)
        ->setUrlKey($product->formatUrlKey("my-sku"))
        ->setCustomAttribute('short_description', "This is a short description of the product");

/* Plus I set other attributes such as price, name, meta etc */

Get Product Code

$product = $this->productRepository->get("my-sku", true, 0, false);
$product->setCustomAttribute('short_description', "This is the NEW UPDATED short description of the product");

Save Product

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

The "use default" checkbox is unchecked for any/all attributes that have their scope not set to Global in the attribute setup.

Use Default not selected in website/store scope

My question is: How do I programmatically create and update products in the global scope while making sure any other scopes the attribute may have, use the default value?

3
  • not sure this will work but you can try to also call $product->setStoreIds([0]) along with the ->setStoreId(0) which you already have
    – Marius
    Aug 8, 2023 at 7:56
  • @Marius "->setStoreIds" does not exist in Product... in M2.4.6 anyway.
    – Fox
    Aug 9, 2023 at 6:02
  • Toes not matter. it will magically call $product->setData('store_ids', [0]) via the __set method from the DataObject class which the product model extends.
    – Marius
    Aug 9, 2023 at 6:48

3 Answers 3

1

I worked out the solution.

I had to set the current store as ZERO (default/global scope) in the script before anything else, via \Magento\Store\Model\StoreManagerInterface. I found Magento doing similar in \Magento\Catalog\Controller\Adminhtml\Product\Edit::execute and they are setting zero as default store ID.

Here is a class using my steps to create a product in global scope so the product attributes "use default" entries.

FYI: Using PHP 8.1

<?php

namespace MyApp\MyModule;

use Magento\Catalog\Model\ProductFactory;
use Magento\Catalog\Model\ProductRepository;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;

class CreateAndSaveProduct
{
  private LoggerInterface             $logger;
  private ProductFactory              $productFactory;
  private ProductRepository           $productRepository;
  private StoreManagerInterface       $storeManager;

  public function __construct(
    LoggerInterface         $logger,
    ProductFactory          $productFactory,
    ProductRepository       $productRepository,
    StoreManagerInterface   $storeManager
  ) {
    $this->logger            = $logger;
    $this->productFactory    = $productFactory;
    $this->productRepository = $productRepository;
    $this->storeManager      = $storeManager;
  }

  public function execute()
  {
    /* This is my code to set current store for global scope. */

    $store = $this->storeManager->getStore(0);
    $this->storeManager->setCurrentStore($store->getCode());

    /* End of setting store code */

    $product = $this->productFactory->create();
    $product
        ->setSku("my-sku")
        ->setName("Product name")
        ->setPrice(55.00)
        ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
        ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
        ->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
        ->setAttributeSetId($product->getDefaultAttributeSetId())
        ->setUrlKey($product->formatUrlKey("my-sku"))
        ->setCustomAttribute('short_description', "This is a short description of the product");

    try {
      $this->productRepository->save($product);
      $this->logger->debug("Saved product {$product->getSku()}");
    } catch (\Exception $e) {
      $this->logger->debug("Error saving product {$product->getSku()}: {$e->getMessage()}");
    }
  }
}
    

I am open to any better solutions that anyone may have on setting the store to global scope!

1
  • Brilliant, thanks, this worked for me.
    – Alan
    Dec 7, 2023 at 10:30
0

Well you have your answer in your own question, you need to set the scope of your attributes to global



use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\ModuleContextInterface;

class UpgradeData implements UpgradeDataInterface
{
    public function __construct(
        EavSetupFactory $eavSetupFactory
    )
    {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        /* Compate you module version */
        if (version_compare($context->getVersion(), '0.1.1', '<')) {
            $updateAttributes = array('attribute_code_LIST');

            foreach ($updateAttributes as $attribute) {
                $eavSetup->updateAttribute(\Magento\Catalog\Model\Product::ENTITY, $attribute, 'global', \Magento\Catalog\Model\ResourceModel\Eav\Attribute::SCOPE_GLOBAL);
            }
        }
    }
}

Or more likely now with magento 2 latest version you can adapt this using a patch.

Also if your products has values in others store scope for an attribute you might have to delete the attribute value in the child store scope to see the modification.

4
  • This is not solving the problem, it is manipulating the issue to go away by removing the checkboxes altogether. If I create a simple product via the admin dashboard, the store scope values "Use Default" checkboxes are checked so I would like to keep the functionality of being able to have attributes either use or not use default values.
    – Fox
    Aug 9, 2023 at 5:12
  • Well if you want to keep the use or not use, the you don't have to change the scope of the attribute. But you will still have to delete the custom store values of your attribute to find back the configuration you are looking for the products already existing. Otherwise i guess the creation will follow the scope already defined in each attribute...if an attribute already have a scope store...you won't be able to do much unless you change his scope or update the product saving process. Anyway you find your answer that's all what matters :)
    – Claims
    Aug 9, 2023 at 12:08
  • There must be a way to iterate over each attributes website and store value then, and update if required. Something to look into for my app incase someone has set an attribute at website or store level.
    – Fox
    Aug 9, 2023 at 14:24
  • Of course magento provides repository and collection for almost every class, so you can iterate the attributes. Worst case scenario you can just check in db with a fast sql request before doing anything.
    – Claims
    Aug 9, 2023 at 14:26
0

Here's a basic outline of how you can achieve this:

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Api\Data\ProductInterfaceFactory;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Model\Product\Visibility;

class CustomProductCreation
{
    protected $productRepository;
    protected $productFactory;
    protected $storeManager;

    public function __construct(
        ProductRepositoryInterface $productRepository,
        ProductInterfaceFactory $productFactory,
        StoreManagerInterface $storeManager
    ) {
        $this->productRepository = $productRepository;
        $this->productFactory = $productFactory;
        $this->storeManager = $storeManager;
    }

    public function createAndSaveProduct()
    {
        // Create a product instance
        /** @var ProductInterface $product */
        $product = $this->productFactory->create();

        // Set product data
        $product->setSku('your_sku');
        $product->setName('Your Product Name');
        $product->setPrice(99.99);
        $product->setStatus(Status::STATUS_ENABLED); // Status is set to enabled
        $product->setVisibility(Visibility::VISIBILITY_BOTH); // Visible in both catalog and search
        $product->setAttributeSetId($product->getDefaultAttributeSetId());
        $product->setTypeId(Type::TYPE_SIMPLE);

        // Set store ID to use default values
        $store = $this->storeManager->getStore();
        $product->setStoreId($store->getId());

        try {
            // Save the product
            $this->productRepository->save($product);
            echo "Product created and saved successfully.";
        } catch (\Exception $e) {
            echo "Error: " . $e->getMessage();
        }
    }
}
1
  • This did not solve the problem. The "Use Default" checkbox remains unchecked in store view scope.
    – Fox
    Aug 9, 2023 at 6:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.