Drupal 8: Fields reborn
-
Upload
pablo-lopez -
Category
Technology
-
view
1.308 -
download
6
Transcript of Drupal 8: Fields reborn
D8: Fields reborn
Pablo López - @plopesc
DrupalCamp SpainValencia, May 2014
Let's do it with Drupal 8
Rubén Teijeiro
Was earlier today :)
Migrate in core
Christian López Espínola
Saturday 1pm
El universo javascript en Drupal 8
Ramón Vilar
Saturday 5pm
Modes and formatters
Jesús Sánchez Balsera
Sunday 10am
Related sessions
Credits: @yched & @swentel
Broken record
Amazing changes• More power for site builders
• Fields & instances moved to config entities
• Formatters, Widgets and Field types are plugins
• Formatters & Widgets work on all fields
• Moved from field to entity storage
Field & Entity API are now one happy big family
... for the 3rd time
yched, amateescu, swentel
fago, Berdir, plach,
effulgentsia, andypost...
Site building
features
New field types• More power for site builders out of the box
• Not always full ports of the corresponding D7 modules
• Sometimes they are not even modules anymore (email, number, more to
come)
Entity reference
Fairly complete port of D7 entity_reference.module
Taxonomy, file, image fields: still separate field types
Date / Datetime
The "repeat" features from D7 stays in contrib
Link
Basic version (URL, text)
No support for internal paths (e.g. node/1)
Now supports internal paths (https://drupal.org/node/2054011)
Email• Input validation
• No anti-spam support out of the box
Phone• HTML5 "tel" input
• Basic display (optional "tel:" link)
In Place Editing
Fieldable blocks
aka Beans in core
Form modes• Similar to "view modes", on the form side
• Build several form variants for your entities:
• User registration / user edit
• Creation / edit forms
• Fields can now be hidden in forms
Beware of required fields with no default values...
Form modes UI
Field Overview UI
Fields are tied to an entity typeA field cannot be "shared" across entity types,
only across bundles of a given entity type
Consequences:
• No need to clutter the field name ('comment_body')
node 'body' != comment 'body'
• A $field_name alone is not enough:
$field = FieldConfig::loadByName($entity_type, $field_name);
<div class="field-name-body field-node--body">
There will be code
APIs !!!
Disclaimer: still in flux...
Data structures
Data model recap• Field value: list of multiple items
• Each item: list of properties depending on the field type
• Fields can be translatable
Data model - D7
Data model - D7
D8 Entity translation• Entity level, not field level
$entity->getTranslation($langcode)
• Implements EntityInterface:
$entity->getTranslation($langcode)->field_foo
• Facets of the same entity
$entity is always loaded/saved as a whole
Data model - D8
Data model - D8
Data model - D8
Data model - D8
Data model - D8
Data model - D8
Data model - D8
Data model - D8
In a nutshell• $entity, $entity->getTranslation('fr') Entity
• $entity->field_foo FieldItemList
• $entity->field_foo[$delta] FieldItem
• $entity->field_foo[$delta]->property
• $entity->field_foo->property for delta 0
Navigating
Field items are "smart":
$items->getEntity() $items->getLangcode()
$items->getFieldDefinition()
$instances = field_info_instances($entity_type, $bundle);foreach ($instances as $field_name => $instance) { $items = $entity[$field_name][$langcode]; do_something($items, $entity, $langcode, $instance);} D7
foreach ($entity as $field_name => $items) { $items->doSomething(); // or $object->doSomething($items);} D8
Everything is a field
Everything
Everything in a ContentEntity is a field• $node->title FieldItemListInterface
• $node->body FieldItemListInterface
• $node->field_custom FieldItemListInterface
Drawback: $node->title->value;
Mitigation: $node->getTitle();
Unified APIs and features• field translation
• field access
• constraints / validation
• output in REST
• widgets / formatters
• In Place Editing
• EntityQuery (EFQ in D7)
• field cache (= entity cache!)
Naming is hard...
Different kinds of fields...• Base fields (former "entity properties")
• defined in code: MyEntityType::baseFieldDefinitions()
• Bundle fields (variations of base fields per bundle)
• defined in code: MyEntityType::bundleFieldDefinitions()
• e.g. node title
• Configurable fields (former "fields") - field.module
• defined in config through an admin UI
Properties: what you find inside a FieldItem ('value', 'format', 'target_id'...)
Code architecture• /core/lib/Drupal/Core/Field
The Field system, widgets, formatters...
baked in the lifecycle of (Content)Entities
• /core/modules/field.module
Configurable fields
Unified FieldStorageDefinitionInterfaceField definition: name, type, label, cardinality, settings, description...
• D7: $field / $instance arrays
$field['type'], $instance['settings']['max']
• D8: FieldStorageDefinitionInterface
$definition->getType(), $definition->getSetting('max')...
• Autocompletion, documentation...
• $field / $instance are mostly abstracted away
Unified FieldStorageDefinitionInterface
Implementations:
• configurable fields: FieldConfig, FieldInstanceConfig
• base fields: FieldDefinition
interface FieldStorageDefinitionInterface { public function getName(); public function getType(); public function getSettings(); public function getSetting($setting_name); public function isTranslatable(); public function getDescription(); public function getCardinality(); public function isMultiple(); // Some others...
Grabbing field definitionsfield_info_fields()field_info_field($field_name)field_info_instances($entity_type, $bundle)field_info_instances($entity_type, $bundle, $field_name) D7
$entity_manager->getFieldDefinitions($entity_type, $bundle)
// On an entity:$entity->getFieldDefinitions()$entity->getFieldDefinition($field_name)$entity->hasField($field_name)
// On items:$item->getFieldDefinition(), $items->getFieldDefinition() D8D8D8
Field storage• D7: pluggable "field storage engines", field per field
• D8: job of the EntityStorageController
• Entities are stored as a whole
• Easier to swap an alternate storage (Mongo...)
• Base class for "generic SQL field storage"
• Per-field tables (same as D7)
• Handles revisions, translations, multi values, multi properties
• ... only for configurable fields
Field storage (@todo, fingers crossed)
Problem:
• Supporting translatable base fields is hard
• 3rd party code can only add fields through config
Plan:
• Let base fields control how they are stored:
• Optimized storage in the entity base tables
• Generic storage, free translation support
• Custom storage somewhere else...
CMI
CMI• Configuration in YML files
• Deployable between environments
• Can be shipped in modules
• ConfigEntities
Field definition structures• $field : "a data bucket"
(name, type, cardinality, entity type, ...)
• $instance : "a field attached to a specific [entity_type, bundle]"
(label, description, required, ...)
D7:
• deep arrays of hell
• {field_config}, {field_config_instance} db tables
D8: Field structures as ConfigEntities• $field :
• FieldConfig (entity type: 'field_config')
• field.field.[entity_type].[field_name].yml
• $instance :
• FieldInstanceConfig (entity type: 'field_instance_config')
• field.instance.[entity_type].[bundle].[field_name].yml
field.field.node.body.ymlid: node.bodyuuid: d9a197db-89e1-4b8b-b50c-122083aeacb1status: truelangcode: enname: bodyentity_type: nodetype: text_with_summarysettings: { }module: textlocked: falsecardinality: 1translatable: falseindexes: { }dependencies: module: - node - text
123456789101112131415161718
field.instance.node.article.body.ymlid: node.article.bodyuuid: a378b8b5-39ac-44da-bc07-7ad0a2479a6astatus: truelangcode: enfield_uuid: d9a197db-89e1-4b8b-b50c-122083aeacb1field_name: bodyentity_type: nodebundle: articlelabel: Bodydescription: ''required: falsedefault_value: { }default_value_function: ''settings: display_summary: true text_processing: truedependencies: entity: - field.field.node.body - node.type.articlefield_type: text_with_summary
12345678910111213141516171819202122
CRUD API - D7Dedicated functions:
+ associated hooks...
field_create_field(array());field_update_field(array());field_delete_field($field_name);............_instance(array()); D7
CRUD API - D8Regular Entity CRUD API:
+ regular hook_entity_[ENTITY_TYPE]_[OP]() hooks
$field = entity_create('field_config', array( 'name' => 'body', 'entity_type' => 'node', 'type' => 'text_with_summary',);$field->save();
$field->cardinality = 2;$field->save();
$field->delete(); D8
EntityDisplay
Display settings in D7Scattered around:
• $instance['display'][$view_mode]
• 'field_bundle_settings_[entity_type]_[bundle]' variable (ew...)
• 3rd party (Display suite, Field groups): in their own tables...
Each with separate "alter" hooks
Loads needless stuff in memory
EntityViewDisplay (ConfigEntity)• "Full recipe" for displaying an entity in a given view mode
• Lists "components", with order and settings
entity_view($entity, $view_mode) :
• Loads the relevant display
• Alters it as a whole
• Injects it into all the callstack
hook_entity_view_display_alter(EntityViewDisplay $display);
EntityViewBuilder::buildContent(array $entities, array $displays);hook_entity_view(EntityInterface $entity, EntityViewDisplay $display);
entity.display.node.article.teaser.ymlid: node.article.teaseruuid: ad345f0f-ff44-4210-8900-b8bdfcd8e671targetEntityType: nodebundle: articlemode: teasercontent: field_image: label: hidden type: image settings: image_style: medium image_link: content weight: -1 body: label: hidden type: text_summary_or_trimmed weight: 0 settings: trim_length: 600 field_tags: type: taxonomy_term_reference_link weight: 10 label: above settings: { }
123456789101112131415161718192021222324
EntityViewDisplay API$display = entity_get_display('node', 'article', 'teaser');
$display->setComponent('body', array( 'type' => 'text_trimmed', 'settings' => array('trim_length' => '600'))->removeComponent('image')->save();
$options = $display->getComponent('body');// array(// 'type' => 'text_default'// 'weight' => 0,// 'settings' => array(),// 'label' => 'hidden',// )// or NULL if 'body' is hidden
EntityFormDisplay• Same thing for forms :-)
• Allows "form modes"
Field types
D7: field type "hooks"function hook_field_info() { }function hook_field_schema($field) { }function hook_field_settings_form($field, $instnce, $has_data) { }function hook_field_instance_settings_form($field, $instance) { }
function hook_field_load($entity_type, $entities, $field, $instances, $langcodefunction hook_field_validate($entity_type, $entity, $field, $instance, $langcodefunction hook_field_presave($entity_type, $entity, $field, $instance, $langcodefunction hook_field_insert($entity_type, $entity, $field, $instance, $langcodefunction hook_field_update($entity_type, $entity, $field, $instance, $langcodefunction hook_field_delete($entity_type, $entity, $field, $instance, $langcodefunction hook_field_delete_revision($entity_type, $entity, $field, $instance,
function hook_field_is_empty($item, $field) { }function hook_field_prepare_view($entity_type, $entities, $field, $instances, function hook_field_prepare_translation($entity_type, $entity, $field, $instance
FieldType plugin type• Discovery folder: Plugin/Field/FieldType
• Annotation: FieldType
• Interface: FieldItemInterface
FieldItemInterface Now in 8.x !
interface FieldItemInterface { public static function schema(FieldDefinitionInterface $field_definition); public static function propertyDefinitions(); public function getConstraints(); public function isEmpty();
public static function defaultSettings(); public static function defaultInstanceSettings();
public function settingsForm(array $form, array &$form_state, $has_data); public function instanceSettingsForm(array $form, array &$form_state);
public function prepareCache(); public function preSave(); public function insert(); public function update(); public function delete();
/core/modules/link/lib/Drupal/link/Plugin/Field/FieldType/LinkItem.phpnamespace Drupal\link\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;use Drupal\Core\Field\FieldStorageDefinitionInterface;use Drupal\Core\TypedData\DataDefinition;use Drupal\Core\TypedData\MapDataDefinition;use Drupal\link\LinkItemInterface;
/** * Plugin implementation of the 'link' field type. * * @FieldType( * id = "link", * label = @Translation("Link"), * description = @Translation("Stores a URL string, optional varchar link text, and optional blob of attributes to assemble a link."), * default_widget = "link_default", * default_formatter = "link", * constraints = {"LinkType" = {}} * ) */class LinkItem extends FieldItemBase implements LinkItemInterface {
/**
1234567891011121314151617181920212223
Formatters
D7: "hooks" (well, magic callbacks)
• Lost within the 50 other functions in your file
• switch dance when you implement several formatters
function mymodule_field_formatter_info() { }
function mymodule_field_formatter_settings_form($field, $instance, $view_mode,function mymodule_field_formatter_settings_summary($field, $instance, $view_mode
function mymodule_field_formatter_prepare_view($entity_type, $entities, $fieldfunction mymodule_field_formatter_view($entity_type, $entity, $field, $instance
D8: FieldFormatter plugins
• All logic nicely self contained.
• OO! Classes! ( = Inheritance!)
• FormatterBase class provides stubs and helpers
namespace Drupal\Core\Field;
interface FormatterInterface extends PluginSettingsInterface { public static function defaultSettings() public function settingsForm(array $form, array &$form_state); public function settingsSummary(); public function prepareView(array $entities_items); public function view(FieldItemListInterface $items); public function viewElements(FieldItemListInterface $items);}
D8: FieldFormatter plugin type• No more _info() hook
• Expose your class as a "Field formatter" plugin
• Discovery folder: Plugin/Field/FieldFormatter
• Annotation: FieldFormatter
Inheritance example
Working in formatters• Access the configuration of the formatter:
$this->getSetting('foo'), $this->getSettings()
• Access the definition of the field:
$this->fieldDefinition->getType()
$this->getFieldSetting('foo'), $this->getFieldSettings()
• Manipulate the FieldItemList and FieldItem objects:
$items->getEntity(), $items->getLangcode()
You really want to extend FormatterBase...
Widgets
FieldWidget plugin type• Discovery folder: Plugin/Field/FieldWidget
• Annotation: FieldWidget
• WidgetBase class
interface WidgetInterface extends WidgetBaseInterface { public static function defaultSettings() public function settingsForm(array $form, array &$form_state); public function settingsSummary(); public function formElement(FieldItemListInterface $items, $delta, array $element public function errorElement(array $element, ConstraintViolationInterface $violation public function massageFormValues(array $values, array $form, array &$form_state}
Working in widgets• Same as formatters...
• New in D8: massageFormValues()
Values produced by the FAPI structure → proper field values
• FAPI / render #callbacks:
$form['#process'] = '_my_process_helper';
$form['#process'] = array($this, 'myProcessHelper');
Please use static methods instead, (avoids serializing $this):
$form['#process'] = array(get_class($this), 'myProcessHelper');
Widgets / formatters on base fields• Powerful flexibility
• Free support for "In Place Editing"
• Hey, EntityDisplays let us store the settings!
/core/modules/node/lib/Drupal/node/Entity/Node.php public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['title'] = FieldDefinition::create('string') ->setLabel(t('Title')) ->setDescription(t('The title of this node, always treated as non-markup plain text.')) // ... ->setSettings(array( // Array settings... )) ->setDisplayOptions('view', array( // Array options... )) ->setDisplayOptions('form', array( // Array options... )) ->setDisplayConfigurable('form', TRUE); //.... } public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $node_type = node_type_load($bundle); $fields = array(); if (isset($node_type->title_label)) { $fields['title'] = clone $base_field_definitions['title']; $fields['title']->setLabel($node_type->title_label); } return $fields; }
1234567891011121314151617181920212223
API changes / beta targets coming up• Content translation sync settings to own storage
• A unified repository of field definitions
• Remove field_info_*() (Patch submitted)
• Apply formatters and widgets to * fields (Partially)
• Make delete fields work with config synchronization
Get involved• [META] Complete the Entity Field API
http://drupal.org/node/2095603
• http://entity.worldempire.ch/
• Weekly IRC meetings: #drupal-entity - Thursday, 18:00 CET
Thanks!
Questions?