6

We are using the group module in Drupal 8, which introduces a custom entity "group". This entity features a "label" base field with the title "Title". As groups administrator, you can create several different group types, which are handled as fieldable bundles of the group entity and inherit its label base field.

Depending on the group type, we wish to override the generic label title within forms, views, templates, and so on to e.g. 'Label', 'Name', 'Branch', 'Department', 'Section', ...

However, the group entity does not expose the label title within its config form as the node module does. So we tried overriding the title using hook_entity_base_field_info_alter() like this:

/**
 * Implements hook_entity_base_field_info_alter().
 */
function mymodule_entity_base_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type) {
  if ($entity_type->id() == 'group' && !empty($fields['label'])) {
    $fields['label']->setLabel(t('Name'));
  }
}

Which works, but changes the title for all bundles.

Our next try was hook_entity_bundle_field_info_alter(). It exposes bundle information, but it does not allow for base fields to be altered. Trying to re-add the base field definition with a different title (in order to let the core merge the field definitions and override the base field definitions) caused different run-time exceptions.

So we ended up in manually altering form build arrays and widget titles, which may cause trouble later when the amount of group types is increasing.

How can we alter the base field's title on a per-bundle base without having to extend the group module or manually altering all occurrences of our group entities' field labels?

4 Answers 4

10

Bundle specific labels for base fields are stored in label of a BaseFieldOverride config entity:

Defines the base field override entity.

Allows base fields to be overridden on the bundle level.

Plugin annotation

@ConfigEntityType(
  id = "base_field_override",
  label = @Translation("Base field override"),
  handlers = {
    "storage" = "Drupal\Core\Field\BaseFieldOverrideStorage",
    "access" = "Drupal\Core\Field\BaseFieldOverrideAccessControlHandler",
  },
  config_prefix = "base_field_override",
  entity_keys = {
    "id" = "id",
    "label" = "label"
  },
  config_export = {
    "id",
    "field_name",
    "entity_type",
    "bundle",
    "label",
    "description",
    "required",
    "translatable",
    "default_value",
    "default_value_callback",
    "settings",
    "field_type",
  }
)

When you get a base field from EntityFieldManager::getFieldDefinitions($entity_type_id, $bundle), set a new label for the field and save the field definition, then this label is not stored in the entity type (which would not be possible, because this is in code and the same for all bundles), but in the mentioned base field override.

4
  • 1
    Thanks, the hint with the BaseFieldOverride class got me going in a short test. Used BaseFieldOverride::createFromBaseFieldDefinition() in hook_entity_bundle_field_info() to add per-bundle base field overrides with altered labels. It's working well. Jan 7, 2018 at 13:47
  • You could try to create the base field override in other place, for example in a submit handler of the bundle config form or in a CRUD hook of the bundle entity.
    – 4uk4
    Jan 7, 2018 at 16:12
  • ``` $ drush php >>> $efm = \Drupal::service('entity_field.manager') >>> $f=$efm->getFieldDefinitions('taxonomy_term', 'forums') >>> $d=$f['description'] >>> $o=\Drupal\Core\Field\Entity\BaseFieldOverride::createFromBaseFieldDefinition($d, 'forums') >>> $o->setSetting('allowed_formats', ['markdown']) >>> $o->save() # HTH! ``` Jun 16, 2019 at 22:56
  • here's an example of BaseFieldOverride in BundleForm submit: github.com/barraponto/attendance/blob/development/src/Form/… Jun 26, 2019 at 2:46
9

The answer of @4k4 got us on the right track. For our solution, we primarily tried altering the labels for some programmatically provided base group types.

In order to help others with a similar use case, here is how we did it:

We implemented hook_entity_bundle_field_info() within the module that provided our group entity bundle and then used BaseFieldOverride to creating the field overrides:

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\Entity\BaseFieldOverride;

/**
 * Implements hook_entity_bundle_field_info().
 */
function ourgroupmodule_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
  $fields = [];

  // Whether the hook implementation was called for our group type.
  if ($entity_type->id() == 'group' && $bundle == 'ourgroup' && !empty($base_field_definitions['label'])) {
    // Create a base field override with custom title.
    $field = BaseFieldOverride::createFromBaseFieldDefinition($base_field_definitions['label'], $bundle);
    $field->setLabel(t('Our group label title'));
    $fields['label'] = $field;
  }

  return $fields;
}

As further stated by @4k4, the override could be done within other places as well. E.g. in form submit handlers to override title labels of groups that are created within the admin UI.

2
  • Now I get the idea, the base field override is not saved, it's only temporary. This is a nice option, if the label is not user input.
    – 4uk4
    Jan 8, 2018 at 10:58
  • 1
    Thanks. Odd that hook_entity_bundle_field_info is the place to do this, instead of the (seemingly more aptly named) hook_entity_bundle_field_info_alter. Feb 21, 2019 at 21:58
2

Here's an implementation of BaseFieldOverride on a bundle form (using ConfigEntityBundleBase as the bundler entity, as suggested by Drupal Console generate:entity:content).

class CustomEntityTypeForm extends EntityForm {
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);
    /* ... */
    $form['target_bundles'] = [
      '#title' => $this->t('Node types this bundle supports.'),
      '#type' => 'checkboxes',
      '#options' => $node_type_options,
      '#default_value' => $this->entity->getTargetBundles() ?: [],
    ];
    return $form;
  }

  public function save(array $form, FormStateInterface $form_state) {
    /*...*/
    $field_manager = \Drupal::service('entity_field.manager');
    $definitions = $field_manager->getFieldDefinitions('attendance', $this->entity->id());

    /* the name of the base field is 'bundles' */
    if ($definitions['bundles'] instanceof BaseFieldOverride) {
      $override = $definitions['bundles'];
    }
    else {
      $override = BaseFieldOverride::createFromBaseFieldDefinition($definitions['bundles'], $this->entity->id());
    }

    /* my actual override just changes handler settings for an entity reference widget */
    $override->setSetting('handler_settings', ['bundles' => $this->entity->get('bundles')]);
    $override->save()

    /* Proceeds to saving the form normally */
  }

}
0

Building on @Capi Etheriel's answer, but making the naming clearer and fixing a bug when creating new bundles:

public function form(array $form, FormStateInterface $form_state) {
  $form = parent::form($form, $form_state);
  ...

  $form['my_bundle_property'] = [
    '#title' => $this->t('My Bundle property'),
    '#description' => $this->t('This will override the "my_overriden_setting" setting of the "my_overriden_field" field on "my_entity_type" that this is a bundle of.',
    '#type' => 'checkboxes',
    '#options' => ['option1', 'option2],
    '#default_value' => $this->entity->get('my_bundle_property),
  ];

  return $form;
}

public function save(array $form, FormStateInterface $form_state) {
  // You must save the entity first, or the override logic will break when adding new bundles.
  $this->entity->save();

  $field_manager = \Drupal::service('entity_field.manager');
  $definitions = $field_manager->getBaseFieldDefinitions('my_entity_type');
  $override = $definitions['my_overridden_field']->getConfig($this->entity->id());

  $override->setSetting('my_overriden_setting', $this->entity->get('my_overriden_field'));
  $override->save();
  
  ...
}

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.