Introduction to Entity API in Drupal 8

Last updated on
14 August 2017

For the Drupal 7 Entity API: Go here.

Background

The Entity System was introduced late in the Drupal 7 development cycle with basic standards for how entities were loaded. The contributed entity.module expanded the API further by adding support for saving and deleting entities and many other improvements.

The majority of those improvements are now included in Drupal 8. Entity validation is now in its own API (which could validate an Entity saved via REST, rather than a form, for example).

The Drupal 8 entity system

Entities are typed classes with methods
Generic methods $entity->id()
Entity type specific methods $node->getTitle()

Both types are defined and documented in interfaces.

Two variants

Entity types in core comes in two variants.

Configuration Entity
Used by the Configuration System. Supports translations and can provide custom defaults for installations. Configuration entities are stored within the common config database table as rows.
Content Entity
Consist of configurable and base fields, can have revisions and support translations. Content entities are stored within a custom database table as rows. The table name is the same as the content entity "id", and the columns are defined by the entity's "baseFieldDefinitions" method.

Bundles

Bundles are Configuration Entities that enhance a Content Entity. Their configuration stores the differences between the content entity types, such as settings and fields. When creating a new entity type that has bundles, you will create both a Content Entity that will manage the content's details and operations, as well as a Configuration Entity that will handle the differences between the content entity types.

Annotations

When creating a new entity type, you'll need to leverage the annotations system built into core. Annotations look like docblock comments above the class, but are parsed and cached by Drupal core. In a lot of ways annotations replace some of the older style hooks you dealt with in Drupal 7.

Syntax - The annotation syntax is surrounded with @ClassName(), is predominantly made up of key/value pairs, and can also contain arrays that use curly braces. Top level keys must not be surrounded with quotation marks, while array keys must. Each key/value pair should be on their own line, and that line should end with a comma. Additionally, there are some available functions that can be executed on values. Notably the @Translation() function.

Non-working example of annotation syntax:

/**
 * @ContentEntityType(
 *   id = "my_entity_type_id",
 *   label = @Translation("My entity type label"),
 *   example_pair = "this_examples_value",
 *   example_array = {
 *     "array_key" = "array_value",
 *     "some_other_key" = "some_other_value",
 *   },
 * )
 */

Common top level annotations

Key = "Example Value", Description Entity Variant
id = "node", The machine-name for the entity type. Content & Config
label = @Translation("Node"), The human readable name for the entity type. Content & Config
admin_permission = "administer nodes", A permission that allows administrative access to configure and manage the entity type. This is necessary if your entity does not define an "access" handler. Content & Config

bundle_label = @Translation("Content type"),

The optional human readable name for bundle entity type. Content
bundle_entity_type = "node_type", When creating a Content Entity that has bundles, this value should be the "id" of the Configuration Entity. In this case, "node_type" is a Configuration Entity. Content
base_table = "node", Database table name for the entity type. Content
fieldable = TRUE, (boolean) Whether or not this entity type can be extended through the fields ui. Content

field_ui_base_route = "entity.node_type.edit_form",

The name of the route the fields ui is attached to on a fieldable entity. Content

Handlers

Handlers are defined in the entity annotation as an array. They support the entity by mapping certain parts of its execution to other PHP classes. Those classes will "handle" the assigned parts of the entity's execution.

Storage handler - Supports loading, saving and deleting entities in storage (database). This includes default support for revisions, translations and configurable fields.
Example: "storage" = "Drupal\node\NodeStorage",

Form handlers - In any entity's handlers annotation there are multiple form handlers that map the entity's add, edit, and delete forms to other PHP classes. 
Example: 

"form" = {
  "add" = "Drupal\block\BlockForm",
  "edit" = "Drupal\block\BlockForm",
  "delete" = "Drupal\block\Form\BlockDeleteForm",
​​​​​​​}

Alternatively, you can define a "default" form to handle both the "add" and "edit" form instead of defining them separately. Worth noting is that the "delete" form will almost always be handled by a separate class from the other forms. This is because the delete form is generally a "confirmation form" that simply asks if the user is sure they want to delete the entity.

View builder - This handler provides a class that will handle the output of the entity when viewed by the end user. For example, when visiting a node on a Drupal 8 site, entity's output is handled by the NodeViewBuilder class.
Example: "view_builder" = "Drupal\node\NodeViewBuilder",

