6

My module provides a configuration form which allows to select a content type and on submit 2 new fields will be added to the selected content types.

I want to add these fields to the content types programmatically and I tried this using hook_entity_bundle_field_info but the fields don't get saved.

How can I save the fields? And how can I delete the fields (if the user changes the content type in the configuration form)?

9

You can use this example from the node module.

This adds a body field when a content type is created and at the same time configures the display modes:

core/modules/node/node.module:

function node_add_body_field(NodeTypeInterface $type, $label = 'Body') {
  // Add or remove the body field, as needed.
  $field_storage = FieldStorageConfig::loadByName('node', 'body');
  $field = FieldConfig::loadByName('node', $type->id(), 'body');
  if (empty($field)) {
    $field = FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => $type->id(),
      'label' => $label,
      'settings' => array('display_summary' => TRUE),
    ]);
    $field->save();

    // Assign widget settings for the 'default' form mode.
    entity_get_form_display('node', $type->id(), 'default')
      ->setComponent('body', array(
        'type' => 'text_textarea_with_summary',
      ))
      ->save();

    // Assign display settings for the 'default' and 'teaser' view modes.
    entity_get_display('node', $type->id(), 'default')
      ->setComponent('body', array(
        'label' => 'hidden',
        'type' => 'text_default',
      ))
      ->save();

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
    $view_modes = \Drupal::entityManager()->getViewModes('node');
    if (isset($view_modes['teaser'])) {
      entity_get_display('node', $type->id(), 'teaser')
        ->setComponent('body', array(
          'label' => 'hidden',
          'type' => 'text_summary_or_trimmed',
        ))
        ->save();
    }
  }

  return $field;
}

The field storage itself is not added or deleted, because this is shared between all content types and you don't want to loose the field data. But you have to do this once when you install the module. The easiest way to do this is to put a yml file for the storage in /config/install. This is how the node module does this:

core/modules/node/config/install/field.storage.node.body.yml:

langcode: en
status: true
dependencies:
  module:
    - node
    - text
id: node.body
field_name: body
entity_type: node
type: text_with_summary
settings: {  }
module: text
locked: false
cardinality: 1
translatable: true
indexes: {  }
persist_with_no_fields: true
custom_storage: false
  • 2
    Test coverage for field types also have loads of examples like this. – mpdonadio Dec 7 '16 at 1:25
5

I like to add, that the node modules code uses some deprecated functions. (We are now 8.5.6).

Here's my part of the code which handles "create a field storage programmatically:

if (empty($fieldStorageMicrositesfield = \Drupal\field\Entity\FieldStorageConfig::loadByName('node', $fieldName))) {
  $fieldStorage = \Drupal\field\Entity\FieldStorageConfig::create(
    [
      'field_name'             => $fieldName,
      'langcode'               => 'en',
      'entity_type'            => 'node',
      'type'                   => 'list_string',
      'settings'               => [
        'allowed_values'          => [],
        'allowed_values_function' => 'I_used_a_custum_function_here_which_you_not_need_I_guess',
      ],
      'module'                 => 'options',
      'locked'                 => FALSE,
      'cardinality'            => -1,
      'translatable'           => TRUE,
      'persist_with_no_fields' => FALSE,
      'custom_storage'         => FALSE,
    ]
  );
  $fieldStorageSaved = $fieldStorage->save();
  if ($fieldStorageSaved) {
    $message = t(
      "Field @a storage created.",
      ['@a' => $fieldName,]
    );
  }
  else {
    $message = t(
      "Field @a storage could not be created.",
      ['@a' => $fieldName,]
    );
  }
}
else {
  $message = t(
    "Field @a storage could not be created because the field already exists",
    ['@a' => $fieldName,]
  );
}

Here's my part of the code which handles the adding of a field to several content types after I created the storage ($bundles holds the array of my content types):

$entityTypemanager = \Drupal::entityTypeManager();

foreach ($bundles as $bundle) {
  $field = FieldConfig::loadByName('node', $bundle, $fieldName);
  if (empty($field)) {
    FieldConfig::create(
      [
        'field_name'   => $fieldName,
        'entity_type'  => 'node',
        'bundle'       => $bundle,
        'label'        => $label,
        'required'     => TRUE,
        'translatable' => TRUE,
        'description'  => t(
          $description
        ),
      ]
    )->save();
    $message .= "\n" . t(
        "Field @a created in bundle @b.",
        ['@a' => $fieldName, '@b' => $bundle]
      );

    // Assign widget settings for the 'default' form mode.
    $displayForm = $entityTypemanager
      ->getStorage('entity_form_display')
      ->load('node.' . $bundle . '.default')
      ->setComponent($fieldName, [
        'type'   => 'options_buttons',
        'weight' => 100,
      ]);
    $messagePart = '';
    if ($displayForm->save()) {
      $messagePart .= ' form-';
    }
    else {
      $message .= "\n" . t(
          "The form display of @a in bundle @b could not be set",
          ['@a' => $fieldName, '@b' => $bundle]
        );
    }
    unset($displayForm);

    // Assign display settings for the 'default' and 'teaser' view modes.
    $displayDefault = $entityTypemanager
      ->getStorage('entity_view_display')
      ->load('node.' . $bundle . '.default')
      ->removeComponent($fieldName);
    if ($displayDefault->save()) {
      $messagePart .= ' default-';
    }
    else {
      $message .= "\n" . t(
          "The default display of @a in bundle @b could not be set",
          ['@a' => $fieldName, '@b' => $bundle]
        );
    }
    unset($displayDefault);

    // The teaser view mode is created by the Standard profile and therefore
    // might not exist.
    $viewModes = \Drupal::service('entity_display.repository')
      ->getViewModes('node');
    if (isset($viewModes['teaser'])) {
      $displayTeaser = $entityTypemanager
        ->getStorage('entity_view_display')
        ->load('node.' . $bundle . '.teaser')
        ->removeComponent($fieldName);
      if ($displayTeaser->save()) {
        $messagePart .= ' teaser-';
      }
      else {
        $message .= "\n" . t(
            "The teaser display of @a in bundle @b could not be set",
            ['@a' => $fieldName, '@b' => $bundle]
          );
      }
      unset($displayTeaser);
    }
    $message .= "\n" . t(
        $messagePart . "Display(s) of @a in bundle @b are set.",
        ['@a' => $fieldName, '@b' => $bundle]
      );

  }
  else {
    $message .= "\n" . t(
        "Field @a already exists in bundle @b.",
        ['@a' => $fieldName, '@b' => $bundle]
      );
  }
}

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.