1

I have created three custom attributes for Customer: myapp_id, codice_univoco and indirizzo_pec

enter image description here

If I enter a value in codice_univoco and/or indirizzo_pec, data is successfully saved:

enter image description here

However, when I insert a value for myapp_id (App ID), and then save, all custom attributes are emptied (including myapp_id):

Image 3

If I check my database, I can see that custom attribute values are first added…

enter image description here

…and then deleted from customer_entity_varchar table

enter image description here

But I know the reason why.

It is because of the following observer I wrote to handle customer_save_after_data_object: (the culprit is the line $customer->save();):

   /**
     * Execute observer
     *
     * @param \Magento\Framework\Event\Observer $observer
     * @return $this
     */
    public function execute (\Magento\Framework\Event\Observer $observer) {

        //Return new customer data
        $customerDataObject = $observer->getData("customer_data_object");

        //Returns old customer data
        $origCustomerDataObject = $observer->getData("orig_customer_data_object");

        if (is_null($origCustomerDataObject)) {

            return;
        }

        //if is first update confirm and send email
        try {
            $customer = $this->customerRegistry->retrieve($customerDataObject->getId());

        } catch (Exception $e) {

            return $this;
        }

         if (!$this->isConfirmed($customer) && 
             $customer->getData("myapp_id") && 
            !$customer->getData("sent_email")
        ) {

            //confirm and send email
            $customer->setConfirmation(null);
            $customer->setData("sent_email",1);
            $customer->save();
        }

        return $this;
 }

Note: custom attribute sent_email is not saved too.

In other words, If I save a customer inside the observer, custom data is not saved.

If I remove the line $customer->save(); everything works fine.

It is driving me crazy.

Additional information:

"app_id" custom attribute definition in InstallData.php

$customerSetup->addAttribute(

    Customer::ENTITY,
    'myapp_id',
    [
        'type' => 'int',
        'label' => 'App ID',
        'input' => 'text',
        'source' => '',
        'required' => false,
        'visible' => true,
        'position' => 333,
        'system' => false,
        'backend' => ''
    ]
);

$customerAttribute = $customerSetup->getEavConfig()->getAttribute(Customer::ENTITY, 'myapp_id');
$customerAttribute->addData([ 'used_in_forms' => [ 'adminhtml_customer' ] ]);
$customerAttribute->save();

"sent_email" custom attribute definition in InstallData.php

$customerSetup->addAttribute(\Magento\Customer\Model\Customer::ENTITY, 'sent_email', [
    'type' => 'int',
    'label' => 'Sent Email',
    'input' => 'boolean',
    'source' => '',
    'required' => true,
    'visible' => false,
    'position' => 333,
    'system' => false,
    'backend' => '',
    'default' => 0
]);

$attribute = $customerSetup->getEavConfig()->getAttribute('customer', 'sent_email')
->addData(['used_in_forms' => [
        'adminhtml_customer'
    ]
]);

$attribute->save();

"codice_univoco" custom attribute definition in UpgradeData.php

