Discover Typed Data API

LUCA LUSSO

Luca Lusso

  • Software developer @ Wellnet
  • (Co-) Mantainer of Devel, Vardumper, XHProf, ...
  • @lussoluca

The problem with D7

(from drupal.org documentation)

PHP is a very loosely typed language. It doesn’t have a clear definition of the different types of data it deals with.

Therefore Drupal used to be the same. For example, there was no consistent way of telling if a field value is a string, an integer or a timestamp.

Or even, to tell if something is translatable or accessible (as in permissions).

Even if you as a developer happen to know that the value of a text field happens to be a string, there’s no consistent programmatic way of fetching this information.

This expose us to (at least) two kinds of problems:

  • Validation
  • Building machine readable APIs for data access

$stub_form = drupal_get_form(
  $node_type . '_node_form', (object) $stub_node
);
$form_state['triggering_element'] = $stub_form['actions']['submit'];

drupal_form_submit(
  $node_type . '_node_form', $form_state, (object)$stub_node
);

if ($errors = form_get_errors()) {
  return services_error(
    implode(" ", $errors), 406, array('form_errors' => $errors));
}
						

How the Services module saves a node posted to a REST endpoint.


function email_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {
  foreach ($items as $delta => $item) {
    if ($item['email'] != '' && !valid_email_address(trim($item['email']))) {
      $message = t('"%mail" is not a valid email address', array('%mail' => $item['email']));
      $errors[$field['field_name']][$langcode][$delta][] = array(
        'error' => "email_invalid",
        'message' => $message,
      );
    }
  }
}
						

Email module hook_field_validate.

What typed data are?

The Typed Data API was created to provide developers with a consistent way of interacting with data in different ways.

The Typed Data API provides means of fetching more information, or metadata, about the actual data.

  • Is this data a string? or an email address?
  • Which constraints it has?
  • Who can access the data (read/write/delete)?
  • Is translatable?

(partial) class diagram of Typed Data API.

The Typed Data API mainly provides three different interfaces.

ComplexDataInterface

Implementations of this interface are used for data that is composed of named properties with more pieces of data.

ListInterface

Implementations of this interface are used for something that is composed of a sequential list of other things, for instance a list of other complex pieces of data. Lists are ordered and may contain duplicate items.

TypedDataInterface

Implementations of this interface are used for something that represents a single piece of typed data, like a string or integer.

This is the smallest building block in the Typed Data API.

TypedDataInterface are implemented as Drupal 8 plugins (with @DataType annotation).

  • filter_format
  • any
  • binary
  • boolean
  • datetime_iso8601
  • duration_iso8601
  • email
  • float
  • integer
  • list
  • language
  • language_reference
  • map
  • string
  • timespan
  • timestamp
  • uri

 /**
 * @DataType(
 *   id = "string",
 *   label = @Translation("String")
 * )
 */
class StringData extends PrimitiveBase implements StringInterface {
						

TypedDataInterface implementations stores the data, the metadata are stored in other classes that implements DataDefinitionInterface.

And we can derive the DataDefinition from the TypedData and vice-versa


TypedDataInterface::getDataDefinition();
DataDefinitionInterface::getDataType();
						

Examples


// Create a new DataDefinition instance 
// wrapping the 'string' TypedData.
$definition = DataDefinition::create('string');

// Create a new data based on the previous definition
$data = \Drupal::typedDataManager()
	->create($definition, 'my string');
						

$defintion = ListDataDefinition::create('string');
$list = \Drupal::typedDataManager()
  ->create($defintion, ['my string1', 'my string2']);
						

$title = DataDefinition::create('string');
$title_data = \Drupal::typedDataManager()
  ->create($title, 'Link title');

$url = DataDefinition::create('uri');
$url_data = \Drupal::typedDataManager()
  ->create($url, 'http://www.drupal.org');

$link = MapDataDefinition::create();
$link_data = \Drupal::typedDataManager()
  ->create($link);
$link_data->set('title', $title_data);
$link_data->set('url', $url_data);
						

DataDefinitions can have constraints to limit the data that they accept.

Constraints are Drupal 8 plugins (with @Constraint annotation) and are build on the Symfony Validator component.


$definition = DataDefinition::create('string')
	->addConstraint('Length', ['max' => 5]);
$data = \Drupal::typedDataManager()
	->create($definition, 'my string');
						
