How to access custom catalog attributes in Magento 2

Magento developers frequently encounter problems accessing custom attributes during product collection loading. For example, when you try to get a product from a quote or a wishlist item. As a result, we’ve identified intriguing workarounds to circumvent this issue, such as repeatedly loading the product. However, there is a proper way to make these attributes accessible. We would like to tell you more about it.

As you know from Magento 1 – custom attributes are not loaded by default when you work with a quote. There is a simple way of accessing a custom attribute on checkout/cart. Magento 2 uses a similar approach which is even more flexible and extensive.

Let’s create a simple module which provides a custom product attribute with product_brand code. The install script for the new attribute is the following.

<?php
/**
 * @author Atwix Team
 * @copyright Copyright (c) 2018 Atwix (https://www.atwix.com/)
 * @package Atwix_CatalogAttribute
 */

namespace Atwix\CatalogAttribute\Setup;

use Exception;
use Magento\Catalog\Model\Product;
use Magento\Config\Model\ResourceModel\Config;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

/**
 * Class InstallData
 */
class InstallData implements InstallDataInterface
{
    /**
     * Resource Config
     *
     * @var Config
     */
    protected $resourceConfig;

    /**
     * Eav Setup Factory
     *
     * @var EavSetupFactory
     */
    protected $eavSetupFactory;

    /**
     * AddDefaultShippingMethodsService constructor
     *
     * @param Config $resourceConfig
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(Config $resourceConfig, EavSetupFactory $eavSetupFactory)
    {
        $this->resourceConfig = $resourceConfig;
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * Installs data for a module
     *
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     *
     * @return void

     * @throws CouldNotSaveException
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();

        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        try {
            $eavSetup->addAttribute(
                Product::ENTITY,
                'product_brand',
                [
                    'type' => 'varchar',
                    'label' => 'Product Brand',
                    'group' => 'General Information',
                    'visible' => true,
                    'required' => false,
                    'user_defined' => false,
                    'default' => '',
                    'input' => 'text',
                    'global' => ScopedAttributeInterface::SCOPE_STORE,
                ]
            );
        } catch (Exception $e) {
            throw new CouldNotSaveException(__('Could create product attribute: "%1"', $e->getMessage()), $e);
        }

        $installer->endSetup();
    }
}

By default the new Brand attribute will be unavailable for the quote and wishlist items. However, there is a simple way to make the Brand attribute accessible. Magento 2 provides a separate config file type for such purpose (catalog_attributes.xml), which is intended to define the attribute names belonging to the corresponding group. You will need to create this XML file in “etc” directory of your module (e.g. app/code/Atwix/CatalogAttribute/etc/catalog_attributes.xml) with the following content:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/catalog_attributes.xsd">
    <group name="quote_item">
        <attribute name="product_brand"/>
    </group>
    <group name="wishlist_item">
        <attribute name="product_brand" />
    </group>
</config>

How it works

Magento 2 implements the Magento\Catalog\Model\Attribute\Config class, which provides the getAttributeNames method, responsible for retrieving names of attributes related to the specified group. In most cases, the result of this method execution is used to determine attributes that need to be added to the selection during the preparation of the entities collection.

For example:

$attributeNames = $this->attributeConfig->getAttributeNames('quote_item');
$productCollection = $this->productCollectionFactory->create();
$productCollection->addAttributeToSelect($attributeNames);

Where the attributeConfig property is the instance of Magento\Catalog\Model\Attribute\Config class.

As you may have noticed, it’s also possible to simply define your own group in catalog_attributes.xml file of your module. Then you can use it as you need by just calling: Magento\Catalog\Model\Attribute\Config::getAttributeNames('your_custom_group_name').

Default Groups

Magento 2 uses the following groups by default.

1. quote_item

The quote items group contains attributes to be added to selected products, that are assigned to quote items. If you want to access the product attribute value on checkout from the quote item – you just need to specify the attribute name within the quote_item group.
This group is processed in Magento\Quote\Model\ResourceModel\Quote\Item\Collection::_assignProducts method in order to add the products to quote items and item options.

$productCollection = $this->_productCollectionFactory->create()->setStoreId(
    $this->getStoreId()
)->addIdFilter(
    $this->_productIds
)->addAttributeToSelect(
    $this->_quoteConfig->getProductAttributes()
);

2. wishlist_item

This group contains attributes to be loaded for the products assigned to wishlist items. You can specify the attribute name within this group if you need it to be accessible from wishlist item.
This group is being processed in Magento\Wishlist\Model\ResourceModel\Item\Collection::_assignProducts method while loading the products collection.

$productCollection->addPriceData()
    ->addTaxPercents()
    ->addIdFilter($this->_productIds)
    ->addAttributeToSelect($this->_wishlistConfig->getProductAttributes())
    ->addOptionsToResult()
    ->addUrlRewrite();

3. catalog_product

The catalog product item group is being processed by Catalog Product Flat Indexer (as well as the quote_item group) in order to retrieve attribute codes to be added to flat tables.
By default, only catalog_product and quote_item groups are used by Product Flat Indexer. However, other groups can also be processed. If you need, you can pass your custom group to $flatAttributeGroups array as a parameter of indexer class constructor method. An example can be found in Magento_Catalog/etc/di.xml file.

<type name="Magento\Catalog\Helper\Product\Flat\Indexer">
    <arguments>
        <argument name="flatAttributeGroups" xsi:type="array">
            <item name="catalog_product" xsi:type="string">catalog_product</item>
        </argument>
    </arguments>
</type>

And another one is defined in Magento_Sales/etc/di.xml file.

<type name="Magento\Catalog\Helper\Product\Flat\Indexer">
    <arguments>
        <argument name="flatAttributeGroups" xsi:type="array">
            <item name="quote_item" xsi:type="string">quote_item</item>
        </argument>
    </arguments>
</type>

4. catalog_category

This group is intended to define the category attributes to be added to selection during category collection preparation process before loading in Magento\Catalog\Model\ResourceModel\Category\Tree class.

5. unassignable

This group contains a list of attributes that cannot be unassigned from an attribute set. The “unassignable” group is being processed in Magento\Catalog\Model\Entity\Product\Attribute\Group\AttributeMapper class during the attribute’s data mapping.

6. used_in_autogeneration

This group contains a list of attributes that allows autogeneration on admin’s product edit form. The “used_in_autogeneration” group is being processed in Magento\Catalog\Helper\Product::getAttributesAllowedForAutogeneration method.

Thanks for reading!