php
query_manipulate.php
<?php
namespace Drupal\my_module_migrate\Plugin\migrate\source\d8;
use Drupal\migrate\Row;
/**
* Base class for D8 source plugins to collect field values from Field API.
*
* Available configuration keys:
* - id: (optional) The id of the content which should get migrated. This can
* be useful to only migrate a selected set of nodes. Excepts multiple ids.
*
* @MigrateSource(
* id = "my_module_d8_gallery",
* source_provider = "my_module_migrate"
* )
*/
class MyModuleGallery extends ContentEntity {
/**
* {@inheritdoc}
*/
public function query() {
$query = parent::query();
if (isset($this->configuration['id'])) {
$entityDefinition = $this->entityTypeManager->getDefinition($this->configuration['entity_type']);
$idKey = $entityDefinition->getKey('id');
$query->condition("d.{$idKey}", $this->configuration['id'], 'IN');
}
// Left join to get the nid of the old image node.
$query->leftJoin('paragraph__field_referenzfeld_galerie', 'pfrg', 'pfrg.entity_id = b.id AND pfrg.revision_id = b.revision_id');
$query->leftJoin('node__field_gallery_bild', 'nfgb', 'nfgb.entity_id = pfrg.field_referenzfeld_galerie_target_id');
$query->leftJoin('node__field_base_public_title', 'nfbpt', 'nfbpt.entity_id = pfrg.field_referenzfeld_galerie_target_id');
$query->addExpression('GROUP_CONCAT(nfgb.field_gallery_bild_target_id)', 'gallery_image_ids');
// Add public title field.
$query->addField('nfbpt', 'field_base_public_title_value', 'field_base_public_title_value');
$query->groupBy('b.id');
return $query;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$return = parent::prepareRow($row);
$nids = [];
$ids_string = $row->getSourceProperty('gallery_image_ids');
if (!empty($ids_string)) {
$ids = explode(',', $ids_string);
foreach ($ids as $id) {
$nids[] = ['nid' => (int) $id];
}
// Add new source property to skip old gallery node within migration.
$row->setSourceProperty('field_referenzfeld_galerie', $nids);
}
$row->setSourceProperty('field_base_public_title', $row->getSourceProperty('field_base_public_title_value'));
return $return;
}
}
query.sql
SELECT
GROUP_CONCAT(nfgb.field_gallery_bild_target_id),
nfbpt.field_base_public_title_value
FROM paragraph__field_referenzfeld_galerie AS pfrg
LEFT JOIN node__field_gallery_bild AS nfgb
ON nfgb.entity_id = pfrg.field_referenzfeld_galerie_target_id
LEFT JOIN node__field_base_public_title AS nfbpt
ON nfbpt.entity_id = pfrg.field_referenzfeld_galerie_target_id
WHERE pfrg.entity_id = 100810 AND pfrg.revision_id = 1919384
10.07.2025 | Lothar Ferreira Neumann
Optimizing Drupal Migration: Using GROUP_CONCAT for Data Aggregation
redirect.install
/**
* Save the bulk delete action to config.
*/
function redirect_update_8104() {
if (!Action::load('redirect_delete_action')) {
$entity_type_manager = \Drupal::entityTypeManager();
$module_handler = \Drupal::moduleHandler();
// Save the bulk delete action to config.
$config_install_path = $module_handler->getModule('redirect')->getPath() . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
$storage = new FileStorage($config_install_path);
$entity_type_manager
->getStorage('action')
->create($storage->read('system.action.redirect_delete_action'))
->trustData()
->save();
}
}
13.11.2025 | Dominik Wille
Install config from config/install dir in update hook.
HelloWorldCommands.php
<?php
declare(strict_types=1);
namespace Drupal\nice_module\Drush\Commands;
use Drupal\Component\DependencyInjection\ContainerInterface;
use Drupal\nice_module\HelloWorldService;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
/**
* A Drush command class for saying "hello" to the world.
*/
final class HelloWorldCommands extends DrushCommands {
public function __construct(
private readonly HelloWorldService $helloWorldService,
) {
parent::__construct();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): self {
return new static($container->get('nice_module.hello_world_service'));
}
#[CLI\Command(name: 'nice_module:say-hello', aliases: ['smci'])]
#[CLI\Argument(name: 'userName', description: 'The name of the user.')]
public function sayHello(?string $userName = NULL): void {
$this->helloWorldService->sayHello($userName, $this->output());
$this->logger()->success('I said "hello" to the user.');
}
}
13.11.2025 | Peter Majmesku
Drush 12+: Simplified Drush commands with PHP 8 attributes
nested_array_merge_deep.php
use Drupal\Component\Utility\NestedArray;
// Parent array with default settings.
$parent_array = [
'config' => [
'theme' => 'dark',
'layout' => [
'width' => 800,
'height' => 600,
],
],
];
// Child array adds more nested data but doesn't override existing values.
$child_array = [
'config' => [
'layout' => [
'depth' => 300, // New nested key
],
'features' => [
'animations' => true, // New section
],
],
];
// Merge both arrays.
$merged_array = NestedArray::mergeDeep($parent_array, $child_array);nested_array_result.php
[
'config' => [
// Unchanged (not overridden)
'theme' => 'dark',
'layout' => [
// Keeps parent value
'width' => 800,
// Keeps parent value
'height' => 600,
// New nested key added
'depth' => 300,
],
'features' => [
// New section added
'animations' => true,
],
],
]
30.07.2026 | Lothar Ferreira Neumann
Merge multidimensional arrays in Drupal
cleanup_aliases.php
<?php
/** @var \Drupal\path_alias\PathAliasStorage */
$alias_storage = \Drupal::entityTypeManager()->getStorage('path_alias');
$db = \Drupal::database();
$langcodes = array_keys(\Drupal::languageManager()->getLanguages());
$total = 0;
$duplicates = "";
// Get a list of all distinct aliases.
$query = $db->select('path_alias', 'pa')
->fields('pa', ['alias'])
->orderBy('alias')
->distinct();
$all_aliases = $query->execute()->fetchAll(\Pdo::FETCH_COLUMN);
foreach ($all_aliases as $alias) {
// Some aliases are 'NULL'.
if (!$alias) {
continue;
}
// We only look for duplicates within the langcode.
foreach ($langcodes as $langcode) {
// (1) Get the newest ID for that alias and langcode.
$query = $db->select('path_alias', 'pa')
->fields('pa', ['id'])
->condition('alias', $alias)
->condition('langcode', $langcode)
->orderBy('id', 'DESC')
->range(0, 1);
$newest_id = $query->execute()->fetchField(0);
if (!$newest_id) {
// There is no alias for that langcode.
// So there are also no duplicates.
continue;
}
// (2) Select all ids lower than it with the same alias and langcode.
$query = $db->select('path_alias', 'pa')
->fields('pa', ['id'])
->condition('alias', $alias)
->condition('langcode', $langcode)
->condition('id', $newest_id, '<');
$duplicate_ids = $query->execute()->fetchAll(\PDO::FETCH_COLUMN);
if (!str_starts_with($alias, "/")) {
$query = $db->select('path_alias', 'pa')
->fields('pa', ['id'])
->condition('alias', "/" . $alias)
->condition('langcode', $langcode);
$ids_with_slash = $query->execute()->fetchAll(\PDO::FETCH_COLUMN);
if (count($ids_with_slash) > 0) {
// We can safely delete the one without.
$to_delete = $alias_storage->loadByProperties([
'alias' => $alias,
'langcode' => $langcode,
]);
$alias_storage->delete($to_delete);
} else {
// It's the only one? Update with slash.
$alias_entitys = $alias_storage->loadByProperties([
'alias' => $alias,
'langcode' => $langcode,
]);
if (!empty($alias_entitys)) {
$alias_entity = reset($alias_entitys);
$alias_entity->set('alias', '/' . $alias);
$alias_entity->save();
}
}
}
// We delete those with the alias storage since we want Drupal to handle it
// and it will delete revisions as well.
$duplicate_aliases = $alias_storage->loadMultiple($duplicate_ids);
$total += count($duplicate_aliases);
if (count($duplicate_aliases) > 0) {
$duplicates = $duplicates . $alias . PHP_EOL;
}
$alias_storage->delete($duplicate_aliases);
}
}
echo(sprintf("Deleting %d duplicate aliases..." . PHP_EOL, $total));
echo($duplicates);
echo("DONE" . PHP_EOL);
21.02.2025 | Nikolas Kopp
Clean up migrated path aliases
If this does not work...
broken_fapi_autocomplete.php
$form['nodes'] = [
'#title' => $this->t('Select articles'),
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#tags' => TRUE,
'#selection_handler' => 'default',
'#selection_settings' => ['target_bundles' => ['article']],
'#default_value' => $default_nodes,
];...try adding an increased maxlength value:
fixed_fapi_autocomplete.php
$form['nodes'] = [
'#title' => $this->t('Select articles'),
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#maxlength' => 1024,
'#tags' => TRUE,
'#selection_handler' => 'default',
'#selection_settings' => ['target_bundles' => ['article']],
'#default_value' => $default_nodes,
];
13.08.2026 | Pascal Crott
Can't save Form API autocomplete when referencing too many elements
static_or_nonstatic_that_is_the_question.php
// Static way.
$slot_paragraph = Paragraph::create([
'type' => 'slot',
]);
$slot_paragraph->save();
// Nonstatic way.
$entity_type_manager = \Drupal::entityTypeManager();
$storage = $entity_type_manager->getStorage('paragraphs');
$slot_paragraph = $storage->create([
'type' => 'slot',
]);
$slot_paragraph->save();
14.02.2025 | Lothar Ferreira Neumann
Best practice for entity creation using entity type manager
entity_key_id.php
$entity_type_id = 'node';
$definition = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
$id_key = $definition->getKey('id');
10.09.2026 | Mathias Grab
Get "key" from entity type
null_return.php
/**
* Retrieves the name of a role based on its ID.
*
* @param string $role_id
* The ID of the role to retrieve.
*
* @return string|null
* The name of the role if it exists, or NULL if the role is not found.
*/
public function getRoleName(string $role_id): ?string {
// Example roles array.
$roles = [
'admin' => 'Administrator',
'editor' => 'Content Editor',
'viewer' => 'Viewer',
];
// Use the null coalescing operator to return the role name or null.
return $roles[$role_id] ?? NULL;
}
05.10.2026 | Lothar Ferreira Neumann