Magento 2 Issues: Detected and Solved

Magento 2 Issues: Detected and Solved

5 min read

Send to you:

Magento® 2 claims to be one of the best eCommerce solutions on the market, as far as the company decided to stop the support of the first Magento version. But what about the changes? Is Magento still powerful or even became much better?

Well, opinions differ. Someone will tell you that Magento 2 is the best eCommerce platform they have ever worked with. Someone will say with a pinch of salt that this platform lacks lots of available functionalities, and its new architecture is odd. Both sides are right partly.

We are here to reveal some technical Magento 2 issues. We’ll discuss how you can cope with them as our team has already done. Sit back, make a cup of tea, and run PhpStorm.

Let’s take control of Magento 2 code together!

Table of Contents

Magento 2 Configuration Issues

Before we start criticizing Magento 2, we should admit that it’s still very flexible and powerful. As it has the new architecture, it calls for new approaches in web development. For example, some customization features that were included out-of-box in the first version now are not available. So we, web developers, need to come up with creative ideas and implement customizations in Magento 2 admin panel ourselves. Let’s look into some of them we’ve already faced.

Navigate to the issue:

Displaying Swatch Attribute of Dropdown Type in Magento 2.2
Configuring Adding Items to Admin Panel Menu in Magento 2.2.0
Displaying the Shopping Cart Content on the Customer View Page in Magento 2 CE
Dealing with Ineffective addAttribute() Method for Customizing Product Listings

Displaying Swatch Attribute of Dropdown Type in Magento 2.2

Problem: We detected the issue with displaying swatch attribute of drop-down type on the product listing page in Magento 2.2. It happens when the attribute of this type configures the configurable product. The issue arises because Magento\Swatches\Block\Product\Renderer\Listing\Configurable block will be output if only the product contains swatch attributes.

  protected function _toHtml()
    {
        $output = '';
        if ($this->isProductHasSwatchAttribute()) {
            $output = parent::_toHtml();
        }

        return $output;
    }

Check the solution

1. Redefine Magento_Swatches::product/listing/renderer.phtml template and set “onlySwatches” option to “false”. It will allow for displaying the swatch attribute of drop-down type on the product listing page.

2. Using frontend/di.xml, redefine SwatchAttributesProvider for Magento\Swatches\Block\Product\Renderer\Listing\Configurable block:

<type name="Magento\Swatches\Block\Product\Renderer\Listing\Configurable">
        <arguments>
            <argument name="swatchAttributesProvider" xsi:type="object">Web4pro\ExtendedSwatch\Model\SwatchAttributesProvider</argument>
        </arguments>
    </type>

3. In Web4pro\ExtendedSwatch\Model\SwatchAttributesProvider class, implement provide() method. In this method, in case the product has no swatch attributes, an additional check for the other  configurable attribute availability will run:

/**
     * @param Product $product
     * @return bool
     */
    public function provide(Product $product)
    {
        $result = parent::provide($product);
        if (empty($result)) {
            $attributes = $this->typeConfigurable->getConfigurableAttributes($product)->getItems();
            $result = count($attributes) > 0;
        };
        return $result;
    }

This approach helps to avoid changes in the whole Magento\Swatches\Block\Product\Renderer\Listing\Configurable class.

Configuring Adding Items to Admin Panel Menu in Magento 2.2.0

Problem: In Magento 2, you can configure adding items to the admin panel menu. In a nutshell, you can add items depending on specific configurations or modules. Here is how it looks like:

<menu>
   <add id="Web4pro_Extended::invoice" title="Invoices" module="Web4pro_Extended" sortOrder="20"
        parent="”
action="web4pro_extended/invoice/index"
        resource="Web4pro_Extended::invoice"
        dependsOnConfig="web4pro_extended/general_settings/enable"
        dependsOnModule=””
/>
</menu>

So that, dependsOnConfig sets the dependency on the configuration, and
dependsOnModule sets dependency on the module. But in Magento 2.2.0, this functionality doesn’t work.

Solution: Implement afterRead() plugin for \Magento\Backend\Model\Menu\Config\Reade class and run necessary checks in this method.

Displaying the Shopping Cart Content on the Customer View Page in Magento 2 CE

Problem: In Magento 2 Commerce, the content of the user’s shopping cart is not displayed on the customer view page.

Check the solution

We can provide the display of a customer shopping cart the following way. First, we should add the separate tab on the customer view page. Let’s implement customer_index_edit.xml layout file in the module. It should have the following content.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="customer_form">
            <block class="Web4pro\Login\Block\Cart" name="shopping_cart" />
        </referenceBlock>
    </body>
</page>

And a block class:

class Cart extends TabWrapper implements TabInterface {

    /**
     * Core registry
     *
     * @var Registry
     */
    protected $coreRegistry = null;

    /**
     * @var bool
     */
    protected $isAjaxLoaded = true;

    /**
     * Constructor
     *
     * @param Context $context
     * @param Registry $registry
     * @param array $data
     */
    public function __construct(Context $context, Registry $registry, array $data = [])
    {
        $this->coreRegistry = $registry;
        parent::__construct($context, $data);
    }

