7

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)?

13

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 (view modes and form modes):

core/modules/node/node.module:

/**
 * Adds the default body field to a node type.
 *
 * @param \Drupal\node\NodeTypeInterface $type
 *   A node type object.
 * @param string $label
 *   (optional) The label for the body instance.
 *
 * @return \Drupal\field\Entity\FieldConfig
 *   A Body field object.
 */
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' => ['display_summary' => TRUE],
    ]);
    $field->save();

    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $display_repository = \Drupal::service('entity_display.repository');

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

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

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

  return $field;
}

Edit: Code updated from 8.8.x to remove deprecated functions, see change record https://www.drupal.org/node/2835616

The field storage itself is not added or deleted because it is shared between all content types. You have to do this once when you install the module. The easiest way is to put a yml file for the storage in /config/install. Example from the node module:

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
7

Edit: Little More Description: For adding entity type (content type, block type, product type, variation type ... ) field programmatically you need

  1. Create Field Storage Configuration: only then
  2. Create Field Configuration

You can use Drupal 8 configuration Single export which can be found here -> /admin/config/development/configuration/single/export

Selecting needed Field Storage or Field config will give you desired fields and their values. enter image description here

Next All Described Very Well

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]
      );
  }
}
  • 1
    Important to note that the uuid in the field storage config is unique to the database. You'll want to remove that so it works in other databases. – powpow12 Aug 6 at 13:58

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.