List builder - The list builder class will handle the list of entities for administrative purposes. This class will define the contents of the headers, rows, and operations when visiting the entities management page for the entity. For example, when visiting the /admin/content uri of your Drupal site, the table contents are provided by the Node entity's list builder class.
Example: "list_builder" = "Drupal\node\NodeListBuilder",

Route provider - An optional handler that, if implemented, will dynamically generate routes for your entity management. The use of this can effectively replace the need for having entity routes defined in the routing.yml file of your module. Note, the route_provider works in conjunction with the Links defined on your entity (see example below Links section). The route_provider annotation is an array.
Example: 

"route_provider" = {
  "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
}

Access - The access handler can be used to dynamically check permissions for your entity. It is a simple mapping to a class that implements the EntityAccessControlHandlerInterface. Core provides an implementation of this interface as EntityAccessControlHandler, but for reliable control over your entity you will want to extend that class with your own.
Example: "access" = "NodeAccessControlHandler",

Views data - The views_data handler allows an entity to extend the Views module with custom data provided by your entity. This can be for adding your entity's baseFieldDefinitions as Views fields, joining tables on entity relationships, or other Views related data alterations.
​​​​​​​Example: "views_data" = "Drupal\node\NodeViewsData", 

Storage - You can define a storage handler to extend the default storage methods of your entity. By default, Content Entities use Drupal\Core\Entity\Sql\SqlContentEntityStorage while Config Entities use Drupal\Core\Config\Entity\ConfigEntityStorage. You may want to do this to provide additional methods for gathering entity revision ids, or determining the number of translations an entity has.
Example: "storage" = "Drupal\node\NodeStorage", 

Storage schema - The storage_schema handler can be implemented to further alter the entity's database storage settings. For instance, adding additional table indexes or foreign keys.
Example: "storage_schema" = "Drupal\node\NodeStorageSchema", 

Translation - The translation handler can be used to alter the way in which your entity forms interact with translations.
Example: "translation" = "Drupal\node\NodeTranslationHandler", 

Full handlers example:

Drupal core provides handlers that you can use out of the box, but in many cases you'll want to extend these classes with your own for greater control and customization of your entity. This example shows a more-complete handlers annotation, using the core classes you can extend.

handlers = {
  "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
  "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
  "access" = "Drupal\Core\Entity\EntityAccessControlHandler",
  "views_data" = "Drupal\views\EntityViewsData",
  "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
  "storage_schema" = "Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema",
  "translation" = "Drupal\content_translation\ContentTranslationHandler",
  "form" = {
    "default" = "Drupal\Core\Entity\ContentEntityForm",
    "add" = "Drupal\Core\Entity\ContentEntityForm",
    "edit" = "Drupal\Core\Entity\ContentEntityForm",
    "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
  },
  "route_provider" = {
    "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
  },
},

Links

Links are defined in the entity annotation with the array syntax. Links have a specific set of keys whose value are URIs where the entity type or single entities of that type can be managed. Both Content and Configuration Entities can have these links defined.

Example:

id = "node",
handlers = {
  "route_provider" = {
    "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider"
  }
},
links = {
  "canonical" = "/node/{node}",
  "add-page" = "/node/add",
  "add-form" = "/node/add/{node_type}",
  "edit-form" = "/node/{node}/edit",
  "delete-form" = "/node/{node}/delete",
  "collection" = "/admin/content",
},

Note, this is not taken verbatim from the Node module, it is only an example.

Creating these links does not automatically create the routes for those URIs. To make these links accessible, your module will need to implement its own routing.yml file, or use a "route_provider" handler in the entity annotation.

Links & Route Provider

The above links working together with a route_provider will make the following named routes available to Drupal.
 

Links Key Route Name Route Example URI Description
canonical entity.node.canonical /node/1 View a specific node
add-page entity.node.add_page /node/add Add node (if only one bundle), or select bundle of node to be added
add-form entity.node.add_form /node/add/article Add node of a specific bundle
edit-form entity.node.edit_form /node/1/edit Edit form for a specific node
delete-form entity.node.delete_form /node/1/delete Delete form for a specific node
collection entity.node.collection /admin/content View all nodes as a list

References:


This page is still a stub, documentation is being written while the Entity API is finalized for Drupal 8.

TODO/Planned:
- Handlers
- - Storage
- - Storage Schema
- - Access
- - Forms
- - View Builder
- - Views Data
- - List Builder
- Bundles
- Configuration entities
- Content entities
- - Revisions
- - Translations
- - Fields
- UUID support
- Entity query
- Routing

Drupal 8 Entity API issues being worked on: