Score:0

How do I set an incremental property on a custom field?

jp flag

I built a custom module which creates a custom field type (with formatter and widget), and I would like to make one property of the field incremental (to increase its value by one for each instance of the field.)

What I have so far is:

  1. web/modules/custom/asciinema/src/Plugin/Field/FieldType/AsciinemaItem.php (the field definition)
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return [
      // columns contains the values that the field will store
      'columns' => [
        // List the values that the field will save.
        'aid' => [
          'type'        => 'serial',
          'size'        => 'normal',
          'description' => 'The id of the asciinema instance.',
          'unsigned'    => TRUE,
          'not null'    => TRUE,
        ],
      ],
      .....
      'unique keys' => [
        'aid' => ['aid'],
      ],
    ];

  // This function ensures that the value is incremented each time a new field instance is saved.
  public function preSave() {
    if ($this->getEntity()->isNew()) {
      // Increase the value of aid when dealing with a new entity.
      $fieldConfig = $this->getFieldDefinition();
      $field_name = $fieldConfig->getName();
      $entity_type = $fieldConfig->getTargetEntityTypeId();
      
      $query = \Drupal::database()->select($entity_type . '__' . $field_name, 'ef')
                ->fields('ef', [$field_name . '_' . 'aid'])
                ->orderBy($field_name . '_' . 'aid', 'DESC');
      $lastIndex = $query->execute()->fetchField();
      
      $result = !empty($lastIndex) ? $lastIndex + 1 : 1;

      $this->values['aid'] = $result;
      $this->setValue($this->values);
    }
  }
  1. web/modules/custom/asciinema/src/Plugin/Field/FieldWidget/AsciinemaDefaultWidget.php (the field widget).
 public function formElement(
    FieldItemListInterface $items,
    $delta,
    array $element,
    array &$form,
    FormStateInterface $form_state
  ) {
    // Current values with fallbacks.
    $aid      = isset($items[$delta]->aid) ? $items[$delta]->aid : 0;
    ....
    $element['asciinema']['id']['aid'] = [
      '#title'          => t('AID'),
      '#description'    => t('Asciinema ID.'),
      '#disabled'       => TRUE,
      '#type'           => 'textfield',
      '#default_value'  => $aid,
    ];
    ....
    return $element;

    ....

  public function massageFormValues(
    array $values,
    array $form,
    FormStateInterface $form_state
  ) {
    foreach ($values as &$value) {
      // ID.
      $value['aid']       = $value['asciinema']['id']['aid'];
    ....
      unset($value['asciinema']);
    }

    return $values;
  }
  1. web/modules/custom/asciinema/src/Plugin/Field/FieldFormatter/AsciinemaDefaultFormatter.php (The formatter):
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    $source = [];

    foreach ($items as $delta => $item) {
      // Render output using asciinema_default theme.
      $source['asciinema'] = [
        '#theme'     => 'asciinema_default',
        '#asciinema' => [
          'aid'          => $item->aid,
        ],
      ];
      ...
      $elements[$delta] = [
        '#markup' => \Drupal::service('renderer')->render($source),
      ];
    }

    return $elements;

As it is right now I can save the values entered in my custom field and the aid gets increased for each field instance. However I can not edit any of the past data because that would generate:

NOTICE: PHP message: Uncaught PHP Exception Drupal\Core\Entity\EntityStorageException: "SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "node_revision__asciinema__asciinema_aid__key"
php_1       | DETAIL:  Key (asciinema_aid)=(4) already exists.:

I would love to find out what's missing or to find a better and cleaner way to deal with incremental fields.

EDIT:

I managed to get around the limitations by removing:

'unique keys' => [
        'aid' => ['aid'],
      ],

... from the schema definitions and adding an extra condition in the presave() function.

public function preSave() {
    if ($this->getEntity()->isNew() || empty($this->values['aid'])) {
      ...
    }

However I do not consider this a definitive answer, but more of a work-around. I would like to make use of database constraints like AUTO INCREMENT or UNIQUE KEY as well. To this date I have not found out how to have it working with custom field properties.

Jaypan avatar
de flag
You may want to include what your overall goal is, as it's very possible there is a more Drupal way to do what you are trying to do, which won't involve any of what you've shown.
dasj19 avatar
jp flag
@Jaypan Well my end goal is to have a field that provides information to display an asciinema player: https://asciinema.org/ . I built a custom field with properties for each part of the required data to display the player. Now I need a unique id for each field instance to be able to have multiple players on the same page and not conflict with each other.
cn flag
Why not use the entity ID? Or if a multi-value field, a combination of entity ID and field delta?
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.