For a recent project, we created a brand new product type in Magento 2. Part of that implementation involved linking child products to a parent. There were a lot of these new product types, so it was very important to make them easily available for import and export.
Enter uRapidFlowPro. uRapidFlowPro is a common tool that allows for the import and export of products and additional data. We recommended uRapidFlowPro during the Define and Design Phase of the process and implemented it early on.
Unfortunately, uRapidFlow didn’t support our new product type by default, and why would it? uRapidFlow had no idea it existed! We needed to extend uRapidFlow to add support for our new product type.
Luckily for us, this process couldn’t have been easier. All in all, we only had to add two files to our module to build in uRapidFlowPro support:
- a class extending \Unirgy\RapidFlowPro\Model\ResourceModel\ProductExtra
urapidflow.xml was the easy part – we were able to model this off of the uRapidFlowPro urapidflow.xml file. Here’s the important parts of what we wrote:
The profile tag is word-for-word copied from uRapidFlowPro and the rest is our custom data. Let’s walk through it piece by piece.
The data_types tag is what defines the types of imports that are available to the user, such as Products and Products Extra. This is where we define our new profile type “Name Products Extra.” We use <row_format> to let the program know this import uses the Fixed Row Format, then we tell it the class to run and specify the options available to the profile in <profile>.
In row_types, we add our new Fixed Row Format import types. We’re going to call this one CPNO, which stands for “Catalog Production Name [Product] Option,” as the links we’ll be defining are options for our new Name Product type. Here we give it a human-readable title to show up in the Row Types to import section, we attach it to a data_type so we know what profile type it belongs in, and then we declare the columns that will exist in the import/export.
Here, the outer tag (sku, linked_sku, position) defines the column name. Inside that are two additional tags: <col> to define the position the column will be in, and <key> which is a Boolean value indicating whether or not the column is required. In our case, we need a sku and a linked_sku, but a position is simply optional.
The trickier part of this customization was the model. We had to extend \Unirgy\RapidFlowPro\Model\ResourceModel\ProductExtra (as revealed by Unirgy’s Technical Staff), however the contents of this file were encrypted! uRapidFlow is highly proprietary, and they use a product called ionCube to prevent other developers from making knock-off modules using their code. The downside is that it’s more difficult to customize. Thankfully, Unirgy pointed us in the right direction, to Unirgy\RapidFlow\Model\ResourceModel\AbstractResource\Fixed.
Fixed is a class somewhere along the chain that we end up extending from when we write our own model. In addition to this class, they told us the function names we’d need to create in order to import and export support for our row type:
- _exportInitCPNO – Responsible for providing the SELECT statement for our export
- _deleteRowCPNO – Responsible for taking a row from the import and deleting it
- _importRowCPNO – Responsible for taking a row from the import and creating it
In our case, CPNO was the name of our row type. If you were to recreate this yourself, you’d replace CPNO in those functions with the name of your own row.
In addition, we were allowed to know about the following methods and variables we’d have access to:
- Magento\Framework\DB\Adapter\AdapterInterface $_read – Adapter to Read from the DB
- Magento\Framework\DB\Adapter\AdapterInterface $_write – Adapter to Write to the DB
- Magento\Framework\DB\Select $_select – Where our Select object should be stored for export
- Unirgy\RapidFlow\Mode\Profile $_profile – The Profile model
- _getIdBySku(string $sku) – Return a Product ID given its SKU
- _t(string $table) – Return the table name with its prefix
Despite the end result being perhaps the easiest of all of these functions, creating it took the longest amount of effort and the most time, with a lot of trial and error to get things just right for uRapidFlowPro’s unstated expectations.
Chiefly, in the resulting Select object, you’re expected to have aliased the table to “main,” and you are expected to alias the columns you want from the database to the names of your row’s columns.
That last one is the most obvious. From here though, things were pretty simple. We assembled a select object using the _read property. Since our import/export is for linking products together, the end result looked a little crazy:
There’s a lot going on here! We select our catalog_product_link table and alias it to main, and then we need four different joins to get all the data we need:
- Main Product SKU
- Linked Product SKU
- ID for the “position” attribute (needed to get #4)
- Value of the “position” attribute
Then at the end we alias those to the export columns we want them to fill. From there we just had to set the _select property to our new Select object, and voila! Simple!
_importRowCPNO and _deleteRowCPNO
While _exportInitCPNO is simpler in application, _import and _delete are simpler in theory. Here, we’re provided a single parameter: an array containing the data in a row, with the method called depending on the prefix of the FRF type, with - being delete, and a + or no prefix being import.
For example, if we had this import file:
First, our _deleteRowCPNO method would be called twice. With our parameter ($data) containing the following information:
And then our _importRowCPNO method would be called twice, with $data containing:
In these methods, we’re responsible for writing our own database queries, and then returning a success or a failure and issuing warnings.
Success, Warning, and Error
Within our _importRow and _deleteRow functions, we need to tell uRapidFlowPro if we were successful, not successful, or otherwise. There are two main methods for submitting the status of your row: the return value/exceptions, and the profile logger.
Return Value / Exceptions
The easiest way is to utilize the return value (for successes) or to throw an exception (for errors).
The two constants we know are self::IMPORT_ROW_RESULT_SUCCESS and self::IMPORT_ROW_RESULT_NOCHANGE, which are fairly self explanatory. The first writes a success to the log, the second increments the “Rows Not Changed” counter.
In the same vein, throwing an exception will write an error to the log for that row with the exception message. I personally recommend you use SPL exceptions such as \InvalidArgumentException and \RuntimeException, which will be processed as a row error, but you can also throw \Unirgy\RapidFlow\Exception\Row.
If you do not throw an exception or return a value, uRapidFlow will not automatically log a message. If you’re going to use the Profile Logger method, you probably won’t want to return a value in most cases.
If you need to be more in-depth with your results, URapidFlow offers a Profile Logger capable of attaching errors, warnings, notices, and success messages to individual columns. With the above method, the error or success is simply attached to the first column of the row.
Interacting with the profile’s logger and its logger is done through $this->_profile and $this->_profile->getLogger(), respectively.
Setting the Column
To set the column for your message, call $this->_profile->getLogger()->setColumn($col) where 0 is the first column, 1 is the second, etc. If you do not call this function, it is assumed that the column is 0.
To report a success, simply call $this->_profile->getLogger()->success($message).
Reporting a Notice
This works the same way as reporting a success: $this->_profile->getLogger()->notice($message)
I do not recommend reporting notices, as they do not show up in the “Profile Status” tab, and they prevent the user from being able to open the Excel Report.
Reporting a Warning
Reporting a warning is ever slightly more complicated:
$this->_profile->addValue(‘num_warnings’) to increment the warning counter, and then $this->_profile->getLogger()->warning($message) to write the message.
Reporting an Error
Reporting an error works the same way: $this->_profile->addValue(‘num_errors’) and $this->_profile->getLogger()->error($message).
And that’s it! After all this hard work, we unlocked the ability to import, export, and delete Name Product Options through uRapidFlowPro profiles.