Joining external attributes to entity in Magento 2

Recently I have written an article on how to include the custom field value to API response in case it is related to the same database table. In addition to the previous blog post, I would like to describe how to add external attributes to a collection, when the attribute values are stored in separate database tables.

Let’s say we need to make the customer’s dob attribute value available among the order attributes when we load order entity or collection.

There is a join element available for the extension_attributes.xml configuration file. The join element defines which object fields and the database table/column to be associated.
Where:

  • reference_table – reference table name;
  • reference_field – reference column name;
  • join_on_field – name of the column that will be used in the join operation;
  • field – one or more fields present in the interface specified in the type (if used).

In the following example, the customer_dob extension attribute is being added to the Magento\Sales\Api\Data\OrderInterface.

<?xml version="1.0"?>
<!-- File: app/code/Atwix/JoinOrderExtAttribute/etc/extension_attributes.xml -->
<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="customer_dob" type="string">
            <join reference_table="customer_entity" reference_field="entity_id" join_on_field="customer_id">
                <field>dob</field>
            </join>
        </attribute>
    </extension_attributes>
</config>

As a result the dob attribute value will be automatically joined by \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface::process method called in \Magento\Sales\Model\OrderRepository::getList. However, this feature will work only for getList method involved in collection loading. In case you need to join the custom attribute when loading a single entity using the get method – the additional implementation is required. Here is an example of the plugin method resolves the mentioned issue.

First of all, we need to define the plugin for order repository (Magento\Sales\Api\OrderRepositoryInterface):

<?xml version="1.0"?>
<!-- File: app/code/Atwix/JoinOrderExtAttribute/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Sales\Api\OrderRepositoryInterface">
        <plugin name="atwix_join_order_ext_attribute"
                type="Atwix\JoinOrderExtAttribute\Plugin\OrderRepositoryAddExtensionAttributePlugin" />
    </type>
</config>

Then we need to create the corresponding plugin method (\Atwix\JoinOrderExtAttribute\Plugin\OrderRepositoryAddExtensionAttributePlugin::afterGet).

<?php

namespace Atwix\JoinOrderExtAttribute\Plugin;

use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Model\ResourceModel\Customer\Collection as CustomerCollection;
use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory as CustomerCollectionFactory;
use Magento\Sales\Api\Data\OrderExtensionFactory;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\OrderRepositoryInterface;

/**
 * Class OrderRepositoryAddExtensionAttributePlugin
 */
class OrderRepositoryAddExtensionAttributePlugin
{
    /**
     * Customer Date of Birthday field name
     */
    const FIELD_NAME_CUSTOMER_DOB = 'dob';

    /**
     * Order Extension Attributes Factory
     *
     * @var OrderExtensionFactory
     */
    protected $extensionFactory;

    /**
     * Customer Repository
     *
     * @var CustomerRepositoryInterface
     */
    protected $customerRepository;

    /**
     * Customer Collection Factory
     *
     * @var CustomerCollectionFactory
     */
    protected $customerCollectionFactory;

    /**
     * OrderRepositoryAddExtensionAttributePlugin constructor
     *
     * @param CustomerRepositoryInterface $customerRepository
     * @param CustomerCollectionFactory $customerCollectionFactory
     * @param OrderExtensionFactory $extensionFactory
     */
    public function __construct(
        CustomerRepositoryInterface $customerRepository,
        CustomerCollectionFactory $customerCollectionFactory,
        OrderExtensionFactory $extensionFactory
    ) {
        $this->customerRepository = $customerRepository;
        $this->customerCollectionFactory = $customerCollectionFactory;
        $this->extensionFactory = $extensionFactory;
    }

    /**
     * Add Customer Date of Birthday extension attribute to order data object in order to make it accessible in API data
     *
     * @param OrderRepositoryInterface $subject
     * @param OrderInterface $order
     *
     * @return OrderInterface
     */
    public function afterGet(OrderRepositoryInterface $subject, OrderInterface $order): OrderInterface
    {
        $customerId = (int) $order->getCustomerId();
        $customerDob = $this->getCustomerDateOfBirthday($customerId);
        $extensionAttributes = $order->getExtensionAttributes();
        $extensionAttributes = $extensionAttributes ? $extensionAttributes : $this->extensionFactory->create();
        $extensionAttributes->setCustomerDob($customerDob);
        $order->setExtensionAttributes($extensionAttributes);

        return $order;
    }

    /**
     * Get Customer Date of Birthday by Entity ID
     *
     * @param int
     *
     * @return string|null
     */
    protected function getCustomerDateOfBirthday(int $customerId): ?string
    {
        /** @var CustomerCollection $customerCollection */
        $customerCollection = $this->customerCollectionFactory->create();
        $customerCollection->addFilter('entity_id', $customerId);
        $customer = $customerCollection->getFirstItem();
        $incrementId = $customer->getData(self::FIELD_NAME_CUSTOMER_DOB);

        return $incrementId;
    }
}

Once the order entity is loaded, the Customer Date of Birthday value will be added to the order extension attributes data object. It’s quite straightforward, isn’t it?

The full module’s source code can be found in Atwix_JoinOrderExtAttribute GIT repository.

Read more about our Magento 2 extension development services. Thanks for reading!