    /**
     * @inheritdoc
     */
    public function canShowTab()
    {
        return $this->coreRegistry->registry(RegistryConstants::CURRENT_CUSTOMER_ID);
    }

    /**
     * Return Tab label
     *
     * @return \Magento\Framework\Phrase
     */
    public function getTabLabel()
    {
        return __('Manage Shopping Cart');
    }

    /**
     * Return URL link to Tab content
     *
     * @return string
     */
    public function getTabUrl()
    {
        return $this->getUrl('customer/*/carts', ['_current' => true]);
    }
}

These actions will help you to add Manage Shopping Cart tab on the Customer View Page and allow loading its content with AJAX. Then implement customer_index_edit.xml layout file with the content:

<?xml version="1.0"?>
<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd">
    <referenceContainer name="root">
        <block class="Magento\Catalog\Block\Adminhtml\Product\Composite\Configure" name="customer.carts.product.configure" before="admin.customer.carts"/>
    </referenceContainer>
</layout>

This code is used to provide the administrator with an ability to reconfigure the products with options in the shopping cart.

Dealing with Ineffective addAttribute() Method for Customizing Product Listings

Problem: When customizing Magento, you often need to add a custom product attribute to a product listing. With Magento 1, you do this by using the addAttribute method of the Mage_Catalog_Block_Product_List class. Its equivalent in Magento 2 to add product custom attribute, use \Magento\Catalog\Block\Product\ListProduct\, inherited this method, and its implementation is simple enough.

public function addAttribute($code)
{
    $this->_getProductCollection()->addAttributeToSelect($code);
    return $this;
}

In Magento 1, the primary way of customizing outputs was to insert your code blocks using layout. Its only shortcoming was that if using flat index tables for a collection of products on the frontend, the required field could be unavailable if the attribute visibility was not set in the product listing.

In Magento 2.0 and Magento 2.1, you could continue using this method, but it’s strongly discouraged starting with version 2.2.

Check the solution

As you can see below, this method requests a collection of products using _getProductCollection and adds the $code attribute to the request. But the implementation of this method changed entirely in Magento 2.2. It looks as follows.

protected function _getProductCollection()
{
    if ($this->_productCollection === null) {
        $this->_productCollection = $this->initializeProductCollection();
    }
    return $this->_productCollection;
}

InitializeProductCollection() method will appear. Let’s look at its implementation.

private function initializeProductCollection()
{
    $layer = $this->getLayer();
    /* @var $layer \Magento\Catalog\Model\Layer */
    if ($this->getShowRootCategory()) {
        $this->setCategoryId($this->_storeManager->getStore()->getRootCategoryId());
    }
    // if this is a product view page
    if ($this->_coreRegistry->registry('product')) {
        // get collection of categories this product is associated with
        $categories = $this->_coreRegistry->registry('product')
            ->getCategoryCollection()->setPage(1, 1)
            ->load();
        // if the product is associated with any category
        if ($categories->count()) {
            // show products from this category
            $this->setCategoryId(current($categories->getIterator())->getId());
        }
    }
    $origCategory = null;
    if ($this->getCategoryId()) {
        try {
            $category = $this->categoryRepository->get($this->getCategoryId());
        } catch (NoSuchEntityException $e) {
            $category = null;
        }
        if ($category) {
            $origCategory = $layer->getCurrentCategory();
            $layer->setCurrentCategory($category);
        }
    }
    $collection = $layer->getProductCollection();
    $this->prepareSortableFieldsByCategory($layer->getCurrentCategory());
    if ($origCategory) {
        $layer->setCurrentCategory($origCategory);
    }
    $toolbar = $this->getToolbarBlock();
    $this->configureToolbar($toolbar, $collection);
    $this->_eventManager->dispatch(
        'catalog_block_product_list_collection',
        ['collection' => $collection]
    );
    return $collection;
}

This doesn’t seem out of the ordinary. But we can observe that the catalog_block_product_list_collection event is dispatched. Dispatching this event loads the collection and several additional entities to it, such as aggregated information about user reviews and the other.

In this case, the collection is loaded, but layered navigation filters, for example, cannot be applied to it, and the filter blocks themselves won’t be displayed on the frontend. Of course, the attribute will not be added to select, since the collection is already loaded. The method has become not only useless but quite harmful.

Why didn’t Magento declare it deprecated in version 2.2?

Problem: Our theory is that Magento left it for the case when a product collection is not taken from the $layer object but is set to the block by the setCollection method. This doesn’t happen on category pages or regular search pages, but at the moment it’s still on advanced search pages. However, the advanced search wasn’t prevalent on the majority of live Magento 1 sites, and it’s practically not used at all on Magento 2. Note that this problem didn’t exist until version 2.2 since the event mentioned above was dispatched in the block’s _beforeToHteml method, and this didn’t cause any problems.

We will now examine alternative solutions that we can apply in Magento 2.2.

Check the solution

First, you need to set the visibility options on the product listing to the required product attributes for display. This will be sufficient to load the attribute both when using an EAV collection or a Flat. Another method, which will only work when using an EAV product collection on the frontend, is to set a plugin for classes Magento\Catalog\Model\Layer\Category\CollectionFilter and Magento\Catalog\Model\Layer\Search\CollectionFilter.

