Do you want to know how to Drupal?

Let's Drupal

Drupal 9

How to check if the current page using admin theme in Drupal?

To check this we need to use router.admin_context service

if (\Drupal::service('router.admin_context')->isAdminRoute()) {
   // Do something here.
}

We also can check a specific route

$route_provider = \Drupal::service('router.route_provider');
$route = $route_provider->getRouteByName('user.page');
if (\Drupal::service('router.admin_context')->isAdminRoute($route)) {
   // Do something here.
}

 


How to load a configuration file from module in Drupal 9?

In some cases we want to load a configuration file from optional / install folders. For example to compare it with active config and apply new changes.
To load a configuration from install folder user the next code snippet.

use Drupal\Core\Config\ExtensionInstallStorage;

$field_storage = 'field.storage.group_content.grequest_status';
$install_storage = new ExtensionInstallStorage(
      \Drupal::service('config.storage'),
      InstallStorage::CONFIG_INSTALL_DIRECTORY,
      InstallStorage::DEFAULT_COLLECTION,
      TRUE,
      NULL
);

From optional folder

use Drupal\Core\Config\ExtensionInstallStorage;

$name = 'views.view.group_pending_members';

$optional_storage = new ExtensionInstallStorage(
      \Drupal::service('config.storage'),
      InstallStorage::CONFIG_OPTIONAL_DIRECTORY,
      InstallStorage::DEFAULT_COLLECTION,
      TRUE,
      NULL
);

and schema configuration

use Drupal\Core\Config\ExtensionInstallStorage;

$name = 'views.view.group_pending_members';

$optional_storage = new ExtensionInstallStorage(
      \Drupal::service('config.storage'),
      InstallStorage::CONFIG_SCHEMA_DIRECTORY,
      InstallStorage::DEFAULT_COLLECTION,
      TRUE,
      NULL
);

 


How to detect the very first login of the user in Drupal 9?

For this we need implement user_login hook and then we check the last access field.

use Drupal\user\Entity\User;

/**
 * Implements hook_user_login().
 */
function mymodule_user_login(User $user) {
  if (empty($user->getLastAccessedTime())) {
    // Do something here.
  }
}

 


How to redirect a user after login in Drupal 9?

Let's imagine we want to send a user to custom route after login in our example to user edit form. How do we do it ?

We need to implement user_login hook

use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\user\Entity\User;

/**
 * Implements hook_user_login().
 */
function mymodule_user_login(User $user) {
    $url = Url::fromRoute('entity.user.edit_form', [
       'user' => $user->id(),
    ]);
    $redirect = new RedirectResponse($url->toString());
    $redirect->send();
}


 


How to show a message in Drupal 9?

Sometimes we need to inform a user about a result of some operations. For example we have submitted the form and we want to show a success message or an error

We need to user service "messenger" service, which will store the message in the session and fetch during the next request.

Get instance of messenger service

# 1. Standard way to get messenger service, in case you want to pass it using dependency injection.
$messenger = \Drupal::service("messenger");

# 2. Fast way to use helper function.

$messenger = \Drupal::messenger();

Also in the form object or controller we don't have include it using dependency injection. We can us directly 

$messenger = $this->messenger();

Methods

1. Add message. Allows to send a message of any type, the second parameter accepts the type

$meesenger->addMessage(t('Your message.'), MessengerInterface::TYPE_WARNING);

But in most of the cases it is better to use directly specific method

 

2. Add status

$messenger->addStatus(t('Your message'));

 

3. Add warning

$messenger->addWarning(t('Your message'));

 

4. Add error

$messenger->addError(t('Your message'));

How to run migration programmatically as a batch operations in Drupal 9?

It is quite easy to do instead of MigrateExecutable class, we need to use MigrateBatchExecutable and instead of import we need to use batchImport

use Drupal\migrate_tools\MigrateExecutable;


$migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);
if (!empty($migration)) {
        // here we can set additional options like limit, update or force the import.
        $options = [
          'limit' => 0,
          'update' => 1,
          'force' => 1,
        ];

        $executable = new MigrateBatchExecutable($migration, new MigrateMessage(), $options);
        $executable->batchImport();
}

 


