Entity API
Entities are the fundamental building blocks of the Drupal system. Almost every component is an entity or relies on entities to work. Examples of entities are Nodes, Users, Blocks, Menus, etc...
In Drupal, there are two entity variants:
- Configuration entities: Used by the Configuration System. Supports translations and can provide custom defaults for installations.
- Content entities: Consist of configurable and base fields, can have revisions and support translations.
Entities can also have subtypes, known as bundles.
Entity Types
Entity classes live in \Drupal\[module_name]\Entity
, meaning they should be found in src/Entity
. The class docblock must use an EntityType
annotation (including ContentEntityType
and ConfigEntityType
).
For example, core/modules/node/src/Entity/Node.php
is responsible for creating nodes:
<?php
namespace Drupal\node\Entity;
use Drupal\Core\Entity\EditorialContentEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Drupal\user\EntityOwnerTrait;
/**
* Defines the node entity class.
*
* @ContentEntityType(
* id = "node",
* label = @Translation("Content"),
* label_collection = @Translation("Content"),
* label_singular = @Translation("content item"),
* label_plural = @Translation("content items"),
* label_count = @PluralTranslation(
* singular = "@count content item",
* plural = "@count content items"
* ),
* bundle_label = @Translation("Content type"),
* handlers = {
* "storage" = "Drupal\node\NodeStorage",
* "storage_schema" = "Drupal\node\NodeStorageSchema",
* "view_builder" = "Drupal\node\NodeViewBuilder",
* "access" = "Drupal\node\NodeAccessControlHandler",
* "views_data" = "Drupal\node\NodeViewsData",
* "form" = {
* "default" = "Drupal\node\NodeForm",
* "delete" = "Drupal\node\Form\NodeDeleteForm",
* "edit" = "Drupal\node\NodeForm",
* "delete-multiple-confirm" = "Drupal\node\Form\DeleteMultiple"
* },
* "route_provider" = {
* "html" = "Drupal\node\Entity\NodeRouteProvider",
* },
* "list_builder" = "Drupal\node\NodeListBuilder",
* "translation" = "Drupal\node\NodeTranslationHandler"
* },
* base_table = "node",
* data_table = "node_field_data",
* revision_table = "node_revision",
* revision_data_table = "node_field_revision",
* show_revision_ui = TRUE,
* translatable = TRUE,
* list_cache_contexts = { "user.node_grants:view" },
* entity_keys = {
* "id" = "nid",
* "revision" = "vid",
* "bundle" = "type",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid",
* "status" = "status",
* "published" = "status",
* "uid" = "uid",
* "owner" = "uid",
* },
* revision_metadata_keys = {
* "revision_user" = "revision_uid",
* "revision_created" = "revision_timestamp",
* "revision_log_message" = "revision_log"
* },
* bundle_entity_type = "node_type",
* field_ui_base_route = "entity.node_type.edit_form",
* common_reference_target = TRUE,
* permission_granularity = "bundle",
* collection_permission = "access content overview",
* links = {
* "canonical" = "/node/{node}",
* "delete-form" = "/node/{node}/delete",
* "delete-multiple-form" = "/admin/content/node/delete",
* "edit-form" = "/node/{node}/edit",
* "version-history" = "/node/{node}/revisions",
* "revision" = "/node/{node}/revisions/{node_revision}/view",
* "create" = "/node",
* }
* )
*/
class Node extends EditorialContentEntityBase implements NodeInterface {
From Entity Types on Drupal.org updated June 2019:
Entity type names should be prefixed with the module name if the entity type and module name aren't the same.
Drupal recommends you type hint functions and methods with interfaces instead of classes. For example, the generic entity storage hooks type hint with EntityInterface
as in hook_entity_insert(EntityInterface $entity)
and the node specific storage hooks type hint with NodeInterface
as in hook_node_insert(NodeInterface $node)
.
Entity fields/properties are often very short, storage-centric and not very self-descriptive. Additionally, content entities don't use defined properties for their fields (including base fields like the node title) at all.
- Methods usually have a get/set/is or similar prefix:
getSomething()
,setSomething($value)
,isSomething()
- Only add methods for things that other code is supposed to change. The last changed date of nodes (
$node->changed
) isn't supposed to be changed, so there is$node->getChangedTime()
but no$node->setChangedTime()
method. - Use self-descriptive method names, for example, the method to access
$node->status
is called$node->isPublished()
.
Working with Entity API
Following examples from Entity API on Drupal.org:
// Make sure that an object is an entity.
if ($object instanceof \Drupal\Core\Entity\EntityInterface) {
}
// Make sure it's a content entity.
if ($entity instanceof \Drupal\Core\Entity\ContentEntityInterface) {
}
// Get the entity type.
$entity->getEntityTypeId();
// Make sure it's a node.
if ($entity instanceof \Drupal\node\NodeInterface) {
}
// Using entityType() works better when the needed entity type is dynamic.
$needed_type = 'node';
if ($entity->getEntityTypeId() == $needed_type) {
}
// Get the ID.
$entity->id();
// Get the bundle.
$entity->bundle();
// Check if the entity is new.
$entity->isNew();
// Get the label of an entity. Replacement for entity_label().
$entity->label().
// Get the URI for an entity.
// @todo: This might still change with the new URI template API.
$entity->uri();
// Create a duplicate that can be saved as a new entity.
$duplicate = $entity->createDuplicate();
// Use the procedural wrapper.
$node = entity_create('node', [
'title' => 'My node',
'body' => 'The body content. This just works like this due to the new Entity Field
API. It will be assigned as the value of the first field item in the
default language.',
]);
// Or you can use the static create() method if you know
// the entity class::
$node = Node::create(['title' => 'The node title']);
// Use the entity manager.
$node = \Drupal::entityTypeManager()->getStorage('node')->create(['type' => 'article', 'title' => 'Another node']);
// To save an entity.
$entity->save();
// The following will attempt to insert a new node with the ID 5, this will fail if that node already exists.
$node->nid->value = 5;
$node->enforceIsNew(TRUE);
$node->save();
// Delete a single entity.
$entity = \Drupal::entityTypeManager()->getStorage('node')->load(1);
$entity->delete();
// Delete multiple entities at once.
\Drupal::entityTypeManager()->getStorage($entity_type)->delete([$id1 => $entity1, $id2 => $entity2]);
// Check view access of an entity.
// This defaults to check access for the currently logged in user.
if ($entity->access('view')) {
}
// Check if a given user can delete an entity.
if ($entity->access('delete', $account)) {
}
// Update a field value in a ContentEntity.
$custom_field_value = $Custom_Entity->custom_field->value;
// Perform some kind of data manipulation
$Custom_Entity->custom_field->value = $custom_field_value;
$Custom_Entity->save();
Entity Validation
From Entity Validation API overview on Drupal.org updated July 2023: Entity validation is in a separate Entity validation API and decoupled from form validation. Decoupling entity validation from forms allows validation entities to be independent of form submissions, such as when changed via the RESTful web service. This new validation API has been implemented based on the Symfony validator.
Overview
Validation is controlled by a set of constraints, such as the maximum length of some text or restriction to a set of allowed values. Symfony comes with many useful constraints, which Drupal extends with more Drupal-specific constraints. Drupal Symfony validator has been integrated with the Typed Data API, such that validation constraints can be used and specified as part of Entity field definitions and, generally, any typed data definition.
Using the API
Validation can be invoked by calling the validate() method on any typed data object as in the following example:
$definition = DataDefinition::create('integer')
->addConstraint('Range', ['min' => 5]);
$typed_data = \Drupal::typedDataManager()->create($definition, 10);
$violations = $typed_data->validate();
This returns a list of violations if passed an empty validation:
if ($violations->count() > 0) {
// Validation failed.
}
Validating entities
Entity fields and field items are typed data objects, and can be validated as in this example:
$violations = $entity->field_text->validate();
Here is an example of validating an entity as a whole:
$violations = $entity->validate();
Putting constraints on field item properties:
Entity field definitions ease putting constraints on individual field item properties via the setPropertyConstraints() method. In the following example, the field definition puts a maximum length constraint on the field item's value property ($field[0]->value
):
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setPropertyConstraints('value', ['Length' => ['max' => 32]]);
Questions
What are examples of Drupal Entities?
What is the difference between an Entity Type ID and a Bundle? Give an example.
For example, a node might give
$node->getEntityTypeId(); // node
$node->bundle(); // article
What Drupal service might you use to retrieve entities by type or property value?
You would use the \Drupal\Core\Entity\EntityStorageInterface
, retrievable by calling \Drupal::entityTypeManager()->getStorage($entity_type_id)
.
As an example, you could retrieve several nodes by ID like so:
\Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids);