4

I've created custom customer attributes via InstallData and want to make them available as extension attributes. But I have trouble with the admin interface and am unsure if I'm doing it right.

I've come so far:

Register extension attributes

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\Customer\Api\Data\CustomerInterface">
        <attribute code="customer_number" type="string"/>
    </extension_attributes>
</config>

Create plugins

<?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\Customer\Api\CustomerRepositoryInterface">
        <plugin name="save_extension_attributes" type="Project\CustomerAttributes\Plugin\CustomerSave"/>
        <plugin name="get_extension_attributes" type="Project\CustomerAttributes\Plugin\CustomerGet"/>
    </type>
    <type name="\Magento\Customer\Api\Data\CustomerInterfaceFactory">
        <plugin name="create_extension_attributes" type="Project\CustomerAttributes\Plugin\CustomerCreate" />
    </type>
</config>

There are plugins for:

  • CustomerRepositoryInterface::beforeSave
  • CustomerRepositoryInterface::afterGetList
  • CustomerRepositoryInterface::afterGet
  • CustomerRepositoryInterface::afterGetById
  • CustomerInterfaceFactory::afterCreate

In the plugins I copy the value from and to the custom attributes. Abbreviated example for a plugin:

public function afterCreate(CustomerInterfaceFactory $subject, CustomerInterface $customer) //@codingStandardsIgnoreLine
{
    $this->extensionAttributes->addFromCustomAttributes($customer);
    return $customer;
}

with this method in a common class

public function addFromCustomAttributes(CustomerInterface $customer)
{
    if ($customer->getExtensionAttributes() === null) {
        $customer->setExtensionAttributes($this->extensionAttributeFactory->create(CustomerInterface::class));
    }
    $customer->getExtensionAttributes()->setCustomerNumber(
        $this->getCustomAttributeValue($customer, 'customer_number')
    );
}

The issue

When saving a customer from the admin panel, the extension attribute data is lost. The controller instantiates the customer like this:

$customer = $this->customerDataFactory->create();
$this->dataObjectHelper->populateWithArray(
    $customer,
    $customerData,
    '\Magento\Customer\Api\Data\CustomerInterface'
);

...

$this->_customerRepository->save($customer);

The $customerData array contains the custom attributes. My beforeSave plugin overwrites them from the extension attributes, which were never populated.

A solution?

Now I could add some checks, like if there are already changed values in the custom attributes, or if the extension attribute value is null. But this will probably lead to subtle bugs if for example I want to set an extension attribute value to null explicitly.

But while debugging, I found that DataObjectHelper::populateWithArray() does something with the extension attributes as well.

For extensible models, it calls \Magento\Framework\Api\ExtensionAttribute\JoinProcessor::extractExtensionAttributes, which looks for "join directives" in the extension attribute configuration. .

I've seen examples like this: How to add extension attribute Magento 2 (this one with an interesting comment in the answer by Magento architect Vitaliy Korotun that the join processor is not used everywhere. But for customers it's obviously used)

But the examples are always with external tables. What if I just want to use the customer EAV tables? I tried to make it explicit:

<attribute code="customer_number" type="string">
    <join reference_table="customer_entity_varchar" join_on_field="entity_id" reference_field="entity_id">
        <field>value</field>
    </join>
</attribute>

This does not work, it looks for the key extension_attribute_customer_number_value in the data array, where I just need customer_number.

So the main question is, is there a standard way to use extension attributes for EAV attributes, with the join processor involved?

Or is such a standard use case really that complicated?

  • I think extension attributes expect to be in their own tables. For EAV attributes, you'd probably want to consider them "custom attributes" – Navarr Jun 27 '17 at 13:13
  • @Navarr So that's not intended at all? But what if I do like to use the advantages of extension attributes? – Fabian Schmengler Jun 27 '17 at 13:28
  • (also, the custom attribute API is horrible to use and to test) – Fabian Schmengler Jun 27 '17 at 13:37
1

same issue on M2.1.8 community edition

  1. magento logic ignore extension attribute data since it's not custom attribute, as result after populateWithArray() we lost extension attribute

  2. if use xml join directive magento logic expects for the key wich built like

"extension_attribute_" + xml attribute code value + xml field value

but actually we have only key without prefixes

  1. as test experiment if we hack vendor code with this key (add expected prefix) the logic starts work as needed, the customer is populated with needed data, interesting..
0

I suppose you are using the community edition? As far as I remember this works out of the box in the enterprise edition. So one could suspect this isn't working by design ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.