How to set a custom redirection after form submission Drupal 9?

To redirect a user after form submit to a custom URL. We need to implement custom submit function.

use Drupal\Core\Form\FormStateInterface;

/** 
 * Implements hook_form_FORM_ID_alter().
 */
function mydoule_form_YOUR_FORM_ID_alter(&$form, FormStateInterface $form_state, $form_id) {
  // add custom function as submit handler.
  $form['#submit'][] = '_mymodule_custom_submit_handler';

  // Sometime forms doesn't into account '#submit' parameter. So, we have to set custom handler to the specific button of the form
  $form['actions']['submit']['#submit'][] = '_mymodule_custom_submit_handler';
}

/**
 * Custom submit handler for your form form.
 */
function _mymodule_custom_submit_handler($form, FormStateInterface $form_state) {
  // Set a custom redirect.
  $form_state->setRedirect('custom.route');
}

You will need to replace module name and put into install file of your module and also don't forget to replace form id.


How to use dependency injection for Plugins in Drupal 9?

Services are very powerful way to make our code reusable. How can we pass service to our plugin in Drupal 9?

For this example we take code of simple plugin EntityLink. It doesn't actually it could be any plugin

use Drupal\views\Plugin\views\field\EntityLink;
use Drupal\views\ResultRow;

/**
 * Field handler to present a link to approval.
 *
 * @ingroup views_field_handlers
 *
 * @ViewsField("approve_link")
 */
class ApproveLInk extends EntityLink {

  /**
   * {@inheritdoc}
   */
  protected function getEntityLinkTemplate() {
    return 'approve';
  }

  /**
   * {@inheritdoc}
   */
  protected function renderLink(ResultRow $row) {
    $this->options['alter']['query'] = $this->getDestinationArray();
    return parent::renderLink($row);
  }

  /**
   * {@inheritdoc}
   */
  protected function getDefaultLabel() {
    return $this->t('Approve');
  }

}

We don't call constructor for our plugin. In order to pass our service. Let's take current_user service.

First we need implement 

class ApproveLInk extends EntityLink implements ContainerFactoryPluginInterface {

Add create method, where we pass our service

/**
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   * @param array $configuration
   * @param string $plugin_id
   * @param mixed $plugin_definition
   *
   * @return static
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('current_user')
    );
  }

After that add parameters to our constructor

/**
   * @var AccountInterface $account
   */
  protected $account;

  /**
   * @param array $configuration
   * @param string $plugin_id
   * @param mixed $plugin_definition
   * @param \Drupal\Core\Session\AccountInterface $account
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $account) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->account = $account;
  }

 


How to change the type of the field in Drupal 9?

There are many cases when we need to switch a type of a field from one to another based on a new business requirement. How to do it and keep the old data.

First you HAVE TO make a back up :) 

I will provide the full code and then step by step we will break it down. To perform this operation we will use hook_update.

Steps to change the type of the field

  1. Load field storage
  2. Fetch the data for this field
  3. Configure a new field storage
  4. Delete the old storage
  5. Purge the data
  6. Create a new storage configuration
  7. Add field to bundles
  8. Insert the data back to db
<?php
use \Drupal\field\Entity\FieldStorageConfig;
use \Drupal\field\Entity\FieldConfig;

/**
 * Change node__field_quote from string_long to string type.
 */
function yourmodule_update_8XXX() {
  $database = \Drupal::database();
  $table = 'node__field_quote ';
  $entity_type = 'node';
  $field_name = 'field_quote';

  // #1 Load storage configuration.
  $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);

  // if storage config is not found, there is nothing to do.
  if (is_null($field_storage)) {
    return;
  }

  $rows = [];

  // #2 Fetch the data for the old field.
  if ($database->schema()->tableExists($table)) {
    // The table data to restore after the update is completed.
    $rows = $database->select($table, 'n')
      ->fields('n')
      ->execute()
      ->fetchAll();
  }

  $new_fields = [];

  // Use existing field config for new field.
  foreach ($field_storage->getBundles() as $bundle => $label) {
    $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
    $new_field = $field->toArray();
    $new_field['field_type'] = 'string';
    $new_field['settings'] = [];

    $new_fields[] = $new_field;
  }

