8: Compound (bundled) fields - your new best friend - Part 2

the Field Type Item definition

In the previous section we learned what a compound/bundled field is, and defined the module files that will wrap our field type plugin. In this section, we will create the file that defines the field type and which sub-fields compose it.

NameItem.php

The field file defines the structure of the field type. The name of the file (less the .php extension) will also be the name of the class defined within the file. The file should be placed in the path <my module>/src/Plugin/Field/FieldType.


namespace Drupal\name_field_type\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldType\FileItem;

These first lines are declaring the name space of the file, which should match up to the path in which you place the file, and the use instructions for dependency injection.


/**
 * @FieldType(
 *   id = "name",
 *   module = "name_field_type",
 *   label = @Translation("Name Widget"),
 *   description = @Translation("This field type stores name information."),
 *   default_widget = "name_widget",
 *   default_formatter = "name_formatter",
 * )
 */

This annotation block defines the widget and identifies the remaining two pieces, which we will create after this one. Note that the id is the same name as the module, and that the other two classes needed, the default widget and default formatter, are named like this one but with different suffixes: DefaultWidget and DefaultFormatter.


class NameItem extends FieldItemBase {

    public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {

        $properties = [];

        $properties['title'] = DataDefinition::create('string')
            ->setLabel(t('Title'))
            ->setDescription(t('Title preceding first name.'));

        $properties['first_name'] = DataDefinition::create('string')
            ->setLabel(t('First name'))
            ->setDescription(t('Given name.'));

        $properties['middle_name'] = DataDefinition::create('string')
            ->setLabel(t('Middle name/initial'));

        $properties['last_name'] = DataDefinition::create('string')
            ->setLabel(t('Last name'))
            ->setDescription(t('Surname / family name.'));

        $properties['maternal_last_name'] = DataDefinition::create('string')
            ->setLabel(t('Maternal last name'));

        $properties['suffix'] = DataDefinition::create('string')
            ->setLabel(t('Suffix'))
            ->setDescription(t('Honorific or generation following the name, e.g. Esq., Jr.'));
        return $properties;
    }

The method propertyDefinitions() defines the field type's properties (sub-fields). Ours use strings, but could just as well have been another type such as integer. Each sub-field needs to be defined, just as it would be in a form. The create() method of the DataDefinition class is invoked to create the sub-field, and the methods setLabel() and setDescription() define the label that precedes the property and the description that follows it, respectively.


    public static function schema(FieldStorageDefinitionInterface $field_definition) {
        $columns = array(
            'title' => array(
                'type' => 'varchar',
                'length' => 255,
            ),
            'first_name' => array(
                'type' => 'varchar',
                'length' => 255,
            ),
            'middle_name' => array(
                'type' => 'varchar',
                'length' => 255,
            ),
            'last_name' => array(
                'type' => 'varchar',
                'length' => 8,
            ),
            'maternal_last_name' => array(
                'type' => 'varchar',
                'length' => 255,
            ),
            'suffix' => array(
                'type' => 'varchar',
                'length' => 255,
            ),
        );

        $schema = array(
            'columns' => $columns,
            'indexes' => [],
        );
        return $schema;
    }

The schema() method is does the same thing that it would in a .install file, it defines the storage format for persisting field instance data in the database.


  public function isEmpty() {
    return empty($this->values['first_name']) && empty($this->values['last_name']);
  }
}

The isEmpty() method is very important. We aren't declaring any of the properties to be required, because we want the field type to be portable, which means not limiting it. However, if we cannot count on any particular sub-field in it have a value, how does Drupal decide whether the field instance is empty? Does it matter? Yes. Because if Drupal thinks that it is not empty, it will create and save an instance of it. Is that a problem? Yes, again, because if we were, for example, to add another in the node form, and Drupal considered every instance as valid, it would save that second instance, too. So, we declare the test that determines whether the field is empty, in this case when both the first and last name are empty.

In Part 3 we will create the file that defines how our field type will be rendered on the node form.

Part 1  Part 3


Of course, the Drupal Community isn't the only place to give back. It's a complex world, and there are as many worthy causes as their are melodies, with most being served by multiple charitable organizations. Here are a few of those that strike a 7th chord with me.