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 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.