0

I need to add a new string field called instagram_owner to all bundles of nodes, media and file entities. If new bundles are created, I need the field on them too. This is what I have so far in my MODULE_NAME.module file.

use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Entity\EntityFieldManager;

/**
 * Implements hook_entity_base_field_info().
 */
function MODULE_NAME_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
    $entity_type_id = $entity_type->id();
    $field_name = 'instagram_owner';
    
    // Check for the entity types
    if ($entity_type_id === 'node' || $entity_type_id === 'media' || $entity_type_id === 'file') { 
        
        // Specify new Instagram owner field
        $fields[$field_name] = BaseFieldDefinition::create('string')
            ->setLabel(new TranslatableMarkup("Instagram owner id"))
            ->setDescription(new TranslatableMarkup("This is the owners Instagram ID"))
            ->setSettings(["max_length" => 255, "text_processing" => 0])
            ->setDefaultValue("")
            ->setDisplayOptions("view", ["label" => "above", "type" => "string", "weight" => -3])
            ->setDisplayOptions("form", ["type" => "string_textfield", "weight" => -3]);
        
        // Get the bundles for this entity type
        $bundle_service = \Drupal::service('entity_type.bundle.info');
        $bundles = $bundle_service->getBundleInfo($entity_type_id);
        
        foreach($bundles as $bundle_id => $bundle) {
            
            // Check to see if our field already exists on this bundle
            $bundle_fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_id, $bundle_id);
            if (!isset($bundle_fields[$field_name])) {
                
                // If not, install it...
                // not sure if this will install the field either? 
                // Where do I get $storage_definition from? This has never been uncommented.
                // BaseFieldDefinition::installFieldStorageDefinition($field_name, $entity_type_id, $bundle_id, FieldStorageDefinitionInterface $storage_definition);
                
            }
        }
        return $fields;
    }
    
}

I know that the problematic line seems to be this one. It's this that throws a 503 error page:

$bundle_fields = \Drupal::service('entity_field.manager')->getFieldDefinitions($entity_type_id, $bundle_id);

In devels php block I get the expected results. I'm using this for testing:

    $entity_type_id = 'node';
    $field_name = 'instagram_owner';

    if ($entity_type_id === 'node' || $entity_type_id === 'media' || $entity_type_id === 'file') {        
                
        $bundle_service = \Drupal::service('entity_type.bundle.info');
        $bundles = $bundle_service->getBundleInfo($entity_type_id);
        dpm($bundles);
        $field_service = \Drupal::service('entity_field.manager');
        foreach($bundles as $bundle_id => $bundle) {
            $bundle_fields = $field_service->getFieldDefinitions($entity_type_id, $bundle_id);
            if (!isset($bundle_fields[$field_name])) {
                
                $ready_to_install = 'yes';
                dpm($ready_to_install);
                
            }
        }
        dpm($bundle_fields);
    }

Any help would be greatly appreciated.

2
  • The error is caused by a recursive call getting field definitions while building field definitions. For the overall goal, I don't know if this is a good way to resolve a conflict between an existing bundle fields and the new base field. If there are already existing bundle fields I would add the same bundle field to the other bundles as well. You can re-use the field storage.
    – 4uk4
    Commented Nov 9, 2022 at 19:44
  • @4uk4 Have you got any tips regarding how you would approach this? When I remove the foreach loop, the field appears as expected but no schema for storage. The foreach was to install the fields on the bundles that don't have it. Am I even on the right track here?
    – gMaximus
    Commented Nov 9, 2022 at 21:26

2 Answers 2

0

Field storage is not bundle specific. The easiest way is to install the field storage via a config yml file in the module config install folder and then add a bundle field to each existing or later added bundle, like core is doing it for the body field. See Add content type field programmatically

Or remove any existing bundle field with that field name and add a non-bundle-specific base field for each entity type in the hook_entity_base_field_info() you are already using.

I would recommend the first way via config, this is in general a more reliable way to install and uninstall fields to existing entities.

