0

There must be something that I'm not seeing, some custom attribute I added to the customer_address_entity table isn't not saved.

The idea is to from admin form add then (get/save) custom attribute into customer_address_entity

app/code/Mag/Ento/etc/db_schema.xml

<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
 <table name="customer_address_entity" resource="default">
 <column xsi:type="int" name="mr_external_id" unsigned="true" nullable="true" comment="External id"/>
 </table>
</schema>

app/code/Mag/Ento/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\Customer\Api\Data\AddressInterface">
 <attribute code="mr_external_id" type="int" />
 </extension_attributes>
</config>

app/code/Mag/Ento/etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
 <preference for="Mag\Ento\Api\Data\CustomerAddressExtensionInterface" type="Mag\Ento\Model\CustomerAddressExtension"/>
 <type name="Magento\Customer\Api\AddressRepositoryInterface">
 <plugin name="mag_ento_customer_address_extension_custom_attr_save"
 type="Mag\Ento\Plugin\CustomerAddressRepositoryPlugin"
 sortOrder="10"/>
 </type>
</config>

app/code/Mag/Ento/Api/Data/CustomerAddressExtensionInterface.php

namespace Mag\Ento\Api\Data;
use Magento\Framework\Api\ExtensionAttributesInterface;
interface CustomerAddressExtensionInterface extends ExtensionAttributesInterface
{
 public const MR_EXTERNAL_ID = 'mr_external_id';
 public const MR_ADDRESS_ID = 'mr_address_id';
 /**
 * @return int|null
 */
 public function getMrExternalId(): ?int;
 /**
 * @param int|null $value
 * @return CustomerAddressExtensionInterface
 */
 public function setMrExternalId(?int $value): CustomerAddressExtensionInterface;
 
}

app/code/Mag/Ento/Model/CustomerAddressExtension.php

namespace Mag\Ento\Model;
use Mag/Ento\Api\Data\CustomerAddressExtensionInterface;
use Magento\Framework\Model\AbstractExtensibleModel;
class CustomerAddressExtension extends AbstractExtensibleModel implements CustomerAddressExtensionInterface
{
 /**
 * Get mrExternalId
 *
 * @return int|null
 */
 public function getMrExternalId(): ?int
 {
 $id = $this->getData(self::MR_EXTERNAL_ID);
 return $id !== null ? (int) $id : null;
 }
 /**
 * Set mrExternalId
 *
 * @param int|null $value
 * @return CustomerAddressExtensionInterface
 */
 public function setMrExternalId(?int $value): CustomerAddressExtensionInterface
 {
 $this->setData(self::MR_EXTERNAL_ID, $value);
 return $this;
 }
}

Now get/save the extension attribute

app/code/Mag/Ento/Plugin/CustomerAddressRepositoryPlugin.php

namespace Mag\Ento\Plugin\CompanyAddress;
use Magento\Customer\Api\AddressRepositoryInterface;
use Magento\Customer\Api\Data\AddressInterface;
use Magento\Customer\Api\Data\AddressInterface as CustomerAddressInterface;
use Magento\Framework\Api\ExtensionAttributesFactory;
class CustomerAddressRepositoryPlugin
{
 /**
 * @param ExtensionAttributesFactory $extensionAttributesFactory
 * @param AddressRepositoryInterface $addressRepositoryInterface
 */
 public function __construct(
 private readonly ExtensionAttributesFactory $extensionAttributesFactory,
 ) {
 }
 public function afterGetById(AddressRepositoryInterface $subject, AddressInterface $address): AddressInterface
 {
 $extensionAttributes = $address->getExtensionAttributes();
 if ($extensionAttributes === null) {
 $extensionAttributes = $this->extensionAttributesFactory->create(CustomerAddressInterface::class);
 }
 $extensionAttributes->setMrExternalId(1234); // 1234 hard code value for test
 $address->setExtensionAttributes($extensionAttributes);
 return $address;
 }
 /**
 * @param AddressRepositoryInterface $subject
 * @param CustomerAddressInterface $address
 * @return array
 */
 public function beforeSave(AddressRepositoryInterface $subject, AddressInterface $address): array
 {
 $extensionAttributes = $address->getExtensionAttributes();
 $extensionAttributes->setMrExternalId(1234); // 1234 hard code value for test
 $address->setExtensionAttributes($extensionAttributes);
 return [$address];
 }
}

Not also working with afterSave

I trigger some save action in another file in order to insert rows in customer_address_entity

$address->setFirstname('john')
...
//also tried
$extensionAttributes->setMrExternalId('1234'); 
$address->setExtensionAttributes($extensionAttributes);
$this->customerAddressRepositoryInterface->save($address);

All the attributes are well saved in DB except mr_external_id attribute value.

nb: What I also don't understand is that in an observer save after, the value is available in the object but not saved in db.

[data_object (Magento\Customer\Model\Address\Interceptor)] => Array
(
 [parent_id] => 3193
 [customer_id] => 3193
 [street] => addr1
 [extension_attributes] => Array
 (
 [mr_external_id] => 1234 //<------
 )
 ...
)