  • Callback
  • Blank
  • Not blank
  • Email
  • Comment author name
  • File URI
  • File Validation
  • Link URI can be accessed by the user.
  • No dangerous external protocols
  • No broken internal links
  • Link data valid for link type.
  • Password required for protected field change
  • User email required
  • User email unique
  • User name
  • User name unique
  • Bundle
  • Entity changed
  • Entity type
  • Entity Reference reference access
  • Entity Reference valid reference
  • Allowed values
  • Complex data
  • Count
  • Null
  • Length
  • NotNull
  • Primitive type
  • Range
  • Regex
  • Unique field constraint

$definition = DataDefinition::create('string')
	->addConstraint('Length', ['max' => 5]);
$data = \Drupal::typedDataManager()
	->create($definition, 'my string');
$validations = $data->validate();
						

$validations is an instance of \Symfony\Component\Validator\ConstraintViolationList

Entity API

The Drupal 8 entity API is built on the Typed Data API.

An entity is a complex piece of data it is composed of other pieces of data, like fields with a list of items.

Entities are complex.


$entity instanceof ComplexDataInterface;
						

Properties are not complex, they’re only a list of items.


$entity->get('image') instanceof ListInterface;
						

Items are complex.


$entity->get('image')
  ->offsetGet(0) instanceof ComplexDataInterface;
						

Item values are primitive.


$entity->get('image')
  ->offsetGet(0)->get('alt') instanceof TypedDataInterface;
						

Get an array with named keys for all fields and their definitions.


$property_definitions = $entity->getFieldDefinitions();
						

Get an array with name keys for all properties and their definitions.


$property_definitions = $entity->image
  ->getFieldDefinition()->getPropertyDefinitions();
						

Get only definition for the ‘alt’ property.


$string_definition = $entity->image
  ->getFieldDefinition()->getPropertyDefinition('alt');
						

Entity API add two plugin instances to TypedData.

entity plugin


/**
 * @DataType(
 *   id = "entity",
 *   label = @Translation("Entity"),
 *   description = 
       @Translation("All kind of entities, e.g. nodes, commentss"),
 *   deriver = 
       "\Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver",
 *   definition_class = 
       "\Drupal\Core\Entity\TypedData\EntityDataDefinition"
 * )
 */
						
  • entity
  • entity:block
  • entity:block_content
  • entity:block_content:basic
  • entity:block_content_type
  • entity:comment
  • entity:comment_type
  • entity:editor
  • entity:field_config
  • entity:field_storage_config
  • entity:file
  • entity:filter_format
  • entity:image_style
  • entity:node
  • entity:node:article
  • entity:node:page
  • entity:node:test
  • entity:node_type
  • entity:rdf_mapping
  • entity:search_page
  • entity:shortcut
  • entity:shortcut:default
  • entity:shortcut_set
  • entity:action
  • entity:menu
  • entity:taxonomy_term
  • entity:taxonomy_term:tags
  • entity:taxonomy_vocabulary
  • entity:tour
  • entity:user_role
  • entity:user
  • entity:menu_link_content
  • entity:view
  • entity:date_format
  • entity:entity_form_display
  • entity:entity_form_mode
  • entity:entity_view_display
  • entity:entity_view_mode
  • entity:base_field_override
  • entity_reference

field_item plugin


/**
 * @DataType(
 *   id = "field_item",
 *   label = 
       @Translation("Field item"),
 *   list_class = 
       "\Drupal\Core\Field\FieldItemList",
 *   deriver = 
       "Drupal\Core\Field\Plugin\DataType\Deriver\FieldItemDeriver"
 * )
 */
						
  • field_item:comment
  • field_item:datetime
  • field_item:file
  • field_item:image
  • field_item:link
  • field_item:list_float
  • field_item:list_integer
  • field_item:list_string
  • field_item:path
  • field_item:text
  • field_item:text_long
  • field_item:text_with_summary
  • field_item:boolean
  • field_item:changed
  • field_item:created
  • field_item:decimal
  • field_item:email
  • field_item:entity_reference
  • field_item:float
  • field_item:integer
  • field_item:language
  • field_item:map
  • field_item:password
  • field_item:string
  • field_item:string_long
  • field_item:timestamp
  • field_item:uri
  • field_item:uuid

Typed Data Explorer

  • Simple module to browse Typed Data.
  • Based on Webprofiler.
  • https://github.com/wellnet/typed_data_explorer

Test Your Drupal Spritz

Book you test with a tweet with #tyds hashtag

Thanks!s

Questions?