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:
- 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); } }
- 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; }
- 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 DrupalCoreEntityEntityStorageException: "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.