Automatic change of product types in Magento 2

Sometimes, for one reason or another, content managers need to convert a particular product from Simple to Virtual or vice versa and change its set attribute. So on. Most often, this falls on the shoulders of developers.

Magento 2 has a feature to achieve this. Before you get to grips with it (I'll call it "auto conversion"), there are a few things you need to keep in mind to get the right results.

Auto conversion from the user's point of view

Let's look at the admin panel from the point of view of the content manager. You can select the type of new product before you create it.

In Magento 1, this is on the Admin > Catalog > Manage Products > Set attribute and product type selection page after clicking on "Add Product."

In Magento 2, on the Admin > Products > Catalog page, click on the arrow to the right of the "Add Product" button.

After selecting the desired type, Magento will show you a new product edit page with predefined options and new sections for the selected type. If you choose the Simple product type or click on “Add Product,” you will see the Simple product edit page, but pay attention to the following three things:

  1. Weight attribute
  2. Configurations section
  3. Downloadable Information section

These three sections define the type of product.

Magento 2 can automatically (when saving) change product types for both new and existing products of the following products:

  • Simple
  • virtual
  • Downloadable
  • Configurable

Consider what needs for this.

Simple and Virtual Products

Suppose you set the weight "Weight" = "The item has no weight" (namely, there is no weight, and do not just delete the value from the input field). In that case, the product is a Virtual product. Still, as soon as you return to the product editing page and specify that the product has a weight, it will be converted to a Simple product immediately after saving.

Downloadable product

To turn a Simple product into a Downloadable, you need to set "Weight" = "The item has no weight" and add one or more links below in the "Downloadable Information" section. To turn this item back into a Simple product, just set "Weight" = "The item has weight" and save the product.

Product configurable

Add any child product to it as mentioned above to turn a Simple product into a Configurable one. And to make it a Simple product again, it is enough to delete all configurations and click on save. As you already understood, this means that Magento 2 cannot have a Configurable product without children.

Learn more about Magento 2: Magento 2 for Beginners 

For developers

Now let's talk about exactly how it all works. We want to start with the most important one, the Magento\Catalog\Model\Product\TypeTransitionManager class.

public function __construct(
        \Magento\Catalog\Model\Product\Edit\WeightResolver $weightResolver,
        array $compatibleTypes
    ) {
        $this->compatibleTypes = $compatibleTypes;
        $this->weightResolver = $weightResolver;
    }

    public function processProduct(Product $product)
    {
        if (in_array($product->getTypeId(), $this->compatibleTypes)) {
            $product->setTypeInstance(null);
            $productTypeId = $this->weightResolver->resolveProductHasWeight($product)
                ? Type::TYPE_SIMPLE
                : Type::TYPE_VIRTUAL;
            $product->setTypeId($productTypeId);
        }
    }

This reasonably simple class has two methods __construct and processProduct. All the magic is in the second method, processProduct($product). As you can see, it checks if the type of the passed product is in the array of compatible types, and if the product has a weight, then it is a Simple product; if not, then a Virtual product.

In Magento 2.1. as you may have guessed, the following types are compatible:

  • Simple
  • virtual
  • Downloadable
  • Configurable

All of them are through di.xml

<type name="Magento\Catalog\Model\Product\TypeTransitionManager">
        <arguments>
            <argument name="compatibleTypes" xsi:type="array">
                <item name="simple" xsi:type="const">Magento\Catalog\Model\Product\Type::TYPE_SIMPLE</item>
                <item name="virtual" xsi:type="const">Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL</item>
            </argument>
        </arguments>
    </type>

Only these types can be converted to Simple or Virtual products.

Magento\Downloadable\Model\Product\TypeTransitionManager\Plugin\Downloadable::aroundProcessProduct() checks that the product type is Simple, Virtual, Downloadable, data with the downloadable key was posted from the frontend and that this product has no weight. If it is, it is Downloadable; otherwise, the original method is run.

public function aroundProcessProduct(
        \Magento\Catalog\Model\Product\TypeTransitionManager $subject,
        Closure $proceed,
        \Magento\Catalog\Model\Product $product
    ) {
        $isTypeCompatible = in_array(
            $product->getTypeId(),
            [
                \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE,
                \Magento\Catalog\Model\Product\Type::TYPE_VIRTUAL,
                \Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE
            ]
        );
        $downloadableData = $this->request->getPost('downloadable');
        $hasDownloadableData = false;
        if (isset($downloadableData)) {
            foreach ($downloadableData as $data) {
                foreach ($data as $rowData) {
                    if (empty($rowData['is_delete'])) {
                        $hasDownloadableData = true;
                        break 2;
                    }
                }
            }
        }
        if ($isTypeCompatible && $hasDownloadableData && !$this->weightResolver->resolveProductHasWeight($product)) {
            $product->setTypeId(\Magento\Downloadable\Model\Product\Type::TYPE_DOWNLOADABLE);
            return;
        }
        $proceed($product);
    }

Magento\ConfigurableProduct\Model\Product\TypeTransitionManager\Plugin\Configurable::aroundProcessProduct() plugin changes the product type to “configurable” if there is data in the request with the “attributes” key; otherwise, the original method is launched.

public function aroundProcessProduct(
        \Magento\Catalog\Model\Product\TypeTransitionManager $subject,
        Closure $proceed,
        \Magento\Catalog\Model\Product $product
    ) {
        $attributes = $this->request->getParam('attributes');
        if (!empty($attributes)) {
            $product->setTypeId(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE);
            return;
        }
        $proceed($product);
    }

How it all works

If you try to save a product from the admin panel, as described above, it will change its type to 1 of the above thanks to this code, but if you try to save the product model through the save() method, then nothing will change. The point is in the controller that handles the saving of goods.

Magento\Catalog\Controller\Adminhtml\Product\Save::execute() uses an instance of Magento\Catalog\Model\Product\TypeTransitionManager class

$product = $this->initializationHelper->initialize($this->productBuilder->build($this->getRequest()));
$this->productTypeManager->processProduct($product);
…
$product->save();

Share with friends and colleagues!