The first plugin will work for the category page, and the second will work for the search results page. The beforeFilter method must be implemented in the plugin, and its input parameters will be the product collection and current category (an empty object for search pages). You can carry out any operations with the collection that do not require its download. Of course, if you use a flat product collection, an attribute for which no input parameter was set on the listing will not be in the collection. But more complex cases are possible.

Suppose the product has two attributes: name and seo_product_name. On the listing, you must enter the seo_product_name as the name, if it is not empty. Otherwise, enter the name. Also, you must resort by name to account for entering the product name on the listing. To do this, we implement the plugin mentioned above in the following way.

public function beforeFilter($model,$collection,$category){
    if(!$collection->isEnabledFlat()){
        $collection->addExpressionAttributeToSelect('seo_name',
            'IF({{seo_product_name}} is NULL,{{name}},{{seo_product_name}}',
            array('name','seo_product_name'));
    }else{
        $collection->getSelect()->columns(array('seo_name'=>'IF(seo_product_name is NULL,name,seo_product_name)'));
    }
    return array($collection,$category);
}

    $toolbar = $this->getToolbarBlock();
    $this->configureToolbar($toolbar, $collection);
    $this->_eventManager->dispatch(
        'catalog_block_product_list_collection',
        ['collection' => $collection]
    );
    return $collection;

As you can see, we have added the seo_name field in the query result, allowing for both EAV collections or Flat. Now you need to change sorting by name to sorting by added field.

This is not difficult since Magento 2 uses a collection of the Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection class in both search listings and category listings. Sometimes, when using various third-party modules, the collection could be replaced. For example, with the installed Amasty_Shopby module the collection class will be Amasty\Shopby\Model\ResourceModel\Fulltext\Collection. In any case, the plugin that will implement two methods must be set to the collection class:

public function beforeSetOrder($subject,$field,$direction){
     if($field == 'name'){
        $field = 'seo_name';
    }
    return array($field,$direction);
}
public function beforeAddAttributeToSort($collection,$attribute,$dir){
    if($attribute == 'seo_name'){
        $collection->getSelect()->order($attribute.' '.$dir);
    }
    return array($attribute,$dir);
}

Outputting the attribute on the product listing page is outside the scope of this article. I will say that this can be done by transferring the list.phtml template to the theme and customizing the template further, or by changing the template in the module.

If someone should decide to use the advanced search, then you can customize the collection for them the same way, only using the plugin in the Magento\CatalogSearch\Model\Advanced class. You will need to implement the beforePrepareProductCollection($model,$collection) method in this plugin, in which you will be able to carry out any available operation with the collection besides loading it. True, you will have to keep in mind that advanced search uses the collection Magento\Catalog\Model\ResourceModel\Product\Collection.

Plugins for it should only be set on the scope frontend so as not to break the administrator panel, and they should be set accounting for the use of the collection in cart and checkout functionalities, as well as in other places where it can be used. You may also need to obtain additional information about the environment to ensure that the advanced search product collection is being used. We will not examine this matter in detail in this article since advanced search is rarely used on live sites.

Now let’s move on to the next section with migration issues.

Migration is quite essential since you won’t lose your previous data and analytics and start with the new version of the store keeping everything that you were working on before. Use our navigation plan below and find the answer that you need.

Problems of Migrating Data from Magento 1 to Magento 2

When switching from the first Magento to the second, the issue arises of transferring already existing data from Magento 1 to Magento 2. The data storage format in a Magento 2 database is different from the storage format in Magento 1. Also, the storage format can differ slightly across different versions of Magento 2. We will be examining problems encountered during migration and ways to solve them.

Tool: UberTheme Migration Tool

UberTheme has implemented a special tool for transferring the built-in entities of Magento 1 to Magento 2, which is called the UberTheme Migration Tool. This tool is a Magento 2 module, which has a part implemented using Yii, including a graphic web interface and data-transfer functionality.

This part is located in the module’s lib directory and during installation will be transferred to the pub/ub_tool directory. Initial versions of this expansion used an SQLite database to store temporary data, but later versions create the particular tables right in the Magento database. This allows for the transferring from Magento 1 to Magento 2 the information about:

  • sites, stores, and store views (except configuration parameters);
  • EAV entities and attributes, attribute sets, etc.;
  • categories and products;
  • users, including saved addresses;
  • users’ carts, orders placed, payments, invoices, deliveries, and returns, as well as pricing rules for carts and aggregated information about purchases (for reporting);
  • core entities (reviews, wishlists).

Process: working with UberTheme Migration Tool

To work with this module, you must configure the Magento 2 database in the pub/ub-tool/protected/config/db.php file and open the module page in the admin panel. There, you can configure access to the Magento 1 database and the specifics of migration at various stages.

After initiating migration, you can address discrepancies at any stage. It is especially pertinent to information about users or orders.

When the data storage format is changed by a Magento 2 version update, UberTheme Migration Tool updates, too. For example, before Magento 2.2, options for products in orders were stored as a serialized array. This information is stored in JSON starting with version 2.2.

