What are Extension Attributes?

Extension Attributes are a part of Magento Service Contracts. Both of them are quite complex, Magento Service Contracts are a set of classes and interfaces, which hide the business logic and ensure the integrity of data.

The Extension Attributes are used to extend the functionalities of those interfaces. Because, by default, you can only use get/set methods which have been defined in the interface.

And remember that the Extension Attributes do not appear on Admin or Store Front. It is featured in the Magento API.

This article will help you to use Extension Attributes and expose these attributes to REST APIs.

Declare Extension Attributes for an Interface

We will use the ProductInterface for this example. Create extension_attributes.xml in etc folder and copy the following codes to that file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
   <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface">
        <!--Scalar -->
       <attribute code="gift" type="string"/>
        <!--Non-Scalar -->
       <attribute code="wrapper" type="Magenest\ExtensionAttributes\Api\Data\WrapperInterface"/>
        <!--Array -->
       <attribute code="post_card" type="string[]">

<!-- Restricts access to the extension attribute to users with the specified permission -->
<resources>
     <resource ref="Magenest_ExtensionAttribute::gift"/>
</resources>
<!--A join operation -->
<join reference_table="gift_post_card" join_on_field="id" reference_field="id" >
   <field>name</field>
 </join>
        </attribute>
   </extension_attributes>
</config>

The interface you are using has to implement Magento\Framework\Api\ExtensibleDataInterface to use the method get/setExtensionAttributes.

Expose attributes to REST APIs

Note: Some entities don’t have the implementation to fetch Extension Attributes. So, we will create a plugin with the afterGetExtensionAttributes method for the ProductInterface, to make sure that they are not null.

class LoadExtensionAttibutes
{
   /**
    * @var \Magento\Catalog\Api\Data\ProductExtensionFactory
    */
   private $extensionFactory;

   /**
    * @param \Magento\Catalog\Api\Data\ProductExtensionFactory $extensionFactory
    */
   public function __construct(
\Magento\Catalog\Api\Data\ProductExtensionFactory $extensionFactory
   ){       
$this->extensionFactory = $extensionFactory;
    }

   /**
    * Loads product entity extension attributes
    *
    * @param \Magento\Catalog\Api\Data\ProductInterface $entity
    * @param \Magento\Catalog\Api\Data\ProductExtensionInterface|null $extension
    * @return \Magento\Catalog\Api\Data\ProductExtensionInterface
    */
   public function afterGetExtensionAttributes(
       \Magento\Catalog\Api\Data\ProductInterface $entity,
       \Magento\Catalog\Api\Data\ProductExtensionInterface $extension = null
   )
   {
       if ($extension === null) {
           $extension = $this->extensionFactory->create();
       }

       return $extension;
   }

And add the below codes to etc/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\Catalog\Api\Data\ProductInterface">
   <plugin name="GiftProductExtensionAttributeOperations"
       type="Magenest\ExtensionAttributes\Plugin\Product\LoadExtensionAttibutes" sortOrder="1"/>
</type>
</config>

The next step is a configuration for the new Extension Attribute. You have to get and process it “manually”.

You can try to request the /V1/products/{{sku}} API to get the Product data by SKU. As a result, the new Extension Attribute is not in the response data or it has the null value.

Note: These Extension Attributes are not like normal attributes. They are not magically saved to the database or populated when the entity is loaded from the database. So the solution is that you should use a plugin or observer to save and get data of these attributes.

That is the reason why you must use a Plugin to modify the class which handles the request. In this example, the class is Magento\Catalog\Api\ProductRepositoryInterface, and the methods you will plugin are save, get and getList (this method is used for getting a list of products).
Copy this following codes to the etc/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\Catalog\Api\ProductRepositoryInterface">
   <plugin name="GiftProductExtensionAttribute"
           type="Magenest\ExtensionAttributes\Plugin\Product\AddGift"/>
</type>
</config>

Then, copy the afterGet method to Magenest\ExtensionAttributes\Plugin\Product\Gift file. As you can see, you must get the value of the new attribute from the database and set it for the Extension Attribute object.

public function afterGet(\Magento\Catalog\Api\ProductRepositoryInterface $subject, \Magento\Catalog\Api\Data\ProductInterface $entity)
{
   /** get current extension attribute from database **/
   $giftData = “cup”;
   $extensionAttributes = $entity->getExtensionAttributes();
   /** set extension attributes by value you have gotten **/
   $extensionAttributes->setGift($giftData);
   $entity->setExtensionAttributes($extensionAttributes);

   return $entity;
}

Now, try to get the product by using API and you will receive a response like this. The “gift” attribute and its value are wrapped in “extension_attributes”:

{
    "id": 19,
    "sku": "matcha",
    "name": "matcha",
    "attribute_set_id": 4,
    "price": 300,
    "status": 1,
    "visibility": 4,
    "type_id": "simple",
    "created_at": "2020-01-07 07:17:52",
    "updated_at": "2020-01-07 07:30:40",
    "extension_attributes": {
        "website_ids": [
            1
        ],
        "gift": "cup"
    }
}

We will use the same way for the afterGetList method, get value from the database and set it for each Extension Attribute object in the collection.

public function afterGetList(
    \Magento\Catalog\Api\ProductRepositoryInterface $subject,  
    \Magento\Framework\Api\SearchResultsInterface $searchCriteria
){
   $products = [];
   foreach ($searchCriteria->getItems() as $entity) {
        /** get current extension attribute from database **/
       $giftData = “cup”;
       $extensionAttributes = $entity->getExtensionAttributes();
       $extensionAttributes->setGift($giftData);
       $entity->setExtensionAttributes($extensionAttributes);

       $products[] = $entity;
   }
   $searchCriteria->setItems($products);
   return $searchCriteria;
}

Move to the afterSave method, this method is a little bit vice versa, you will get the value of the Extension Attribute from the requested data and process the value.

public function afterSave(
 \Magento\Catalog\Api\ProductRepositoryInterface $subject,    \Magento\Catalog\Api\Data\ProductInterface $entity
){
   $extensionAttributes = $entity->getExtensionAttributes();
   /** get current extension attributes from entity(product) **/
   $giftData = $extensionAttributes->getGift();
   /** process the value **/
   $this->saveGiftAttribute($giftData);
   return $entity;
}

Then, try to create a new product by using the API with the following data and check the value at where you have stored it:

{
    "id": 21,
    "sku": "T-shirt",
    "name": "T-shirt",
    "attribute_set_id": 4,
    "price": 300,
    "status": 1,
    "visibility": 4,
    "type_id": "simple",
    "extension_attributes": {
        "website_ids": [
            1
        ],
        "gift": "necktie"
    },
}

The Extension Attributes help third-party can use Magento APIs without meddling to the core code. They can easily integrate and the data of objects is guaranteed.

Hope this article will help you to understand clearly and know how to use the Extension Attributes of Magento Service Contracts.

Thank you for reading!