I already implemented extension attributes but I don't see what's wrong in this case.

asked Apr 9 at 2:12
2
  • I think there might be a spelling mistake. You created the method public function setMrExternalId, but when saving, you're calling $extensionAttributes->setMiExternalId instead. Commented Apr 9 at 6:48
  • @MohitPatel thanks for your comment, It was a copy and paste error, I just corrected it, it is indeed the correct variable that I have. Commented Apr 9 at 7:33

3 Answers 3

0

You cannot directly save the extension attribute value in the database. It is important to understand the purpose of extension attributes. Extension attributes are used to expose your custom attribute data (or extension data) in API responses.

Third-party developers cannot change the API data interfaces defined in the Adobe Commerce and Magento Open Source code. 
However, most of these entities have a feature called extension attributes. Check the interface for the methods getExtensionAttributes() and setExtensionAttributes() to determine if they are available for the entity.

Refer to this document: https://developer.adobe.com/commerce/php/development/components/add-attributes/#add-plugin-to-product-repository

Setting an extension attribute does not automatically save it to the database.

In this document, you can see the afterSave method:

$ourCustomData = $extensionAttributes->getOurCustomData();
$this->customDataRepository->save($ourCustomData);

Here, $ourCustomData is saved through the customDataRepository and is not automatically saved just by setting the extension attribute object.

So, you must manually save the data using the appropriate repository.

answered Apr 9 at 8:29
1
  • thanks for your reply, yes, this is why I added before/afterSave plugin Commented Apr 9 at 8:53
0

As the customer address is eav type entity.So, i suggest using add field using the data setup script:

app/code/Mag/Ento/Setup/Patch/Data/AddMrExternalIdCustomerAddressAttribute.php

Instead of db_schema.xml.

Here is an example to create an attribute using a data patch

<?php
declare(strict_types=1);
namespace Mag\Ento\Setup\Patch\Data;
use Magento\Customer\Model\Indexer\Address\AttributeProvider;
use Magento\Customer\Setup\CustomerSetup;
use Magento\Customer\Setup\CustomerSetupFactory;
use Magento\Eav\Model\Entity\Attribute\Set;
use Magento\Eav\Model\Entity\Attribute\SetFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
class AddMrExternalIdCustomerAddressAttribute implements DataPatchInterface, PatchRevertableInterface
{
 /**
 * Constructor
 *
 * @param ModuleDataSetupInterface $moduleDataSetup
 * @param CustomerSetupFactory $customerSetupFactory
 * @param SetFactory $attributeSetFactory
 */
 public function __construct(
 private ModuleDataSetupInterface $moduleDataSetup,
 private CustomerSetupFactory $customerSetupFactory,
 private SetFactory $attributeSetFactory
 ) {
 
 }
 /**
 * @inheritdoc
 */
 public function apply(): void
 {
 $this->moduleDataSetup->getConnection()->startSetup();
 /** @var CustomerSetup $customerSetup */
 $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]);
 $customerEntity = $customerSetup->getEavConfig()->getEntityType(AttributeProvider::ENTITY);
 $attributeSetId = $customerEntity->getDefaultAttributeSetId();
 
 /** @var Set $attributeSet */
 $attributeSet = $this->attributeSetFactory->create();
 $attributeGroupId = $attributeSet->getDefaultGroupId($attributeSetId);
 
 $customerSetup->addAttribute(AttributeProvider::ENTITY, 'mr_external_id', [
 'type' => 'static',// it will add on customer_address_entity
 'label' => 'mr_external_id',
 'input' => 'text', // change accordingly 
 'source' => '',
 'required' => false,
 'visible' => true,
 'position' => 333,
 'system' => false,
 'backend' => ''
 ]);
 /// Allow the attrubute for form
 
 $attribute = $customerSetup->getEavConfig()->getAttribute(AttributeProvider::ENTITY, 'mr_external_id');
 $attribute->addData([
 'used_in_forms' => [
 'customer_address_edit'
 ]
 ]);
 $attribute->addData([
 'attribute_set_id' => $attributeSetId,
 'attribute_group_id' => $attributeGroupId
 
 ]);
 $attribute->save();
 $this->moduleDataSetup->getConnection()->endSetup();
 }
 public function revert(): void
 {
 $this->moduleDataSetup->getConnection()->startSetup();
 /** @var CustomerSetup $customerSetup */
 $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]);
 $customerSetup->removeAttribute(\Magento\Customer\Model\Customer::ENTITY, 'mr_external_id');
 $this->moduleDataSetup->getConnection()->endSetup();
 }
 /**
 * {@inheritdoc}
 */
 public function getAliases(): array
 {
 return [];
 }
 /**
 * {@inheritdoc}
 */
 public static function getDependencies(): array
 {
 return [
 
 ];
 }
}

Must remove <preference for="Mag\Ento\Api\Data\CustomerAddressExtensionInterface" type="Mag\Ento\Model\CustomerAddressExtension"/> from di.xml and your attribute treat as extension.

