Do you want to know how to Drupal?

Let's Drupal

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