Define and use a compound¶
A compound entity carries only its identity: id, label, description and
includes. Saving it registers its derived entity type cfi_<id> (see
compound items as entities). Each sub-field is then ordinary
FieldStorageConfig and FieldConfig on that type and bundle, created as on
any other content entity type. Run this once in a bootstrapped development
environment with Compound Field enabled:
use Drupal\compound_field\Entity\CompoundField;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
$compound = CompoundField::create([
'id' => 'speaker',
'label' => 'Speaker card',
]);
$compound->save();
FieldStorageConfig::create([
'field_name' => 'name',
'entity_type' => 'cfi_speaker',
'type' => 'string',
])->save();
FieldConfig::create([
'field_name' => 'name',
'entity_type' => 'cfi_speaker',
'bundle' => 'cfi_speaker',
'label' => 'Name',
])->save();
FieldStorageConfig::create([
'field_name' => 'topic',
'entity_type' => 'cfi_speaker',
'type' => 'string',
'cardinality' => 3,
])->save();
FieldConfig::create([
'field_name' => 'topic',
'entity_type' => 'cfi_speaker',
'bundle' => 'cfi_speaker',
'label' => 'Topics',
])->save();
\Drupal\compound_field\CompoundFieldCanonicalSubFields::write($compound_id,
$name, $definition) does the same in one call, taking storage_settings and
settings in the runtime shape FieldStorageConfig::getSettings() answers.
Field UI, composition mirroring and the structural Drush commands all use it,
so a sub-field written by any of them is spelled identically; use it when
scripting several sub-fields from one array. Field UI
remains the interactive way, at /admin/structure/compound-field/<id>/fields/....
Attach compound_field:speaker to a content type through normal
FieldStorageConfig and FieldConfig on the host entity type, or
through Field UI. The host storage's cardinality can
be unlimited. The sub-field caps are separate: each is the cardinality on that
sub-field's own storage configuration.
Read, validate and save through the host¶
With a loaded Conference node and its field_speakers field:
$node->set('field_speakers', [
[
'name' => 'Ada Lovelace',
'topic' => [
['value' => 'Computing history'],
['value' => 'Analytical engines'],
],
],
]);
$violations = $node->validate();
if ($violations->count() !== 0) {
throw new \RuntimeException('Conference values failed validation.');
}
$node->save();
$name = $node->get('field_speakers')->first()->get('name')->get('value')->getValue();
The entity API does not validate every programmatic save. Handle violations before persisting user-supplied values, and do not expose internal exceptions to an untrusted client. To read nested values by path, or build tokens, see nested values, tokens and Search API. To load, query and save one item through its own storage, see compound items as entities.
Storage settings¶
storage_settings belong to a sub-field's storage configuration, such as a
string's maximum length or a reference's target entity type; settings are
field settings, such as a reference handler and its allowed bundles. Defaults
come from the field type. Both take the runtime shape
FieldStorageConfig::getSettings() answers for any field, for example an
Options field's allowed_values as the map ['red' => 'Red'], not the
configuration-export list of pairs. Core converts between the two on save and
export, exactly as for a field created through Field UI; Compound Field keeps
no settings shape of its own.
Changing datetime_type on an in-use sub-field is refused even when its column
specification is unchanged, because it changes how stored values are read.
Retiring populated allowed-value keys needs explicit consent.
Include another compound's fields¶
Given a person compound, this export makes employee include its sub-fields:
id: employee
label: Employee
includes:
- person
employee's own sub-fields, such as cv, are separate
field.storage.cfi_employee.cv / field.field.cfi_employee.cfi_employee.cv
configuration, created like any other sub-field. Composition mirrors each
included compound's sub-fields onto every dependent's derived type, marked
with the compound_field.inherited_from third-party setting so they are never
mistaken for the dependent's own declarations.
Use the compound's Composition tab or set includes in configuration.
getDeclaredFields() returns only local definitions; getFields() resolves
included fields first, transitively, then local fields, and a repeated base
contributes once. Duplicate sub-field names, missing bases and cycles are
refused.
An inclusion adds no value nesting: a sub-field of type compound_field:person
is what holds a nested person record. Both count toward the expanded column
budget. Edit inherited fields on their declaring compound; the change reaches
every dependent host storage. An in-use dependent prevents deleting its base,
and deleting an unused base can cascade to unused dependents.
Change an in-use definition¶
There is no setFields() mutator. Sub-fields are field configuration, so a
structural change is planned against a candidate snapshot and applied through
the structural pipeline, never by saving the definition directly:
use Drupal\compound_field\StructuralChange\CompoundFieldDefinitionSet;
$fields = $compound->getDeclaredFields();
$fields['phone'] = ['field_type' => 'telephone', 'label' => 'Phone'];
unset($fields['fax']);
$candidate = CompoundFieldDefinitionSet::fromCompound($compound, $fields);
$request = \Drupal\compound_field\StructuralChange\CompoundFieldStructuralChangeRequest::create()
->acknowledgeRemoval('fax');
$pipeline = \Drupal::service('compound_field.structural_change_pipeline');
$plan = $pipeline->plan($candidate, $compound, $request);
// Inspect $plan->describe() before applying.
$pipeline->apply($candidate, $request);
Changes to includes use this pipeline too; removing an inclusion requires
acknowledging the sub-fields it removes. See the
deployment guide for imports and recovery.