Requirements: You must use the version of the UberTheme Migration Tool that is compatible with your version of Magento 2 (for Magento 2.2, this is UberTheme Migration Tool version 3.1.2). Even so, problems could arise, which are examined below.

Navigate to the issue:

Migration of Magento Using Composer and Set up 2.2 Version
Migration with Ubertheme Migration Tool 3.1.2 and Magento 2.1
Rollback of Changes in the Magento 2 Database After Migration
Migration When It Is Combined with the Merging of Different Sites
Migration of Configurable Products with Custom Attributes
Merging Sites on the Same Host
Migration in a Local Environment with Transfer to Stage or Pre-Production Using Git
Problems with Categories in the Database When Migration Errors Occur

Migration of Magento from an Earlier Version Using Composer and Set up 2.2 Version Using Migration Tool 3.1.2.

Problem: Options for a product in an order may be incorrectly copied during migration (for example, in JSON instead of as a serialized array). This causes the pages for viewing orders in the admin panel to crash.

Cause: During the update, only the version of the core, and not the variable “version,” was updated in the composer.json file.

Check the solution

Solution: The UberTheme Migration Tool determines the M2 version from the variable “version” in the composer.json file, as you can see by the method in the UbMigrate class.

The variable must be updated.

public static function getM2Version()
{
    $version = '';
    $composerFile = Yii::app()->basePath . "/../../../composer.json";
    if (file_exists($composerFile)) {
        $data = @file_get_contents($composerFile);
        $data = json_decode($data);
        if (isset($data->version)) {
            $version = trim($data->version);
        }
    }
    return $version;
}

Migration with UberTheme Migration Tool 3.1.2 and Magento 2.1

Problem: Existing users’ carts may be broken.

Cause: During migration, product options info_buyRequest are serialized into JSON without checking the Magento 2 version. It is a mistake on the part of UberTheme developers.

Check the solution

