extension attributes

Requirements

Describe how to extend existing entities. What mechanisms are available to extend existing classes, for example by adding a new attribute, a new field in the database, or a new related entity?

Attention

There are two types of attributes you can use to extend Magento functionality:

  • Custom and Entity-Attribute-Value (EAV) attributes — Custom attributes are those added on behalf of a merchant. For example, a merchant might need to add attributes to describe products, such as shape or volume. A merchant can add these attributes in the Magento Admin panel. See the merchant documentation for information about managing custom attributes.Custom attributes are a subset of EAV attributes. Objects that use EAV attributes typically store values in several MySQL tables. The Customer and Catalog modules are the primary models that use EAV attributes. Other modules, such as ConfigurableProduct, GiftMessage, and Tax, use the EAV functionality for Catalog.
  • Extension attributes. Extension attributes are new in Magento 2. They are used to extend functionality and often use more complex data types than custom attributes. These attributes do not appear in the Magento Admin.

Today we are going to talk about extension attributes.

Experiment

Preparation

We are going to create an entity, you can skip this step.

etc/db_schema.xml

<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
  <table name="order_custom_attribute" resource="default" engine="innodb" comment="order_custom_attribute">
    <column xsi:type="int" name="entity_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Entity ID"/>
    <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Order ID"/>
    <column xsi:type="varchar" name="bar" nullable="true" length="255" comment="bar"/>
    <constraint xsi:type="primary" referenceId="PRIMARY">
        <column name="entity_id"/>
    </constraint>
    <index referenceId="ORDER_CUSTOM_ATTRIBUTE_ORDER_ID" indexType="btree">
      <column name="order_id"/>
    </index>
    <constraint xsi:type="foreign" referenceId="ORDER_CUSTOM_ATTRIBUTE_ORDER_ID_SALES_ORDER_ENTITY_ID" table="order_custom_attribute" column="order_id" referenceTable="sales_order" referenceColumn="entity_id" />
  </table>
</schema>

you can create db_schema_whitelist.json using command bin/magento setup:db-declaration:generate-whitelist --module-name=VendorName_TestModule

etc/db_schema_whitelist.json

{
    "order_custom_attribute": {
        "column": {
            "entity_id": true,
            "order_id": true,
            "bar": true
        },
        "index": {
            "ORDER_CUSTOM_ATTRIBUTE_ORDER_ID": true
        },
        "constraint": {
            "PRIMARY": true,
            "ORDER_CUSTOM_ATTRIBUTE_ORDER_ID_SALES_ORDER_ENTITY_ID": true
        }
    }
}

After bin/magento setup:upgrade, we get order_custom_attribute table.

<?php
namespace VendorName\TestModule\Model;

class Attribute extends \Magento\Framework\Model\AbstractModel implements \VendorName\TestModule\Api\Data\AttributeInterface
{
    protected function _construct()
    {
        $this->_init('VendorName\TestModule\Model\ResourceModel\Attribute');
    }

    public function setOrderId($value)
    {
        return $this->setData('order_id', $value);
    }

    public function getOrderId()
    {
        return $this->getData('order_id');
    }

    public function setBar($value)
    {
        return $this->setData('bar', $value);
    }

    public function getBar()
    {
        return $this->getData('bar');
    }

}
<?php
namespace VendorName\TestModule\Api\Data;

Interface AttributeInterface {
    /**
     * Set order id
     * @param string $value
     * @return $this
     */
    public function setOrderId($value);

    /**
     * get order id
     * @return string
     */
    public function getOrderId();

    /**
     * Set bar
     * @param string $value
     * @return $this
     */
    public function setBar($value);

    /**
     * get bar
     * @return string
     */
    public function getBar();
}

The interface must have proper dock blocks, otherwise, when you call api, you will get an error ‘Each method must have a doc block ….’

<?php
namespace VendorName\TestModule\Model\ResourceModel;

class Attribute extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
    protected function _construct()
    {
        $this->_init('order_custom_attribute', 'entity_id');
    }
}
<?php
namespace VendorName\TestModule\Model\ResourceModel\Attribute;

class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
    protected function _construct()
    {
        $this->_init('VendorName\TestModule\Model\Attribute', 'VendorName\TestModule\Model\ResourceModel\Attribute');
    }
}

etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="VendorName\TestModule\Api\Data\AttributeInterface" type="VendorName\TestModule\Model\Attribute"/>
</config>

Let’s create the extension_attributes.xml file

<!-- etc/extension_attributes.xml -->
<?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\Sales\Api\Data\OrderInterface">
        <attribute code="order_custom_attribute" type="VendorName\TestModule\Api\Data\AttributeInterface" />
    </extension_attributes>
</config>

Create a plugin to save and retrieve the new attributes

etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="VendorName\TestModule\Api\Data\AttributeInterface" type="VendorName\TestModule\Model\Attribute"/>
    <type name="Magento\Sales\Api\OrderRepositoryInterface">
        <plugin name="save_custom_attribute" type="VendorName\TestModule\Plugin\OrderSave"/>
        <plugin name="get_custom_attribute" type="VendorName\TestModule\Plugin\OrderGet"/>
    </type>
