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.
3 Answers 3
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.
-
thanks for your reply, yes, this is why I added before/afterSave plugin2025年04月09日 08:53:35 +00:00Commented Apr 9 at 8:53
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.
-
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.2025年04月19日 13:48:14 +00:00Commented Apr 19 at 13:48
-
Amit, you can take a look to my answer magento.stackexchange.com/a/376724/483552025年04月23日 18:29:57 +00:00Commented Apr 23 at 18:29
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;
}
Explore related questions
See similar questions with these tags.
public function setMrExternalId, but when saving, you're calling$extensionAttributes->setMiExternalIdinstead.