  // #3 Take the old storage configuration present it as an array and update it's settings.
  $new_field_storage = $field_storage->toArray();
  $new_field_storage['type'] = 'string';
  $new_field_storage['settings'] = [
    'max_length' => 255,
    'is_ascii' => FALSE,
    'case_sensitive' => FALSE,
  ];

  // #4 Deleting field storage which will also delete bundles(fields).
  $field_storage->delete();

  // #5 Purge field data now to allow new field and field_storage with same name
  // to be created. You may need to increase batch size.
  field_purge_batch(100);

  // #6 Create new field storage, create a new storage based on configuration above.
  $new_field_storage = FieldStorageConfig::create($new_field_storage);
  $new_field_storage->save();

  // #7 Create new fields.
  foreach ($new_fields as $new_field) {
    $new_field = FieldConfig::create($new_field);
    $new_field->save();
  }

  // #8 Restore existing data in the same table.
  if (!empty($rows)) {
    foreach ($rows as $row) {
	  // We can do some manipulation with data here.
      $database->insert($table)
        ->fields((array) $row)
        ->execute();
    }
  }

}

 

Step 1 - Load the old field storage

First of all of course we need to load the old field storage configuration. A field storage always attached to entity type: node, media, paragraph and etc.

  $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);

Step 2 - Fetch the data for this field

We fetch all the data for the old field, for any fields and for any bundles. For example this fiels used in articles (node content type) and pages (node content type). We want to keep the data for all of the fields. Later we can perform some transformation of the data. Keep in mind if you have many items in this table, you will need to use batch operations.

 

// #2 Fetch the data for the old field.
  if ($database->schema()->tableExists($table)) {
    // The table data to restore after the update is completed.
    $rows = $database->select($table, 'n')
      ->fields('n')
      ->execute()
      ->fetchAll();
  }

 

Step 3  -  Configure a new field storage

In this example we actually use the old storage configuration and just update few settings.

// #3 Take the old storage configuration present it as an array and update it's settings.
  $new_field_storage = $field_storage->toArray();
  $new_field_storage['type'] = 'string';
  $new_field_storage['settings'] = [
    'max_length' => 255,
    'is_ascii' => FALSE,
    'case_sensitive' => FALSE,
  ];

But in some cases we can get it from configuration files or just pass an array with configuration directly. For example
 

// Get field storage from config file.
$install_storage = new ExtensionInstallStorage(
    \Drupal::service('config.storage'),
    InstallStorage::CONFIG_INSTALL_DIRECTORY
);
$field_storage_config = $install_storage->read("field.storage.node.field_quote");

Step 4  -  Delete the old storage

// #4 Deleting field storage which will also delete bundles(fields).
  $field_storage->delete();

 

Step 5 - Purge the data

Before recreating the new field we need to delete all the data for these fields. If you have a lot of items consider to change the value.

// #5 Purge field data now to allow new field and field_storage with same name
  // to be created. You may need to increase batch size.
  field_purge_batch(100);

 

Step 6 - Create a new storage configuration

Here we just pass a field storage configuration to create method as an array

// #6 Create new field storage, create a new storage based on configuration above.
$new_field_storage = FieldStorageConfig::create($new_field_storage);
$new_field_storage->save();

 

Step 7 - Add field to bundles

We recreate field instances here. We again use the previous configuration of the field and update the settings.
 

// #7 Create new fields.
foreach ($new_fields as $new_field) {
    $new_field = FieldConfig::create($new_field);
    $new_field->save();
}

and then we can also recreate the field completely from scratch, something like this

FieldConfig::create([
      'field_storage' => FieldStorageConfig::loadByName($entity_type, $field_name),
      'bundle' => $bundle,
      'label' => t('Our field label'),
      'required' => TRUE,
      'default_value' => [[
        'value' => 1,
      ],
      ],
])->save();

 

Step 8 - Insert the data back to the field table.

Here we can convert data or just insert as it is.

  // #8 Restore existing data in the same table.
  if (!empty($rows)) {
    foreach ($rows as $row) {
      // We can do some manipulation with data here.
      $database->insert($table)
        ->fields((array) $row)
        ->execute();
    }
  }

 


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;
  }
}