private function _convertQuoteItemOptionCodeValue($productId2, &$optCode, &$optValue, $keepProductId)
{
    //convert to Magento 2 code and value
    if ($optCode == 'info_buyRequest') {
        $buyRequest = unserialize($optValue);
        //simple
        if (!$keepProductId AND isset($buyRequest['product']) AND $buyRequest['product']) {
            $buyRequest['product'] = UBMigrate::getM2EntityId(5, 'catalog_product_entity', $buyRequest['product']);
        }
        //bundle
        if (!$keepProductId AND isset($buyRequest['bundle_option']) AND $buyRequest['bundle_option']) {
            $bundleOption = [];
            $bundleOptionQty = [];
            foreach ($buyRequest['bundle_option'] as $optionId => $selectionId) {
                $optionId2 = UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_option', $optionId);
                if (is_array($selectionId)) {
                    foreach ($selectionId as $key => $sltId) {
                        if ($sltId) {
                            $bundleOption[$optionId2][$key] =  UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_selection', $sltId);
                        }
                    }
                } else {
                    if ($selectionId) {
                        $bundleOption[$optionId2] = UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_selection', $selectionId);
                    }
                    if (isset($buyRequest['bundle_option_qty'][$optionId])) {
                        $bundleOptionQty[$optionId2] = $buyRequest['bundle_option_qty'][$optionId];
                    }
                }
            }
            $buyRequest['bundle_option'] = $bundleOption;
            $buyRequest['bundle_option_qty'] = $bundleOptionQty;
        }
        //downloadable
        if (!$keepProductId AND isset($buyRequest['links']) AND $buyRequest['links']) {
            $links2 = [];
            foreach ($buyRequest['links'] as $key => $linkId) {
                $links2[$key] = UBMigrate::getM2EntityId('5_product_download', 'downloadable_link', $linkId);
            }
            $buyRequest['links'] = $links2;
        }
        //configurable
        if (isset($buyRequest['super_attribute']) AND $buyRequest['super_attribute']) {
            $superAttribute = [];
            foreach ($buyRequest['super_attribute'] as $attributeId => $attributeOptionId) {
                $attributeId2 = isset($mappingAttributes[$attributeId]) ? $mappingAttributes[$attributeId] : null;
                $superAttribute[$attributeId2] = UBMigrate::getM2EntityId('3_attribute_option', 'eav_attribute_option', $attributeOptionId);
            }
            $buyRequest['super_attribute'] = $superAttribute;
        }
        //virtual
        if (!$keepProductId AND isset($buyRequest['options']) AND $buyRequest['options']) {
            $options2 = [];
            foreach ($buyRequest['options'] as $productOptionId => $value) {
                if (is_numeric($productOptionId)) {
                    $productOptionId2 = UBMigrate::getM2EntityId('5_product_option', 'catalog_product_option', $productOptionId);
                    $options2[$productOptionId2] = $value;
                } else {
                    $options2[$productOptionId] = $value;
                }
            }
            //re-update
            $buyRequest['options'] = $options2;
        }
        //re-update value
        //$optValue = serialize($buyRequest);
        $optValue = UBMigrate::encodeJson($buyRequest); // this was changed from Magento ver.2.2.0
    } elseif ($optCode == 'attributes') {
        $values = unserialize($optValue);
        foreach ($values as $attributeId => $attributeOptionId) {
            $attributeId2 = isset($mappingAttributes[$attributeId]) ? $mappingAttributes[$attributeId] : null;
            $values[$attributeId2] = UBMigrate::getM2EntityId('3_attribute_option', 'eav_attribute_option', $attributeOptionId);
        }
        //$optValue = serialize($values);
        $optValue = UBMigrate::encodeJson($values); // this was changed from Magento ver.2.2.0
    } elseif (substr($optCode, 0, 12) == 'product_qty_') {
        $optCode = "product_qty_{$productId2}";
    } elseif ($optCode == 'simple_product') {
        $optValue = $productId2;
    } elseif ($optCode == 'parent_product_id') {
        $optValue = (!$keepProductId) ? UBMigrate::getM2EntityId(5, 'catalog_product_entity', $optValue) : $optValue;
    } elseif (substr($optCode, 0, 14) == 'selection_qty_') {
        $selectionId = substr($optCode, 14);
        if (is_numeric($selectionId)) {
            $selectionId = (!$keepProductId) ? UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_selection', $selectionId) : $selectionId;
            $optCode = "selection_qty_{$selectionId}";
        }
    } elseif (!$keepProductId AND $optCode == 'bundle_identity') {
        $values = explode('_', $optValue);
        $values[0] = $productId2;
        foreach ($values as $key => $value) {
            if ($value AND ($key % 2 == 1)) {
                $values[$key] = UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_selection', $value);
            }
        }
        $optValue = implode('_', $values);
    } elseif ($optCode == 'bundle_option_ids') {
        $values = unserialize($optValue);
        foreach ($values as $key => $bundleOptionId) {
            $bundleOptionId = (!$keepProductId) ? UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_option', $bundleOptionId) : $bundleOptionId;
            $values[$key] = $bundleOptionId;
        }
        //$optValue = serialize($values);
        $optValue = UBMigrate::encodeJson($values); // this was changed from Magento ver.2.2.0
    } elseif ($optCode == 'bundle_selection_ids') {
        $values = unserialize($optValue);
        foreach ($values as $key => $bundleSelectionId) {
            if ($bundleSelectionId) {
                $bundleSelectionId = (!$keepProductId) ? UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_selection', $bundleSelectionId) : $bundleSelectionId;
                $values[$key] = $bundleSelectionId;
            }
        }
        //$optValue = serialize($values);
        $optValue = UBMigrate::encodeJson($values); // this was changed from Magento ver.2.2.0
    } elseif (!$keepProductId AND $optCode == 'selection_id') {
        if ($optValue) {
            $optValue = UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_selection', $optValue);
        }
    } elseif ($optCode == 'bundle_selection_attributes') {
        $values = unserialize($optValue);
        if (isset($values['option_id']) AND $values['option_id']) {
            $values['option_id'] = (!$keepProductId) ? UBMigrate::getM2EntityId('5_product_option', 'catalog_product_bundle_option', $values['option_id']) : $values['option_id'];
        }
        //$optValue = serialize($values);
        $optValue = UBMigrate::encodeJson($values); // this was changed from Magento ver.2.2.0
    } elseif ($optCode == 'downloadable_link_ids') {
        $values = explode(',', $optValue);
        if ($values) {
            foreach ($values as $key => $value) {
                if (is_numeric($value)) {
                    $values[$key] = (!$keepProductId) ? UBMigrate::getM2EntityId('5_product_download', 'downloadable_link', $value) : $value;
                }
            }
            //$optValue = implode(',', $values);
            $optValue = UBMigrate::encodeJson($values); // this was changed from Magento ver.2.2.0
        }
    } elseif ($optCode == 'option_ids') {
        $values = preg_split('/,\s*/', $optValue);
        if ($values) {
            foreach ($values as $key => $value) {
                if (is_numeric($value)) {
                    $values[$key] = (!$keepProductId) ? UBMigrate::getM2EntityId('5_product_option', 'catalog_product_option', $value) : $value;
                }
            }
            //$optValue = implode(',', $values);
            $optValue = UBMigrate::encodeJson($values); // this was changed from Magento ver.2.2.0
        }
    } elseif (!$keepProductId AND substr($optCode, 0, 7) == 'option_') {
        $productOptionId = (int)substr($optCode, 7);
        $optCode = "option_" . UBMigrate::getM2EntityId('5_product_option', 'catalog_product_option', $productOptionId);
        if (is_numeric($optValue)) {
            $optValue = UBMigrate::getM2EntityId('5_product_option', 'catalog_product_option_type_price', $optValue);
        }
    }
    return true;
}

Rollback of Changes in the Magento 2 Database After Migration

Problem: In some cases, dependent entities (for example, product attributes) are incorrectly assigned, or you can get the 500 internal server error during migration requests.

Solution: contents of the directory pub/ub-tool/protected/runtime/cache must be deleted. The cache of dependencies is stored in this directory.

Migration When It Is Combined with the Merging of Different Sites

