A certain type of entities. Also typically used as a variable name for the entity type definition, consisting of the ID, labels, handlers and other information that makes up an entity type.
Node/Content (node), Term (taxonomy_term), User (user), User Role (user_role)
\Drupal\Core\Entity\EntityTypeInterface
A generic concept to store and access data. A specific entity, identified by entity type and ID.
Node 1, User 17.
\Drupal\Core\Entity\EntityInterface
\Drupal\node\Entity\Node (\Drupal\node\NodeInterface)
A group of entity types that use fields and typed data. Many more features like revisions, translations. Complex but predictable data structures.
Node, User, File, Term.
\Drupal\Core\Entity\ContentEntityInterface
A group of entity types that use the config system as storage. Machine names as ID, inherits features of config (export, deploy, config translations). Plain data. Often uses one or multiple plugins.
User Role, Vocabulary, Node Type, Block, View
\Drupal\Core\Config\Entity\ConfigEntityInterface
An extensible system to add functionality to entity types that can be customized per entity type.
Storage, Access Control Handler, Forms, View Builder, Views Data
Variants/subtypes of content entities that can have different fields, form/view displays, templates.
Bundles are often managed as config entities themselves but do not have to be, can be other plugins or hardcoded.
Node: Node type, Term: Vocabulary, Media: Media type.
$entity->bundle(), hook_entity_bundle_info()
A generic API to describe data and data structures. Entities and related objects are either typed data or a typed data representation of it can be accessed.
A list contains N items with the same definition, complex data (map) contains a fixed amount of items identified by a key.
\Drupal\Core\TypedData\TypedDataInterface
\Drupal\Core\TypedData\PrimitiveValues
\Drupal\Core\TypedData\ListInterface
\Drupal\Core\TypedData\ComplexDataInterface
$s = \Drupal::entityTypeManager()->getStorage('entity_type');
// Create an entity.
$entity = $s->create(['type' => 'page', 'title' => 'My page']);
$entity == Node::create();
// Load
$entity = $s->load(1);
$entities = $s->loadMultiple([1,2,3]);
$entity = Node::load(1);
$entities = Node::loadMultiple([1,2,3]);
// Save
$entity->save();
// Delete
$entity->delete();
Find entities based on their values, works for content and config entities. API documentation
// Or: \Drupal::entityQuery('node')
$fids = $s->getQuery()
// Conditions.
->condition('type', ['page', 'article'], 'IN')
// Content entities support relationships.
->condition('uid.entity.roles', 'administrator', '=')
// D10 requires to enable/disable access checks.
->accessCheck(TRUE)
// Sorting and limit results, pager is supported too.
->sort('changed', 'DESC')
->range(0, 100)
// Returns a list of IDs, keys are revision ids.
->execute();
// Get the ID of an entity.
$entity->id();
// Get the label.
$entity->label();
// Get the UUID.
$entity->uuid();
// Get all the values as an array.
$entity->toArray();
Link templates define common URLs around entities, drupal.org/docs/drupal-apis/entity-api/link-templates. Defaults to canonical, and edit-form for config entities.
// Generate a URL Template and work with it.
$url = $entity->toUrl('canonical');
$url->toString();
$url->toRenderArray();
// Create a Link.
$link = $entity->toLink('Edit me', 'edit-form');
// Check if an entity has a certain link template.
$entity->hasLinkTemplate('edit-form');
Allows to check if a user can view/update/delete or create an entity. API documentation
// Boolean check.
$entity->access('view|update|delete') == TRUE;
// Access object, to use further or check cacheability info.
$access = $entity->access('view', $account, TRUE);
$access->isAllowed();
$object->addCacheableDependency($access);
// Fields have access too.
$node->get('title')->access('view');
// Create access.
$etm->getAccesControlHandler('entity_type')->createAccess()
// Access has 3 states, use in access hooks.
AccessResult::allowed();
AccessResult::neutral();
AccessResult::forbidden();
React to things happening: CRUD and viewing, access.
// Before save, entities be changed.
function hook_entity_presave($entity) {
}
// After saving a new entity.
function hook_entity_insert($entity) {
}
// After saving an existing entity.
function hook_entity_update($entity) {
// Check if a value changes on update.
if ($entity->label() != $entity->original->label()
}
// Hooks also exist for specific entity types.
function hook_ENTITY_TYPE_presave() {
}
Name | Interface | Typed Data |
---|---|---|
Entity | ContentEntityInterface | ::getTypedData() EntityAdapter (complex data) |
Field | FieldItemListInterface | ListInterface |
Field item | FieldItemInterface | ComplexDataInterface |
Property | PrimitiveInterface (non-computed, stored properites) or TypedDataInterface |
// Complete
$entity->get('field')->get(0)->get('property')->getValue();
// Array access
$entity->get('field')[0]->get('property')->getValue();
// __get() for properties equals ->get('property)->getValue();
$entity->get('field')[0]->property;
// Delta 0 is the default for properties when using __get()
$entity->get('field')->property;
// Complete
$entity->get('field')->get(0)->get('property')->setValue('.');
// Magic
$entity->get('field')->property = 'myvalue';
// ContentEntityInterface::set(), single/main property.
$entity->set('field', 'myvalue');
// ContentEntityInterface::set(), multiple properties.
$node->set('body', ['value' => 'Hello', 'format' => 'plain_text']);
// ContentEntityInterface::set(), multiple items.
$user->set('roles', ['content_editor', 'administrator']);
// Append an item.
$user->get('roles')->appendItem('administrator');
getValue()
// On a field item property.
$user->get('roles')[0]->get('target_id')->getValue()
=> "content_editor"
// On a field item.
$user->get('roles')[0]->getValue();
=> ["target_id" => "content_editor"]
// On a field.
$user->get('roles')->getValue();
=> [
0 => ["target_id" => "content_editor"],
1 => ["target_id" => "administrator"],
]
// Shorthand, get a single property of all items.
array_column($user->get('roles')->getValue(), 'target_id')
=> ["content_editor", "administrator"]
Loops
// Loop over all fields of an entity.
foreach ($entity as $field_name => $field) {
// Loop over all items of a field.
foreach ($field as $item) {
if ($item->entity) {
}
}
}
FieldStorageDefinitionInterface
FieldDefinitionInterface
BaseFieldDefinition implements FieldStorageDefinitionInterface, FieldDefinitionInterface
::baseFieldDefinitions
of the entity class and shared across
all bundles.
FieldStorageConfig implements FieldStorageDefinitionInterface
FieldConfig implements FieldDefinitionInterface
$entity->getFieldDefinitions();
$entity->getFieldDefinition('field_name')
$entity->get('field_name')->getFieldDefinition()
$field_definition->getFieldStorageDefinition()
$efm = \Drupal::service('entity_field.manager')
$efm->getFieldStorageDefinitions($entity_type_id)
$efm->getFieldDefinitions($entity_type_id, $bundle)
$node->get('title')->value
// is shorthand for
$node->get('title')->get('value')->getValue()
// And not the same as
$node->get('title')->getValue()
// ->entity on entity reference fields is the target.
$user->get('roles')->entity == $role;
// ->getEntity() is the host/parent entity.
$user->get('roles')->getEntity() == $user;
$node->title->value
// is shorthand for
$node->get('title')->value
// __get()/__set() support non-fields, get() does not:
$node->foo = 'bar';
// throws an exception:
$node->set('foo', 'bar');
// Various "API"s in rely on this behavior at the moment:
$entity->original, $entity->view, $entity->_referringItem;
Recommendation: Avoid for fields #3281720
ContentEntityInterface implement ...\RevisionableInterface.
// Get the current revision ID.
$entity->getRevisionId();
// Is/was the default revision or is latest revision.
$entity->isDefaultRevision();
$entity->wasDefaultRevision();
$entity->isLatestRevision();
// Set a new non-default revision.
$entity->setNewRevision(TRUE);
$entity->isDefaultRevision(FALSE);
$entity->save();
::setSyncing(TRUE)
)
// Get the current language.
$entity->language()->getId();
// Check if a translation exists.
$entity->hasTranslation('de');
// Get a specific translation.
$translation = $entity->getTranslation('de');
// Add a translation.
$translation = $entity->addTranslation('de', $values);
// Remove a translation.
$entity->removeTranslation('de');
$er = \Drupal::service('entity.repository');
$translation = $er->getTranslationFromContext($entity, 'de');
> SELECT nid, vid, langcode FROM node;
+-----+------+----------+
| nid | vid | langcode |
+-----+------+----------+
| 1 | 2 | en |
+-----+------+----------+
> SELECT nid, vid, langcode AS lang, status,title,
default_langcode AS def, revision_translation_affected AS affected
FROM node_field_data;
+-----+-----+------+--------+------------+-----+----------+
| nid | vid | lang | status | title | def | affected |
+-----+-----+------+--------+------------+-----+----------+
| 1 | 2 | de | 1 | Example DE | 0 | 1 |
| 1 | 2 | en | 1 | Example EN | 1 | NULL |
+-----+-----+------+--------+------------+-----+----------+
> select nid, vid, langcode, revision_default from node_revision;
+-----+-----+----------+------------------+
| nid | vid | langcode | revision_default |
+-----+-----+----------+------------------+
| 1 | 1 | en | 1 |
| 1 | 2 | en | 1 |
| 1 | 3 | en | 0 |
| 1 | 4 | en | 0 |
+-----+-----+----------+------------------+
> SELECT nid,vid,langcode AS lang,title,status,default_langcode AS def,revision_translation_affected As affected FROM node_field_revision;
+-----+-----+------+------------------+--------+-----+----------+
| nid | vid | lang | title | status | def | affected |
+-----+-----+------+------------------+--------+-----+----------+
| 1 | 1 | en | Example EN | 1 | 1 | 1 |
| 1 | 2 | de | Example DE | 1 | 0 | 1 |
| 1 | 2 | en | Example EN | 1 | 1 | NULL |
| 1 | 3 | de | Example DE | 1 | 0 | NULL |
| 1 | 3 | en | Example EN Draft | 0 | 1 | 1 |
| 1 | 4 | de | Example DE | 1 | 0 | NULL |
| 1 | 4 | en | Example EN | 1 | 1 | NULL |
| 1 | 4 | fr | Example FR Draft | 0 | 0 | 1 |
+-----+-----+------+------------------+--------+-----+----------+
// Render an entity.
$view_builder = $entity_type_manager->getViewBuilder('node');
$build = $view_builder->view($entity, 'teaser');
// Render a single field using configuration for a given view mode.
$build = $entity->get('body')->view('teaser');
// Render a field using fixed configuration.
$build = $entity->get('body')->view([
'label' => 'hidden',
'type' => '...',
'settings' => [],
);
content
: Render arrays for each field created based on configured formattersnode
: The entity object, useful to check raw valueselements
, it contains fields too, content
exists because {{ elements }}
would be an endless recursion
{# Display a single field including wrapping field template #}
{{ content.field_foo }}
{# Display first delta of a field, without any wrapping HTML #}
{{ content.field_foo.0 }}}
{# Display a field if it has value #}
{% if node.field_name.value %}
{{ content.field_name.0 }}
{% end %}
{# Check for a field value on a referenced entity #}
{% if node.field_category.entity.field_highlight.value %}
{{ content.field_category.0 }}
{% else %}
{{ content.field_category.0 }}
{% end %}
class BasicPage extends Node implements BasicPageInterface {
// Implement whatever business logic specific to basic pages.
public function hasHighlightCategory(): bool {
if ($this->get('field_category')->entity) {
return $this->get('field_category')->get('entity')
->get('field_highlight')->value;
}
return FALSE;
}
}
function mymodule_entity_bundle_info_alter(array &$bundles) {
if (isset($bundles['node']['page'])) {
$bundles['node']['page']['class'] = BasicPage::class;
}
}
Twig can use any method starting with get/has/is
{# Before #}
{% if node.field_category.entity.field_highlight.value %}
{# After #}
{% if node.hasHighlightCategory() %}