Sometimes you need to bulk add or modify products in Magento but you want to use the upgrade script instead of the backend UI. This can be done with models, repositories, and factories. But when you do it, things get complicated. As we create bundle-type products and the dependencies multiply, the whole script becomes messy and prone to errors. Also – why even rediscover the wheel in the first place? There is a built-in importer known from the backend UI – Magento core importer – and we’re about to use it today.
The Magento core importer can work in three modes: add/update
, replace
, and delete
. For the sake of this tutorial, we’ll use the add/update mode and update the bundle-type product (SKU: parent-product-1), adding an option called “Digital product”. Next, we’ll link a subproduct to it (SKU: child-product-1) – all in one go.
Dependencies
Let’s start by creating a base class for our script. We will use it later e.g. in the upgrade script. First, we’ll add dependencies to it to allow for using the importer.
<?php
namespace Magently\Catalog\Setup\Data;
class BulkProductActions
{
/**
* @var \Magento\ImportExport\Model\ImportFactory
*/
private $importFactory;
/**
* @var \Magento\ImportExport\Model\ResourceModel\Import\Data
*/
private $importDataModel;
/**
* @param \Magento\ImportExport\Model\ImportFactory $importFactory
* @param \Magento\ImportExport\Model\ResourceModel\Import\Data $importDataModel
*/
public function __construct(
\Magento\ImportExport\Model\ImportFactory $importFactory,
\Magento\ImportExport\Model\ResourceModel\Import\Data $importDataModel
) {
$this->importFactory = $importFactory;
$this->importDataModel = $importDataModel;
}
}
Products data
The next step is to add a method to our class that will return an array with products’ data – two, in our case. It will serve us to pass the data to the importer.
<?php
namespace Magently\Catalog\Setup\Data;
class BulkProductActions
{
...
/**
* @return array
*/
private function getData()
{
return [
[
'sku' => 'parent-product-1',
'product_type' => 'bundle',
'additional_attributes' =>
'has_options=1,shipment_type=together,quantity_and_stock_status=In Stock,required_options=1',
'bundle_values' => 'name=Digital
Product,type=radio,required=1,sku=child-product-1,default=1,default_qty=10,can_change_qty=0',
'is_in_stock' => 1,
'_attribute_set' => 'Default',
'_product_websites' => 'base',
'_store' => null
],
[
'sku' => 'child-product-1',
'product_type' => 'virtual',
'is_in_stock' => 1,
'qty' => 100,
'_attribute_set' => 'Default',
'_product_websites' => 'base',
'_store' => null
]
];
}
}
Each element in the array requires a sku
to identify a product and product_type
to allow the importer to load the logic responsible for a specific product type. Additionally, we need to place _attribute_set
, _product_websites
and _store
in each element because they’re required by the importer. Other values are optional in this case and will serve us to create relations between two products.
Hint 💡
Magento developers included sample data in vendor/magento/module-import-export/Files/Sample/catalog_product.csv
– There, you will also find out what product properties can be modified.
Making operations with the Magento core importer
The last step in this example is going to be creating an execute method, which we’re going to call in our upgrade script.
<?php
namespace Magently\Catalog\Setup\Data;
class BulkProductActions
{
...
/**
* @return void
*/
public function execute()
{
/**
* Clean queue from old entries
* and add data from previous step
*/
$this->importDataModel->cleanBunches();
$this->importDataModel->saveBunch('catalog_product', 'append', $this->getData());
/**
* Run import and refresh indexes
*/
$import = $this->importFactory->create();
$import->importSource();
$import->invalidateIndex();
/**
* Clean queue
*/
$this->importDataModel->cleanBunches();
}
}
Summary
The importer is best suited for mass operations on products, but can also be used for other database objects – you will find a full list of examples in the vendor/magento/module-import-export/Files/Sample folder of your project.