Problem: Attributes will be incorrectly assigned if data has been migrated from site A and you would like to migrate data from site B on the same Magento 2 host, but the websites are different.

Solution: not only must the cache be cleared, but the Ubertheme tables in Magento 2 must also be cleared, so that the entities from site B do not get mixed up with the objects from site A due to overlapping numeric identifiers in tables.

However, there are several issues with dependencies that UberTheme migration module cannot solve.

Migration of Configurable Products with Custom Attributes

Problem: Magento 1 has configurable products with the custom attribute default_product_id. By default, it stores the identifier of the product option. During migration, the value of this attribute will be transferred “as is,” but the identifiers of the products in the Magento 2 database will be different. This leads to inconsistencies in the data.

Solution: Dependencies between products of the form SKU=>SKU must be exported from the Magento 1 database to a CSV file, and then imported using the Magento 2 API.

Merging Sites on the Same Host

Problem: Products on sites A and B have identical SKU but different attribute values. For example, suppose site A is a retail store, while site B is wholesale, and the prices for the same products differ on sites A and B. In this case, the prices are specified in Magento 1 for the default site. In this instance, after migrating data from site A, migration from site B will cause product attributes, including price, to be overwritten.

Solution: The scope of the price attribute must first be set in the website value in the Magento 2 configuration, by using a standard export to export product prices to a CSV file after migrating from site A. Then they must be imported using a standard import. To do this, we re-write the code for the default store view of site A using a standard import.

Limitations of the UberTheme Migration Tool

The UberTheme Migration Tool cannot transfer:

  • custom entities;
  • images from the media directory of Magento 1 to pub/media in Magento 2;
  • static pages and blocks.

These elements will have to be transferred manually.

UberTheme can transfer custom attributes of EAV entities.

If it’s a category, user, or user address, then to implement the output of a custom attribute in the admin panel, you will need to implement the corresponding changes in the UI component.

If the attribute has a custom backend, frontend, or source module, then the developer is required to implement the corresponding attributes in Magento 2 and write them into the metadata attributes, so their logic is not violated.

Migration in a Local Environment with Transfer to Stage or Pre-Production Using Git

Problem: Yii classes don’t support the standards of Magento 2, but if they are located in the module directory, Magento will try to include them in the di compilation, which will result in a bin/magento setup:di:compile command error.

Solution: Since the part based on Yii will be copied to pub/ub_tool when it is installed or updated, to successfully transfer it, it needs to be added to the repository before the command bin/magento setup:upgrade is executed.

If the part based on Yii is transferred to pub/ub_tool, there will be no issues.

Saving Categories in the Database When Migration Errors Occur

Problem: In the event of migration errors, categories in the database could be cut off from the tree. It means that there could be identifiers for categories that do not exist in the “path” field of the catalog_category_entity table when being transferred to the category. These non-existent categories aren’t displayed in the category tree in the admin panel, but they can break the standard import of products.

Solution: These categories must be removed from the catalog_category_entity table, and the problem will be solved.

In general, the best method for transferring data from Magento 1 to Magento 2 doesn’t currently exist, though there will be a need for it until Magento 1 becomes a thing of the past. Remember to keep up with updates and choose the UberTheme version that’s compatible with your current version of Magento 2. Also, be prepared for the fact that after migration you may have to reconcile some errors in the data.

Next, we move into the topic on how to work with Magento 2 using UI components.

Issues Related to UI Components in Magento 2

There is a new feature in Magento 2 used for output on both admin and frontend parts. This feature is the UI Component. Magento 2 UI Component has browser part and the server-side part that generates data by using the special object. This object is called Data Provider. Often this object’s class extends from \Magento\Ui\DataProvider\AbstractDataProvider as it should support Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface. The browser part is a JavaScript knockout component and template, that is downloaded asynchronously after the page has been downloaded. Then JavaScript will bind data to template elements by using knockout javascript framework.

UI components are used in most admin pages from the core, sidebar mini cart. Some elements on the cart page and checkout are also implemented with their use. We can define Magento UI Component with XML. They can be customized simpler than blocks, but developers should know, how to do that. Often developers without experience in use and customization of UI Components have difficulties with customizing some grid, product edit form or customer edit form in admin. Below, we will examine the most common questions and how to solve them.

Navigate to the issue:

Displaying an Image from a UI Grid Column in the Administrator Panel
Custom Product Attribute Renderer on the Product Editing Page
Adding UI Element After Outputting Shipping Methods

Displaying an Image from a UI Grid Column in the Administrator Panel

Problem: We need to display the image from the UI Grid column in the admin panel.

Check the solution

Solution: An image from a UI Grid column can be displayed in the following way. The XML field of the UI component must be described using the specific UI component and class.

The class must be implemented.

<column name="image" class="Web4pro\Socialprofile\Ui\Component\Listing\Column\Thumbnail">
    <argument name="data" xsi:type="array">
        <item name="config" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/thumbnail</item>
            <item name="sortable" xsi:type="boolean">false</item>
            <item name="altField" xsi:type="string">title</item>
            <item name="has_preview" xsi:type="string">1</item>
            <item name="label" xsi:type="string" translate="true">Thumbnail</item>
        </item>
    </argument>
