Drupal 9: Loading Options From An Option Field

I was recently working on a Drupal form where I needed a user to select from a list of options that were available in a field attached to an entity. As the entity itself didn't exist when the user was filling in the form I needed a way of finding out the options that could be applied to the field. 

Getting a list of values from a field allows for numerous possibilities within Drupal, including configuring modules and creating wizards for generating content. As it took me a little while to track down how to do this I thought a quick post might come in handy for anyone looking to do the same.

In order to get the values available in a field we first need to create a blank entity. This will be used by the field storage system to load the correct field definitions.

There are a few ways to go about creating a new entity, but the most generic method is to use the entity_type.manager service to load the entity definition and create a blank entity from the bundle name. Here is the code that does this.

$entityType = 'node';
$bundle = 'article';

/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
$entityTypeManager = \Drupal::service('entity_type.manager');

// Create a new "blank" entity.
$entity = $entityTypeManager
  ->getStorage($entityType)
  ->create(['type' => $bundle]);

With the blank entity in place we can now use this to fetch the needed field configurations for the field in question.

Using the same entity_type.manager service we can then load the configuration of the field in question. In this case I'm loading the definition of a field called "field_article_cateory", which stores the taxonomy terms for an article.

$fieldName = 'field_article_category';

/** @var \Drupal\field\Entity\FieldStorageConfig $fieldStorage */
$fieldStorage = $entityTypeManager
  ->getStorage('field_storage_config')
  ->load($entityType . '.' . $fieldName);

Getting the options for this field is now pretty simple. We just need to get the options provider (passing in the empty entity) and then get the available settable options for the field.

$options = $fieldStorage
      ->getOptionsProvider('target_id', $entity)
      ->getSettableOptions();

The $options variable now contains a list of the taxonomy terms that can be selected in the field.

This code should work for all options lists. Essentially, anything that implements the OptionsProviderInterface interface should work in the same way, which includes entity reference fields and options fields. If the field in question does not implement the OptionsProviderInterface then the result of getOptionsProvider() will be null.

Note that the getOptionsProvider takes two parameters above, the property name (in this case "target_id") and the concrete entity we want to get the options for. The property name, however, is not used, at least in Drupal core fields. This means that we don't actually have to pass in the correct string so the "target_id" above could even be null.

That being said, it's probably best if we do pass the correct value here since not doing so might introduce a bug in a future update of Drupal (or third party modules) that would be difficult to track down.

To rectify this we just need to find out the field key that is used to reference the value of the field. The simplest way I have found to do this is to use the field storage object to load in the property definitions. We can then use the keys of that definition list to find the property key we need.

$propertyKeys = array_keys($fieldStorage->getPropertyDefinitions());
$propertyKey = array_shift($propertyKeys);

$options = $fieldStorage
  ->getOptionsProvider($propertyKey, $entity)
  ->getSettableOptions();

The getSettableOptions() method has an optional parameter of an Account object. Again, this isn't actually used by the Drupal core option classes, but it's probably best practice to pass in the current account so that extended classes that do use this information can make use of it.

$options = $fieldStorage
  ->getOptionsProvider($propertyKey, $entity)
  ->getSettableOptions(\Drupal::currentUser());

As the account information is not actually used you should therefore be very careful when displaying this to users as you can expose unpublished entities or other private entities to users who might not have have access to them.

You can also use the getSettableValues() method in the same way as the getSettableOptions() to get the keys of the options array.

Let's create a function that pulls of these items together so that you can just ask it for a list of options. This assumes you have dependency injected the entity_type.manager service into the class and called the property $entityTypeManager.

 /**
   * Get the options for a given field instance.
   *
   * @param string $fieldName
   *   The field to pull the options from.
   * @param string $entityType
   *   The entity type that the field is connected to.
   * @param string $bundle
   *   The bundle that the field is connected to.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The user account to pass to the options finder.
   *
   * @return array
   *   The list of options connected to the field.
   */
  public function getOptionsFromField($fieldName, $entityType, $bundle, AccountInterface $account) {
    $entity = $this->entityTypeManager
      ->getStorage($entityType)
      ->create(['type' => $bundle]);

    /** @var \Drupal\field\Entity\FieldStorageConfig $fieldStorage */
    $fieldStorage = $this->entityTypeManager
      ->getStorage('field_storage_config')
      ->load($entityType . '.' . $fieldName);

    $propertyKeys = array_keys($fieldStorage->getPropertyDefinitions());
    $propertyKey = array_shift($propertyKeys);

    return $fieldStorage
      ->getOptionsProvider($propertyKey, $entity)
      ->getSettableOptions($account);
  }

This method would be called like this, with a field called field_article_category on the article content type.

$options = $this->getOptionsFromField('field_article_category', 'node', 'article', $account);

The options list we create here can be fed into a form field to allow users to select from a given list of options. As these options are pulled from an existing field they will update inline with the options set in that field.

$form['options'] = [
  '#type' => 'select',
  '#title' => $this->t('Options'),
  '#options' => $options,
];

One word of caution here is that the options list will contain all available options. This means that if you have a free-tagging field with thousands of items then the entire list will be loaded into memory. Loading in large lists of items for a select field is a known performance issue in Drupal and so I would advise this technique only being used for smaller field lists. Static category lists or option fields with a few items are fine.

More in this series

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
14 + 3 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.