2
  • Apologies, my dog of 13 years died unexpectedly. Pretty tough. In the example given, the function is node_add_body_field. I'm working on instagram_feeds whilst the field is instagram_owner. Does that mean I should call mine instagram_feeds_add_instagram_owner_field? If this isn't a hook, how does this code get invoked? I'm fairly new from D7. This example is specific to content types. How would I achieve the same for the other entity types? Do I need separate field storage definitions for each of the three entity types I'm interested in? I really appreciate your time helping.
    – gMaximus
    Commented Nov 13, 2022 at 12:15
  • This is called in the content type submit handler NodeTypeForm::save. You could run this code also in a hook_entity_insert() for the bundle of the entity types, not for the entity types themselve (the core File entity has no bundles by the way).
    – 4uk4
    Commented Nov 13, 2022 at 16:02
1

There were a few pieces of the puzzle I hadn't understood. Firstly, hook_entity_base_field_info() just defines the specification of the base field. I originally thought I also had to install the field on each bundle within that hook. Not true.

Once the field was defined in this hook, the field appears on every bundle because it is a base field. However values won't actually get saved.

So the next thing I couldn't get my head around was how to install the schema to store the values. I learned that this can be taken care of with hook_install, hook_uninstall and if needed hook_update_N.

So, 3 days of pain later, this is what I ended up with:

my_module.module

/**
 * Implements hook_entity_base_field_info().
 */
function MY_MODULE_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
    
    // Only install on nodes and media.
    $entity_type_id = $entity_type->id();
    if ($entity_type_id === 'node' || $entity_type_id === 'media') { 
        if ($entity_type_id === 'node') {
            $field_name = 'instagram_owner_display';
        }
        elseif ($entity_type_id === 'media') {
            $field_name = 'instagram_owner_media';
        }
        
        // Specify new Instagram owner field
        $fields[$field_name] = BaseFieldDefinition::create('string')
            ->setLabel(new TranslatableMarkup("Instagram owner ID"))
            ->setDescription(new TranslatableMarkup("This is the Instagram Account ID of who imported this."))
            ->setSettings(["max_length" => 255, "text_processing" => 0])
            ->setDefaultValue("")
            ->setDisplayOptions("view", ["label" => "above", "type" => "string"])
            ->setDisplayOptions("form", ["type" => "string_textfield"]);
        return $fields;
    }
}

my_module.install

/**
 * Implements hook_install().
 */
function MY_MODULE_install() {
    
    // Install Instagram owner field on nodes and media
    $field_manager = \Drupal::service('entity_field.manager'); 
    $field_storage_manager = \Drupal::service('field_storage_definition.listener'); 
    
    $node_definition = $field_manager->getFieldStorageDefinitions('node')['instagram_owner_display']; 
    $field_storage_manager->onFieldStorageDefinitionCreate($node_definition);
    
    $media_definition = $field_manager->getFieldStorageDefinitions('media')['instagram_owner_media'];
    $field_storage_manager->onFieldStorageDefinitionCreate($media_definition);
}

/**
 * Implements hook_uninstall().
 */
function MY_MODULE_uninstall() {
    
    // Delete Instagram owner field on nodes and media
    $field_manager = \Drupal::service('entity_field.manager'); 
    $field_storage_manager = \Drupal::service('field_storage_definition.listener'); 
    
    $node_definition = $field_manager->getFieldStorageDefinitions('node')['instagram_owner_display']; 
    $field_storage_manager->onFieldStorageDefinitionDelete($node_definition);
    
    $media_definition = $field_manager->getFieldStorageDefinitions('media')['instagram_owner_media'];
    $field_storage_manager->onFieldStorageDefinitionDelete($media_definition);
}

/**
 * Install the new Instagram owner field on existing media and node bundles.
 */
function MY_MODULE_update_N() {
    
    // Install Instagram owner field on nodes and media
    $field_manager = \Drupal::service('entity_field.manager'); 
    $field_storage_manager = \Drupal::service('field_storage_definition.listener'); 
    
    $node_definition = $field_manager->getFieldStorageDefinitions('node')['instagram_owner_display']; 
    $field_storage_manager->onFieldStorageDefinitionCreate($node_definition);
    
    $media_definition = $field_manager->getFieldStorageDefinitions('media')['instagram_owner_media'];
    $field_storage_manager->onFieldStorageDefinitionCreate($media_definition);
}

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.