Do you want to know how to Drupal?

Let's Drupal

Entity

How to extract entity id from autocomplete string in Drupal?

When we use autocomplete field, the submitted value will look something like this "Title of the entity (10)". To extract the current entity id from this string we can use helper function extractEntityIdFromAutocompleteInput  from EntityAutocomplete

$autocomplete_data = 'Title of the entity (10)';
$entity_id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($autocomplete_data);

 


How to list content entities in Drupal 9?

If you need to get a list of content entitles in your Drupal 9 website for example for drop down on the configuration page you can use the next code snippet
use Drupal\Core\Entity\ContentEntityType;

$content_entities = [];

// Get all entities definitions.
$entity_type_definations = \Drupal::entityTypeManager()->getDefinitions();

foreach ($entity_type_definations as $definition) {
  // Check that definition is instance of ContentEntityType
  if ($definition instanceof ContentEntityType) {
    $content_entities [] = $definition;
  }
}

 


How to remove (uninstall) entity type in Drupal 8?

If we don't need some entity type anymore we can easily remove it.
// Get update manager.
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
// Get entity type.
$entity_type = $definition_update_manager->getEntityType('your_entity_type_id');
if ($entity_type ) {
    // Uninstall entity type.
    $definition_update_manager->uninstallEntityType($entity_type);
}

 


How to update entity field in Drupal 8?

Sometimes we need to update some settings for a field. In our case we will be update drop down field
function your_module_update_8001() {
  // First we need to get update manager.
  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();

  // Load the storage definition of the field.
  $field_storage = $definition_update_manager->getFieldStorageDefinition('field_name',
    'node');
  // Set a new list of options for the list field.
  $field_storage->setSettings([
    'allowed_values' => [
      'link' => 'Link',
      'posts' => 'Posts',
      'events' => 'Events',
      'page' => 'Page',
    ],
  ]);

  // Update the field storage definition.
  $definition_update_manager->updateFieldStorageDefinition($field_storage);
}

 


How to render programatically custom entity form mode in Drupal?

Sometime we want to render entity form on a custom page or even a block. It is possible to do in Drupal 8, because we can create custom entity form mode.

Let's imagine we have a node type "Post" and  this content type has a lot of custom fields. We have technical field "E-mails" and we want to send e-mails, when someone comments this node.

For the sake of the example we don't want to "pollute" the main node form with "E-mails" field and we want to create a separate form mode "email_settings" for it.

If you try to display it programatically, you will face the exception:

public function emailSettings($node) {
    return $this->entityFormBuilder()->getForm($node, 'email_settings');
}

 

The website encountered an unexpected error. Please try again later.

Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException: The "node" entity type did not specify a "email_settings" form class. in Drupal\Core\Entity\EntityTypeManager->getFormObject() (line 223 of core/lib/Drupal/Core/Entity/EntityTypeManager.php). Drupal\Core\Entity\EntityFormBuilder->getForm(Object, 'email_settings') (Line: 10)
Drupal\custom_entity_form_mode\Controller\CustomEntityFormModeController->emailSettings(Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 582)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 693)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

but it the same time if we use "default" form mode, it works perfectly

public function emailSettings($node) {
    return $this->entityFormBuilder()->getForm($node, 'default');
}

Why it happens? Well, it is quite simple the entity, in our case node, just has no idea how to handle our custom form mode. To be more precise entity doesn't know, which class to use to handle form mode.

If you check node entity annotation, there is no information about our custom form mode. Unfortunately, It is not done automatically.
 

 *     "form" = {
 *       "default" = "Drupal\node\NodeForm",
 *       "delete" = "Drupal\node\Form\NodeDeleteForm",
 *       "edit" = "Drupal\node\NodeForm",
 *       "delete-multiple-confirm" = "Drupal\node\Form\DeleteMultiple"
 *     },

We have to alter the entity for this we will use hook_entity_type_alter. Full code could be found here: https://github.com/LOBsTerr/drupal-modules-examples/tree/master/custom_entity_form_mode

/**
 * Implements hook_entity_type_alter().
 */
function custom_entity_form_mode_entity_type_alter(array &$entity_types) {
  // You can provide more complex logic and checks here, we are sure that node
  // module is active and it is enabled in our case.
  $default_handler_class = $entity_types['node']->getHandlerClasses()['form']['default'];
  $entity_types['node']->setFormClass('emails_settings', $default_handler_class);
}

Now you can create a node of content type, for which you have create form mode and open your custom page. In our example /email-settings/[nid]

 

Now you can display your form in any place you want

 


How to add a SQL index to existing entity tables

Drupal 8 provides a nice Entity API, which handles a lot of complicated mechanisms for us automatically. For example, the handling of tables for Content Entity. In some cases if we are fetching frequently entity tables and we need to improve the performance by providing additional indexes in database tables. There are different ways to do so. The first option you can think of to add directly using SQL query or Schema API. Obviously, it is not a the best way, because a lot of thing can go wrong here.

Luckily Drupal 8 provides nice way to do it.

First of all we need to define a Storage Schema class. Let's take as an example node module:

<?php

namespace Drupal\node;

use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\Field\FieldStorageDefinitionInterface;

/**
 * Defines the node schema handler.
 */
class NodeStorageSchema extends SqlContentEntityStorageSchema {

  /**
   * {@inheritdoc}
   */
  protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
    $schema = parent::getEntitySchema($entity_type, $reset);

