Score:2

add field to user in hook_update_n

pl flag

The latest release of our existing module needs to define MANY new fields that are attached to the drupal user. For each field, in {module}/config/optional there is a field config and a field storage config. I then implement hook_update_n in {module}/{module}.install to detect new config entries and add them if they don't exist. Running the code installs the config (I can see it with drush cget), but does not create the corresponding DB tables for the new fields.

If I go to most pages, such as /admin/config/people/accounts/fields, I get "The website encountered an unexpected error. Please try again later."

If I clear the caches (drush cr), I get an error that says,

In ExceptionHandler.php line 53:

  SQLSTATE[42S02]: Base table or view not found: 1146 Table 'intranet.user__field_s_uid' doesn't exist: SELECT "t".*
  FROM
  "user__field_s_uid" "t"
  WHERE ("entity_id" IN (:db_condition_placeholder_0)) AND ("deleted" = :db_condition_placeholder_1) AND ("langcode" IN (:db_condition_placeholder_2, :db_c
  ondition_placeholder_3, :db_condition_placeholder_4))
  ORDER BY "delta" ASC; Array
  (
      [:db_condition_placeholder_0] => 0
      [:db_condition_placeholder_1] => 0
      [:db_condition_placeholder_2] => en
      [:db_condition_placeholder_3] => und
      [:db_condition_placeholder_4] => zxx
  )

In StatementWrapper.php line 116:

  SQLSTATE[42S02]: Base table or view not found: 1146 Table 'intranet.user__field_s_uid' doesn't exist

To get the site working again I have to delete the config (drush cdel...).

What am I missing that keeps the db storage from being created? Do I need to programatically create the db table for the field to use? (if so,how?)

file: {module}.install

<?php                                                              
use Drupal\Component\Serialization\Yaml;                           
use Drupal\Component\Utility\NestedArray;                          
use Drupal\field\Entity\FieldStorageConfig;                        
use Drupal\field\Entity\FieldConfig;                               
                                                                   
function {module}_update_9001(&$sandbox = NULL){             
  $modulePath = \Drupal::service('extension.list.module')          
        ->getPath('{module}');                      
  $config_factory = \Drupal::configFactory();                      
  $configPathObjects = \Drupal::service('file_system')             
    ->scanDirectory($modulePath.'/config/optional','~field.*~');   
                                                                   
  foreach ($configPathObjects as $configPath){                     
                                                                   
    $config = $config_factory->getEditable($configPath->name);     
    if($config->isNew()){                                          
      $configSettings = NestedArray::mergeDeep(                    
          Yaml::decode(                                            
            file_get_contents(                                     
              "$modulePath/config/optional/{$configPath->filename}"
              )                                                    
            ),$config                                              
          );                                                       
      $config->setdata($configSettings);                           
      $config->save(TRUE);                                         
    }                                                              
  }                                                                
}                                                                  

file: config/optional/field.field.user.user.field_s_uid.yml

langcode: en                                                                   
status: true                                                                   
dependencies:                                                                  
  config:                                                                      
    - field.storage.user.field_s_uid                                           
  module:                                                                      
    - user                                                                     
id: user.user.field_s_uid                                                      
field_name: field_s_uid                                                        
entity_type: user                                                              
bundle: user                                                                   
label: s_uid                                                                   
description: 'The unique ID for this user, set automatically by the DB, should not be modified by users/admins.'                                              
required: false                                                                
translatable: false                                                            
default_value: {  }                                                            
default_value_callback: ''                                                     
settings:                                                                      
  min: null                                                                    
  max: null                                                                    
  prefix: ''                                                                   
  suffix: ''                                                                   
field_type: integer                                                            

file: config/optional/field.storage.user.field_s_uid.yml

langcode: en
status: true
dependencies:
  module:
    - user
id: user.field_s_uid
field_name: field_s_uid
entity_type: user
type: integer
settings:
  unsigned: false
  size: normal
module: core
locked: false
cardinality: 1
translatable: true
indexes: {  }
persist_with_no_fields: false
custom_storage: false
ru flag
You must make sure FieldStorage is installed first
Score:2
cn flag

You need the config entity classes FieldStorageConfig::create() and FieldConfig::create(). You have already the use statements and are not using them. Otherwise the entity events like postcreate and presave are not invoked.

BTW if you modify the configuration of a config entity via $config_factory->getEditable() this only works as long as you don't modify anything affecting the mentioned entity events. If you need to save config entities override free, then better load them instead with ConfigEntityStorageInterface::loadOverrideFree().

ru flag
The use of `FieldStorageConfig` or `FieldConfig` means that you explicitly have to go over each config (-type) one by one. Is there no possibility to install an entire directory of arbitrary config files at once? I've used a similar method as @Kevin Finkenbinder , which I've seen in the [Varbase distribution install file](https://git.drupalcode.org/project/varbase_bootstrap_paragraphs/-/blob/9.0.x/varbase_bootstrap_paragraphs.install#L39): FieldStorage first, then everything else. Is this approach not save?
4uk4 avatar
cn flag
The linked code works only because of $config_installer->installOptionalConfig() which resaves the added config entities afterwards.
4uk4 avatar
cn flag
and at the end the code is using a trick to correct any update errors https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21EntityDefinitionUpdateManager.php/function/EntityDefinitionUpdateManager%3A%3AapplyUpdates/8.9.x. This method is no longer available.
pl flag
Thank you. This gave me what I needed and is so marked as accepted answer. For the aid of others, such as @Hudri, I also posted by final code below (https://drupal.stackexchange.com/a/307977/19071) that allows me to iterate through multiple files being added.
Score:2
pl flag

Final Solution based on accepted solution from @4k4 (see answer).

<?php
use Drupal\Core\Database\Database;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\NestedArray;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\Core\Utility\UpdateException;

/**
 *  Add required fields to user entities.
 */
function {module}_update_9001(){
  $modulePath = \Drupal::service('extension.list.module')
        ->getPath('{module}');
  $configPathStorageObjects = \Drupal::service('file_system')
    ->scanDirectory($modulePath.'/config/optional','~field\.storage\.user\..*~');
  \Drupal::logger('staffdb')->notice('storage objects: <pre>'.print_r($configPathStorageObjects,true).'</pre>');

  //add missing field storage
  foreach ($configPathStorageObjects as $configPath){
    $config = FieldStorageConfig::loadByName('user',str_replace('field.storage.user.','',$configPath->name));
    if(is_null($config)){
      $configSettings = Yaml::decode(
          file_get_contents(
            "$modulePath/config/optional/{$configPath->filename}"
            )
          );
      $fieldStorage = FieldStorageConfig::create($configSettings);
      $fieldStorage->save();
    }
  }

  //add missing fields to users
  $configPathFieldObjects = \Drupal::service('file_system')
    ->scanDirectory($modulePath.'/config/optional','~field\.field\.user\.user\..*~');
  foreach ($configPathFieldObjects as $configPath){
    $config = FieldConfig::loadByName('user', 'user', str_replace('field.field.user.user.','',$configPath->name));
    if(is_null($config)){
      $configSettings = Yaml::decode(
          file_get_contents(
            "$modulePath/config/optional/{$configPath->filename}"
            )
          );
      $field = FieldConfig::create($configSettings);
      $field->save();
    }
  }
}
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.