2

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
1
  • You must make sure FieldStorage is installed first
    – Hudri
    Nov 2, 2021 at 6:25

2 Answers 2

2

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().

4
  • 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: FieldStorage first, then everything else. Is this approach not save?
    – Hudri
    Nov 2, 2021 at 9:16
  • The linked code works only because of $config_installer->installOptionalConfig() which resaves the added config entities afterwards.
    – 4uk4
    Nov 2, 2021 at 9:29
  • and at the end the code is using a trick to correct any update errors api.drupal.org/api/drupal/…. This method is no longer available.
    – 4uk4
    Nov 2, 2021 at 10:15
  • 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 (drupal.stackexchange.com/a/307977/19071) that allows me to iterate through multiple files being added. Nov 2, 2021 at 17:51
2

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();
    }
  }
}

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.