</column>

class Thumbnail extends \Magento\Ui\Component\Listing\Columns\Column {

   public function prepareDataSource(array $dataSource)
    {
        if(isset($dataSource['data']['items'])) {
            $fieldName = $this->getData('name');
            foreach($dataSource['data']['items'] as &$item) {

               $item[$fieldName . '_src'] = $item['instagram_link'];
                $item[$fieldName . '_alt'] = '';
                $item[$fieldName . '_link'] = '';
                $item[$fieldName . '_orig_src'] = $item['instagram_link'];
            }
        }
       return $dataSource;
    }
}

As you can see, the prepareDataSource() method should return an array whose elements contain non-empty fields with the keys $fieldName.’_src’ and $fieldName.’_orig_src’.

$fieldName.’_alt’ can be set to be non-empty to set the image’s alt attribute.

Custom Product Attribute Renderer on the Product Editing Page

Problem: Suppose there is a product attribute of the type Multiselect, each option of which is associated with a particular image that should be shown after the attribute (on the right) when you hover over an option in the element.

Check the solution

Solution: In Magento 2, a modifier must be implemented to do this, in which the UI component that implements this functionality is described:

di.xml (create only in the adminhtml section)

<?xml version="1.0" ?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="instagram_link" xsi:type="array">
                    <item name="class" xsi:type="string">Web4pro\Socialprofile\Ui\DataProvider\Product\Form\Modifier\Link</item>
                    <item name="sortOrder" xsi:type="number">160</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

Modifier class:

class Link extends \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier
{

   protected $arrayManager;
   protected $opts;

   public function __construct(\Magento\Framework\Stdlib\ArrayManager $arrayManager,
                                \Web4pro\Socialprofile\Model\Link $opts)
    {
        $this->arrayManager = $arrayManager;
        $this->opts = $opts;
    }

   /**

* @param array $data
* @return array
*/
public function modifyData(array $data)

{
       return $data;
    }

   /**

* @param array $meta
* @return array
*/
public function modifyMeta(array $meta){

       $fieldCode = 'instagram_link'; //your custom attribute code

$elementPath = $this->arrayManager->findPath($fieldCode, $meta, null, 'children');

       $containerPath = $this->arrayManager->findPath(static::CONTAINER_PREFIX . $fieldCode, $meta, null, 'children');
        if (!$elementPath) {
            return $meta;
        }

       $meta = $this->arrayManager->merge(
            $containerPath,
            $meta,
            [
                'children'  => [
                    $fieldCode.'_img' => [
                        'arguments' => [
                            'data' => [
                                'config' => [
                                    'component' => 'Web4pro_Socialprofile/js/product_links',
                                    'componentType' => 'component',
                                    'disableLabel'  => true,
                                    'multiple'      => false,
                                    'options'       => $this->getOptions()
                                ],
                            ],
                        ],
                    ]
                ]
            ]
        );
        $meta=$this->arrayManager->merge($elementPath,$meta,
            ['arguments'=>
                ['data'=>
                    ['config'=>
                        ['service'=>
                            [ 'template'      => 'Web4pro_Socialprofile/image.html']
                        ]
                    ]
                ]
            ]);
        return $meta;
    }

We put the UI component template in service.template of the element $fieldCode, not in $fieldCode.’_img’. That’s because the contents of the template will be uploaded to the same <div> where the element is located, not outputted after it on the following line, since the template /vendor/magento/module-ui/view/base/element/web/templates/form/field.html is responsible for outputting form elements, with the following form

<div class="admin__field"
     visible="visible"
     css="$data.additionalClasses"
     attr="'data-index': index">
    <label class="admin__field-label" if="$data.label" visible="$data.labelVisible" attr="for: uid">
        <span text="label" attr="'data-config-scope': $data.scopeLabel"/>
    </label>
    <div class="admin__field-control"
         css="'_with-tooltip': $data.tooltip, '_with-reset': $data.showFallbackReset && $data.isDifferedFromDefault">
        <render args="elementTmpl" ifnot="hasAddons()"/>

       <div class="admin__control-addon" if="hasAddons()">
            <render args="elementTmpl"/>
           <label class="admin__addon-prefix" if="$data.addbefore" attr="for: uid">
                <span text="addbefore"/>
            </label>
            <label class="admin__addon-suffix" if="$data.addafter" attr="for: uid">
                <span text="addafter"/>
            </label>
        </div>

       <render args="tooltipTpl" if="$data.tooltip"/>
       <render args="fallbackResetTpl" if="$data.showFallbackReset && $data.isDifferedFromDefault"/>

       <label class="admin__field-error" if="error" attr="for: uid" text="error"/>
       <div class="admin__field-note" if="$data.notice" attr="id: noticeId">
            <span text="notice"/>
        </div>

