Entity CRUD, editing, and view hooks in Drupal 10
Same name and namespace in other branches
- 8 core/lib/Drupal/Core/Entity/entity.api.php \entity_crud
- 9 core/lib/Drupal/Core/Entity/entity.api.php \entity_crud
Hooks used in various entity operations.
Entity create, read, update, and delete (CRUD) operations are performed by entity storage classes; see the Entity API topic for more information. Most entities use or extend the default classes: \Drupal\Core\Entity\Sql\SqlContentEntityStorage for content entities, and \Drupal\Core\Config\Entity\ConfigEntityStorage for configuration entities. For these entities, there is a set of hooks that is invoked for each CRUD operation, which module developers can implement to affect these operations; these hooks are actually invoked from methods on \Drupal\Core\Entity\EntityStorageBase.
For content entities, viewing and rendering are handled by a view builder class; see the Entity API topic for more information. Most view builders extend or use the default class \Drupal\Core\Entity\EntityViewBuilder.
Entity editing (including adding new entities) is handled by entity form classes; see the Entity API topic for more information. Most entity editing forms extend base classes \Drupal\Core\Entity\EntityForm or \Drupal\Core\Entity\ContentEntityForm. Note that many other operations, such as confirming deletion of entities, also use entity form classes.
This topic lists all of the entity CRUD and view operations, and the hooks and other operations that are invoked (in order) for each operation. Some notes:
- Whenever an entity hook is invoked, there is both a type-specific entity hook, and a generic entity hook. For instance, during a create operation on a node, first hook_node_create() and then hook_entity_create() would be invoked.
- The entity-type-specific hooks are represented in the list below as hook_ENTITY_TYPE_... (hook_ENTITY_TYPE_create() in this example). To implement one of these hooks for an entity whose machine name is "foo", define a function called mymodule_foo_create(), for instance. Also note that the entity or array of entities that are passed into a specific-type hook are of the specific entity class, not the generic Entity class, so in your implementation, you can make the $entity argument something like $node and give it a specific type hint (which should normally be to the specific interface, such as \Drupal\node\NodeInterface for nodes).
- $storage in the code examples is assumed to be an entity storage class. See the Entity API topic for information on how to instantiate the correct storage class for an entity type.
- $view_builder in the code examples is assumed to be an entity view builder class. See the Entity API topic for information on how to instantiate the correct view builder class for an entity type.
- During many operations, static methods are called on the entity class, which implements \Drupal\Core\Entity\EntityInterface.
Entities, revisions and translations
A content entity can have multiple stored variants: based on its definition, it can be revisionable, translatable, or both.
A revisionable entity can keep track of the changes that affect its data. In fact all previous revisions of the entity can be stored and made available as "historical" information. The "default" revision is the canonical variant of the entity, the one that is loaded when no specific revision is requested. Only changes to the default revision may be performed without triggering the creation of a new revision, in any other case revision data is not supposed to change. Aside from historical revisions, there can be "pending" revisions, that contain changes that did not make their way into the default revision. Typically these revisions contain data that is waiting for some form of approval, before being accepted as canonical.
A translatable entity can contain multiple translations of the same content. Content entity data is stored via fields, and each field can have one version for each enabled language. Some fields may be defined as untranslatable, which means that their values are shared among all translations. The "default" translation is the canonical variant of the entity, the one whose content will be accessible in the entity field data. Other translations can be instantiated from the default one. Every translation has an "active language" that is used to determine which field translation values should be handled. Typically the default translation's active language is the language of the content that was originally entered and served as source for the other translations.
An entity that is both revisionable and translatable has all the features described above: every revision can contain one or more translations. The canonical variant of the entity is the default translation of the default revision. Any revision will be initially loaded as the default translation, the other revision translations can be instantiated from this one. If a translation has changes in a certain revision, the translation is considered "affected" by that revision, and will be flagged as such via the "revision_translation_affected" field. With the built-in UI, every time a new revision is saved, the changes for the edited translations will be stored, while all field values for the other translations will be copied as-is. However, if multiple translations of the default revision are being subsequently modified without creating a new revision when saving, they will all be affected by the default revision. Additionally, all revision translations will be affected when saving a revision containing changes for untranslatable fields. On the other hand, pending revisions are not supposed to contain multiple affected translations, even when they are being manipulated via the API.
Create operations
To create an entity:
$entity = $storage
->create();
// Add code here to set properties on the entity.
// Until you call save(), the entity is just in memory.
$entity
->save();
There is also a shortcut method on entity classes, which creates an entity with an array of provided property values: \Drupal\Core\Entity::create().
Hooks invoked during the create operation:
- hook_ENTITY_TYPE_create()
- hook_entity_create()
- When handling content entities, if a new translation is added to the entity object:
See Save operations below for the save portion of the operation.
Read/Load operations
To load (read) a single entity:
$entity = $storage
->load($id);
To load multiple entities:
$entities = $storage
->loadMultiple($ids);
Since load() calls loadMultiple(), these are really the same operation. Here is the order of hooks and other operations that take place during entity loading:
- Entity is loaded from storage.
- postLoad() is called on the entity class, passing in all of the loaded entities.
- hook_entity_load()
- hook_ENTITY_TYPE_load()
When an entity is loaded, normally the default entity revision is loaded. It is also possible to load a different revision, for entities that support revisions, with this code:
$entity = $storage
->loadRevision($revision_id);
This involves the same hooks and operations as regular entity loading.
The "latest revision" of an entity is the most recently created one, regardless of it being default or pending. If the entity is translatable, revision translations are not taken into account either. In other words, any time a new revision is created, that becomes the latest revision for the entity overall, regardless of the affected translations. To load the latest revision of an entity:
$revision_id = $storage
->getLatestRevisionId($entity_id);
$entity = $storage
->loadRevision($revision_id);
As usual, if the entity is translatable, this code instantiates into $entity the default translation of the revision, even if the latest revision contains only changes to a different translation:
$is_default = $entity
->isDefaultTranslation();
// returns TRUE
The "latest translation-affected revision" is the most recently created one that affects the specified translation. For example, when a new revision introducing some changes to an English translation is saved, that becomes the new "latest revision". However, if an existing Italian translation was not affected by those changes, then the "latest translation-affected revision" for Italian remains what it was. To load the Italian translation at its latest translation-affected revision:
$revision_id = $storage
->getLatestTranslationAffectedRevisionId($entity_id, 'it');
$it_translation = $storage
->loadRevision($revision_id)
->getTranslation('it');
Save operations
To update an existing entity, you will need to load it, change properties, and then save; as described above, when creating a new entity, you will also need to save it. Here is the order of hooks and other events that happen during an entity save:
- preSave() is called on the entity object, and field objects.
- hook_ENTITY_TYPE_presave()
- hook_entity_presave()
- Entity is saved to storage.
- For updates on content entities, if there is a translation added that was not previously present:
- For updates on content entities, if there was a translation removed:
- postSave() is called on the entity object.
- hook_ENTITY_TYPE_insert() (new) or hook_ENTITY_TYPE_update() (update)
- hook_entity_insert() (new) or hook_entity_update() (update)
Some specific entity types invoke hooks during preSave() or postSave() operations. Examples:
- Field configuration preSave(): hook_field_storage_config_update_forbid()
- Node postSave(): hook_node_access_records() and hook_node_access_records_alter()
- Config entities that are acting as entity bundles in postSave(): hook_entity_bundle_create()
- Comment: hook_comment_publish() and hook_comment_unpublish() as appropriate.
Note that all translations available for the entity are stored during a save operation. When saving a new revision, a copy of every translation is stored, regardless of it being affected by the revision.
Editing operations
When an entity's add/edit form is used to add or edit an entity, there are several hooks that are invoked:
- hook_entity_prepare_form()
- hook_ENTITY_TYPE_prepare_form()
- hook_entity_form_display_alter() (for content entities only)
Delete operations
To delete one or more entities, load them and then delete them:
$entities = $storage
->loadMultiple($ids);
$storage
->delete($entities);
During the delete operation, the following hooks and other events happen:
- preDelete() is called on the entity class.
- hook_ENTITY_TYPE_predelete()
- hook_entity_predelete()
- Entity and field information is removed from storage.
- postDelete() is called on the entity class.
- hook_ENTITY_TYPE_delete()
- hook_entity_delete()
Some specific entity types invoke hooks during the delete process. Examples:
- Entity bundle postDelete(): hook_entity_bundle_delete()
Individual revisions of an entity can also be deleted:
$storage
->deleteRevision($revision_id);
This operation invokes the following operations and hooks:
- Revision is loaded (see Read/Load operations above).
- Revision and field information is removed from the database.
- hook_ENTITY_TYPE_revision_delete()
- hook_entity_revision_delete()
View/render operations
To make a render array for a loaded entity:
// You can omit the language ID if the default language is being used.
$build = $view_builder
->view($entity, 'view_mode_name', $language
->getId());
You can also use the viewMultiple() method to view multiple entities.
Hooks invoked during the operation of building a render array:
- hook_entity_view_mode_alter()
- hook_ENTITY_TYPE_build_defaults_alter()
- hook_entity_build_defaults_alter()
View builders for some types override these hooks, notably:
- The Tour view builder does not invoke any hooks.
- The Block view builder invokes hook_block_view_alter() and hook_block_view_BASE_BLOCK_ID_alter(). Note that in other view builders, the view alter hooks are run later in the process.
During the rendering operation, the default entity viewer runs the following hooks and operations in the pre-render step:
- hook_entity_view_display_alter()
- hook_entity_prepare_view()
- Entity fields are loaded, and render arrays are built for them using their formatters.
- hook_entity_display_build_alter()
- hook_ENTITY_TYPE_view()
- hook_entity_view()
- hook_ENTITY_TYPE_view_alter()
- hook_entity_view_alter()
Some specific builders have specific hooks:
- The Node view builder invokes hook_node_links_alter().
- The Comment view builder invokes hook_comment_links_alter().
After this point in rendering, the theme system takes over. See the Theme system and render API topic for more information.
Other entity hooks
Some types of entities invoke hooks for specific operations:
- Searching nodes:
- hook_ranking()
- Query is executed to find matching nodes
- Resulting node is loaded
- Node render array is built
- comment_node_update_index() is called (this adds "N comments" text)
- hook_node_search_result()
- Search indexing nodes:
- Node is loaded
- Node render array is built
- hook_node_update_index()
See also
\Drupal\Core\Entity\RevisionableInterface
\Drupal\Core\Entity\RevisionableStorageInterface
\Drupal\Core\Entity\TranslatableInterface
\Drupal\Core\Entity\TranslatableStorageInterface
\Drupal\Core\Entity\TranslatableRevisionableInterface
\Drupal\Core\Entity\TranslatableRevisionableStorageInterface
File
- core/
lib/ Drupal/ Core/ Entity/ entity.api.php, line 19 - Hooks and documentation related to entities.
Functions
Name | Location | Description |
---|---|---|
hook_entity_build_defaults_alter |
core/ |
Alter entity renderable values before cache checking during rendering. |
hook_entity_bundle_delete |
core/ |
Act on entity_bundle_delete(). |
hook_entity_create |
core/ |
Acts when creating a new entity. |
hook_entity_delete |
core/ |
Respond to entity deletion. |
hook_entity_display_build_alter |
core/ |
Alter the render array generated by an EntityDisplay for an entity. |
hook_entity_field_values_init |
core/ |
Acts when initializing a fieldable entity object. |
hook_entity_form_display_alter |
core/ |
Alter the settings used for displaying an entity form. |
hook_entity_form_mode_alter |
core/ |
Change the form mode used to build an entity form. |
hook_entity_insert |
core/ |
Respond to creation of a new entity. |
hook_entity_load |
core/ |
Act on entities when loaded. |
hook_entity_predelete |
core/ |
Act before entity deletion. |
hook_entity_preload |
core/ |
Act on an array of entity IDs before they are loaded. |
hook_entity_prepare_form |
core/ |
Acts on an entity object about to be shown on an entity form. |
hook_entity_prepare_view |
core/ |
Act on entities as they are being prepared for view. |
hook_entity_presave |
core/ |
Act on an entity before it is created or updated. |
hook_entity_revision_create |
core/ |
Respond to entity revision creation. |
hook_entity_revision_delete |
core/ |
Respond to entity revision deletion. |
hook_entity_translation_create |
core/ |
Acts when creating a new entity translation. |
hook_entity_translation_delete |
core/ |
Respond to entity translation deletion. |
hook_entity_translation_insert |
core/ |
Respond to creation of a new entity translation. |
hook_ENTITY_TYPE_build_defaults_alter |
core/ |
Alter entity renderable values before cache checking during rendering. |
hook_ENTITY_TYPE_create |
core/ |
Acts when creating a new entity of a specific type. |
hook_ENTITY_TYPE_delete |
core/ |
Respond to entity deletion of a particular type. |
hook_ENTITY_TYPE_field_values_init |
core/ |
Acts when initializing a fieldable entity object. |
hook_ENTITY_TYPE_insert |
core/ |
Respond to creation of a new entity of a particular type. |
hook_ENTITY_TYPE_load |
core/ |
Act on entities of a specific type when loaded. |
hook_ENTITY_TYPE_predelete |
core/ |
Act before entity deletion of a particular entity type. |
hook_ENTITY_TYPE_prepare_form |
core/ |
Acts on a particular type of entity object about to be in an entity form. |
hook_ENTITY_TYPE_presave |
core/ |
Act on a specific type of entity before it is created or updated. |
hook_ENTITY_TYPE_revision_create |
core/ |
Respond to entity revision creation. |
hook_ENTITY_TYPE_revision_delete |
core/ |
Respond to entity revision deletion of a particular type. |
hook_ENTITY_TYPE_translation_create |
core/ |
Acts when creating a new entity translation of a specific type. |
hook_ENTITY_TYPE_translation_delete |
core/ |
Respond to entity translation deletion of a particular type. |
hook_ENTITY_TYPE_translation_insert |
core/ |
Respond to creation of a new entity translation of a particular type. |
hook_ENTITY_TYPE_update |
core/ |
Respond to updates to an entity of a particular type. |
hook_ENTITY_TYPE_view |
core/ |
Act on entities of a particular type being assembled before rendering. |
hook_ENTITY_TYPE_view_alter |
core/ |
Alter the results of the entity build array for a particular entity type. |
hook_entity_update |
core/ |
Respond to updates to an entity. |
hook_entity_view |
core/ |
Act on entities being assembled before rendering. |
hook_entity_view_alter |
core/ |
Alter the results of the entity build array. |
hook_entity_view_display_alter |
core/ |
Alter the settings used for displaying an entity. |
hook_entity_view_mode_alter |
core/ |
Change the view mode of an entity that is being displayed. |
hook_node_search_result |
core/ |
Act on a node being displayed as a search result. |
hook_node_update_index |
core/ |
Act on a node being indexed for searching. |
hook_ranking |
core/ |
Provide additional methods of scoring for core search results for nodes. |