Magento 2 provides some default attributes that are added to the quote and order item data. Here is how to go about adding custom Attributes to Magento 2 quotes and orders.
If you’ve read Adding Custom Attributes to Quotes and Orders in Magento 1, then the below steps aren’t too dissimilar from adding custom attributes in the original versions of Magento.
Assuming that you’ve already defined a custom module with a registration.php
and a module.xml
file, then you are ready to add the code from the steps below.
To start with, define the custom attribute in a catalog_attributes.xml
within a quote_item
group.
In this example, a dropdown_attribute
attribute (of type dropdown) will be added to Magento.
<?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="dropdown_attribute"/> </group> </config>
Add the InstallData.php
class to add the custom attribute programmatically. In this example, the attribute created will be added to the Default
attribute set, within the General
group.
Take note that the attribute is also added as a column to the quote_item
and sales_order_item
tables via $quoteSetup
and $salesSetup
respectively.
<?php namespace [Vendor]\[Module]\Setup; use Magento\Eav\Setup\EavSetup; use Magento\Eav\Setup\EavSetupFactory; use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\InstallDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Sales\Setup\SalesSetupFactory; use Magento\Quote\Setup\QuoteSetupFactory; /** * @codeCoverageIgnore */ class InstallData implements InstallDataInterface { /** * EAV setup factory * * @var EavSetupFactory */ private $eavSetupFactory; /** * @var QuoteSetupFactory */ private $quoteSetupFactory; /** * @var SalesSetup */ private $salesSetupFactory; /** * InstallData constructor. * @param EavSetupFactory $eavSetupFactory * @param QuoteSetupFactory $quoteSetupFactory */ public function __construct( EavSetupFactory $eavSetupFactory, QuoteSetupFactory $quoteSetupFactory, SalesSetupFactory $salesSetupFactory ) { $this->eavSetupFactory = $eavSetupFactory; $this->quoteSetupFactory = $quoteSetupFactory; $this->salesSetupFactory = $salesSetupFactory; } public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { /** @var EavSetup $eavSetup */ $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); /** @var QuoteSetup $quoteSetup */ $quoteSetup = $this->quoteSetupFactory->create(['setup' => $setup]); /** @var SalesSetup $salesSetup */ $salesSetup = $this->salesSetupFactory->create(['setup' => $setup]); /** * Add attributes to the eav/attribute */ $eavSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'dropdown_attribute', [ 'type' => 'int', 'label' => 'Dropdown Attribute', 'input' => 'select', 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, 'visible' => true, 'required' => false, 'user_defined' => true, 'default' => '', 'searchable' => false, 'filterable' => false, 'comparable' => false, 'visible_on_front' => false, 'used_in_product_listing' => false, 'unique' => false, 'option' => [ 'values' => [ 'Option 1', 'Option 2', 'Option 3' ], ] ] ); $attributeSetId = $eavSetup->getDefaultAttributeSetId('catalog_product'); $eavSetup->addAttributeToSet( 'catalog_product', $attributeSetId, 'General', 'dropdown_attribute' ); $attributeOptions = [ 'type' => Table::TYPE_TEXT, 'visible' => true, 'required' => false ]; $quoteSetup->addAttribute('quote_item', 'dropdown_attribute', $attributeOptions); $salesSetup->addAttribute('order_item', 'dropdown_attribute', $attributeOptions); } }
The next step is to ensure that the product dropdown attribute is saved in the quote when the sales_quote_item_set_product
event is fired.
Add an observer to this event via the events.xml
file.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="sales_quote_item_set_product"> <observer name="set_item_dropdown_attribute" instance="[Vendor]\[Module]\Observer\SetItemDropdownAttribute" /> </event> </config>
Now add the SetItemCustomAttribute.php
observer. This will simply set the product’s dropdown attribute option, which can be retrieved using $product->getAttributeText('dropdown_attribute')
, to the quote item using setDropdownAttribute()
.
<?php namespace [Vendor]\[Module]\Observer; use Magento\Framework\Event\ObserverInterface; class SetItemDropdownAttribute implements ObserverInterface { /** * @param \Magento\Framework\Event\Observer $observer * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute(\Magento\Framework\Event\Observer $observer) { $quoteItem = $observer->getQuoteItem(); $product = $observer->getProduct(); $quoteItem->setDropdownAttribute($product->getAttributeText('dropdown_attribute')); } }
The last step should be (see explanation below…) to add a fieldset.xml
file that defines the attribute to be copied over from the quote to order object when an order is placed. This is similar to what happens in Magento 1 using the module’s config.xml
file.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Object/etc/fieldset.xsd"> <scope id="global"> <fieldset id="quote_convert_item"> <field name="dropdown_attribute"> <aspect name="to_order_item" /> </field> </fieldset> </scope> </config>
If you now enable the module, clear the cache and run the database upgrade via the commands provided, you should notice that the dropdown_attribute attribute has been added to Magento’s eav_attribute
table in the database, and also as a column to the quote_item
and sales_order_item
tables.
Now assign an existing product a value from the dropdown_attribute attribute within the admin, and when adding the product to your basket, you should notice that the dropdown_attribute
column in the quote_item
table is now populated for the item entry!
Finally, place an order which will convert the quote item data to an order item entry in the sales_order_item
table. Whilst the default column data from quote_item
is copied across, the dropdown_attribute
column is not.
It is not clear whether this is intentional from Magento, or whether it is more likely a bug, but for some reason the fieldset.xml
configuration in custom modules doesn’t take into account the conversion of the quote item to order item.
Therefore to fix this problem, create a plugin that will function around the convert()
method of the Magento\Quote\Model\Quote\Item\ToOrderItem
class. This is the class responsible for converting the quote data to order data.
Start by defining the plugin within your module’s di.xml
file.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Quote\Model\Quote\Item\ToOrderItem"> <plugin name="dropdown_attribute_quote_to_order_item" type="[Vendor]\[Module]\Plugin\DropdownAttributeQuoteToOrderItem"/> </type> </config>
Then add the aroundConvert()
plugin.
<?php namespace [Vendor]\[Module]\Plugin; class DropdownAttributeQuoteToOrderItem { public function aroundConvert( \Magento\Quote\Model\Quote\Item\ToOrderItem $subject, \Closure $proceed, \Magento\Quote\Model\Quote\Item\AbstractItem $item, $additional = [] ) { /** @var $orderItem \Magento\Sales\Model\Order\Item */ $orderItem = $proceed($item, $additional); $orderItem->setDropdownAttribute($item->getDropdownAttribute()); return $orderItem; } }
Now when you place an order, the quote item’s dropdown_attribute
value will copy across to the sales_order_item
table.
Hopefully the plugin step will not be needed in the future when Magento fix the conversion issue. However for now, the plugin solution is sufficient.
Note: This article is based on Magento Open Source version 2.2.