The Best Creative Minds – Zynovo

Magento is our forte!

Zynovo is a full-service digital commerce agency, focused on end-to-end implementations of the most flexible enterprise-class commerce platform to help online merchants fulfill their business and e-commerce goals in a way that is both economical and efficient. We provide strategic planning, design, development and post-launch maintenance.

Mon – Fri: 09:00 am – 06:00 pm
Contact +1 (818) 743-2444
Follow

Magento 2 Extension Attributes

Overview

In some cases, Magento 2 functionality requires modifications in order to fit store owner’s expectations. For example, an Order should have additional information provided by a customer during checkout. In this case there are 2 ways of extending existing entities: custom attributes or extension attributes. In Magento 2 extension attributes allows to add custom additional data into an entity such as Product, Customer, Order etc. In this article we are going to review usage of the “shipping_assignment” extension attribute for the Magento\Quote module.

 

Motivation

Magento 2 extension attributes introduce a flexible and declarative way of entity associations. It allows to add additional complex data to an existing entity class. Additional data usually persisted separately and loaded from an external module or database table. It is unlikely to use ALTER TABLE MySQL statements for new columns to modify existing Magento 2 tables from a custom module.
The following diagram illustrates an EntityInterface interface of a Module A. This interface has setExtensionAttributes() method which is used to add new instance of a CustomAttributeInterface interface from Module B.

Extension Attributes allow to add unlimited amount of data without modifying existing entity classes and interfaces.

 

How It Works

The \Magento\Framework\Api\ExtensibleDataInterface interface is used by an entity to provide an extension point for a functionality introduced by a custom module. An entity should implement the ExtensibleDataInterface interface to give an ability for other modules to set custom data via setExtensionAttributes() method of an entity’s interface. Basically the ExtensibleDataInterface interface is used as a parent interface for an entity interface.
The \Magento\Quote\Api\Data\CartInterface interface provides setExtensionAttributes() and getExtensionAttributes() methods in addition to all other getter and setter methods which help to operate with Cart related data (e.g. shipping and billing addresses, customer, store etc).


namespace Magento\Quote\Api\Data;

use Magento\Quote\Api\Data\CartExtensionInterface;
use Magento\Framework\Api\ExtensibleDataInterface;

interface CartInterface extends ExtensibleDataInterface
{
   /**
    * Retrieve existing extension attributes object or create a new one.
    *
    * @return CartExtensionInterface|null
    */
   public function getExtensionAttributes();

   /**
    * Set an extension attributes object.
    *
    * @param CartExtensionInterface $extensionAttributes
    * @return $this
    */
   public function setExtensionAttributes(CartExtensionInterface $extensionAttributes);

   /** more code */
}

The CartExtensionInterface interface follows naming convention provided by Magento 2 Framework. It allows to identify name of a class or interface which should be generated by Object Manager library.
Pattern: <Vendor>\<Name>\<Api>\<Name>ExtensionInterface

What does it mean, you ask?
Every time ObjectManager::get($name) or ObjectManager::create($name) method of the \Magento\Framework\App\ObjectManager class is called $name variable is checked. The “ExtensionInterface” suffix of an interface gives ObjectManager a call to generate new interface and put it to a <magento>/var/generation/ directory. With that said, the Magento\Quote\Api\Data\CartExtensionInterface interface is going to be generated.


namespace Magento\Quote\Api\Data;

interface CartExtensionInterface extends \Magento\Framework\Api\ExtensionAttributesInterface
{}

The CartExtensionInterface interface extends the ExtensionAttributesInterface interface for other classes involved into Extension Attributes processing. This interface is used to verify that given instance of an interface (in our case CartExtensionInterface) is an Extension Attribute.
The CartExtensionInterface interface will be generated without any methods unless new Extension Attribute is declared via configuration.

 

Extension Attribute Declaration

Magento 2 Framework gives a declarative way of adding an extension attribute. In order to add extension attribute the extension_attributes.xml configuration file should be used. Read more about supported configuration when adding Magento 2 extension attributes here.
The following configuration adds “shipping_assignments” attribute to extension attributes list of the CartInterface interface.
Configuration file: <magento>/vendor/module-quote/etc/extension_attributes.xml