if (version_compare($context->getVersion(), '1.0.1') < 0) {

$codiceunivoco = $this->eavConfig->getAttribute(\Magento\Customer\Model\Customer::ENTITY, 'codice_univoco');

if (!$codiceunivoco->getId()) {

    $eavSetup->addAttribute(
        \Magento\Customer\Model\Customer::ENTITY,
        'codice_univoco',
        [
            'type'         => 'varchar',
            'label'        => 'Codice univoco',
            'input'        => 'text',
            'required'     => true,
            'visible'      => true,
            'user_defined' => false,
            'position'     => 999,
            'system'       => 0,
        ]
    );

    $codiceunivoco = $this->eavConfig->getAttribute(\Magento\Customer\Model\Customer::ENTITY, 'codice_univoco');
}

$codiceunivoco->setData(
    'used_in_forms',
    ['adminhtml_customer','customer_account_create']

);

$codiceunivoco->save();

"indirizzo_pec" custom attribute definition in UpgradeData.php

$indirizzopec = $this->eavConfig->getAttribute(\Magento\Customer\Model\Customer::ENTITY, 'indirizzo_pec');

if (!$indirizzopec->getId()) {

    // Add Indirizzo Pec

    $eavSetup->addAttribute(
        \Magento\Customer\Model\Customer::ENTITY,
        'indirizzo_pec',
        [
            'type'         => 'varchar',
            'label'        => 'Indirizzo Pec',
            'input'        => 'text',
            'required'     => false,
            'visible'      => true,
            'user_defined' => false,
            'position'     => 999,
            'system'       => 0,
        ]
    );

    $indirizzopec = $this->eavConfig->getAttribute(\Magento\Customer\Model\Customer::ENTITY, 'indirizzo_pec');

}

$indirizzopec->setData(
    'used_in_forms',
    [
        'adminhtml_customer',
        'customer_account_create'
    ]

);

$indirizzopec->save();

Note 2: don't know if it is important: I have assigned a custom attribute with the same name (app_id) to other two entities: Category and Customer Address

3
1

Many, many thanks to Devidas who pointed me in the right direction (see first comment to my question) .

Here's what I've learned so far:

Consider two use-case scenario:

  1. Custom attribute is set by User in a form
  2. Custom attribute is set in code

Let's examine them in details

Scenario 1: Custom attribute set by User

Four conditions must be met in order for custom attribute values to be saved to database:

  1. Set the attribute option "system" => 0 in attribute parameters inside the install/upgrade script
       $customerSetup->addAttribute(
       Customer::ENTITY,
       'sent_email',
       [
            …

            'system' => 0,

            …
         ]
       );
  1. Assign your attribute to the entity attribute set inside the install/upgrade script
   $eavSetup->addAttributeToSet(
      CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
      CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
      null,
      'sent_email');

      $customerSetup->getEavConfig()->getAttribute(
         Customer::ENTITY, 'sent_email')
            ->addData([
            …

            'attribute_set_id' => $attributeSetId,

            …
      ])->save();
  1. Assign your attribute to the entity attribute group inside the install/upgrade script
   $customerSetup->getEavConfig()->getAttribute(
      Customer::ENTITY, 'sent_email')
      ->addData([
      …

      'attribute_group_id' => $attributeGroupId,

      …
   ])->save();

In this case entity is Customer, but it could be Product, Category, etc.

  1. Avoid using getData() and/or setData() to access/change custom attribute values.

    Use magic methods instead.

    I.e. to get custom attribute sent_email do not use:

      $customer->getData('sent_email'); 

. use:

      $customer->getSentEmail();

Scenario 2: Custom attribute set in code

In addition to meeting all conditions of the first use-case scenario, you must back your custom attribute with a custom field in the core entity table, like this:

   $setup->getConnection()->addColumn(
      $setup->getTable('customer_entity'),
      'sent_email',
      [
         'type' => Table::TYPE_INTEGER,
         'nullable' => true, 
         'comment' => 'Sent email'
      ]
     );

Be sure to flush cache storage before data get persistently saved.

Although your attributes are created, the cache is what triggers the data to be saved afterwards.

Here is the full code of a sample module where the concepts discussed above are applied.

job_title is a custom Customer attribute whose value can be set by User in a form.

sent_email is a custom Customer attribute whose value is set programmatically in code inside an observer.

magentostackexchange\Sample\etc\events.xml

   <?xml version="1.0" ?>
   <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       
      xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd>

   <event name="customer_save_after_data_object">

      <observer instance=
         "Magentostackexchange\Sample\Observer\Customer\SaveAfterDataObject" name="magentostackexchange_sample_observer_customer_saveafterdataobject_customer_save_after_data_object"/>

      </event>
   </config>

magentostackexchange\Sample\etc\module.xml

   <?xml version="1.0" ?>
   <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           ù
      xsi:noNamespaceSchemaLocation=
         "urn:magento:framework:Module/etc/module.xs">

      <module name="Magentostackexchange_Sample" setup_version="1.0.0" schema_version="1.0.0">
         <sequence>
            <module name="Magento_Customer"/>
         </sequence>
      </module>
   </config>

magentostackexchange\Sample\Observer\Customer\SaveAfterDataObject.php

   <?php

   namespace Magentostackexchange\Sample\Observer\Customer;

   use \Exception;
   use Magento\Customer\Model\Authentication;
   use Magento\Customer\Model\CustomerRegistry;
   use Magento\Customer\Model\EmailNotification;

   class SaveAfterDataObject implements \Magento\Framework\Event\ObserverInterface
   {

      /**
      * @var CustomerRegistry  $customerRegistry
      */
      protected $customerRegistry;

      /**
      * @var EmailNotification $emailNotification
      */
      protected $emailNotification;

      /**
      * @var Authentication $customerAuthentication
      */
      protected $customerAuthentication;

      /**
      * Constructor 
      * 
      * @param CustomerRegistry  $customerRegistry      
      * @param EmailNotification $emailNotification     
      * @param Authentication    $customerAuthentication
      */
      public function __construct(
         CustomerRegistry $customerRegistry,
         EmailNotification $emailNotification,
         Authentication $customerAuthentication
      )
      {
         $this->customerRegistry = $customerRegistry;
         $this->emailNotification = $emailNotification;
         $this->customerAuthentication = $customerAuthentication;
      }

      /**
      * Execute observer
      *
      * @param \Magento\Framework\Event\Observer $observer
      * @return void
      */
      public function execute(
         \Magento\Framework\Event\Observer $observer
      ) {

            $customerDataObject = $observer->getCustomerDataObject();
            $origCustomerDataObject = $observer->getOrigCustomerDataObject();

            if (is_null($origCustomerDataObject)) {
               return;
            }

            //If is first update confirm and send email
            try {
                  $customer = 
                     $this->customerRegistry
                        ->retrieve($customerDataObject->getId());

            } catch (Exception $e) {
               return $this;
         }

         if (!$customer->getSentEmail()) {
            //Confirm and send email
            $customer->setConfirmation(null);
            $customer->setSentEmail(1);
            $customer->save();

            //Send email
            $this->emailNotification->newAccount(
               $customerDataObject,
               $type = EmailNotification::NEW_ACCOUNT_EMAIL_REGISTERED,
               $backUrl = '',
               $customerDataObject->getStoreId()
            );
         }

         return $this;

      }
   }

magentostackexchange\Sample\Setup\InstallData.php

   <?php

   namespace Magentostackexchange\Sample\Setup;

   use Exception;
   use \Magento\Customer\Model\Customer;
   use Magento\Customer\Setup\CustomerSetupFactory;
   use Magento\Framework\Setup\InstallDataInterface;
   use Magento\Framework\Setup\ModuleContextInterface;
   use Magento\Framework\Setup\ModuleDataSetupInterface;
   use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
   use Magento\Customer\Api\CustomerMetadataInterface;

   /**
    * @codeCoverageIgnore
    */
   class InstallData implements InstallDataInterface
   {
      /**
      * @var CustomerSetupFactory 
      */
      private $customerSetupFactory;

      /**
      * @var AttributeSetFactory 
      */
      private $attributeSetFactory;

    /**
     * @var \Magento\Eav\Setup\EavSetupFactory 
     */
      private $eavSetupFactory;

      /**
      * InstallData constructor.
      * @param CustomerSetupFactory $customerSetupFactory
      * @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory
      * @param \Magento\Eav\Model\Config $eavConfig
      * @param AttributeSetFactory $attributeSetFactory
      */
      public function __construct(
         CustomerSetupFactory $customerSetupFactory,
         \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory,
         \Magento\Eav\Model\Config $eavConfig,
         AttributeSetFactory $attributeSetFactory
      ) {
            $this->customerSetupFactory = $customerSetupFactory;
            $this->eavSetupFactory = $eavSetupFactory;
            $this->eavConfig = $eavConfig;
            $this->attributeSetFactory = $attributeSetFactory;
        }

     /**
      * @param ModuleDataSetupInterface $setup
      * @param ModuleContextInterface $context
      */
      public function install(ModuleDataSetupInterface $setup, 
         ModuleContextInterface $context) {

         echo "\n\n********** RUNNING INSTALL DATA (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

         $installer = $setup;   

         $installer->startSetup();

         echo "\n********** SETUP STARTED (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

         try {

                /** @var CustomerSetup $customerSetup */
                $customerSetup = 
                   $this->customerSetupFactory
                      ->create(['setup' => $setup]);

                $eavSetup = $this->eavSetupFactory
                   ->create(['setup' => $setup]);

                $customerEntity = $customerSetup
                   ->getEavConfig()->getEntityType('customer');

                $attributeSetId = $customerEntity
                   ->getDefaultAttributeSetId();

                $attributeSet = $this->attributeSetFactory->create();

                $attributeGroupId = $attributeSet
                   ->getDefaultGroupId($attributeSetId);

                // job_title for Customer

                echo "\n********** CREATING JOB_TITLE ATTR FOR CUSTOMER (MAGENTOSTACKEXCHANGE_SAMPLE) **********";

                $eavSetup->addAttribute(
                Customer::ENTITY,
                'job_title',
                [
                   'type'         => 'varchar',
                   'label'        => 'Job Title',
                   'input'        => 'text',
                   'required'     => true,
                   'visible'      => true,
                   'user_defined' => false,
                   'position'     => 999,
                   'system'       => 0,
                ]
             );

                $eavSetup->addAttributeToSet(
                   CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
                   CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
                   null,
                   'job_title');

                $customerSetup->getEavConfig()
                   ->getAttribute(Customer::ENTITY, 'job_title')
                      ->addData([
                         'attribute_set_id' => $attributeSetId,
                         'attribute_group_id' => $attributeGroupId,

                         'used_in_forms' => 
                            ['adminhtml_customer',
                             'customer_account_create']

                ])->save();

                echo "\n********** JOB_TITLE ATTR FOR CUSTOMER CREATED SUCCESFULLY (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

                // Sent Email for Customer

                echo "\n********** CREATING SENT_EMAIL ATTR FOR CUSTOMER (MAGENTOSTACKEXCHANGE_SAMPLE) **********";

                $customerSetup->addAttribute(
                Customer::ENTITY,
                'sent_email',
                [
                   'type' => 'int',
                   'label' => 'Sent Email',
                   'input' => 'boolean',
                   'source' => '',
                   'required' => true,
                   'visible' => false,
                   'position' => 333,
                   'system' => false,
                   'backend' => '',
                   'default' => 0
                ]
                );

                $eavSetup->addAttributeToSet(
                   CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
                   CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
                   null,
                  'sent_email');

                $customerSetup->getEavConfig()
                   ->getAttribute(Customer::ENTITY,'sent_email')
                      ->addData([
                         'attribute_set_id' => $attributeSetId,
                         'attribute_group_id' => $attributeGroupId,
                         'used_in_forms' => ['adminhtml_customer'],
                      ])->save();

                echo "\n********** SENT_EMAIL ATTR FOR CUSTOMER CREATED SUCCESFULLY (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

            } catch (Exception $ex) {

                echo "\n********** ERROR: LOGGING EXCEPTION (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

                error_log('MAGENTOSTACKEXCHANGE_SAMPLE MODULE EXCEPTION: ' 
                   . $ex->getMessage());
            }
            finally{

               echo "\n********** EXITING INSTALL DATA (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

               $installer->endSetup();

               echo "\n********** INSTALL DATA FINISHED (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n\n";
         }  
      }
   }

magentostackexchange\Sample\Setup\InstallSchema.php

   <?php

   namespace Magentostackexchange\Sample\Setup;

   use Exception;
   use Magento\Framework\DB\Ddl\Table;
   use Magento\Framework\Setup\InstallSchemaInterface;
   use Magento\Framework\Setup\SchemaSetupInterface;
   use Magento\Framework\Setup\ModuleContextInterface;


   /**
    * Class InstallSchema
    */
   class InstallSchema implements InstallSchemaInterface
   {
      /**
      * @param SchemaSetupInterface $setup
      * @param ModuleContextInterface $context
      */
      public function install(SchemaSetupInterface $setup,
            ModuleContextInterface $context)
      {
         echo "\n\n********** RUNNING INSTALL SCHEMA  (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

         $setup->startSetup();

         try {

            echo "\n********** SETUP STARTED (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

            // Used in UpgradeSchema.php
            // if (version_compare($context->getVersion(), '1.0.1') < 0) { 

            // sent_email

            echo "\n********** ADDING COLUMN SENT_EMAIL TO CUSTOMER  (MAGENTOSTACKEXCHANGE_SAMPLE) **********";

            $setup->getConnection()->addColumn(
               $setup->getTable('customer_entity'),
                  'sent_email',
                  [
                     'type' => Table::TYPE_INTEGER,
                     'nullable' => true,
                     'comment' => 'Sent email'
                  ]
            );

            echo "\n********** COLUMN SENT_EMAIL ADDED SUCCESFULLY TO CUSTOMER (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n";

         } catch (Exception $ex) {

            echo "\n********** ERROR: LOGGING EXCEPTION (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n\n";

            error_log('MAGENTOSTACKEXCHANGE_SAMPLE MODULE EXCEPTION: ' 
               . $ex->getMessage());

         }

         // }

         echo "\n********** INSTALL SCHEMA FINISHED (MAGENTOSTACKEXCHANGE_SAMPLE) **********\n\n";
     }
   }

magentostackexchange\Sample\ registration.php

   <?php

   \Magento\Framework\Component\ComponentRegistrar::register(
      \Magento\Framework\Component\ComponentRegistrar::MODULE,
      'Magentostackexchange_Sample',
      __DIR__
   );

Tip: use Mage2Gen to develop the basics of your Magento 2 Modules

https://mage2gen.com/

It is possible to create Customer and other entity attributes through an easy interface.

You can generate modules for Magento 2.2 and 2.3:

In Modules generated for version 2.3, custom attribute definitions include partial assignment to the entity attribute set/group

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.