Drupal Helper Utility
This module provides helper utilities for Drupal development, simplifying common tasks like creating taxonomy terms, managing configuration, and processing large datasets in deploy hooks. It offers a clean facade to access various functionalities such as entity and user management, redirects, and menu link creation.
Development of this project takes place on GitHub.
Please submit issues there.
Features
Static facade for clean deploy hooks
Access all helpers through Helper::term(), Helper::config(), etc. - no need to inject services or know container names. One use statement is all you need.
use Drupal\drupal_helpers\Helper; Helper::term()->createTree('tags', ['News', 'Events', 'Blog']); Helper::config()->set('system.site', 'name', 'My Site');
Batch processing for large datasets
Pass the $sandbox array from your deploy hook and the helper automatically batches operations across multiple requests - no manual tracking of $sandbox['#finished'].
// Batch-update every article node: function my_module_deploy_001(array &$sandbox): ?string { return Helper::entity($sandbox)->batchEntity('node', 'article', function ($node) { $node->set('field_migrated', TRUE); $node->save(); }); } // Batch-delete all articles: function my_module_deploy_002(array &$sandbox): ?string { return Helper::entity($sandbox)->deleteAll('node', 'article'); } // Batch-process arbitrary items with a callback: function my_module_deploy_003(array &$sandbox): ?string { $emails = ['[email protected]', '[email protected]', /* ... hundreds more */]; return Helper::user($sandbox)->batch($emails, function ($email) { Helper::user()->create($email, ['editor']); }, 'users'); }
Operation logging and result reporting
Every helper feeds a shared reporter that tallies what a run created, updated, skipped, deleted, or failed, logs each operation to a dedicated drupal_helpers logger channel, and surfaces it through the messenger. Return the tally from a deploy hook with Helper::report() - it renders as a single line for both drush and update.php, then resets so the next hook starts clean.
use Drupal\drupal_helpers\Helper; function my_module_deploy_001(array &$sandbox): ?string { Helper::term()->createTree('topics', $large_tree); // e.g. "Created 12, skipped 3." return Helper::report(); }
Pass continue_on_error: TRUE to the batch helpers to tolerate per-item failures: each is reported as a warning and counted, and the run keeps going instead of aborting.
Taxonomy, menu, block, display, field, entity, config, module, user, role, redirect, URL alias, and translation helpers
Common deploy hook operations covered out of the box:
- Create taxonomy term trees (flat or nested) with safe, update, and sync reconciliation modes, plus export back to PHP or YAML.
- Build menu link hierarchies from arrays with the same reconciliation modes and export.
- Place theme blocks and create or delete block content.
- Set or hide components on entity form and view displays.
- Create fields with sensible display defaults, attach them to more bundles, and delete fields or instances with automatic data purging.
- Import config YAML from modules.
- Install and uninstall modules, including force-removal of orphaned modules whose code is gone.
- Create users with roles and auto-generated passwords.
- Create and delete roles and grant or revoke permissions (unknown permissions rejected).
- Create, export, import (CSV round trip), delete, and clean up redirects.
- Create, find, update, import (CSV), and clean up URL aliases (core, no contrib).
- Add or update interface translations, with context support.
Extendable via Drupal services
Every helper is a standard Drupal service registered in drupal_helpers.services.yml. You can override, decorate, or inject them into your own services using Drupal's dependency injection container.
# Use a helper as a dependency in your own service: services: my_module.migrator: class: Drupal\my_module\Migrator arguments: ['@drupal_helpers.term', '@drupal_helpers.entity']
Module requirement checking
Helpers that depend on contrib modules (e.g., Redirect requires the redirect module) declare their requirements via requiredModules(). The facade checks these at access time and throws a clear error if a module is missing - no cryptic "service not found" exceptions.
Installation
composer require drupal/drupal_helpers drush pm:install drupal_helpers
Usage
All helpers are accessed via the Helper facade:
use Drupal\drupal_helpers\Helper; // Simple - no sandbox: Helper::term()->createTree('topics', $tree); Helper::field()->delete('field_old'); // Batched - with sandbox: function my_module_deploy_001(array &$sandbox): ?string { return Helper::entity($sandbox)->deleteAll('node', 'article'); }
Available methods
Helper Description Alias URL alias helpers for deploy hooks. Block Block placement and block content helpers for deploy hooks. Config Configuration helpers for deploy hooks. Display Entity form and view display helpers for deploy hooks. Entity Entity helpers for deploy hooks. Field Field helpers for deploy hooks. Menu Menu link helpers for deploy hooks. Module Module install and uninstall helpers for deploy hooks. Redirect Redirect helpers for deploy hooks. Role Role and permission helpers for deploy hooks. Term Taxonomy term helpers for deploy hooks. Translation Interface translation helpers for deploy hooks. User User helpers for deploy hooks.Alias
URL alias helpers for deploy hooks.
create(string $path, string $alias, ?string $langcode = NULL, bool $skip_existing = TRUE): ?PathAliasInterface
Create a URL alias.
Helper::alias()->create('/node/1', '/about-us'); Helper::alias()->create('/node/2', '/a-propos', 'fr');
createMultiple(array $aliases): int
Create multiple URL aliases.
Helper::alias()->createMultiple([ ['path' => '/node/1', 'alias' => '/about-us'], ['path' => '/node/2', 'alias' => '/a-propos', 'langcode' => 'fr'], ]);
findByPath(string $path, ?string $langcode = NULL): ?PathAliasInterface
Find an alias by system path.
$alias = Helper::alias()->findByPath('/node/1');
findByAlias(string $alias, ?string $langcode = NULL): ?PathAliasInterface
Find an alias by its alias string.
$alias = Helper::alias()->findByAlias('/about-us');
updateByPath(string $path, string $alias, ?string $langcode = NULL): ?PathAliasInterface
Rename the alias for a system path.
Helper::alias()->updateByPath('/node/1', '/about-us');
updateByAlias(string $alias, string $path, ?string $langcode = NULL): ?PathAliasInterface
Retarget an alias to a new system path.
Helper::alias()->updateByAlias('/about-us', '/node/5');
deleteByPath(string $path, ?string $langcode = NULL): int
Delete aliases by system path.
Helper::alias()->deleteByPath('/node/1');
deleteByAlias(string $alias, ?string $langcode = NULL): int
Delete aliases by their alias string.
Helper::alias()->deleteByAlias('/about-us');
deleteAll(): ?string
Delete all URL aliases.
Helper::alias()->deleteAll();
importFromCsv(string $file_path): ?string
Import URL aliases from a CSV file.
Helper::alias()->importFromCsv('/path/to/aliases.csv'); // With sandbox for large files: function my_module_deploy_001(array &$sandbox): ?string { return Helper::alias($sandbox)->importFromCsv('/path/to/aliases.csv'); }
Block
Block placement and block content helpers for deploy hooks.
place(string $plugin_id, string $theme, string $region, array $options = [], bool $skip_existing = TRUE): ?EntityInterface
Place a plugin block into a theme region.
Helper::block()->place('system_powered_by_block', 'olivero', 'footer', [ 'weight' => 10, ]); // With visibility conditions: Helper::block()->place('system_branding_block', 'olivero', 'header', [ 'visibility' => [ 'request_path' => [ 'id' => 'request_path', 'pages' => '/admin/*', 'negate' => TRUE, ], ], ]);
placeMultiple(array $blocks): int
Place multiple plugin blocks.
Helper::block()->placeMultiple([ [ 'plugin' => 'system_powered_by_block', 'theme' => 'olivero', 'region' => 'footer', ], [ 'plugin' => 'system_branding_block', 'theme' => 'olivero', 'region' => 'header', 'options' => ['weight' => -10], ], ]);
remove(string $id): bool
Remove a placed block.
Helper::block()->remove('olivero_system_powered_by_block');
createContent(string $bundle, array $values = [], bool $skip_existing = TRUE): ?EntityInterface
Create a block content entity.
Helper::block()->createContent('basic', [ 'info' => 'Footer contact', 'body' => 'Call us on 1234', ]);
createContentMultiple(array $blocks): int
Create multiple block content entities.
Helper::block()->createContentMultiple([ ['type' => 'basic', 'info' => 'Footer contact', 'body' => 'Call us'], ['type' => 'basic', 'info' => 'Opening hours', 'body' => '9am - 5pm'], ]);
deleteContent(string $info, ?string $bundle = NULL): int
Delete block content by info label.
Helper::block()->deleteContent('Footer contact'); Helper::block()->deleteContent('Footer contact', 'basic');
Config
Configuration helpers for deploy hooks.
set(string $config_name, string $key, mixed $value, mixed $expected = self::NO_EXPECTED): string
Set a value in a configuration object, optionally guarded.
// Unconditional write: Helper::config()->set('system.site', 'name', 'My Site'); // Guarded - applies only while the live value is still 'Old Name': return Helper::config()->set('system.site', 'name', 'New Name', 'Old Name');
get(string $config_name, string $key): mixed
Get a value from a configuration object.
$site_name = Helper::config()->get('system.site', 'name');
delete(string $config_name): void
Delete a configuration object.
Helper::config()->delete('my_module.settings');
import(string $module, string $config_name, string $subdirectory = 'install'): void
Import a config from a module's config/install directory.
Helper::config()->import('my_module', 'views.view.my_view'); Helper::config()->import('my_module', 'node.type.page', 'optional');
importMultiple(string $module, array $config_names, string $subdirectory = 'install'): void
Import multiple configs from a module.
Helper::config()->importMultiple('my_module', [ 'views.view.my_view', 'field.storage.node.field_custom', ]);
setFrontPage(string $path): void
Set the site front page.
Helper::config()->setFrontPage('/node/1');
Display
Entity form and view display helpers for deploy hooks.
setFormComponent(string $entity_type, string $bundle, string $mode, string $field_name, array $options = []): EntityDisplayInterface
Set a widget component on an entity form display.
Helper::display()->setFormComponent('node', 'article', 'default', 'field_subtitle', [ 'type' => 'string_textfield', 'weight' => 5, ]);
setViewComponent(string $entity_type, string $bundle, string $mode, string $field_name, array $options = []): EntityDisplayInterface
Set a formatter component on an entity view display.
Helper::display()->setViewComponent('node', 'article', 'teaser', 'field_subtitle', [ 'type' => 'string', 'label' => 'hidden', 'weight' => 5, ]);
hideFormComponent(string $entity_type, string $bundle, string $mode, string $field_name): EntityDisplayInterface
Hide a component on an entity form display.
Helper::display()->hideFormComponent('node', 'article', 'default', 'field_subtitle');
hideViewComponent(string $entity_type, string $bundle, string $mode, string $field_name): EntityDisplayInterface
Hide a component on an entity view display.
Helper::display()->hideViewComponent('node', 'article', 'teaser', 'field_subtitle');
Entity
Entity helpers for deploy hooks.
create(string $entity_type, string $bundle, array $values, ?string $identity = NULL): EntityInterface
Create an entity of a given type and bundle.
Helper::entity()->create('node', 'article', [ 'title' => 'Welcome', 'body' => 'Hello world', ]); // Skip re-creating an entity that already has the same identity value: Helper::entity()->create('node', 'article', [ 'title' => 'Welcome', ], identity: 'title');
createMultiple(string $entity_type, string $bundle, array $rows, ?string $identity = NULL): ?string
Create multiple entities with optional sandbox batching.
$rows = [ ['title' => 'Page one'], ['title' => 'Page two'], ]; Helper::entity()->createMultiple('node', 'article', $rows, identity: 'title'); // With sandbox for large datasets: function my_module_deploy_001(array &$sandbox): ?string { return Helper::entity($sandbox)->createMultiple('node', 'article', $rows, identity: 'title'); }
update(string $entity_type, array $properties, array $values): ?string
Update entities matched by a set of properties.
Helper::entity()->update('node', ['type' => 'article'], ['status' => 0]); // With sandbox for large datasets: function my_module_deploy_001(array &$sandbox): ?string { return Helper::entity($sandbox)->update('node', ['type' => 'article'], ['status' => 0]); }
deleteAll(string $entity_type, ?string $bundle = NULL): ?string
Delete all entities of a given type and optional bundle.
Helper::entity()->deleteAll('node', 'article'); // With sandbox for large datasets: function my_module_deploy_001(array &$sandbox): ?string { return Helper::entity($sandbox)->deleteAll('node', 'article'); }
batchQuery(QueryInterface $query, callable $callback, bool $continue_on_error = FALSE, ?string $status = Reporter::PROCESSED): ?string
Process entities matching an entity query with optional sandbox batching.
// Migrate a value on every legacy article, tolerating per-item failures: function my_module_deploy_001(array &$sandbox): ?string { $query = \Drupal::entityQuery('node') ->condition('type', 'article') ->condition('field_legacy', 1); return Helper::entity($sandbox)->batchQuery($query, function ($node): void { $node->set('field_migrated', TRUE); $node->save(); }, continue_on_error: TRUE); }
batchSetField(QueryInterface $query, string $field_name, mixed $value, bool $continue_on_error = FALSE): ?string
Set a field value on every entity matching an entity query.
// Archive every article: function my_module_deploy_001(array &$sandbox): ?string { $query = \Drupal::entityQuery('node')->condition('type', 'article'); return Helper::entity($sandbox)->batchSetField($query, 'field_status', 'archived'); }
Field
Field helpers for deploy hooks.
create(string $entity_type, string $bundle, string $field_name, array $settings): FieldConfigInterface
Create a field storage and instance on a bundle from a settings array.
Helper::field()->create('node', 'article', 'field_subtitle', [ 'type' => 'string', 'label' => 'Subtitle', ]);
attachToBundles(string $field_name, string $entity_type, array $bundles): array
Attach an existing field storage to one or more additional bundles.
Helper::field()->attachToBundles('field_subtitle', 'node', ['page', 'landing']);
delete(string $field_name): void
Delete a field from all entity bundles and purge its data.
Helper::field()->delete('field_subtitle');
deleteInstance(string $field_name, string $entity_type, string $bundle): void
Delete a field instance from a specific entity bundle.
Helper::field()->deleteInstance('field_subtitle', 'node', 'article');
Menu
Menu link helpers for deploy hooks.
createTree(string $menu_name, array $tree, string $mode = self::MODE_SAFE): array
Create menu links from a nested tree structure.
$tree = [ 'Home' => '/', 'About' => [ 'path' => '/about', 'children' => [ 'Team' => '/about/team', 'Contact' => '/about/contact', ], ], 'External' => 'https://example.com', ]; Helper::menu()->createTree('main', $tree); // Reconcile: re-apply the tree to existing links and delete any not listed. Helper::menu()->createTree('main', $tree, mode: Menu::MODE_SYNC);
exportTree(string $menu_name, string $format = self::FORMAT_ARRAY): array|string
Export a menu to the nested tree accepted by createTree().
// Snapshot structure as data: $tree = Helper::menu()->exportTree('main'); // Render as ready-to-paste PHP or YAML: $php = Helper::menu()->exportTree('main', Menu::FORMAT_PHP); $yaml = Helper::menu()->exportTree('main', Menu::FORMAT_YAML);
deleteTree(string $menu_name): ?string
Delete all menu links from a menu.
Helper::menu()->deleteTree('main');
findItem(string $menu_name, array $properties): ?MenuLinkContentInterface
Find a menu link by properties.
$link = Helper::menu()->findItem('main', ['title' => 'About']);
updateItem(string $menu_name, array $find_properties, array $updates): ?MenuLinkContentInterface
Update properties on an existing menu link found by properties.
Helper::menu()->updateItem('main', ['title' => 'About'], [ 'path' => '/about-us', 'weight' => 5, ]);
Module
Module install and uninstall helpers for deploy hooks.
install(string $module): string
Install a module and its dependencies.
Helper::module()->install('pathauto');
uninstall(string $module, ?callable $callback = NULL): string
Uninstall a module.
Helper::module()->uninstall('legacy_feature'); // Orphaned module (code removed, still in the database): Helper::module()->uninstall('ghost_module', function (string $module): void { \Drupal::database()->schema()->dropTable('ghost_module_data'); });
Redirect
Redirect helpers for deploy hooks.
create(string $source_path, string $target_path, int $status_code = 301, bool $skip_existing = TRUE, ?string $langcode = NULL): mixed
Create a redirect.
Helper::redirect()->create('old-page', '/new-page'); Helper::redirect()->create('legacy', 'https://example.com', 302); Helper::redirect()->create('vieux', '/nouveau', 301, TRUE, 'fr');
createMultiple(array $redirects): int
Create multiple redirects.
Helper::redirect()->createMultiple([ ['source' => 'old-page', 'target' => '/new-page'], ['source' => 'legacy', 'target' => 'https://example.com', 'status_code' => 302], ]);
deleteBySource(string $source_path): int
Delete redirects by source path.
Helper::redirect()->deleteBySource('old-page');
deleteAll(): ?string
Delete all redirect entities.
Helper::redirect()->deleteAll();
exportToCsv(string $file_path): string
Export all redirects to a CSV file.
Helper::redirect()->exportToCsv('/path/to/redirects.csv');
importFromCsv(string $file_path): ?string
Import redirects from a CSV file.
Helper::redirect()->importFromCsv('/path/to/redirects.csv'); // With sandbox for large files: function my_module_deploy_001(array &$sandbox): ?string { return Helper::redirect($sandbox)->importFromCsv('/path/to/redirects.csv'); }
deleteFromCsv(string $file_path): ?string
Delete redirects listed in a CSV file.
Helper::redirect()->deleteFromCsv('/path/to/remove.csv'); // With sandbox for large files: function my_module_deploy_001(array &$sandbox): ?string { return Helper::redirect($sandbox)->deleteFromCsv('/path/to/remove.csv'); }
Role
Role and permission helpers for deploy hooks.
create(string $id, string $label): RoleInterface
Create a user role.
Helper::role()->create('editor', 'Editor');
delete(string $id): void
Delete a user role.
Helper::role()->delete('editor');
grantPermissions(string $id, array $permissions): RoleInterface
Grant permissions to a role.
Helper::role()->grantPermissions('editor', [ 'access content overview', 'edit any article content', ]);
revokePermissions(string $id, array $permissions): RoleInterface
Revoke permissions from a role.
Helper::role()->revokePermissions('editor', ['edit any article content']);
Term
Taxonomy term helpers for deploy hooks.
createTree(string $vocabulary, array $tree, string $mode = self::MODE_SAFE): array
Create terms from a nested tree structure.
// Flat list: Helper::term()->createTree('tags', ['News', 'Events', 'Blog']); // Nested hierarchy: Helper::term()->createTree('topics', [ 'Finance' => [ 'Budgets', 'Grants', ], 'Governance' => [ 'Policy' => [ 'Internal', 'External', ], 'Compliance', ], 'Operations', ]); // Reconcile: re-apply the tree to existing terms and delete any not listed. $tree = ['Finance' => ['Budgets', 'Grants'], 'Operations']; Helper::term()->createTree('topics', $tree, mode: Term::MODE_SYNC);
exportTree(string $vocabulary, string $format = self::FORMAT_ARRAY): array|string
Export a vocabulary to the nested tree accepted by createTree().
// Snapshot structure as data: $tree = Helper::term()->exportTree('topics'); // Render as ready-to-paste PHP or YAML: $php = Helper::term()->exportTree('topics', Term::FORMAT_PHP); $yaml = Helper::term()->exportTree('topics', Term::FORMAT_YAML);
deleteAll(string $vocabulary): ?string
Delete all terms from a vocabulary.
Helper::term()->deleteAll('tags');
find(string $name, ?string $vocabulary = NULL): ?TermInterface
Find a term by name in a vocabulary.
$term = Helper::term()->find('News', 'tags');
Translation
Interface translation helpers for deploy hooks.
set(string $langcode, string $source, string $translation, string $context = ''): void
Add or update the translation of a source string for a language.
Helper::translation()->set('fr', 'Submit', 'Envoyer'); // Disambiguate a source string that carries a context: Helper::translation()->set('fr', 'May', 'Mai', 'Long month name');
User
User helpers for deploy hooks.
create(string $email, array $roles = [], array $fields = []): UserInterface
Create a user account.
Helper::user()->create('[email protected]', ['administrator']); Helper::user()->create('[email protected]', ['editor'], [ 'name' => 'editor1', 'status' => 1, ]);
createMultiple(array $emails, array $roles = [], array $fields = []): array
Create multiple user accounts.
Helper::user()->createMultiple([ '[email protected]', '[email protected]', ], ['editor']);
assignRoles(string $user_identifier, array $roles): void
Assign roles to an existing user.
Helper::user()->assignRoles('[email protected]', ['administrator']);
removeRoles(string $user_identifier, array $roles): void
Remove roles from an existing user.
Helper::user()->removeRoles('[email protected]', ['administrator']);
Requirements
- PHP 8.2+
- Drupal 10 or Drupal 11
- The
redirectmodule is optional - only needed for redirect helpers.