answered Apr 18 at 12:24
2
  • Thanks @Amit Bera for your response, I thought about it (data patch), but I realized that since Magento 2.3, InstallData and InstallSchema have been replaced by the declarative schema (db_schema.xml) and it is no longer necessary to use them. Using a data patch on a newer 2.4 version bothers me a little. What do you think about? Otherwise I found some solution, I set/save the extension attributes values ​​via a direct query (resourceModel) in afterGet/afterSave plugins. I will share the solution soon. Commented Apr 19 at 13:48
  • Amit, you can take a look to my answer magento.stackexchange.com/a/376724/48355 Commented Apr 23 at 18:29
0

I still don’t know why I couldn’t save the values directly, but I found another solution. It might not be the best one, but it works.

If anyone has a better idea or knows why it wasn’t working, I’d be glad to hear it.

The idea is to save them using a direct query via resourceModel

app/code/Mag/Ento/Plugin/CustomerAddressRepositoryPlugin.php

<?php
namespace Mag\Ento\Plugin\CompanyAddress;
use Magento\Customer\Api\AddressRepositoryInterface;
use Magento\Customer\Api\Data\AddressInterface;
use Magento\Customer\Api\Data\AddressInterface as CustomerAddressInterface;
use Magento\Framework\Api\ExtensionAttributesFactory;
use Mag\Ento\Model\ResourceModel\CompanyAddress as CompanyAddressResource;
class CustomerAddressRepositoryPlugin
{
 /**
 * @param ExtensionAttributesFactory $extensionAttributesFactory
 * @param AddressRepositoryInterface $addressRepositoryInterface
 */
 public function __construct(
 private readonly ExtensionAttributesFactory $extensionAttributesFactory,
 private readonly CompanyAddressResource $companyAddressResource
 ) {
 }
 public function afterGetById(AddressRepositoryInterface $subject, AddressInterface $address): AddressInterface
 {
 $extensionAttributes = $address->getExtensionAttributes();
 if ($extensionAttributes === null) {
 $extensionAttributes = $this->extensionAttributesFactory->create(CustomerAddressInterface::class);
 }
 //resourceModel
 $extensionData = $this->companyAddressResource->getExtensionAttributes((int)$address->getId());
 if ($extensionData) {
 $extensionAttributes->setMrExternalId($extensionData['mr_external_id']);
 $extensionAttributes->setMrAddressId($extensionData['mr_address_id']); //second attribute
 }
 $address->setExtensionAttributes($extensionAttributes);
 return $address;
 }
 /**
 * @param AddressRepositoryInterface $subject
 * @param CustomerAddressInterface $address
 * @param CustomerAddressInterface $entity
 * @return CustomerAddressInterface
 */
 public function afterSave(
 AddressRepositoryInterface $subject,
 AddressInterface $address,
 AddressInterface $entity
 ):AddressInterface {
 $extensionAttributes = $entity->getExtensionAttributes();
 if ($extensionAttributes === null) {
 return $result;
 }
 //resourceModel
 $this->companyAddressResource->saveExtensionAttributes(
 (int)$entity->getId(),
 $extensionAttributes->getMrExternalId(),
 $extensionAttributes->getMrAddressId()
 );
 return $result;
 }
}

app/code/Mag/Ento/Model/ResourceModel/CompanyAddress.php

<?php
namespace Mag\Ento\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Mag\Ento\Api\Data\CustomerAddressExtensionInterface
class CompanyAddress extends AbstractDb
{
 public const CUSTOMER_ADDRESS_TABLE = 'customer_address_entity';
//...
 /**
 * Save Extension Attributes
 *
 * @param int $addressId
 * @param int|null $mrExternalId
 * @param int|null $mrAddressId
 * @return void
 */
 public function saveExtensionAttributes(int $addressId, ?int $mrExternalId, ?int $mrAddressId): void
 {
 $connection = $this->getConnection();
 $table = $connection->getTableName(self::CUSTOMER_ADDRESS_TABLE);
 $connection->update(
 $table,
 [
 CustomerAddressExtensionInterface::MR_EXTERNAL_ID => $mrExternalId,
 CustomerAddressExtensionInterface::MR_ADDRESS_ID => $mrAddressId,
 ],
 [ 'entity_id = ?' => $addressId]
 );
 }
 /**
 * Get Extension Attributes
 *
 * @param int $addressId
 * @return array|null
 */
 public function getExtensionAttributes(int $addressId): ?array
 {
 $connection = $this->getConnection();
 $table = $connection->getTableName(self::CUSTOMER_ADDRESS_TABLE);
 $select = $connection->select()
 ->from($table, [
 CustomerAddressExtensionInterface::MR_EXTERNAL_ID,
 CustomerAddressExtensionInterface::MR_ADDRESS_ID
 ])
 ->where('entity_id = ?', $addressId);
 $data = $connection->fetchRow($select);
 if ($data) {
 return [
 'mr_external_id' => $data[CustomerAddressExtensionInterface::MR_EXTERNAL_ID] ?? null,
 'mr_address_id' => $data[CustomerAddressExtensionInterface::MR_ADDRESS_ID] ?? null)
 ];
 }
 return null;
 }
answered Apr 23 at 18:27

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.