    if ($data_table = $this->storage->getDataTable()) {
      $schema[$data_table]['indexes'] += [
        'node__frontpage' => ['promote', 'status', 'sticky', 'created'],
        'node__title_type' => ['title', ['type', 4]],
      ];
    }

    return $schema;
  }

  /**
   * {@inheritdoc}
   */
  protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
    $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
    $field_name = $storage_definition->getName();

    if ($table_name == 'node_revision') {
      switch ($field_name) {
        case 'langcode':
          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
          break;

        case 'revision_uid':
          $this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
          break;
      }
    }

    if ($table_name == 'node_field_data') {
      switch ($field_name) {
        case 'promote':
        case 'status':
        case 'sticky':
        case 'title':
          // Improves the performance of the indexes defined
          // in getEntitySchema().
          $schema['fields'][$field_name]['not null'] = TRUE;
          break;

        case 'changed':
        case 'created':
          // @todo Revisit index definitions:
          //   https://www.drupal.org/node/2015277.
          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
          break;
      }
    }

    return $schema;
  }

}


I will explain a little bit the code above before we continue. We have few options here

1) Add composite index, which includes several fields like this:
 

 if ($data_table = $this->storage->getDataTable()) {
      $schema[$data_table]['indexes'] += [
        'node__frontpage' => ['promote', 'status', 'sticky', 'created'],
        'node__title_type' => ['title', ['type', 4]],
      ];
}

2) Add an index for specific field:

case 'langcode':
          $this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
          break;

3) Add a foreign key

case 'revision_uid':
          $this->addSharedTableFieldForeignKey($storage_definition, $schema, 'users', 'uid');
          break;

4) Add additional properties for the fields

case 'title':
          // Improves the performance of the indexes defined
          // in getEntitySchema().
          $schema['fields'][$field_name]['not null'] = TRUE;
          break;

 

Now, when we have a class for Storage Schema, we need to add it to entity definition. Check Drupal\node\Entity\Node.php

 *   handlers = {
 *     "storage" = "Drupal\node\NodeStorage",
 *     "storage_schema" = "Drupal\node\NodeStorageSchema",
 *     "view_builder" = "Drupal\node\NodeViewBuilder",

No we to update our entity and set Storage Schema class for existing entity. We can do it using hook update

<?php
use Drupal\your_module\YourStorageSchema;

/**
 * Add storage schema to entity type.
 */
function your_module_update_8017() {
  $manager = \Drupal::entityDefinitionUpdateManager();

  // Get the current entity type definition, ensure the storage schema
  // class is set.
  $entity_type = $manager->getEntityType('enity_type')
    ->setHandlerClass('storage_schema', YourStorageSchema::class);

  // Regenerate entity type indexes.
  $manager->updateEntityType($entity_type);
}

Or if we have already had the Storage Schema class before, we can just update Entity type

<?php
use Drupal\your_module\YourStorageSchema;

/**
 * Update entity type.
 */
function your_module_update_8017() {
  $manager = \Drupal::entityDefinitionUpdateManager();
  $entity_type = $manager->getEntityType('enity_type');

  // Regenerate entity type indexes.
  $manager->updateEntityType($entity_type);
}

 


How to delete entity in Drupal?

In order to delete an entity in Drupal we can use entity storage to load an entity and then call "delete" method for it.

 

// Delete a node.
$node = \Drupal::entityTypeManager()->getStorage('node')->load(1);
if (!empty($node)) {
   $node->delete();
}

 

Or we can load an entity using static method "load" and then remove it.

 

// Delete a node.
$node = Node::load(1);
if (!empty($node)) {
   $node->delete();
}

 

Delete multiple nodes in one operation.

 

\Drupal::entityTypeManager()->getStorage('node')->delete([
   $nid1 => $node1,
   $nid2 => $node2,
]);

 


How to edit an Entity in Drupal?

In order to edit an entity in Drupal firstly we need to load it. We can use the static method of specific entity class, in the current example it is Node class

 

$node = Node::load(1);

 

We can also use entity storage to load entities

 

// Load single entity
$entity = \Drupal::entityTypeManager()->getStorage('node')->load(1);

// Load multiple entities
$entities = \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple([1, 2, 3]);

 

Then you can set necessary fields and properties of the entity

 

// Call the specific setter
$node->setTitle('new Title');

// Call general set method
$node->set('body', 'Body text');

// Save the entity
$node->save();

 


How to create an Entity in Drupal?

In order to create an entity in Drupal 8 in a general case we need to Entity type manager to get a storage for a specific entity type, for example node 

// Use the entity manager to get node storage.
$node_storage = \Drupal::entityTypeManager()->getStorage('node');

// Call create method passing values for a new node entity.
$node_storage->create([
   'type' => 'page', 
   'title' => 'Page title',
]);

Or we can write it shorter

$node = \Drupal::entityTypeManager()->getStorage('node')->create(['type' => 'page', 'title' => 'Page title']);

We can also call a wrapper - a function, which provides the same functionality. I don't recommend to use it, because it is deprecated and  can be removed at any moment

$node = entity_create('node', [
  'type' => 'page',
  'title' => 'Page title',
  'body' => 'This a text of your page.',
]);

We can also call directly call method create of a specific entity

$node = Node::create([
  'type' => 'page',
  'title' => 'The page title',
]);