</config>
<?php
namespace VendorName\TestModule\Plugin;

use Magento\Framework\Exception\NoSuchEntityException;

class OrderGet
{
    protected $orderExtensionFactory;

    protected $attributeFactory;

    public function __construct(
        \Magento\Sales\Api\Data\OrderExtensionFactory $orderExtensionFactory,
        \VendorName\TestModule\Model\AttributeFactory $attributeFactory
    ) {
        $this->orderExtensionFactory = $orderExtensionFactory;
        $this->attributeFactory = $attributeFactory;
    }

    public function afterGet(
        \Magento\Sales\Api\OrderRepositoryInterface $subject,
        \Magento\Sales\Api\Data\OrderInterface $resultOrder
    ) {
        $resultOrder = $this->getCustomAttribute($resultOrder);
        return $resultOrder;
    }

    private function getCustomAttribute(\Magento\Sales\Api\Data\OrderInterface $order)
    {
        try {
            // The actual implementation of the repository is omitted
            // but it is where you would load your value from the database (or any other persistent storage)
            // $customAttribute = $this->attributeRepository->get($order->getEntityId());

            // We use model load for example
            $customAttribute = $this->attributeFactory->create();
            $customAttribute->load($order->getEntityId());
            if (! $customAttribute->getEntityId()) {
                throw new NoSuchEntityException();
            }

        } catch (NoSuchEntityException $e) {
            return $order;
        }

        $extensionAttributes = $order->getExtensionAttributes();
        $orderExtension = $extensionAttributes ? $extensionAttributes : $this->orderExtensionFactory->create();

        $orderExtension->setOrderCustomAttribute($customAttribute);
        $order->setExtensionAttributes($orderExtension);

        return $order;
    }
}
<?php
namespace VendorName\TestModule\Plugin;

use Magento\Framework\Exception\CouldNotSaveException;

class OrderSave
{
    public function afterSave(
        \Magento\Sales\Api\OrderRepositoryInterface $subject,
        \Magento\Sales\Api\Data\OrderInterface $resultOrder
    ) {
        $resultOrder = $this->saveCustomAttribute($resultOrder);
        return $resultOrder;
    }

    private function saveCustomAttribute(\Magento\Sales\Api\Data\OrderInterface $order)
    {
        $extensionAttributes = $order->getExtensionAttributes();
        if (null !== $extensionAttributes &&
            null !== $extensionAttributes->getOrderCustomAttribute()
        ) {
            /* @var \Magento\GiftMessage\Api\Data\MessageInterface $giftMessage */
            $customAttribute = $extensionAttributes->getOrderCustomAttribute();
            try {

                // The actual implementation of the repository is omitted
                // but it is where you would load your value from the database (or any other persistent storage)
                // $customAttribute = $this->attributeRepository->save($order->getEntityId());

                // We use model save for example
                $customAttribute->save();

            } catch (\Exception $e) {
                throw new CouldNotSaveException();
            }
        }
        return $order;
    }
}

Let’s create a controller for test.

...
    ini_set('display_errors', 1);

    $orderResp = $this->_objectManager->get('Magento\Sales\Api\OrderRepositoryInterface');
    $order = $orderResp->get(1);
    // $order = $orderResp->get(2);

    /** @param \Magento\Sales\Api\Data\OrderExtension */
    // var_dump(get_class($order->getExtensionAttributes()));
    // var_dump(get_class_methods($order->getExtensionAttributes()));

    $extensionAttribute = $order->getExtensionAttributes();
    $orderCustomAttribute = $extensionAttribute->getOrderCustomAttribute();
    if ($orderCustomAttribute) {
        var_dump($orderCustomAttribute->getData());
        $orderCustomAttribute->setBar('898989');
    }

    $orderResp->save($order);
...

Magento\Sales\Api\Data\OrderInterface->getExtensionAttributes() return an instance of Magento\Sales\Api\Data\OrderExtension

Magento\Sales\Api\Data\OrderExtension->getOrderCustomAttribute() (specified in extension_attributes.xml) return an instance of VendorName\TestModule\Api\Data\AttributeInterface

Let’s call rest api

Practice test

You are building an tool that imports products from an ERP. There are 20 columns of additional information that are associated with each product. This extra information must also be associated with an update time to know when to refresh the data. Keeping maintainability in mind, how do you build this into Magento?

  • A. Override the Product model and add the fields.
  • B. Create a separate model and build code to associate the two record types.
  • C. Create 20 EAV attributes and check their updated_at column.
  • D. Utilize an extension attribute

Answer D

You are adding an extension attribute to the CustomerInterface class. You have specified data for
the extension attribute, but when you check the database, nothing has been saved.
Why is that?
A. The appropriate columns in customer_entity have not been created.
B. You need to ensure that the extension attribute getter and setter exists in CustomerExtensionInterface.
C. You need to add a node to the extension attribute XML details.
D. Extension attribute data is not automatically persisted to the database.

Answer D

Reference

EAV and extension attributes
How To Add Extension Attribute To Order In Magento 2?

发表评论

电子邮件地址不会被公开。 必填项已用*标注