<config>
   <extension_attributes for="Magento\Quote\Api\Data\CartInterface">
       <attribute code="shipping_assignments" type="Magento\Quote\Api\Data\ShippingAssignmentInterface[]" />
   </extension_attributes>
</config>

Take in mind that compilation should take place in order to re-generate the interface and make the shipping_assignments attribute visible from the CartExtensionInterface interface. The new version of the Magento\Quote\Api\Data\CartExtensionInterface is the following:


namespace Magento\Quote\Api\Data;

interface CartExtensionInterface extends \Magento\Framework\Api\ExtensionAttributesInterface
{
   /**
    * @return \Magento\Quote\Api\Data\ShippingAssignmentInterface[]|null
    */
   public function getShippingAssignments();

   /**
    * @param \Magento\Quote\Api\Data\ShippingAssignmentInterface[] $shippingAssignments
    * @return $this
    */
   public function setShippingAssignments($shippingAssignments);
}

Setting Extension Attribute

Let’s overview the Magento\Quote\Model\QuoteRepository\LoadHandler class as an example of setting the “shipping_assignment” extension attribute to a CartInterface quote object:


namespace Magento\Quote\Model\QuoteRepository;

use Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor;

class LoadHandler
{
    /** more code */
    /**
     * @param CartInterface $quote
     * @return CartInterface
     */
    public function load(CartInterface $quote)
    {
        /** more code */
        $shippingAssignments = [];
        if (!$quote->isVirtual() && $quote->getItemsQty() > 0) {
            $shippingAssignments[] = $this->shippingAssignmentProcessor->create($quote);
        }
        $cartExtension = $quote->getExtensionAttributes();
        if ($cartExtension === null) {
            $cartExtension = $this->cartExtensionFactory->create();
        }
        $cartExtension->setShippingAssignments($shippingAssignments);
        $quote->setExtensionAttributes($cartExtension);

        return $quote;
    }
}

The LoadHandler::load() method triggers ShippingAssignmentProcessor object to create new Shipping Assignment instance and sets it into a CartExtension object. The CartExtension object then added as new Extension Attribute of a CartInterface object.
Note: The getExtensionAttributes() method can return NULL in case there was no object added. Always check in your custom code when using this method.

 

Saving Extension Attribute

In order to save extension attribute a handler class should be executed in context of saving main entity. The following SaveHandler::processShippingAssignment() method reads CartInterface::getExtensionAttributes() method and delegates processing of a “shipping_assignment” extension attribute to a ShippingAssignmentPersister class. The shipping assignment attribute is stored separately from a CartInterface object.


namespace Magento\Quote\Model\QuoteRepository;

use Magento\Quote\Api\Data\CartInterface;
use Magento\Framework\Exception\InputException;
use Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentPersister;

class SaveHandler
{
   /** more code */

   /**
    * @param \Magento\Quote\Model\Quote $quote
    * @return void
    * @throws InputException
    */
   private function processShippingAssignment($quote)
   {
       $extensionAttributes = $quote->getExtensionAttributes();
       if (!$quote->isVirtual() && $extensionAttributes && $extensionAttributes->getShippingAssignments()) {
           $shippingAssignments = $extensionAttributes->getShippingAssignments();
           if (count($shippingAssignments) > 1) {
               throw new InputException(__("Only 1 shipping assignment can be set"));
           }
           $this->shippingAssignmentPersister->save($quote, $shippingAssignments[0]);
       }
   }
}

The ShippingAssignmentPersister::save() method prepares new Shipping Address of current CartInterface object for persisting it in a database.

 

Benefits

There are following benefits when using extension attributes in Magento 2:

  • Declarative way of extending existing interfaces.
  • Type hinting gives extension developers exact method name declared in an auto-generated extension Interface. Modern editors such as PHPStorm will easily show available methods and its arguments.
  • Restrict Magento 2 extension attributes permission when retrieving entity via WebAPI calls. You can restrict some attributes from reading information from main entity.
  • Preserve backward compatibility when adding new data to an entity.

 

Summary

In this article we have reviewed the “shipping_assignment” extension attribute declaration and processing by SaveHandler and LoadHandler classes. By using extension attributes it is expected your custom module will not override Magento 2 class or store information as part of Magento 2 database table. Custom module should rather have an extension attribute declaration in an extension_attributes.xml configuration file and handlers required to process this attribute.