       <div class="admin__additional-info" if="$data.additionalInfo" html="$data.additionalInfo"></div>
       <render args="$data.service.template" if="$data.hasService()"/>
    </div>
</div>

The template image.html looks the following way:

<img id="instagram_img"/>

The file product_links.js looks as follows.

define(['jquery','uiComponent','jquery/ui'],function($,Component){

   return Component.extend({
        initialize: function () {
            this._super();
            var self = this;
            $(document).on('mouseenter',this.options.element,function(){self.changeOption(this)});
        },
       changeOption:function(opt){
            for(var i in this.options){
                if(parseInt(i)&&this.options[i].value==opt.value){
                    var img=$('#instagram_img');
                    img[0].src = this.options[i]['image'];
                }
            }
        }

   });
});

It’s critical that the component must be initiated before the form element is uploaded. Therefore, the event handler must be attached in precisely this way.

Adding the UI Element After Outputting Shipping Methods

Problem: Additional UI elements must be added after the standard output of shipping methods on one-page checkout.

Check the solution

Solution:

1. Define the checkout_index_index.xml layout file in its module.
2. Add a child element to the shippingAdditional element, which, in turn, will be a component, the value of which should be recorded in a JS file. Example:

<item name="component" xsi:type="string">JohnnySk_Additional/js/view/shipping/newmail/form</item>

This the full example of a custom layout file:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="checkout" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="config" xsi:type="array">
                                                        <item name="shippingMethodNewMailItemTemplate" xsi:type="string">Beautywave_NewMail/shipping-address/shipping-method-item</item>
                                                        <item name="shippingMethodListTemplate" xsi:type="string">Beautywave_NewMail/shipping-address/shipping-method-list</item>
                                                    </item>
                                                    <!-- Add additional shipping component -->
                                                    <item name="children" xsi:type="array">
                                                        <item name="shippingAdditional" xsi:type="array">
                                                            <item name="component" xsi:type="string">uiComponent</item>
                                                            <item name="displayArea" xsi:type="string">shippingAdditional</item>
                                                            <item name="children" xsi:type="array">
                                                                <item name="newmail_carrier_form" xsi:type="array">
                                                                    <item name="component" xsi:type="string">JohnnySk_Additional/js/view/shipping/newmail/form</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

3. Define the JS file of our UI element (we see the path to it in the example above):

JohnnySk_Additional/js/view/shipping/newmail/form

Sample JS file:

/** * Created by JohnnySk. Don't touch this if you don't understand what's going on here ;)
 */
define([    'jquery',
    'ko',
    'uiComponent',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/shipping-service',
    'mage/translate'
], function ($, ko, Component, quote, shippingService, t) {
    'use strict';
    return Component.extend({
        defaults: { template: 'JohnnySk_Additional/shipping/newmail/form'
 },
    initialize: function (config) {
            this.someObsarvableItems    = ko.observableArray();
           this.selectedObsarvableItem = ko.observable();console.log('JohnnySk was here!!!');            this._super();
        }
    });
});

This code is for reference only; from here, you should implement the logic you need for your purposes. The dependencies in the component are also just random; you can include any dependencies you wish here (the first parameter of the defined functionality).

Bonus: How to Create an Alias for Console Commands

Problem: Linux console is slow when running Magento 2

Check the solution

Solution: A convenient way to speed up the Linux console is to use aliases (creating other names for console commands or sequences of commands):

To quickly switch between versions of PHP:

alias m2tom1="sudo a2dismod php7.1&&sudo a2enmod php5.6&&sudo service apache2 restart"
alias m1tom2="sudo a2dismod php5.6&&sudo a2enmod php7.1&&sudo service apache2 restart"

Assigning commands to change permissions:

alias m2perm="sudo find . -type d -exec chmod 775 {} \; && sudo find . -type f -exec chmod 664 {} \; && sudo chmod u x bin/magento"

Full permissions:

alias m2pall="sudo chmod -R 777 ."

Magento 2 console commands:

alias m2="bin/magento"
alias m2 s:u="m2 setup:upgrade"
alias m2 c:f="m2 cache:flush"
alias m2 i:r="m2 indexer:reindex"
alias m2 c:f="m2 setup:static-content:deploy"

To save commands, they must be written to the file ~/.bashrc

Magento 2 issues we discussed are just a part of all the things that you can face. What conclusions can we make? What does all this mean for us? For web developers, Magento 2 is still a powerful tool. But it requires additional technical knowledge, new creative approaches, and learning a lot of skills from scratch regarding the platform’s changed architecture. Also, we should keep in mind that first of all, Magento 2 is a tool. We, Magento developers, and its large community are the masters who can make this product better day by day. For businesses, Magento 2 is a complex solution that allows them to extend and change their online stores flexibly.

SUM MARY

Now we can expect the continued high demand for using Magento 2 in eCommerce. The only thing the business owners should remember is that Magento 2 requires spending additional costs on a powerful hosting and web development. But all these investments can be covered by business revenue with a good website and an effective marketing strategy. We believe that Magento 2 is the best option for eCommerce business, and it will be among the market leaders in the nearest future.

Posted on: May 29, 2018

5.0/5.0

Article rating (14 Reviews)

Do you find this article useful? Please, let us know your opinion and rate the post!

  • Not bad
  • Good
  • Very Good
  • Great
  • Awesome